@olane/o-node 0.7.44 → 0.7.46
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.d.ts +4 -0
- package/dist/src/connection/o-node-connection.d.ts.map +1 -1
- package/dist/src/connection/o-node-connection.js +15 -3
- package/dist/src/connection/o-node-connection.manager.d.ts +32 -2
- package/dist/src/connection/o-node-connection.manager.d.ts.map +1 -1
- package/dist/src/connection/o-node-connection.manager.js +216 -42
- package/dist/src/decorators/index.d.ts +2 -0
- package/dist/src/decorators/index.d.ts.map +1 -0
- package/dist/src/decorators/index.js +1 -0
- package/dist/src/decorators/synchronized.d.ts +21 -0
- package/dist/src/decorators/synchronized.d.ts.map +1 -0
- package/dist/src/decorators/synchronized.js +39 -0
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +1 -0
- package/dist/src/o-node.d.ts +2 -0
- package/dist/src/o-node.d.ts.map +1 -1
- package/dist/src/o-node.js +26 -0
- package/dist/src/o-node.tool.d.ts.map +1 -1
- package/dist/src/o-node.tool.js +5 -1
- package/dist/src/utils/index.d.ts +1 -0
- package/dist/src/utils/index.d.ts.map +1 -1
- package/dist/src/utils/index.js +1 -0
- package/dist/src/utils/lock-manager.d.ts +50 -0
- package/dist/src/utils/lock-manager.d.ts.map +1 -0
- package/dist/src/utils/lock-manager.js +101 -0
- package/package.json +7 -7
|
@@ -13,6 +13,10 @@ export declare class oNodeConnection extends oConnection {
|
|
|
13
13
|
constructor(config: oNodeConnectionConfig);
|
|
14
14
|
get remotePeerId(): import("@olane/o-config").PeerId;
|
|
15
15
|
get remoteAddr(): import("@olane/o-config").Multiaddr;
|
|
16
|
+
/**
|
|
17
|
+
* Get the connection configuration for compatibility checking.
|
|
18
|
+
*/
|
|
19
|
+
get connectionConfig(): oNodeConnectionConfig;
|
|
16
20
|
supportsAddress(address: oNodeAddress): boolean;
|
|
17
21
|
getOrCreateStream(): Promise<oNodeConnectionStream>;
|
|
18
22
|
createStream(): Promise<oNodeConnectionStream>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"o-node-connection.d.ts","sourceRoot":"","sources":["../../../src/connection/o-node-connection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAU,MAAM,iBAAiB,CAAC;AACrD,OAAO,EAEL,WAAW,EAGX,QAAQ,EACR,SAAS,EACV,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,qBAAqB,EAAE,MAAM,0CAA0C,CAAC;AACjF,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,KAAK,EAEV,iBAAiB,EAClB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAC3D,OAAO,EAAE,qBAAqB,EAAE,MAAM,+BAA+B,CAAC;AAGtE,qBAAa,eAAgB,SAAQ,WAAW;IAKlC,SAAS,CAAC,QAAQ,CAAC,MAAM,EAAE,qBAAqB;IAJrD,aAAa,EAAE,UAAU,CAAC;IACjC,SAAS,CAAC,aAAa,EAAE,aAAa,CAAC;IACvC,SAAS,CAAC,WAAW,EAAE,iBAAiB,CAAC;gBAEV,MAAM,EAAE,qBAAqB;
|
|
1
|
+
{"version":3,"file":"o-node-connection.d.ts","sourceRoot":"","sources":["../../../src/connection/o-node-connection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAU,MAAM,iBAAiB,CAAC;AACrD,OAAO,EAEL,WAAW,EAGX,QAAQ,EACR,SAAS,EACV,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,qBAAqB,EAAE,MAAM,0CAA0C,CAAC;AACjF,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,KAAK,EAEV,iBAAiB,EAClB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAC3D,OAAO,EAAE,qBAAqB,EAAE,MAAM,+BAA+B,CAAC;AAGtE,qBAAa,eAAgB,SAAQ,WAAW;IAKlC,SAAS,CAAC,QAAQ,CAAC,MAAM,EAAE,qBAAqB;IAJrD,aAAa,EAAE,UAAU,CAAC;IACjC,SAAS,CAAC,aAAa,EAAE,aAAa,CAAC;IACvC,SAAS,CAAC,WAAW,EAAE,iBAAiB,CAAC;gBAEV,MAAM,EAAE,qBAAqB;IAQ5D,IAAI,YAAY,qCAEf;IAED,IAAI,UAAU,wCAEb;IAED;;OAEG;IACH,IAAI,gBAAgB,IAAI,qBAAqB,CAE5C;IAED,eAAe,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO;IAMzC,iBAAiB,IAAI,OAAO,CAAC,qBAAqB,CAAC;IAkBnD,YAAY,IAAI,OAAO,CAAC,qBAAqB,CAAC;IA6BpD,IAAI,OAAO,IAAI,qBAAqB,EAAE,CAcrC;IAEK,QAAQ,CAAC,OAAO,EAAE,QAAQ,GAAG,OAAO,CAAC,SAAS,CAAC;IA6C/C,YAAY,CAAC,MAAM,EAAE,qBAAqB;IAI1C,KAAK,CAAC,KAAK,EAAE,KAAK;IAMlB,KAAK;CAKZ"}
|
|
@@ -8,6 +8,7 @@ export class oNodeConnection extends oConnection {
|
|
|
8
8
|
this.p2pConnection = config.p2pConnection;
|
|
9
9
|
this.streamHandler = new StreamHandler(this.logger);
|
|
10
10
|
this.reusePolicy = config.reusePolicy ?? 'none';
|
|
11
|
+
console.log('oNodeConnection constructor', this.reusePolicy);
|
|
11
12
|
}
|
|
12
13
|
get remotePeerId() {
|
|
13
14
|
return this.p2pConnection.remotePeer;
|
|
@@ -15,16 +16,27 @@ export class oNodeConnection extends oConnection {
|
|
|
15
16
|
get remoteAddr() {
|
|
16
17
|
return this.p2pConnection.remoteAddr;
|
|
17
18
|
}
|
|
19
|
+
/**
|
|
20
|
+
* Get the connection configuration for compatibility checking.
|
|
21
|
+
*/
|
|
22
|
+
get connectionConfig() {
|
|
23
|
+
return this.config;
|
|
24
|
+
}
|
|
18
25
|
supportsAddress(address) {
|
|
19
26
|
return address.libp2pTransports.some((transport) => {
|
|
20
27
|
return transport.toString() === this.remoteAddr.toString();
|
|
21
28
|
});
|
|
22
29
|
}
|
|
23
30
|
async getOrCreateStream() {
|
|
24
|
-
|
|
25
|
-
|
|
31
|
+
console.log('getOrCreateStream', this.reusePolicy);
|
|
32
|
+
if (this.reusePolicy === 'reuse') {
|
|
33
|
+
this.logger.debug('Reusing stream...');
|
|
26
34
|
// search for streams that allow re-use
|
|
27
|
-
|
|
35
|
+
if (this.streams.length > 0) {
|
|
36
|
+
this.logger.debug('Returning reuse stream: ', this.streams[0].p2pStream.protocol);
|
|
37
|
+
// search for streams that allow re-use
|
|
38
|
+
return this.streams[0];
|
|
39
|
+
}
|
|
28
40
|
}
|
|
29
41
|
return this.createStream();
|
|
30
42
|
}
|
|
@@ -9,6 +9,8 @@ export declare class oNodeConnectionManager extends oConnectionManager {
|
|
|
9
9
|
private defaultDrainTimeoutMs?;
|
|
10
10
|
private connectionsByAddress;
|
|
11
11
|
private pendingDialsByAddress;
|
|
12
|
+
/** Cache of oNodeConnection instances keyed by p2p connection ID */
|
|
13
|
+
private nodeConnectionByConnectionId;
|
|
12
14
|
constructor(config: oNodeConnectionManagerConfig);
|
|
13
15
|
/**
|
|
14
16
|
* Set up listeners to maintain connection cache state
|
|
@@ -27,6 +29,26 @@ export declare class oNodeConnectionManager extends oConnectionManager {
|
|
|
27
29
|
* @returns The peer ID string or null if not found
|
|
28
30
|
*/
|
|
29
31
|
private getPeerIdFromAddress;
|
|
32
|
+
/**
|
|
33
|
+
* Select the best connection from an array of connections.
|
|
34
|
+
* Prioritizes connections with active streams, then by direction based on context.
|
|
35
|
+
*
|
|
36
|
+
* @param connections - Array of connections to choose from
|
|
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
|
|
44
|
+
* @returns A valid oNodeConnection or null if none found
|
|
45
|
+
*/
|
|
46
|
+
private getCachedNodeConnection;
|
|
47
|
+
/**
|
|
48
|
+
* Cache an oNodeConnection by its p2p connection ID for potential reuse.
|
|
49
|
+
* @param conn - The oNodeConnection to cache
|
|
50
|
+
*/
|
|
51
|
+
private cacheNodeConnection;
|
|
30
52
|
getOrCreateConnection(nextHopAddress: oAddress, address: oAddress): Promise<Connection>;
|
|
31
53
|
private performDial;
|
|
32
54
|
answer(config: oConnectionConfig & {
|
|
@@ -34,7 +56,7 @@ export declare class oNodeConnectionManager extends oConnectionManager {
|
|
|
34
56
|
reuse?: boolean;
|
|
35
57
|
}): Promise<oNodeConnection>;
|
|
36
58
|
/**
|
|
37
|
-
* Connect to a given address, reusing libp2p connections when possible
|
|
59
|
+
* Connect to a given address, reusing oNodeConnection and libp2p connections when possible
|
|
38
60
|
* @param config - Connection configuration
|
|
39
61
|
* @returns The connection object
|
|
40
62
|
*/
|
|
@@ -56,11 +78,14 @@ export declare class oNodeConnectionManager extends oConnectionManager {
|
|
|
56
78
|
* @returns Object containing cache statistics
|
|
57
79
|
*/
|
|
58
80
|
getCacheStats(): {
|
|
59
|
-
|
|
81
|
+
cachedAddresses: number;
|
|
82
|
+
totalCachedConnections: number;
|
|
60
83
|
pendingDials: number;
|
|
84
|
+
cachedNodeConnections: number;
|
|
61
85
|
connectionsByPeer: Array<{
|
|
62
86
|
peerId: string;
|
|
63
87
|
status: string;
|
|
88
|
+
addressKey: string;
|
|
64
89
|
}>;
|
|
65
90
|
};
|
|
66
91
|
/**
|
|
@@ -68,5 +93,10 @@ export declare class oNodeConnectionManager extends oConnectionManager {
|
|
|
68
93
|
* @returns Number of connections removed
|
|
69
94
|
*/
|
|
70
95
|
cleanupStaleConnections(): number;
|
|
96
|
+
/**
|
|
97
|
+
* Clean up all stale oNodeConnections from cache
|
|
98
|
+
* @returns Number of oNodeConnections removed
|
|
99
|
+
*/
|
|
100
|
+
cleanupStaleNodeConnections(): number;
|
|
71
101
|
}
|
|
72
102
|
//# 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,EAAU,MAAM,iBAAiB,CAAC;AAC7D,OAAO,EAAE,4BAA4B,EAAE,MAAM,kDAAkD,CAAC;AAEhG,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAGzD,qBAAa,sBAAuB,SAAQ,kBAAkB;
|
|
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,EAAU,MAAM,iBAAiB,CAAC;AAC7D,OAAO,EAAE,4BAA4B,EAAE,MAAM,kDAAkD,CAAC;AAEhG,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAGzD,qBAAa,sBAAuB,SAAQ,kBAAkB;IAUhD,QAAQ,CAAC,MAAM,EAAE,4BAA4B;IATzD,SAAS,CAAC,OAAO,EAAE,MAAM,CAAC;IAC1B,OAAO,CAAC,oBAAoB,CAAC,CAAS;IACtC,OAAO,CAAC,qBAAqB,CAAC,CAAS;IACvC,OAAO,CAAC,oBAAoB,CAAwC;IACpE,OAAO,CAAC,qBAAqB,CAA+C;IAC5E,oEAAoE;IACpE,OAAO,CAAC,4BAA4B,CACxB;gBAES,MAAM,EAAE,4BAA4B;IAWzD;;OAEG;IACH,OAAO,CAAC,wBAAwB;IAyChC;;;;;OAKG;IACH,OAAO,CAAC,aAAa;IASrB;;;;OAIG;IACH,OAAO,CAAC,oBAAoB;IAa5B;;;;;;;OAOG;IACH,OAAO,CAAC,oBAAoB;IA8D5B;;;;OAIG;IACH,OAAO,CAAC,uBAAuB;IAc/B;;;OAGG;IACH,OAAO,CAAC,mBAAmB;IAIrB,qBAAqB,CACzB,cAAc,EAAE,QAAQ,EACxB,OAAO,EAAE,QAAQ,GAChB,OAAO,CAAC,UAAU,CAAC;YAgFR,WAAW;IAwBnB,MAAM,CACV,MAAM,EAAE,iBAAiB,GAAG;QAAE,aAAa,EAAE,UAAU,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,GACzE,OAAO,CAAC,eAAe,CAAC;IA2C3B;;;;OAIG;IACG,OAAO,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,eAAe,CAAC;IA6ClE;;;;OAIG;IACH,QAAQ,CAAC,OAAO,EAAE,QAAQ,GAAG,OAAO;IAiDpC;;;;OAIG;IACH,yBAAyB,CAAC,OAAO,EAAE,QAAQ,GAAG,UAAU,GAAG,IAAI;IA6D/D;;;OAGG;IACH,aAAa,IAAI;QACf,eAAe,EAAE,MAAM,CAAC;QACxB,sBAAsB,EAAE,MAAM,CAAC;QAC/B,YAAY,EAAE,MAAM,CAAC;QACrB,qBAAqB,EAAE,MAAM,CAAC;QAC9B,iBAAiB,EAAE,KAAK,CAAC;YACvB,MAAM,EAAE,MAAM,CAAC;YACf,MAAM,EAAE,MAAM,CAAC;YACf,UAAU,EAAE,MAAM,CAAC;SACpB,CAAC,CAAC;KACJ;IA6BD;;;OAGG;IACH,uBAAuB,IAAI,MAAM;IA6BjC;;;OAGG;IACH,2BAA2B,IAAI,MAAM;CAgBtC"}
|
|
@@ -6,6 +6,8 @@ export class oNodeConnectionManager extends oConnectionManager {
|
|
|
6
6
|
this.config = config;
|
|
7
7
|
this.connectionsByAddress = new Map();
|
|
8
8
|
this.pendingDialsByAddress = new Map();
|
|
9
|
+
/** Cache of oNodeConnection instances keyed by p2p connection ID */
|
|
10
|
+
this.nodeConnectionByConnectionId = new Map();
|
|
9
11
|
this.p2pNode = config.p2pNode;
|
|
10
12
|
this.defaultReadTimeoutMs = config.defaultReadTimeoutMs;
|
|
11
13
|
this.defaultDrainTimeoutMs = config.defaultDrainTimeoutMs;
|
|
@@ -22,11 +24,31 @@ export class oNodeConnectionManager extends oConnectionManager {
|
|
|
22
24
|
if (!connection) {
|
|
23
25
|
return;
|
|
24
26
|
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
+
const connectionId = connection.id;
|
|
49
|
+
if (this.nodeConnectionByConnectionId.has(connectionId)) {
|
|
50
|
+
this.logger.debug('Connection closed, removing oNodeConnection for connection ID:', connectionId);
|
|
51
|
+
this.nodeConnectionByConnectionId.delete(connectionId);
|
|
30
52
|
}
|
|
31
53
|
});
|
|
32
54
|
}
|
|
@@ -63,6 +85,81 @@ export class oNodeConnectionManager extends oConnectionManager {
|
|
|
63
85
|
return null;
|
|
64
86
|
}
|
|
65
87
|
}
|
|
88
|
+
/**
|
|
89
|
+
* Select the best connection from an array of connections.
|
|
90
|
+
* Prioritizes connections with active streams, then by direction based on context.
|
|
91
|
+
*
|
|
92
|
+
* @param connections - Array of connections to choose from
|
|
93
|
+
* @param reuseContext - If true, prefer inbound connections; otherwise prefer outbound
|
|
94
|
+
* @returns The best connection or null if none are suitable
|
|
95
|
+
*/
|
|
96
|
+
selectBestConnection(connections, reuseContext = false) {
|
|
97
|
+
// Filter to only open connections
|
|
98
|
+
const openConnections = connections.filter((c) => c.status === 'open');
|
|
99
|
+
if (openConnections.length === 0) {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
// Priority 1: Connections with active o-protocol streams
|
|
103
|
+
const connectionsWithStreams = openConnections
|
|
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;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
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;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
// Priority 3: Return first open connection
|
|
137
|
+
this.logger.debug('Selected first available open connection', connectionsWithStreams);
|
|
138
|
+
return openConnections[0];
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Get a cached oNodeConnection for the given p2p connection if it exists and is valid.
|
|
142
|
+
* @param p2pConnection - The p2p connection to look up
|
|
143
|
+
* @returns A valid oNodeConnection or null if none found
|
|
144
|
+
*/
|
|
145
|
+
getCachedNodeConnection(p2pConnection) {
|
|
146
|
+
const cached = this.nodeConnectionByConnectionId.get(p2pConnection.id);
|
|
147
|
+
if (cached && cached.p2pConnection?.status === 'open') {
|
|
148
|
+
return cached;
|
|
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;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Cache an oNodeConnection by its p2p connection ID for potential reuse.
|
|
158
|
+
* @param conn - The oNodeConnection to cache
|
|
159
|
+
*/
|
|
160
|
+
cacheNodeConnection(conn) {
|
|
161
|
+
this.nodeConnectionByConnectionId.set(conn.p2pConnection.id, conn);
|
|
162
|
+
}
|
|
66
163
|
async getOrCreateConnection(nextHopAddress, address) {
|
|
67
164
|
if (!nextHopAddress) {
|
|
68
165
|
throw new Error('Invalid address passed');
|
|
@@ -72,22 +169,32 @@ export class oNodeConnectionManager extends oConnectionManager {
|
|
|
72
169
|
if (!addressKey) {
|
|
73
170
|
throw new Error(`Unable to extract address key from address: ${nextHopAddress.toString()}`);
|
|
74
171
|
}
|
|
75
|
-
// Check if we have
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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;
|
|
80
178
|
}
|
|
81
|
-
// Clean up stale
|
|
82
|
-
if (
|
|
83
|
-
|
|
84
|
-
|
|
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);
|
|
189
|
+
}
|
|
85
190
|
}
|
|
86
191
|
// Check if libp2p has an active connection for this address
|
|
87
192
|
const libp2pConnection = this.getCachedLibp2pConnection(nextHopAddress);
|
|
88
193
|
if (libp2pConnection && libp2pConnection.status === 'open') {
|
|
89
194
|
this.logger.debug('Caching existing libp2p connection for address:', addressKey);
|
|
90
|
-
this.connectionsByAddress.
|
|
195
|
+
const connections = this.connectionsByAddress.get(addressKey) || [];
|
|
196
|
+
connections.push(libp2pConnection);
|
|
197
|
+
this.connectionsByAddress.set(addressKey, connections);
|
|
91
198
|
return libp2pConnection;
|
|
92
199
|
}
|
|
93
200
|
// Check if dial is already in progress for this address key
|
|
@@ -101,8 +208,10 @@ export class oNodeConnectionManager extends oConnectionManager {
|
|
|
101
208
|
this.pendingDialsByAddress.set(addressKey, dialPromise);
|
|
102
209
|
try {
|
|
103
210
|
const connection = await dialPromise;
|
|
104
|
-
//
|
|
105
|
-
this.connectionsByAddress.
|
|
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);
|
|
106
215
|
return connection;
|
|
107
216
|
}
|
|
108
217
|
finally {
|
|
@@ -137,9 +246,16 @@ export class oNodeConnectionManager extends oConnectionManager {
|
|
|
137
246
|
requestHandler: config.requestHandler ?? undefined,
|
|
138
247
|
reusePolicy: reuse ? 'reuse' : 'none',
|
|
139
248
|
});
|
|
249
|
+
// Cache the new connection by its p2p connection ID
|
|
250
|
+
this.cacheNodeConnection(connection);
|
|
140
251
|
const addressKey = this.getAddressKey(nextHopAddress);
|
|
141
252
|
if (addressKey) {
|
|
142
|
-
this.connectionsByAddress.
|
|
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
|
+
}
|
|
143
259
|
}
|
|
144
260
|
else {
|
|
145
261
|
this.logger.error('Should not happen! Failed to generate an address key for address:', nextHopAddress);
|
|
@@ -147,13 +263,21 @@ export class oNodeConnectionManager extends oConnectionManager {
|
|
|
147
263
|
return connection;
|
|
148
264
|
}
|
|
149
265
|
/**
|
|
150
|
-
* Connect to a given address, reusing libp2p connections when possible
|
|
266
|
+
* Connect to a given address, reusing oNodeConnection and libp2p connections when possible
|
|
151
267
|
* @param config - Connection configuration
|
|
152
268
|
* @returns The connection object
|
|
153
269
|
*/
|
|
154
270
|
async connect(config) {
|
|
155
271
|
const { address, nextHopAddress, callerAddress, readTimeoutMs, drainTimeoutMs, } = config;
|
|
272
|
+
// First get or create the underlying p2p connection
|
|
156
273
|
const p2pConnection = await this.getOrCreateConnection(nextHopAddress, address);
|
|
274
|
+
// Check for existing valid oNodeConnection for this p2p connection
|
|
275
|
+
const existingConnection = this.getCachedNodeConnection(p2pConnection);
|
|
276
|
+
if (existingConnection) {
|
|
277
|
+
this.logger.debug('Reusing cached oNodeConnection for connection ID:', p2pConnection.id);
|
|
278
|
+
return existingConnection;
|
|
279
|
+
}
|
|
280
|
+
// No valid cached connection, create new one
|
|
157
281
|
const connection = new oNodeConnection({
|
|
158
282
|
nextHopAddress: nextHopAddress,
|
|
159
283
|
address: address,
|
|
@@ -166,6 +290,8 @@ export class oNodeConnectionManager extends oConnectionManager {
|
|
|
166
290
|
runOnLimitedConnection: this.config.runOnLimitedConnection ?? false,
|
|
167
291
|
requestHandler: config.requestHandler ?? undefined,
|
|
168
292
|
});
|
|
293
|
+
// Cache the new connection by its p2p connection ID
|
|
294
|
+
this.cacheNodeConnection(connection);
|
|
169
295
|
return connection;
|
|
170
296
|
}
|
|
171
297
|
/**
|
|
@@ -180,8 +306,9 @@ export class oNodeConnectionManager extends oConnectionManager {
|
|
|
180
306
|
return false;
|
|
181
307
|
}
|
|
182
308
|
// Check our address-based cache first
|
|
183
|
-
const
|
|
184
|
-
|
|
309
|
+
const cachedConnections = this.connectionsByAddress.get(addressKey) || [];
|
|
310
|
+
const bestConnection = this.selectBestConnection(cachedConnections, false);
|
|
311
|
+
if (bestConnection) {
|
|
185
312
|
return true;
|
|
186
313
|
}
|
|
187
314
|
// Fall back to checking libp2p's connections
|
|
@@ -194,7 +321,11 @@ export class oNodeConnectionManager extends oConnectionManager {
|
|
|
194
321
|
if (hasOpenConnection) {
|
|
195
322
|
const openConnection = connections.find((conn) => conn.status === 'open');
|
|
196
323
|
if (openConnection) {
|
|
197
|
-
this.connectionsByAddress.
|
|
324
|
+
const existingConnections = this.connectionsByAddress.get(addressKey) || [];
|
|
325
|
+
if (!existingConnections.includes(openConnection)) {
|
|
326
|
+
existingConnections.push(openConnection);
|
|
327
|
+
this.connectionsByAddress.set(addressKey, existingConnections);
|
|
328
|
+
}
|
|
198
329
|
}
|
|
199
330
|
}
|
|
200
331
|
return hasOpenConnection;
|
|
@@ -216,9 +347,10 @@ export class oNodeConnectionManager extends oConnectionManager {
|
|
|
216
347
|
return null;
|
|
217
348
|
}
|
|
218
349
|
// Check address-based cache first
|
|
219
|
-
const
|
|
220
|
-
|
|
221
|
-
|
|
350
|
+
const cachedConnections = this.connectionsByAddress.get(addressKey) || [];
|
|
351
|
+
const bestConnection = this.selectBestConnection(cachedConnections, false);
|
|
352
|
+
if (bestConnection) {
|
|
353
|
+
return bestConnection;
|
|
222
354
|
}
|
|
223
355
|
const peerId = this.getPeerIdFromAddress(address);
|
|
224
356
|
if (!peerId) {
|
|
@@ -227,15 +359,22 @@ export class oNodeConnectionManager extends oConnectionManager {
|
|
|
227
359
|
// Query libp2p for connections to this peer
|
|
228
360
|
const connections = this.p2pNode.getConnections();
|
|
229
361
|
const filteredConnections = connections.filter((conn) => conn.remotePeer?.toString() === peerId);
|
|
230
|
-
// Find
|
|
231
|
-
const
|
|
232
|
-
// If we found
|
|
233
|
-
if (
|
|
234
|
-
this.connectionsByAddress.
|
|
235
|
-
|
|
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);
|
|
236
375
|
}
|
|
237
|
-
// Clean up stale cache
|
|
238
|
-
if (
|
|
376
|
+
// Clean up stale cache entries if connections are no longer open
|
|
377
|
+
if (cachedConnections.length > 0) {
|
|
239
378
|
this.connectionsByAddress.delete(addressKey);
|
|
240
379
|
}
|
|
241
380
|
return null;
|
|
@@ -250,13 +389,22 @@ export class oNodeConnectionManager extends oConnectionManager {
|
|
|
250
389
|
* @returns Object containing cache statistics
|
|
251
390
|
*/
|
|
252
391
|
getCacheStats() {
|
|
392
|
+
const allConnections = [];
|
|
393
|
+
for (const [addressKey, connections,] of this.connectionsByAddress.entries()) {
|
|
394
|
+
for (const conn of connections) {
|
|
395
|
+
allConnections.push({
|
|
396
|
+
peerId: conn.remotePeer?.toString() ?? 'unknown',
|
|
397
|
+
status: conn.status,
|
|
398
|
+
addressKey,
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
}
|
|
253
402
|
return {
|
|
254
|
-
|
|
403
|
+
cachedAddresses: this.connectionsByAddress.size,
|
|
404
|
+
totalCachedConnections: allConnections.length,
|
|
255
405
|
pendingDials: this.pendingDialsByAddress.size,
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
status: conn.status,
|
|
259
|
-
})),
|
|
406
|
+
cachedNodeConnections: this.nodeConnectionByConnectionId.size,
|
|
407
|
+
connectionsByPeer: allConnections,
|
|
260
408
|
};
|
|
261
409
|
}
|
|
262
410
|
/**
|
|
@@ -265,10 +413,19 @@ export class oNodeConnectionManager extends oConnectionManager {
|
|
|
265
413
|
*/
|
|
266
414
|
cleanupStaleConnections() {
|
|
267
415
|
let removed = 0;
|
|
268
|
-
for (const [addressKey,
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
416
|
+
for (const [addressKey, connections,] of this.connectionsByAddress.entries()) {
|
|
417
|
+
const openConnections = connections.filter((conn) => conn.status === 'open');
|
|
418
|
+
const staleCount = connections.length - openConnections.length;
|
|
419
|
+
if (staleCount > 0) {
|
|
420
|
+
removed += staleCount;
|
|
421
|
+
if (openConnections.length === 0) {
|
|
422
|
+
// Remove the entire entry if no connections remain
|
|
423
|
+
this.connectionsByAddress.delete(addressKey);
|
|
424
|
+
}
|
|
425
|
+
else {
|
|
426
|
+
// Keep only the open connections
|
|
427
|
+
this.connectionsByAddress.set(addressKey, openConnections);
|
|
428
|
+
}
|
|
272
429
|
}
|
|
273
430
|
}
|
|
274
431
|
if (removed > 0) {
|
|
@@ -276,4 +433,21 @@ export class oNodeConnectionManager extends oConnectionManager {
|
|
|
276
433
|
}
|
|
277
434
|
return removed;
|
|
278
435
|
}
|
|
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
|
+
}
|
|
279
453
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/decorators/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './synchronized.js';
|
|
@@ -0,0 +1,21 @@
|
|
|
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
|
|
@@ -0,0 +1 @@
|
|
|
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"}
|
|
@@ -0,0 +1,39 @@
|
|
|
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
|
+
}
|
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,+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"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,kBAAkB,CAAC;AACjC,cAAc,uBAAuB,CAAC;AACtC,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
CHANGED
|
@@ -11,6 +11,7 @@ import { oToolBase } from '@olane/o-tool';
|
|
|
11
11
|
import { oConnectionHeartbeatManager } from './managers/o-connection-heartbeat.manager.js';
|
|
12
12
|
import { oNodeConnectionConfig } from './connection/index.js';
|
|
13
13
|
import { oReconnectionManager } from './managers/o-reconnection.manager.js';
|
|
14
|
+
import { LockManager } from './utils/lock-manager.js';
|
|
14
15
|
export declare class oNode extends oToolBase {
|
|
15
16
|
peerId: PeerId;
|
|
16
17
|
p2pNode: Libp2p;
|
|
@@ -23,6 +24,7 @@ export declare class oNode extends oToolBase {
|
|
|
23
24
|
protected didRegister: boolean;
|
|
24
25
|
protected hooksStartFinished: any[];
|
|
25
26
|
protected hooksInitFinished: any[];
|
|
27
|
+
protected lockManager: LockManager;
|
|
26
28
|
constructor(config: oNodeConfig);
|
|
27
29
|
get leader(): oNodeAddress | null;
|
|
28
30
|
get networkConfig(): Libp2pConfig;
|
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;
|
|
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,3 +1,12 @@
|
|
|
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
|
+
};
|
|
1
10
|
import { createNode, defaultLibp2pConfig, KEEP_ALIVE, } from '@olane/o-config';
|
|
2
11
|
import { v4 as uuidv4 } from 'uuid';
|
|
3
12
|
import { oNodeRouter } from './router/o-node.router.js';
|
|
@@ -11,12 +20,15 @@ import { oMethodResolver, oToolBase } from '@olane/o-tool';
|
|
|
11
20
|
import { oLeaderResolverFallback } from './router/index.js';
|
|
12
21
|
import { oNodeNotificationManager } from './o-node.notification-manager.js';
|
|
13
22
|
import { oReconnectionManager } from './managers/o-reconnection.manager.js';
|
|
23
|
+
import { LockManager } from './utils/lock-manager.js';
|
|
24
|
+
import { Synchronized } from './decorators/synchronized.js';
|
|
14
25
|
export class oNode extends oToolBase {
|
|
15
26
|
constructor(config) {
|
|
16
27
|
super(config);
|
|
17
28
|
this.didRegister = false;
|
|
18
29
|
this.hooksStartFinished = [];
|
|
19
30
|
this.hooksInitFinished = [];
|
|
31
|
+
this.lockManager = new LockManager();
|
|
20
32
|
this.config = config;
|
|
21
33
|
}
|
|
22
34
|
get leader() {
|
|
@@ -522,6 +534,8 @@ export class oNode extends oToolBase {
|
|
|
522
534
|
resetState() {
|
|
523
535
|
// Reset registration flag
|
|
524
536
|
this.didRegister = false;
|
|
537
|
+
// Clear all locks
|
|
538
|
+
this.lockManager.clearAll();
|
|
525
539
|
// Clear peer references
|
|
526
540
|
this.peerId = undefined;
|
|
527
541
|
this.p2pNode = undefined;
|
|
@@ -638,3 +652,15 @@ export class oNode extends oToolBase {
|
|
|
638
652
|
}
|
|
639
653
|
}
|
|
640
654
|
}
|
|
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);
|
|
@@ -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;
|
|
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;IAY3B,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
|
@@ -13,6 +13,7 @@ import { ConnectionUtils } from './utils/connection.utils.js';
|
|
|
13
13
|
export class oNodeTool extends oTool(oServerNode) {
|
|
14
14
|
async handleProtocolReuse(address) {
|
|
15
15
|
const reuseProtocol = address.protocol + '/reuse';
|
|
16
|
+
this.logger.debug('Handling protocol reuse: ' + reuseProtocol);
|
|
16
17
|
const protocols = this.p2pNode.getProtocols();
|
|
17
18
|
if (protocols.find((p) => p === reuseProtocol)) {
|
|
18
19
|
// already handling
|
|
@@ -21,10 +22,11 @@ export class oNodeTool extends oTool(oServerNode) {
|
|
|
21
22
|
const maxOutboundsStreams = process.env.MAX_OUTBOUND_STREAMS
|
|
22
23
|
? parseInt(process.env.MAX_OUTBOUND_STREAMS)
|
|
23
24
|
: 1000;
|
|
24
|
-
await this.p2pNode.handle(reuseProtocol, this.
|
|
25
|
+
await this.p2pNode.handle(reuseProtocol, this.handleStreamReuse.bind(this), {
|
|
25
26
|
maxInboundStreams: 10000,
|
|
26
27
|
maxOutboundStreams: maxOutboundsStreams,
|
|
27
28
|
});
|
|
29
|
+
this.logger.debug('Handled protocol reuse: ' + reuseProtocol);
|
|
28
30
|
}
|
|
29
31
|
async handleProtocol(address) {
|
|
30
32
|
const protocols = this.p2pNode.getProtocols();
|
|
@@ -72,6 +74,7 @@ export class oNodeTool extends oTool(oServerNode) {
|
|
|
72
74
|
callerAddress: this.address,
|
|
73
75
|
p2pConnection: connection,
|
|
74
76
|
reuse,
|
|
77
|
+
// requestHandler: this.execute.bind(this), TODO: do we need this?
|
|
75
78
|
});
|
|
76
79
|
// Use StreamHandler for consistent stream handling
|
|
77
80
|
// This follows libp2p v3 best practices for length-prefixed streaming
|
|
@@ -108,6 +111,7 @@ export class oNodeTool extends oTool(oServerNode) {
|
|
|
108
111
|
}));
|
|
109
112
|
}
|
|
110
113
|
// create downward direction connection
|
|
114
|
+
this.logger.debug('Pinging child to confirm access');
|
|
111
115
|
await this.useChild(childAddress, {
|
|
112
116
|
method: 'ping',
|
|
113
117
|
params: {},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,cAAc,uBAAuB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,cAAc,uBAAuB,CAAC;AACtC,cAAc,mBAAmB,CAAC"}
|
package/dist/src/utils/index.js
CHANGED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Promise-based lock manager for handling concurrent access to critical sections
|
|
3
|
+
* Supports named locks for fine-grained concurrency control
|
|
4
|
+
*/
|
|
5
|
+
export declare class LockManager {
|
|
6
|
+
private locks;
|
|
7
|
+
private lockStates;
|
|
8
|
+
private resolvers;
|
|
9
|
+
/**
|
|
10
|
+
* Acquire a named lock, waiting if necessary
|
|
11
|
+
* @param lockName - Unique identifier for the lock
|
|
12
|
+
* @param timeoutMs - Optional timeout in milliseconds
|
|
13
|
+
* @throws Error if timeout is reached
|
|
14
|
+
*/
|
|
15
|
+
acquire(lockName: string, timeoutMs?: number): Promise<void>;
|
|
16
|
+
/**
|
|
17
|
+
* Release a named lock
|
|
18
|
+
* @param lockName - Unique identifier for the lock to release
|
|
19
|
+
*/
|
|
20
|
+
release(lockName: string): void;
|
|
21
|
+
/**
|
|
22
|
+
* Execute a function with automatic lock acquisition/release
|
|
23
|
+
* @param lockName - Unique identifier for the lock
|
|
24
|
+
* @param fn - Async function to execute while holding the lock
|
|
25
|
+
* @param timeoutMs - Optional timeout for lock acquisition
|
|
26
|
+
* @returns The result of the function execution
|
|
27
|
+
*/
|
|
28
|
+
withLock<T>(lockName: string, fn: () => Promise<T>, timeoutMs?: number): Promise<T>;
|
|
29
|
+
/**
|
|
30
|
+
* Check if a lock is currently held
|
|
31
|
+
* @param lockName - Unique identifier for the lock
|
|
32
|
+
* @returns true if the lock is currently held
|
|
33
|
+
*/
|
|
34
|
+
isLocked(lockName: string): boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Clear all locks (for teardown/reset)
|
|
37
|
+
* Resolves any pending waiters
|
|
38
|
+
*/
|
|
39
|
+
clearAll(): void;
|
|
40
|
+
/**
|
|
41
|
+
* Get statistics about current lock states (useful for debugging)
|
|
42
|
+
* @returns Object containing lock statistics
|
|
43
|
+
*/
|
|
44
|
+
getStats(): {
|
|
45
|
+
totalLocks: number;
|
|
46
|
+
activeLocks: number;
|
|
47
|
+
lockNames: string[];
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=lock-manager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lock-manager.d.ts","sourceRoot":"","sources":["../../../src/utils/lock-manager.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,KAAK,CAAyC;IACtD,OAAO,CAAC,UAAU,CAAmC;IACrD,OAAO,CAAC,SAAS,CAAsC;IAEvD;;;;;OAKG;IACG,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA6BlE;;;OAGG;IACH,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAS/B;;;;;;OAMG;IACG,QAAQ,CAAC,CAAC,EACd,QAAQ,EAAE,MAAM,EAChB,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACpB,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,CAAC,CAAC;IASb;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO;IAInC;;;OAGG;IACH,QAAQ,IAAI,IAAI;IAUhB;;;OAGG;IACH,QAAQ,IAAI;QACV,UAAU,EAAE,MAAM,CAAC;QACnB,WAAW,EAAE,MAAM,CAAC;QACpB,SAAS,EAAE,MAAM,EAAE,CAAC;KACrB;CAWF"}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Promise-based lock manager for handling concurrent access to critical sections
|
|
3
|
+
* Supports named locks for fine-grained concurrency control
|
|
4
|
+
*/
|
|
5
|
+
export class LockManager {
|
|
6
|
+
constructor() {
|
|
7
|
+
this.locks = new Map();
|
|
8
|
+
this.lockStates = new Map();
|
|
9
|
+
this.resolvers = new Map();
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Acquire a named lock, waiting if necessary
|
|
13
|
+
* @param lockName - Unique identifier for the lock
|
|
14
|
+
* @param timeoutMs - Optional timeout in milliseconds
|
|
15
|
+
* @throws Error if timeout is reached
|
|
16
|
+
*/
|
|
17
|
+
async acquire(lockName, timeoutMs) {
|
|
18
|
+
// Wait for any existing lock to be released
|
|
19
|
+
while (this.lockStates.get(lockName)) {
|
|
20
|
+
const currentLock = this.locks.get(lockName);
|
|
21
|
+
if (currentLock) {
|
|
22
|
+
if (timeoutMs) {
|
|
23
|
+
await Promise.race([
|
|
24
|
+
currentLock,
|
|
25
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error(`Lock timeout: ${lockName}`)), timeoutMs)),
|
|
26
|
+
]);
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
await currentLock;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
// Acquire the lock
|
|
34
|
+
this.lockStates.set(lockName, true);
|
|
35
|
+
const lockPromise = new Promise((resolve) => {
|
|
36
|
+
this.resolvers.set(lockName, resolve);
|
|
37
|
+
});
|
|
38
|
+
this.locks.set(lockName, lockPromise);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Release a named lock
|
|
42
|
+
* @param lockName - Unique identifier for the lock to release
|
|
43
|
+
*/
|
|
44
|
+
release(lockName) {
|
|
45
|
+
const resolver = this.resolvers.get(lockName);
|
|
46
|
+
if (resolver) {
|
|
47
|
+
resolver();
|
|
48
|
+
this.resolvers.delete(lockName);
|
|
49
|
+
}
|
|
50
|
+
this.lockStates.set(lockName, false);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Execute a function with automatic lock acquisition/release
|
|
54
|
+
* @param lockName - Unique identifier for the lock
|
|
55
|
+
* @param fn - Async function to execute while holding the lock
|
|
56
|
+
* @param timeoutMs - Optional timeout for lock acquisition
|
|
57
|
+
* @returns The result of the function execution
|
|
58
|
+
*/
|
|
59
|
+
async withLock(lockName, fn, timeoutMs) {
|
|
60
|
+
await this.acquire(lockName, timeoutMs);
|
|
61
|
+
try {
|
|
62
|
+
return await fn();
|
|
63
|
+
}
|
|
64
|
+
finally {
|
|
65
|
+
this.release(lockName);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Check if a lock is currently held
|
|
70
|
+
* @param lockName - Unique identifier for the lock
|
|
71
|
+
* @returns true if the lock is currently held
|
|
72
|
+
*/
|
|
73
|
+
isLocked(lockName) {
|
|
74
|
+
return this.lockStates.get(lockName) ?? false;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Clear all locks (for teardown/reset)
|
|
78
|
+
* Resolves any pending waiters
|
|
79
|
+
*/
|
|
80
|
+
clearAll() {
|
|
81
|
+
// Resolve any pending waiters
|
|
82
|
+
for (const lockName of this.lockStates.keys()) {
|
|
83
|
+
this.release(lockName);
|
|
84
|
+
}
|
|
85
|
+
this.locks.clear();
|
|
86
|
+
this.lockStates.clear();
|
|
87
|
+
this.resolvers.clear();
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Get statistics about current lock states (useful for debugging)
|
|
91
|
+
* @returns Object containing lock statistics
|
|
92
|
+
*/
|
|
93
|
+
getStats() {
|
|
94
|
+
const activeLocks = Array.from(this.lockStates.entries()).filter(([_, isLocked]) => isLocked);
|
|
95
|
+
return {
|
|
96
|
+
totalLocks: this.lockStates.size,
|
|
97
|
+
activeLocks: activeLocks.length,
|
|
98
|
+
lockNames: activeLocks.map(([name]) => name),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@olane/o-node",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.46",
|
|
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.46",
|
|
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.46",
|
|
64
|
+
"@olane/o-core": "0.7.46",
|
|
65
|
+
"@olane/o-protocol": "0.7.46",
|
|
66
|
+
"@olane/o-tool": "0.7.46",
|
|
67
67
|
"debug": "^4.4.1",
|
|
68
68
|
"dotenv": "^16.5.0",
|
|
69
69
|
"json5": "^2.2.3"
|
|
70
70
|
},
|
|
71
|
-
"gitHead": "
|
|
71
|
+
"gitHead": "48c45f37c6e2e1ee626ce1fb0f0de175ca2db3b6"
|
|
72
72
|
}
|