@olane/o-node 0.7.46 → 0.7.48
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/connection/o-node-connection.manager.d.ts +25 -34
- package/dist/src/connection/o-node-connection.manager.d.ts.map +1 -1
- package/dist/src/connection/o-node-connection.manager.js +90 -241
- package/dist/src/index.d.ts +0 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +0 -1
- package/dist/src/o-node.d.ts.map +1 -1
- package/dist/src/o-node.js +0 -22
- package/dist/src/o-node.tool.d.ts +1 -0
- package/dist/src/o-node.tool.d.ts.map +1 -1
- package/dist/src/o-node.tool.js +7 -3
- package/dist/src/utils/connection.utils.d.ts +5 -0
- package/dist/src/utils/connection.utils.d.ts.map +1 -1
- package/dist/src/utils/connection.utils.js +33 -10
- package/package.json +7 -7
- package/dist/src/decorators/index.d.ts +0 -2
- package/dist/src/decorators/index.d.ts.map +0 -1
- package/dist/src/decorators/index.js +0 -1
- package/dist/src/decorators/synchronized.d.ts +0 -21
- package/dist/src/decorators/synchronized.d.ts.map +0 -1
- package/dist/src/decorators/synchronized.js +0 -39
- package/dist/test/astream-reuse.spec.d.ts +0 -2
- package/dist/test/astream-reuse.spec.d.ts.map +0 -1
- package/dist/test/astream-reuse.spec.js +0 -107
|
@@ -5,58 +5,55 @@ import { oNodeConnection } from './o-node-connection.js';
|
|
|
5
5
|
export declare class oNodeConnectionManager extends oConnectionManager {
|
|
6
6
|
readonly config: oNodeConnectionManagerConfig;
|
|
7
7
|
protected p2pNode: Libp2p;
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
private nodeConnectionByConnectionId;
|
|
8
|
+
protected defaultReadTimeoutMs?: number;
|
|
9
|
+
protected defaultDrainTimeoutMs?: number;
|
|
10
|
+
/** Single cache of oNodeConnection instances keyed by address */
|
|
11
|
+
protected cachedConnections: Map<string, oNodeConnection[]>;
|
|
12
|
+
protected pendingDialsByAddress: Map<string, Promise<Connection>>;
|
|
14
13
|
constructor(config: oNodeConnectionManagerConfig);
|
|
15
14
|
/**
|
|
16
15
|
* Set up listeners to maintain connection cache state
|
|
17
16
|
*/
|
|
18
|
-
|
|
17
|
+
protected setupConnectionListeners(): void;
|
|
19
18
|
/**
|
|
20
19
|
* Build a stable cache key from an address.
|
|
21
20
|
*
|
|
22
21
|
* We key the cache by address value (e.g., "o://my-tool") to maintain
|
|
23
22
|
* a simple one-to-one mapping between addresses and connections.
|
|
24
23
|
*/
|
|
25
|
-
|
|
24
|
+
protected getAddressKey(address: oAddress): string | null;
|
|
26
25
|
/**
|
|
27
26
|
* Extract peer ID string from an address
|
|
28
27
|
* @param address - The address to extract peer ID from
|
|
29
28
|
* @returns The peer ID string or null if not found
|
|
30
29
|
*/
|
|
31
|
-
|
|
30
|
+
protected getPeerIdFromAddress(address: oAddress): string | null;
|
|
32
31
|
/**
|
|
33
|
-
*
|
|
34
|
-
*
|
|
32
|
+
* Get the first valid (open) connection for the given address key.
|
|
33
|
+
* Cleans up stale connections from the cache automatically.
|
|
35
34
|
*
|
|
36
|
-
* @param
|
|
37
|
-
* @param reuseContext - If true, prefer inbound connections; otherwise prefer outbound
|
|
38
|
-
* @returns The best connection or null if none are suitable
|
|
39
|
-
*/
|
|
40
|
-
private selectBestConnection;
|
|
41
|
-
/**
|
|
42
|
-
* Get a cached oNodeConnection for the given p2p connection if it exists and is valid.
|
|
43
|
-
* @param p2pConnection - The p2p connection to look up
|
|
35
|
+
* @param addressKey - The address key to look up
|
|
44
36
|
* @returns A valid oNodeConnection or null if none found
|
|
45
37
|
*/
|
|
46
|
-
|
|
38
|
+
protected getValidConnection(addressKey: string): oNodeConnection | null;
|
|
47
39
|
/**
|
|
48
|
-
* Cache an oNodeConnection by its
|
|
40
|
+
* Cache an oNodeConnection by its address key.
|
|
49
41
|
* @param conn - The oNodeConnection to cache
|
|
42
|
+
* @param addressKey - The address key to cache under
|
|
43
|
+
*/
|
|
44
|
+
protected cacheConnection(conn: oNodeConnection, addressKey: string): void;
|
|
45
|
+
/**
|
|
46
|
+
* Get or create a raw p2p connection to the given address.
|
|
47
|
+
* Subclasses can override connect() and use this method to get the underlying p2p connection.
|
|
50
48
|
*/
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
private performDial;
|
|
49
|
+
protected getOrCreateP2pConnection(nextHopAddress: oAddress, addressKey: string): Promise<Connection>;
|
|
50
|
+
protected performDial(nextHopAddress: oAddress, addressKey: string): Promise<Connection>;
|
|
54
51
|
answer(config: oConnectionConfig & {
|
|
55
52
|
p2pConnection: Connection;
|
|
56
53
|
reuse?: boolean;
|
|
57
54
|
}): Promise<oNodeConnection>;
|
|
58
55
|
/**
|
|
59
|
-
* Connect to a given address, reusing oNodeConnection
|
|
56
|
+
* Connect to a given address, reusing oNodeConnection when possible
|
|
60
57
|
* @param config - Connection configuration
|
|
61
58
|
* @returns The connection object
|
|
62
59
|
*/
|
|
@@ -68,11 +65,11 @@ export declare class oNodeConnectionManager extends oConnectionManager {
|
|
|
68
65
|
*/
|
|
69
66
|
isCached(address: oAddress): boolean;
|
|
70
67
|
/**
|
|
71
|
-
* Get an existing
|
|
68
|
+
* Get an existing cached oNodeConnection for the target address
|
|
72
69
|
* @param address - The address to get a connection for
|
|
73
|
-
* @returns The
|
|
70
|
+
* @returns The oNodeConnection or null if not found
|
|
74
71
|
*/
|
|
75
|
-
|
|
72
|
+
getCachedConnection(address: oAddress): oNodeConnection | null;
|
|
76
73
|
/**
|
|
77
74
|
* Get cache statistics for monitoring and debugging
|
|
78
75
|
* @returns Object containing cache statistics
|
|
@@ -81,7 +78,6 @@ export declare class oNodeConnectionManager extends oConnectionManager {
|
|
|
81
78
|
cachedAddresses: number;
|
|
82
79
|
totalCachedConnections: number;
|
|
83
80
|
pendingDials: number;
|
|
84
|
-
cachedNodeConnections: number;
|
|
85
81
|
connectionsByPeer: Array<{
|
|
86
82
|
peerId: string;
|
|
87
83
|
status: string;
|
|
@@ -93,10 +89,5 @@ export declare class oNodeConnectionManager extends oConnectionManager {
|
|
|
93
89
|
* @returns Number of connections removed
|
|
94
90
|
*/
|
|
95
91
|
cleanupStaleConnections(): number;
|
|
96
|
-
/**
|
|
97
|
-
* Clean up all stale oNodeConnections from cache
|
|
98
|
-
* @returns Number of oNodeConnections removed
|
|
99
|
-
*/
|
|
100
|
-
cleanupStaleNodeConnections(): number;
|
|
101
92
|
}
|
|
102
93
|
//# sourceMappingURL=o-node-connection.manager.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"o-node-connection.manager.d.ts","sourceRoot":"","sources":["../../../src/connection/o-node-connection.manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAChF,OAAO,EAAE,MAAM,EAAE,UAAU,
|
|
1
|
+
{"version":3,"file":"o-node-connection.manager.d.ts","sourceRoot":"","sources":["../../../src/connection/o-node-connection.manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAChF,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,EAAE,4BAA4B,EAAE,MAAM,kDAAkD,CAAC;AAEhG,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAEzD,qBAAa,sBAAuB,SAAQ,kBAAkB;IAQhD,QAAQ,CAAC,MAAM,EAAE,4BAA4B;IAPzD,SAAS,CAAC,OAAO,EAAE,MAAM,CAAC;IAC1B,SAAS,CAAC,oBAAoB,CAAC,EAAE,MAAM,CAAC;IACxC,SAAS,CAAC,qBAAqB,CAAC,EAAE,MAAM,CAAC;IACzC,iEAAiE;IACjE,SAAS,CAAC,iBAAiB,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,EAAE,CAAC,CAAa;IACxE,SAAS,CAAC,qBAAqB,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC,CAAa;gBAEzD,MAAM,EAAE,4BAA4B;IAWzD;;OAEG;IACH,SAAS,CAAC,wBAAwB,IAAI,IAAI;IA+B1C;;;;;OAKG;IACH,SAAS,CAAC,aAAa,CAAC,OAAO,EAAE,QAAQ,GAAG,MAAM,GAAG,IAAI;IASzD;;;;OAIG;IACH,SAAS,CAAC,oBAAoB,CAAC,OAAO,EAAE,QAAQ,GAAG,MAAM,GAAG,IAAI;IAahE;;;;;;OAMG;IACH,SAAS,CAAC,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI;IAkBxE;;;;OAIG;IACH,SAAS,CAAC,eAAe,CAAC,IAAI,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI;IAY1E;;;OAGG;cACa,wBAAwB,CACtC,cAAc,EAAE,QAAQ,EACxB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,UAAU,CAAC;cAoCN,WAAW,CACzB,cAAc,EAAE,QAAQ,EACxB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,UAAU,CAAC;IAqBhB,MAAM,CACV,MAAM,EAAE,iBAAiB,GAAG;QAAE,aAAa,EAAE,UAAU,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,GACzE,OAAO,CAAC,eAAe,CAAC;IAqD3B;;;;OAIG;IACG,OAAO,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,eAAe,CAAC;IAyDlE;;;;OAIG;IACH,QAAQ,CAAC,OAAO,EAAE,QAAQ,GAAG,OAAO;IAcpC;;;;OAIG;IACH,mBAAmB,CAAC,OAAO,EAAE,QAAQ,GAAG,eAAe,GAAG,IAAI;IAc9D;;;OAGG;IACH,aAAa,IAAI;QACf,eAAe,EAAE,MAAM,CAAC;QACxB,sBAAsB,EAAE,MAAM,CAAC;QAC/B,YAAY,EAAE,MAAM,CAAC;QACrB,iBAAiB,EAAE,KAAK,CAAC;YACvB,MAAM,EAAE,MAAM,CAAC;YACf,MAAM,EAAE,MAAM,CAAC;YACf,UAAU,EAAE,MAAM,CAAC;SACpB,CAAC,CAAC;KACJ;IAyBD;;;OAGG;IACH,uBAAuB,IAAI,MAAM;CAuBlC"}
|
|
@@ -4,10 +4,9 @@ export class oNodeConnectionManager extends oConnectionManager {
|
|
|
4
4
|
constructor(config) {
|
|
5
5
|
super(config);
|
|
6
6
|
this.config = config;
|
|
7
|
-
|
|
7
|
+
/** Single cache of oNodeConnection instances keyed by address */
|
|
8
|
+
this.cachedConnections = new Map();
|
|
8
9
|
this.pendingDialsByAddress = new Map();
|
|
9
|
-
/** Cache of oNodeConnection instances keyed by p2p connection ID */
|
|
10
|
-
this.nodeConnectionByConnectionId = new Map();
|
|
11
10
|
this.p2pNode = config.p2pNode;
|
|
12
11
|
this.defaultReadTimeoutMs = config.defaultReadTimeoutMs;
|
|
13
12
|
this.defaultDrainTimeoutMs = config.defaultDrainTimeoutMs;
|
|
@@ -24,31 +23,18 @@ export class oNodeConnectionManager extends oConnectionManager {
|
|
|
24
23
|
if (!connection) {
|
|
25
24
|
return;
|
|
26
25
|
}
|
|
27
|
-
// Clean up libp2p connection cache
|
|
28
|
-
// for (const [
|
|
29
|
-
// addressKey,
|
|
30
|
-
// cachedConnections,
|
|
31
|
-
// ] of this.connectionsByAddress.entries()) {
|
|
32
|
-
// const index = cachedConnections.indexOf(connection);
|
|
33
|
-
// if (index !== -1) {
|
|
34
|
-
// this.logger.debug(
|
|
35
|
-
// 'Connection closed, removing from cache for address:',
|
|
36
|
-
// addressKey,
|
|
37
|
-
// );
|
|
38
|
-
// cachedConnections.splice(index, 1);
|
|
39
|
-
// // Remove the address key entirely if no connections remain
|
|
40
|
-
// if (cachedConnections.length === 0) {
|
|
41
|
-
// this.connectionsByAddress.delete(addressKey);
|
|
42
|
-
// } else {
|
|
43
|
-
// this.connectionsByAddress.set(addressKey, cachedConnections);
|
|
44
|
-
// }
|
|
45
|
-
// }
|
|
46
|
-
// }
|
|
47
|
-
// Clean up oNodeConnection cache by connection ID
|
|
48
26
|
const connectionId = connection.id;
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
27
|
+
// Clean up cached connections by filtering out the closed connection
|
|
28
|
+
for (const [key, conns] of this.cachedConnections.entries()) {
|
|
29
|
+
const filtered = conns.filter((c) => c.p2pConnection.id !== connectionId);
|
|
30
|
+
if (filtered.length === 0) {
|
|
31
|
+
this.logger.debug('Connection closed, removing all cached connections for address:', key);
|
|
32
|
+
this.cachedConnections.delete(key);
|
|
33
|
+
}
|
|
34
|
+
else if (filtered.length !== conns.length) {
|
|
35
|
+
this.logger.debug('Connection closed, updating cached connections for address:', key);
|
|
36
|
+
this.cachedConnections.set(key, filtered);
|
|
37
|
+
}
|
|
52
38
|
}
|
|
53
39
|
});
|
|
54
40
|
}
|
|
@@ -86,117 +72,53 @@ export class oNodeConnectionManager extends oConnectionManager {
|
|
|
86
72
|
}
|
|
87
73
|
}
|
|
88
74
|
/**
|
|
89
|
-
*
|
|
90
|
-
*
|
|
75
|
+
* Get the first valid (open) connection for the given address key.
|
|
76
|
+
* Cleans up stale connections from the cache automatically.
|
|
91
77
|
*
|
|
92
|
-
* @param
|
|
93
|
-
* @
|
|
94
|
-
* @returns The best connection or null if none are suitable
|
|
78
|
+
* @param addressKey - The address key to look up
|
|
79
|
+
* @returns A valid oNodeConnection or null if none found
|
|
95
80
|
*/
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
.map((conn) => ({
|
|
105
|
-
conn,
|
|
106
|
-
activeStreamCount: conn.streams.filter((s) => s.protocol.includes('/o/') &&
|
|
107
|
-
s.status === 'open' &&
|
|
108
|
-
s.writeStatus === 'writable' &&
|
|
109
|
-
s.remoteReadStatus === 'readable').length,
|
|
110
|
-
}))
|
|
111
|
-
.filter((item) => item.activeStreamCount > 0)
|
|
112
|
-
.sort((a, b) => b.activeStreamCount - a.activeStreamCount);
|
|
113
|
-
if (connectionsWithStreams.length > 0) {
|
|
114
|
-
this.logger.debug('Selected connection with active streams', {
|
|
115
|
-
streamCount: connectionsWithStreams[0].activeStreamCount,
|
|
116
|
-
});
|
|
117
|
-
return connectionsWithStreams[0].conn;
|
|
118
|
-
}
|
|
119
|
-
// Priority 2: Based on reuse context
|
|
120
|
-
if (reuseContext) {
|
|
121
|
-
// Prefer inbound connections
|
|
122
|
-
const inbound = openConnections.find((c) => c.direction === 'inbound' || !c.direction);
|
|
123
|
-
if (inbound) {
|
|
124
|
-
this.logger.debug('Selected inbound connection (reuse context)');
|
|
125
|
-
return inbound;
|
|
81
|
+
getValidConnection(addressKey) {
|
|
82
|
+
const connections = this.cachedConnections.get(addressKey) || [];
|
|
83
|
+
// Filter to open connections
|
|
84
|
+
const valid = connections.filter((c) => c.p2pConnection?.status === 'open');
|
|
85
|
+
// Update cache if we cleaned any stale connections
|
|
86
|
+
if (valid.length !== connections.length) {
|
|
87
|
+
if (valid.length === 0) {
|
|
88
|
+
this.cachedConnections.delete(addressKey);
|
|
126
89
|
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
// Prefer outbound connections
|
|
130
|
-
const outbound = openConnections.find((c) => c.direction === 'outbound');
|
|
131
|
-
if (outbound) {
|
|
132
|
-
this.logger.debug('Selected outbound connection (non-reuse context)');
|
|
133
|
-
return outbound;
|
|
90
|
+
else {
|
|
91
|
+
this.cachedConnections.set(addressKey, valid);
|
|
134
92
|
}
|
|
135
93
|
}
|
|
136
|
-
|
|
137
|
-
this.logger.debug('Selected first available open connection', connectionsWithStreams);
|
|
138
|
-
return openConnections[0];
|
|
94
|
+
return valid[0] ?? null;
|
|
139
95
|
}
|
|
140
96
|
/**
|
|
141
|
-
*
|
|
142
|
-
* @param
|
|
143
|
-
* @
|
|
97
|
+
* Cache an oNodeConnection by its address key.
|
|
98
|
+
* @param conn - The oNodeConnection to cache
|
|
99
|
+
* @param addressKey - The address key to cache under
|
|
144
100
|
*/
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
// Clean up stale entry if connection is no longer open
|
|
151
|
-
if (cached) {
|
|
152
|
-
this.nodeConnectionByConnectionId.delete(p2pConnection.id);
|
|
153
|
-
}
|
|
154
|
-
return null;
|
|
101
|
+
cacheConnection(conn, addressKey) {
|
|
102
|
+
this.logger.debug('Caching connection for address:', addressKey, conn.p2pConnection.id, conn.p2pConnection.direction);
|
|
103
|
+
const existing = this.cachedConnections.get(addressKey) || [];
|
|
104
|
+
existing.push(conn);
|
|
105
|
+
this.cachedConnections.set(addressKey, existing);
|
|
155
106
|
}
|
|
156
107
|
/**
|
|
157
|
-
*
|
|
158
|
-
*
|
|
108
|
+
* Get or create a raw p2p connection to the given address.
|
|
109
|
+
* Subclasses can override connect() and use this method to get the underlying p2p connection.
|
|
159
110
|
*/
|
|
160
|
-
|
|
161
|
-
this
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
if (!addressKey) {
|
|
170
|
-
throw new Error(`Unable to extract address key from address: ${nextHopAddress.toString()}`);
|
|
171
|
-
}
|
|
172
|
-
// Check if we have cached connections by address key
|
|
173
|
-
const cachedConnections = this.connectionsByAddress.get(addressKey) || [];
|
|
174
|
-
const bestConnection = this.selectBestConnection(cachedConnections, false);
|
|
175
|
-
if (bestConnection) {
|
|
176
|
-
this.logger.debug('Reusing cached connection for address:', nextHopAddress?.value, bestConnection.id);
|
|
177
|
-
return bestConnection;
|
|
178
|
-
}
|
|
179
|
-
// Clean up stale connections if they exist but are not open
|
|
180
|
-
if (cachedConnections.length > 0) {
|
|
181
|
-
const openConnections = cachedConnections.filter((c) => c.status === 'open');
|
|
182
|
-
if (openConnections.length === 0) {
|
|
183
|
-
this.logger.debug('Removing all stale connections for address:', addressKey);
|
|
184
|
-
this.connectionsByAddress.delete(addressKey);
|
|
185
|
-
}
|
|
186
|
-
else if (openConnections.length < cachedConnections.length) {
|
|
187
|
-
this.logger.debug('Cleaning up some stale connections for address:', addressKey);
|
|
188
|
-
this.connectionsByAddress.set(addressKey, openConnections);
|
|
111
|
+
async getOrCreateP2pConnection(nextHopAddress, addressKey) {
|
|
112
|
+
// Check if libp2p already has an active connection for this peer
|
|
113
|
+
const peerId = this.getPeerIdFromAddress(nextHopAddress);
|
|
114
|
+
if (peerId) {
|
|
115
|
+
const connections = this.p2pNode.getConnections();
|
|
116
|
+
const existingConnection = connections.find((conn) => conn.remotePeer?.toString() === peerId && conn.status === 'open');
|
|
117
|
+
if (existingConnection) {
|
|
118
|
+
this.logger.debug('Found existing libp2p connection for address:', addressKey);
|
|
119
|
+
return existingConnection;
|
|
189
120
|
}
|
|
190
121
|
}
|
|
191
|
-
// Check if libp2p has an active connection for this address
|
|
192
|
-
const libp2pConnection = this.getCachedLibp2pConnection(nextHopAddress);
|
|
193
|
-
if (libp2pConnection && libp2pConnection.status === 'open') {
|
|
194
|
-
this.logger.debug('Caching existing libp2p connection for address:', addressKey);
|
|
195
|
-
const connections = this.connectionsByAddress.get(addressKey) || [];
|
|
196
|
-
connections.push(libp2pConnection);
|
|
197
|
-
this.connectionsByAddress.set(addressKey, connections);
|
|
198
|
-
return libp2pConnection;
|
|
199
|
-
}
|
|
200
122
|
// Check if dial is already in progress for this address key
|
|
201
123
|
const pendingDial = this.pendingDialsByAddress.get(addressKey);
|
|
202
124
|
if (pendingDial) {
|
|
@@ -207,12 +129,7 @@ export class oNodeConnectionManager extends oConnectionManager {
|
|
|
207
129
|
const dialPromise = this.performDial(nextHopAddress, addressKey);
|
|
208
130
|
this.pendingDialsByAddress.set(addressKey, dialPromise);
|
|
209
131
|
try {
|
|
210
|
-
|
|
211
|
-
// Add the established connection to the cache array
|
|
212
|
-
const connections = this.connectionsByAddress.get(addressKey) || [];
|
|
213
|
-
connections.push(connection);
|
|
214
|
-
this.connectionsByAddress.set(addressKey, connections);
|
|
215
|
-
return connection;
|
|
132
|
+
return await dialPromise;
|
|
216
133
|
}
|
|
217
134
|
finally {
|
|
218
135
|
this.pendingDialsByAddress.delete(addressKey);
|
|
@@ -233,6 +150,17 @@ export class oNodeConnectionManager extends oConnectionManager {
|
|
|
233
150
|
}
|
|
234
151
|
async answer(config) {
|
|
235
152
|
const { address, nextHopAddress, callerAddress, readTimeoutMs, drainTimeoutMs, p2pConnection, reuse, } = config;
|
|
153
|
+
const addressKey = this.getAddressKey(nextHopAddress);
|
|
154
|
+
if (!addressKey) {
|
|
155
|
+
this.logger.error('Failed to generate an address key for address:', nextHopAddress);
|
|
156
|
+
throw new Error(`Unable to extract address key from address: ${nextHopAddress.toString()}`);
|
|
157
|
+
}
|
|
158
|
+
// Check if we already have a cached connection for this address
|
|
159
|
+
const existingConnection = this.getValidConnection(addressKey);
|
|
160
|
+
if (existingConnection) {
|
|
161
|
+
this.logger.debug('Reusing cached connection for answer:', addressKey, existingConnection.p2pConnection.id);
|
|
162
|
+
return existingConnection;
|
|
163
|
+
}
|
|
236
164
|
const connection = new oNodeConnection({
|
|
237
165
|
nextHopAddress: nextHopAddress,
|
|
238
166
|
address: address,
|
|
@@ -246,38 +174,33 @@ export class oNodeConnectionManager extends oConnectionManager {
|
|
|
246
174
|
requestHandler: config.requestHandler ?? undefined,
|
|
247
175
|
reusePolicy: reuse ? 'reuse' : 'none',
|
|
248
176
|
});
|
|
249
|
-
// Cache the new connection
|
|
250
|
-
this.
|
|
251
|
-
const addressKey = this.getAddressKey(nextHopAddress);
|
|
252
|
-
if (addressKey) {
|
|
253
|
-
const connections = this.connectionsByAddress.get(addressKey) || [];
|
|
254
|
-
// Only add if not already in the cache
|
|
255
|
-
if (!connections.includes(p2pConnection)) {
|
|
256
|
-
connections.push(p2pConnection);
|
|
257
|
-
this.connectionsByAddress.set(addressKey, connections);
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
else {
|
|
261
|
-
this.logger.error('Should not happen! Failed to generate an address key for address:', nextHopAddress);
|
|
262
|
-
}
|
|
177
|
+
// Cache the new connection
|
|
178
|
+
this.cacheConnection(connection, addressKey);
|
|
263
179
|
return connection;
|
|
264
180
|
}
|
|
265
181
|
/**
|
|
266
|
-
* Connect to a given address, reusing oNodeConnection
|
|
182
|
+
* Connect to a given address, reusing oNodeConnection when possible
|
|
267
183
|
* @param config - Connection configuration
|
|
268
184
|
* @returns The connection object
|
|
269
185
|
*/
|
|
270
186
|
async connect(config) {
|
|
271
187
|
const { address, nextHopAddress, callerAddress, readTimeoutMs, drainTimeoutMs, } = config;
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
const
|
|
188
|
+
if (!nextHopAddress) {
|
|
189
|
+
throw new Error('Invalid address passed');
|
|
190
|
+
}
|
|
191
|
+
const addressKey = this.getAddressKey(nextHopAddress);
|
|
192
|
+
if (!addressKey) {
|
|
193
|
+
throw new Error(`Unable to extract address key from address: ${nextHopAddress.toString()}`);
|
|
194
|
+
}
|
|
195
|
+
// Check for existing valid cached connection
|
|
196
|
+
const existingConnection = this.getValidConnection(addressKey);
|
|
276
197
|
if (existingConnection) {
|
|
277
|
-
this.logger.debug('Reusing cached
|
|
198
|
+
this.logger.debug('Reusing cached connection for address:', addressKey, existingConnection.p2pConnection.id);
|
|
278
199
|
return existingConnection;
|
|
279
200
|
}
|
|
280
|
-
//
|
|
201
|
+
// Get or create the underlying p2p connection
|
|
202
|
+
const p2pConnection = await this.getOrCreateP2pConnection(nextHopAddress, addressKey);
|
|
203
|
+
// Create new oNodeConnection
|
|
281
204
|
const connection = new oNodeConnection({
|
|
282
205
|
nextHopAddress: nextHopAddress,
|
|
283
206
|
address: address,
|
|
@@ -290,8 +213,8 @@ export class oNodeConnectionManager extends oConnectionManager {
|
|
|
290
213
|
runOnLimitedConnection: this.config.runOnLimitedConnection ?? false,
|
|
291
214
|
requestHandler: config.requestHandler ?? undefined,
|
|
292
215
|
});
|
|
293
|
-
// Cache the new connection
|
|
294
|
-
this.
|
|
216
|
+
// Cache the new connection
|
|
217
|
+
this.cacheConnection(connection, addressKey);
|
|
295
218
|
return connection;
|
|
296
219
|
}
|
|
297
220
|
/**
|
|
@@ -305,30 +228,7 @@ export class oNodeConnectionManager extends oConnectionManager {
|
|
|
305
228
|
if (!addressKey) {
|
|
306
229
|
return false;
|
|
307
230
|
}
|
|
308
|
-
|
|
309
|
-
const cachedConnections = this.connectionsByAddress.get(addressKey) || [];
|
|
310
|
-
const bestConnection = this.selectBestConnection(cachedConnections, false);
|
|
311
|
-
if (bestConnection) {
|
|
312
|
-
return true;
|
|
313
|
-
}
|
|
314
|
-
// Fall back to checking libp2p's connections
|
|
315
|
-
const peerId = this.getPeerIdFromAddress(address);
|
|
316
|
-
// the following works since the peer id param is not really required: https://github.com/libp2p/js-libp2p/blob/0bbf5021b53938b2bffcffca6c13c479a95c2a60/packages/libp2p/src/connection-manager/index.ts#L508
|
|
317
|
-
const connections = this.p2pNode.getConnections(peerId); // ignore since converting to a proper peer id breaks the browser implementation
|
|
318
|
-
// Check if we have at least one open connection
|
|
319
|
-
const hasOpenConnection = connections.some((conn) => conn.status === 'open');
|
|
320
|
-
// If libp2p has an open connection, update our cache
|
|
321
|
-
if (hasOpenConnection) {
|
|
322
|
-
const openConnection = connections.find((conn) => conn.status === 'open');
|
|
323
|
-
if (openConnection) {
|
|
324
|
-
const existingConnections = this.connectionsByAddress.get(addressKey) || [];
|
|
325
|
-
if (!existingConnections.includes(openConnection)) {
|
|
326
|
-
existingConnections.push(openConnection);
|
|
327
|
-
this.connectionsByAddress.set(addressKey, existingConnections);
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
return hasOpenConnection;
|
|
231
|
+
return this.getValidConnection(addressKey) !== null;
|
|
332
232
|
}
|
|
333
233
|
catch (error) {
|
|
334
234
|
this.logger.debug('Error checking cached connection:', error);
|
|
@@ -336,48 +236,17 @@ export class oNodeConnectionManager extends oConnectionManager {
|
|
|
336
236
|
}
|
|
337
237
|
}
|
|
338
238
|
/**
|
|
339
|
-
* Get an existing
|
|
239
|
+
* Get an existing cached oNodeConnection for the target address
|
|
340
240
|
* @param address - The address to get a connection for
|
|
341
|
-
* @returns The
|
|
241
|
+
* @returns The oNodeConnection or null if not found
|
|
342
242
|
*/
|
|
343
|
-
|
|
243
|
+
getCachedConnection(address) {
|
|
344
244
|
try {
|
|
345
245
|
const addressKey = this.getAddressKey(address);
|
|
346
246
|
if (!addressKey) {
|
|
347
247
|
return null;
|
|
348
248
|
}
|
|
349
|
-
|
|
350
|
-
const cachedConnections = this.connectionsByAddress.get(addressKey) || [];
|
|
351
|
-
const bestConnection = this.selectBestConnection(cachedConnections, false);
|
|
352
|
-
if (bestConnection) {
|
|
353
|
-
return bestConnection;
|
|
354
|
-
}
|
|
355
|
-
const peerId = this.getPeerIdFromAddress(address);
|
|
356
|
-
if (!peerId) {
|
|
357
|
-
return null;
|
|
358
|
-
}
|
|
359
|
-
// Query libp2p for connections to this peer
|
|
360
|
-
const connections = this.p2pNode.getConnections();
|
|
361
|
-
const filteredConnections = connections.filter((conn) => conn.remotePeer?.toString() === peerId);
|
|
362
|
-
// Find open connections
|
|
363
|
-
const openConnections = filteredConnections.filter((conn) => conn.status === 'open');
|
|
364
|
-
// If we found open connections in libp2p, add them to cache and select best
|
|
365
|
-
if (openConnections.length > 0) {
|
|
366
|
-
const existingConnections = this.connectionsByAddress.get(addressKey) || [];
|
|
367
|
-
// Add any new connections that aren't already cached
|
|
368
|
-
for (const conn of openConnections) {
|
|
369
|
-
if (!existingConnections.includes(conn)) {
|
|
370
|
-
existingConnections.push(conn);
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
this.connectionsByAddress.set(addressKey, existingConnections);
|
|
374
|
-
return this.selectBestConnection(existingConnections, false);
|
|
375
|
-
}
|
|
376
|
-
// Clean up stale cache entries if connections are no longer open
|
|
377
|
-
if (cachedConnections.length > 0) {
|
|
378
|
-
this.connectionsByAddress.delete(addressKey);
|
|
379
|
-
}
|
|
380
|
-
return null;
|
|
249
|
+
return this.getValidConnection(addressKey);
|
|
381
250
|
}
|
|
382
251
|
catch (error) {
|
|
383
252
|
this.logger.debug('Error getting cached connection:', error);
|
|
@@ -390,20 +259,19 @@ export class oNodeConnectionManager extends oConnectionManager {
|
|
|
390
259
|
*/
|
|
391
260
|
getCacheStats() {
|
|
392
261
|
const allConnections = [];
|
|
393
|
-
for (const [addressKey, connections
|
|
262
|
+
for (const [addressKey, connections] of this.cachedConnections.entries()) {
|
|
394
263
|
for (const conn of connections) {
|
|
395
264
|
allConnections.push({
|
|
396
|
-
peerId: conn.remotePeer?.toString() ?? 'unknown',
|
|
397
|
-
status: conn.status,
|
|
265
|
+
peerId: conn.p2pConnection?.remotePeer?.toString() ?? 'unknown',
|
|
266
|
+
status: conn.p2pConnection?.status ?? 'unknown',
|
|
398
267
|
addressKey,
|
|
399
268
|
});
|
|
400
269
|
}
|
|
401
270
|
}
|
|
402
271
|
return {
|
|
403
|
-
cachedAddresses: this.
|
|
272
|
+
cachedAddresses: this.cachedConnections.size,
|
|
404
273
|
totalCachedConnections: allConnections.length,
|
|
405
274
|
pendingDials: this.pendingDialsByAddress.size,
|
|
406
|
-
cachedNodeConnections: this.nodeConnectionByConnectionId.size,
|
|
407
275
|
connectionsByPeer: allConnections,
|
|
408
276
|
};
|
|
409
277
|
}
|
|
@@ -413,18 +281,16 @@ export class oNodeConnectionManager extends oConnectionManager {
|
|
|
413
281
|
*/
|
|
414
282
|
cleanupStaleConnections() {
|
|
415
283
|
let removed = 0;
|
|
416
|
-
for (const [addressKey, connections
|
|
417
|
-
const openConnections = connections.filter((conn) => conn.status === 'open');
|
|
284
|
+
for (const [addressKey, connections] of this.cachedConnections.entries()) {
|
|
285
|
+
const openConnections = connections.filter((conn) => conn.p2pConnection?.status === 'open');
|
|
418
286
|
const staleCount = connections.length - openConnections.length;
|
|
419
287
|
if (staleCount > 0) {
|
|
420
288
|
removed += staleCount;
|
|
421
289
|
if (openConnections.length === 0) {
|
|
422
|
-
|
|
423
|
-
this.connectionsByAddress.delete(addressKey);
|
|
290
|
+
this.cachedConnections.delete(addressKey);
|
|
424
291
|
}
|
|
425
292
|
else {
|
|
426
|
-
|
|
427
|
-
this.connectionsByAddress.set(addressKey, openConnections);
|
|
293
|
+
this.cachedConnections.set(addressKey, openConnections);
|
|
428
294
|
}
|
|
429
295
|
}
|
|
430
296
|
}
|
|
@@ -433,21 +299,4 @@ export class oNodeConnectionManager extends oConnectionManager {
|
|
|
433
299
|
}
|
|
434
300
|
return removed;
|
|
435
301
|
}
|
|
436
|
-
/**
|
|
437
|
-
* Clean up all stale oNodeConnections from cache
|
|
438
|
-
* @returns Number of oNodeConnections removed
|
|
439
|
-
*/
|
|
440
|
-
cleanupStaleNodeConnections() {
|
|
441
|
-
let removed = 0;
|
|
442
|
-
for (const [connectionId, conn,] of this.nodeConnectionByConnectionId.entries()) {
|
|
443
|
-
if (conn.p2pConnection?.status !== 'open') {
|
|
444
|
-
this.nodeConnectionByConnectionId.delete(connectionId);
|
|
445
|
-
removed++;
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
if (removed > 0) {
|
|
449
|
-
this.logger.debug(`Cleaned up ${removed} stale oNodeConnections`);
|
|
450
|
-
}
|
|
451
|
-
return removed;
|
|
452
|
-
}
|
|
453
302
|
}
|
package/dist/src/index.d.ts
CHANGED
package/dist/src/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,kBAAkB,CAAC;AACjC,cAAc
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,kBAAkB,CAAC;AACjC,cAAc,+BAA+B,CAAC;AAC9C,cAAc,+BAA+B,CAAC;AAC9C,cAAc,uBAAuB,CAAC;AACtC,cAAc,kBAAkB,CAAC;AACjC,cAAc,kBAAkB,CAAC;AACjC,cAAc,oCAAoC,CAAC;AACnD,cAAc,mBAAmB,CAAC;AAClC,cAAc,kCAAkC,CAAC"}
|
package/dist/src/index.js
CHANGED
package/dist/src/o-node.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"o-node.d.ts","sourceRoot":"","sources":["../../src/o-node.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,MAAM,EACN,YAAY,EAEb,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAEzC,OAAO,EAAE,qBAAqB,EAAE,MAAM,+BAA+B,CAAC;AACtE,OAAO,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAC5D,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAC9D,OAAO,EAKL,QAAQ,EAER,oBAAoB,EAGrB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAC1D,OAAO,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AACpE,OAAO,EAAE,sBAAsB,EAAE,MAAM,2CAA2C,CAAC;AAEnF,OAAO,EAAmB,SAAS,EAAE,MAAM,eAAe,CAAC;AAG3D,OAAO,EAAE,2BAA2B,EAAE,MAAM,8CAA8C,CAAC;AAC3F,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAC9D,OAAO,EAAE,oBAAoB,EAAE,MAAM,sCAAsC,CAAC;AAC5E,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"o-node.d.ts","sourceRoot":"","sources":["../../src/o-node.ts"],"names":[],"mappings":"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"}
|
package/dist/src/o-node.js
CHANGED
|
@@ -1,12 +1,3 @@
|
|
|
1
|
-
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
-
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
-
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
-
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
-
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
-
};
|
|
7
|
-
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
|
-
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
-
};
|
|
10
1
|
import { createNode, defaultLibp2pConfig, KEEP_ALIVE, } from '@olane/o-config';
|
|
11
2
|
import { v4 as uuidv4 } from 'uuid';
|
|
12
3
|
import { oNodeRouter } from './router/o-node.router.js';
|
|
@@ -21,7 +12,6 @@ import { oLeaderResolverFallback } from './router/index.js';
|
|
|
21
12
|
import { oNodeNotificationManager } from './o-node.notification-manager.js';
|
|
22
13
|
import { oReconnectionManager } from './managers/o-reconnection.manager.js';
|
|
23
14
|
import { LockManager } from './utils/lock-manager.js';
|
|
24
|
-
import { Synchronized } from './decorators/synchronized.js';
|
|
25
15
|
export class oNode extends oToolBase {
|
|
26
16
|
constructor(config) {
|
|
27
17
|
super(config);
|
|
@@ -652,15 +642,3 @@ export class oNode extends oToolBase {
|
|
|
652
642
|
}
|
|
653
643
|
}
|
|
654
644
|
}
|
|
655
|
-
__decorate([
|
|
656
|
-
Synchronized('register:parent'),
|
|
657
|
-
__metadata("design:type", Function),
|
|
658
|
-
__metadata("design:paramtypes", []),
|
|
659
|
-
__metadata("design:returntype", Promise)
|
|
660
|
-
], oNode.prototype, "registerParent", null);
|
|
661
|
-
__decorate([
|
|
662
|
-
Synchronized('register:leader'),
|
|
663
|
-
__metadata("design:type", Function),
|
|
664
|
-
__metadata("design:paramtypes", []),
|
|
665
|
-
__metadata("design:returntype", Promise)
|
|
666
|
-
], oNode.prototype, "registerLeader", null);
|
|
@@ -11,6 +11,7 @@ export declare class oNodeTool extends oNodeTool_base {
|
|
|
11
11
|
private streamHandler;
|
|
12
12
|
handleProtocolReuse(address: oAddress): Promise<void>;
|
|
13
13
|
handleProtocol(address: oAddress): Promise<void>;
|
|
14
|
+
initializeProtocols(): Promise<void>;
|
|
14
15
|
initialize(): Promise<void>;
|
|
15
16
|
handleStreamReuse(stream: Stream, connection: Connection): Promise<void>;
|
|
16
17
|
handleStream(stream: Stream, connection: Connection, reuse?: boolean): Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"o-node.tool.d.ts","sourceRoot":"","sources":["../../src/o-node.tool.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAoB,MAAM,eAAe,CAAC;AAErE,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;;AAMrD;;;;GAIG;AACH,qBAAa,SAAU,SAAQ,cAAkB;IAC/C,OAAO,CAAC,aAAa,CAAiB;IAEhC,mBAAmB,CAAC,OAAO,EAAE,QAAQ;IAsBrC,cAAc,CAAC,OAAO,EAAE,QAAQ;IAoBhC,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"o-node.tool.d.ts","sourceRoot":"","sources":["../../src/o-node.tool.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAoB,MAAM,eAAe,CAAC;AAErE,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;;AAMrD;;;;GAIG;AACH,qBAAa,SAAU,SAAQ,cAAkB;IAC/C,OAAO,CAAC,aAAa,CAAiB;IAEhC,mBAAmB,CAAC,OAAO,EAAE,QAAQ;IAsBrC,cAAc,CAAC,OAAO,EAAE,QAAQ;IAoBhC,mBAAmB;IAWnB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAM3B,iBAAiB,CACrB,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,UAAU,GACrB,OAAO,CAAC,IAAI,CAAC;IAIV,YAAY,CAChB,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,UAAU,EACtB,KAAK,CAAC,EAAE,OAAO,GACd,OAAO,CAAC,IAAI,CAAC;IAyCV,cAAc,IAAI,OAAO,CAAC,GAAG,CAAC;IAQ9B,oBAAoB,CAAC,OAAO,EAAE,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC;CAiC5D"}
|
package/dist/src/o-node.tool.js
CHANGED
|
@@ -47,15 +47,19 @@ export class oNodeTool extends oTool(oServerNode) {
|
|
|
47
47
|
});
|
|
48
48
|
await this.handleProtocolReuse(address);
|
|
49
49
|
}
|
|
50
|
-
async
|
|
51
|
-
|
|
52
|
-
this.streamHandler = new StreamHandler(this.logger);
|
|
50
|
+
async initializeProtocols() {
|
|
51
|
+
this.logger.info('Initializing custom protocols for node...');
|
|
53
52
|
await this.handleProtocol(this.address);
|
|
54
53
|
if (this.staticAddress &&
|
|
55
54
|
this.staticAddress?.toString() !== this.address.toString()) {
|
|
56
55
|
await this.handleProtocol(this.staticAddress);
|
|
57
56
|
}
|
|
58
57
|
}
|
|
58
|
+
async initialize() {
|
|
59
|
+
await super.initialize();
|
|
60
|
+
this.streamHandler = new StreamHandler(this.logger);
|
|
61
|
+
await this.initializeProtocols();
|
|
62
|
+
}
|
|
59
63
|
async handleStreamReuse(stream, connection) {
|
|
60
64
|
return this.handleStream(stream, connection, true);
|
|
61
65
|
}
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { Connection } from '@olane/o-config';
|
|
2
2
|
import { oObject } from '@olane/o-core';
|
|
3
3
|
export declare class ConnectionUtils extends oObject {
|
|
4
|
+
/**
|
|
5
|
+
* Waits for a peer to appear in the peer store with sufficient protocol information.
|
|
6
|
+
* Implements retry logic to handle race conditions where peer store is not immediately populated.
|
|
7
|
+
*/
|
|
8
|
+
private static waitForPeerInStore;
|
|
4
9
|
static addressFromConnection(options: {
|
|
5
10
|
currentNode: any;
|
|
6
11
|
connection: Connection;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"connection.utils.d.ts","sourceRoot":"","sources":["../../../src/utils/connection.utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAA2B,MAAM,iBAAiB,CAAC;AACtE,OAAO,EAAuB,OAAO,EAAE,MAAM,eAAe,CAAC;AAI7D,qBAAa,eAAgB,SAAQ,OAAO;
|
|
1
|
+
{"version":3,"file":"connection.utils.d.ts","sourceRoot":"","sources":["../../../src/utils/connection.utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAA2B,MAAM,iBAAiB,CAAC;AACtE,OAAO,EAAuB,OAAO,EAAE,MAAM,eAAe,CAAC;AAI7D,qBAAa,eAAgB,SAAQ,OAAO;IAC1C;;;OAGG;mBACkB,kBAAkB;WAmCnB,qBAAqB,CAAC,OAAO,EAAE;QACjD,WAAW,EAAE,GAAG,CAAC;QACjB,UAAU,EAAE,UAAU,CAAC;KACxB;CA8DF"}
|
|
@@ -2,25 +2,48 @@ import { oError, oErrorCodes, oObject } from '@olane/o-core';
|
|
|
2
2
|
import { oNodeAddress } from '../router/o-node.address.js';
|
|
3
3
|
import { oNodeTransport } from '../router/o-node.transport.js';
|
|
4
4
|
export class ConnectionUtils extends oObject {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Waits for a peer to appear in the peer store with sufficient protocol information.
|
|
7
|
+
* Implements retry logic to handle race conditions where peer store is not immediately populated.
|
|
8
|
+
*/
|
|
9
|
+
static async waitForPeerInStore(p2pNode, remotePeerId) {
|
|
10
|
+
const MAX_RETRIES = 100; // 5 seconds total (100 * 50ms)
|
|
11
|
+
const RETRY_DELAY_MS = 50;
|
|
12
|
+
const MIN_PROTOCOLS = 2;
|
|
13
|
+
for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
|
|
10
14
|
const peers = await p2pNode.peerStore.all();
|
|
11
15
|
const remotePeer = peers.find((peer) => {
|
|
12
|
-
return peer.id.toString() ===
|
|
16
|
+
return peer.id.toString() === remotePeerId.toString();
|
|
13
17
|
});
|
|
14
|
-
if
|
|
15
|
-
|
|
16
|
-
|
|
18
|
+
// Check if peer exists and has sufficient protocol information
|
|
19
|
+
if (remotePeer && remotePeer.protocols.length > MIN_PROTOCOLS) {
|
|
20
|
+
console.log(`Peer found in store after ${attempt} attempts (${attempt * RETRY_DELAY_MS}ms)`);
|
|
21
|
+
return remotePeer;
|
|
22
|
+
}
|
|
23
|
+
// Wait before next retry
|
|
24
|
+
if (attempt < MAX_RETRIES - 1) {
|
|
25
|
+
await new Promise((resolve) => setTimeout(resolve, RETRY_DELAY_MS));
|
|
17
26
|
}
|
|
27
|
+
}
|
|
28
|
+
// Timeout exceeded
|
|
29
|
+
throw new Error(`Timeout waiting for peer ${remotePeerId.toString()} to appear in peer store with sufficient protocols (waited ${MAX_RETRIES * RETRY_DELAY_MS}ms)`);
|
|
30
|
+
}
|
|
31
|
+
// TODO: improve this logic (poor implementation for now)
|
|
32
|
+
static async addressFromConnection(options) {
|
|
33
|
+
try {
|
|
34
|
+
const { currentNode, connection } = options;
|
|
35
|
+
const p2pNode = currentNode.p2pNode;
|
|
36
|
+
// Wait for peer to appear in peer store with sufficient protocol information
|
|
37
|
+
// This handles race conditions where peer store is not immediately populated
|
|
38
|
+
const remotePeer = await this.waitForPeerInStore(p2pNode, connection.remotePeer);
|
|
18
39
|
// Get origin address for comparison
|
|
19
40
|
const originAddress = currentNode.address?.value;
|
|
20
41
|
if (!originAddress) {
|
|
21
42
|
throw new Error('Origin address is not configured');
|
|
22
43
|
}
|
|
23
|
-
|
|
44
|
+
console.log('Deriving address from connection protocols:', remotePeer.protocols);
|
|
45
|
+
const oProtocol = remotePeer.protocols.find((p) => p.startsWith('/o/') &&
|
|
46
|
+
p.includes(currentNode?.address?.protocol) === false);
|
|
24
47
|
if (!oProtocol) {
|
|
25
48
|
throw new Error('Failed to extract remote address, could not find o-protocol in peer protocols.');
|
|
26
49
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@olane/o-node",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.48",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/src/index.js",
|
|
6
6
|
"types": "dist/src/index.d.ts",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"@eslint/eslintrc": "^3.3.1",
|
|
42
42
|
"@eslint/js": "^9.29.0",
|
|
43
|
-
"@olane/o-test": "0.7.
|
|
43
|
+
"@olane/o-test": "0.7.48",
|
|
44
44
|
"@tsconfig/node20": "^20.1.6",
|
|
45
45
|
"@types/jest": "^30.0.0",
|
|
46
46
|
"@types/json5": "^2.2.0",
|
|
@@ -60,13 +60,13 @@
|
|
|
60
60
|
"typescript": "5.4.5"
|
|
61
61
|
},
|
|
62
62
|
"dependencies": {
|
|
63
|
-
"@olane/o-config": "0.7.
|
|
64
|
-
"@olane/o-core": "0.7.
|
|
65
|
-
"@olane/o-protocol": "0.7.
|
|
66
|
-
"@olane/o-tool": "0.7.
|
|
63
|
+
"@olane/o-config": "0.7.48",
|
|
64
|
+
"@olane/o-core": "0.7.48",
|
|
65
|
+
"@olane/o-protocol": "0.7.48",
|
|
66
|
+
"@olane/o-tool": "0.7.48",
|
|
67
67
|
"debug": "^4.4.1",
|
|
68
68
|
"dotenv": "^16.5.0",
|
|
69
69
|
"json5": "^2.2.3"
|
|
70
70
|
},
|
|
71
|
-
"gitHead": "
|
|
71
|
+
"gitHead": "1810818bb9682b586e9db2febcc2a89da5057449"
|
|
72
72
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/decorators/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from './synchronized.js';
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Method decorator that automatically acquires and releases a lock
|
|
3
|
-
* when the decorated method is called. Ensures only one invocation
|
|
4
|
-
* of the method executes at a time.
|
|
5
|
-
*
|
|
6
|
-
* @param lockName - Optional custom lock name. If not provided,
|
|
7
|
-
* defaults to "ClassName:methodName"
|
|
8
|
-
* @param timeoutMs - Optional timeout for lock acquisition in milliseconds
|
|
9
|
-
*
|
|
10
|
-
* @example
|
|
11
|
-
* class MyNode extends oNode {
|
|
12
|
-
* protected lockManager = new LockManager();
|
|
13
|
-
*
|
|
14
|
-
* @Synchronized('register:leader')
|
|
15
|
-
* async registerLeader(): Promise<void> {
|
|
16
|
-
* // This method is automatically synchronized
|
|
17
|
-
* }
|
|
18
|
-
* }
|
|
19
|
-
*/
|
|
20
|
-
export declare function Synchronized(lockName?: string, timeoutMs?: number): (target: any, propertyKey: string, descriptor: PropertyDescriptor) => PropertyDescriptor;
|
|
21
|
-
//# sourceMappingURL=synchronized.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"synchronized.d.ts","sourceRoot":"","sources":["../../../src/decorators/synchronized.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,YAAY,CAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,YAEtD,GAAG,eACE,MAAM,cACP,kBAAkB,wBA8BjC"}
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Method decorator that automatically acquires and releases a lock
|
|
3
|
-
* when the decorated method is called. Ensures only one invocation
|
|
4
|
-
* of the method executes at a time.
|
|
5
|
-
*
|
|
6
|
-
* @param lockName - Optional custom lock name. If not provided,
|
|
7
|
-
* defaults to "ClassName:methodName"
|
|
8
|
-
* @param timeoutMs - Optional timeout for lock acquisition in milliseconds
|
|
9
|
-
*
|
|
10
|
-
* @example
|
|
11
|
-
* class MyNode extends oNode {
|
|
12
|
-
* protected lockManager = new LockManager();
|
|
13
|
-
*
|
|
14
|
-
* @Synchronized('register:leader')
|
|
15
|
-
* async registerLeader(): Promise<void> {
|
|
16
|
-
* // This method is automatically synchronized
|
|
17
|
-
* }
|
|
18
|
-
* }
|
|
19
|
-
*/
|
|
20
|
-
export function Synchronized(lockName, timeoutMs) {
|
|
21
|
-
return function (target, propertyKey, descriptor) {
|
|
22
|
-
const originalMethod = descriptor.value;
|
|
23
|
-
// Generate lock name if not provided
|
|
24
|
-
const finalLockName = lockName || `${target.constructor.name}:${propertyKey}`;
|
|
25
|
-
descriptor.value = async function (...args) {
|
|
26
|
-
// Ensure lockManager exists on the instance
|
|
27
|
-
const lockManager = this.lockManager;
|
|
28
|
-
if (!lockManager) {
|
|
29
|
-
throw new Error(`LockManager not initialized on instance. ` +
|
|
30
|
-
`Ensure 'protected lockManager = new LockManager()' is declared in ${target.constructor.name}`);
|
|
31
|
-
}
|
|
32
|
-
// Execute the method with automatic lock management
|
|
33
|
-
return lockManager.withLock(finalLockName, async () => {
|
|
34
|
-
return originalMethod.apply(this, args);
|
|
35
|
-
}, timeoutMs);
|
|
36
|
-
};
|
|
37
|
-
return descriptor;
|
|
38
|
-
};
|
|
39
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"astream-reuse.spec.d.ts","sourceRoot":"","sources":["../../test/astream-reuse.spec.ts"],"names":[],"mappings":""}
|
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
import { expect } from 'chai';
|
|
2
|
-
import { TestEnvironment } from './helpers/index.js';
|
|
3
|
-
import { NetworkTopologies, } from './helpers/network-builder.js';
|
|
4
|
-
import { oNodeAddress } from '../src/router/o-node.address.js';
|
|
5
|
-
describe('Stream Reuse', () => {
|
|
6
|
-
const env = new TestEnvironment();
|
|
7
|
-
let builder;
|
|
8
|
-
afterEach(async () => {
|
|
9
|
-
if (builder) {
|
|
10
|
-
await builder.cleanup();
|
|
11
|
-
}
|
|
12
|
-
await env.cleanup();
|
|
13
|
-
});
|
|
14
|
-
describe('Stream Reuse with reusePolicy="reuse"', () => {
|
|
15
|
-
it('should reuse the same stream across multiple requests', async () => {
|
|
16
|
-
builder = await NetworkTopologies.twoNode();
|
|
17
|
-
const leader = builder.getNode('o://leader');
|
|
18
|
-
const child = builder.getNode('o://child');
|
|
19
|
-
// Track stream IDs
|
|
20
|
-
const streamIds = [];
|
|
21
|
-
// Make first request
|
|
22
|
-
const response1 = await leader.use(new oNodeAddress(child.address.toString(), child.address.libp2pTransports), {
|
|
23
|
-
method: 'echo',
|
|
24
|
-
params: { message: 'first' },
|
|
25
|
-
});
|
|
26
|
-
expect(response1.result.success).to.be.true;
|
|
27
|
-
// Get active connection to inspect streams
|
|
28
|
-
const connectionManager = leader.connectionManager;
|
|
29
|
-
const connection = connectionManager.getCachedLibp2pConnection(child.address);
|
|
30
|
-
connection.reusePolicy;
|
|
31
|
-
if (connection) {
|
|
32
|
-
// Store initial stream count and first stream ID
|
|
33
|
-
const initialStreamCount = connection.streams.length;
|
|
34
|
-
if (connection.streams.length > 0) {
|
|
35
|
-
streamIds.push(connection.streams[0].id);
|
|
36
|
-
}
|
|
37
|
-
// Make second request
|
|
38
|
-
const response2 = await leader.use(new oNodeAddress(child.address.toString(), child.address.libp2pTransports), {
|
|
39
|
-
method: 'echo',
|
|
40
|
-
params: { message: 'second' },
|
|
41
|
-
});
|
|
42
|
-
expect(response2.result.success).to.be.true;
|
|
43
|
-
// Get stream count after second request
|
|
44
|
-
const finalStreamCount = connection.streams.length;
|
|
45
|
-
if (connection.streams.length > 0) {
|
|
46
|
-
streamIds.push(connection.streams[0].id);
|
|
47
|
-
}
|
|
48
|
-
// With default behavior (reusePolicy='none'), new streams are created
|
|
49
|
-
// This test verifies current behavior
|
|
50
|
-
expect(streamIds.length).to.be.greaterThan(0);
|
|
51
|
-
expect(finalStreamCount).to.be.greaterThan(0);
|
|
52
|
-
}
|
|
53
|
-
});
|
|
54
|
-
});
|
|
55
|
-
describe('Stream Creation with reusePolicy="none"', () => {
|
|
56
|
-
it('should create new streams for each request', async () => {
|
|
57
|
-
builder = await NetworkTopologies.twoNode();
|
|
58
|
-
const leader = builder.getNode('o://leader');
|
|
59
|
-
const child = builder.getNode('o://child');
|
|
60
|
-
// Make multiple requests
|
|
61
|
-
for (let i = 0; i < 3; i++) {
|
|
62
|
-
const response = await leader.use(new oNodeAddress(child.address.toString(), child.address.libp2pTransports), {
|
|
63
|
-
method: 'echo',
|
|
64
|
-
params: { message: `request-${i}` },
|
|
65
|
-
});
|
|
66
|
-
expect(response.result.success).to.be.true;
|
|
67
|
-
}
|
|
68
|
-
// Get connection to verify stream behavior
|
|
69
|
-
const connectionManager = leader.connectionManager;
|
|
70
|
-
const connection = connectionManager.getCachedLibp2pConnection(child.address);
|
|
71
|
-
// With default reusePolicy='none', streams are closed after each request
|
|
72
|
-
// So we expect minimal open streams
|
|
73
|
-
expect(connection).to.exist;
|
|
74
|
-
if (connection) {
|
|
75
|
-
// The number of open streams should be limited since old ones are closed
|
|
76
|
-
expect(connection.streams.length).to.be.lessThan(10);
|
|
77
|
-
}
|
|
78
|
-
});
|
|
79
|
-
});
|
|
80
|
-
describe('Stream Persistence After Transmission', () => {
|
|
81
|
-
it('should close streams after transmission with reusePolicy="none"', async () => {
|
|
82
|
-
builder = await NetworkTopologies.twoNode();
|
|
83
|
-
const leader = builder.getNode('o://leader');
|
|
84
|
-
const child = builder.getNode('o://child');
|
|
85
|
-
// Make a request
|
|
86
|
-
const response = await leader.use(new oNodeAddress(child.address.toString(), child.address.libp2pTransports), {
|
|
87
|
-
method: 'echo',
|
|
88
|
-
params: { message: 'test' },
|
|
89
|
-
});
|
|
90
|
-
expect(response.result.success).to.be.true;
|
|
91
|
-
// Get connection
|
|
92
|
-
const connectionManager = leader.connectionManager;
|
|
93
|
-
const connection = connectionManager.getCachedLibp2pConnection(child.address);
|
|
94
|
-
expect(connection).to.exist;
|
|
95
|
-
if (connection) {
|
|
96
|
-
// Make another request to verify new stream can be created
|
|
97
|
-
const response2 = await leader.use(new oNodeAddress(child.address.toString(), child.address.libp2pTransports), {
|
|
98
|
-
method: 'echo',
|
|
99
|
-
params: { message: 'test2' },
|
|
100
|
-
});
|
|
101
|
-
expect(response2.result.success).to.be.true;
|
|
102
|
-
// Verify connection is still functional
|
|
103
|
-
expect(connection.status).to.equal('open');
|
|
104
|
-
}
|
|
105
|
-
});
|
|
106
|
-
});
|
|
107
|
-
});
|