@olane/o-node 0.7.45 → 0.7.47
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 +20 -1
- package/dist/src/connection/o-node-connection.manager.d.ts.map +1 -1
- package/dist/src/connection/o-node-connection.manager.js +86 -20
- 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 +4 -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
|
|
@@ -36,6 +38,17 @@ export declare class oNodeConnectionManager extends oConnectionManager {
|
|
|
36
38
|
* @returns The best connection or null if none are suitable
|
|
37
39
|
*/
|
|
38
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;
|
|
39
52
|
getOrCreateConnection(nextHopAddress: oAddress, address: oAddress): Promise<Connection>;
|
|
40
53
|
private performDial;
|
|
41
54
|
answer(config: oConnectionConfig & {
|
|
@@ -43,7 +56,7 @@ export declare class oNodeConnectionManager extends oConnectionManager {
|
|
|
43
56
|
reuse?: boolean;
|
|
44
57
|
}): Promise<oNodeConnection>;
|
|
45
58
|
/**
|
|
46
|
-
* Connect to a given address, reusing libp2p connections when possible
|
|
59
|
+
* Connect to a given address, reusing oNodeConnection and libp2p connections when possible
|
|
47
60
|
* @param config - Connection configuration
|
|
48
61
|
* @returns The connection object
|
|
49
62
|
*/
|
|
@@ -68,6 +81,7 @@ export declare class oNodeConnectionManager extends oConnectionManager {
|
|
|
68
81
|
cachedAddresses: number;
|
|
69
82
|
totalCachedConnections: number;
|
|
70
83
|
pendingDials: number;
|
|
84
|
+
cachedNodeConnections: number;
|
|
71
85
|
connectionsByPeer: Array<{
|
|
72
86
|
peerId: string;
|
|
73
87
|
status: string;
|
|
@@ -79,5 +93,10 @@ export declare class oNodeConnectionManager extends oConnectionManager {
|
|
|
79
93
|
* @returns Number of connections removed
|
|
80
94
|
*/
|
|
81
95
|
cleanupStaleConnections(): number;
|
|
96
|
+
/**
|
|
97
|
+
* Clean up all stale oNodeConnections from cache
|
|
98
|
+
* @returns Number of oNodeConnections removed
|
|
99
|
+
*/
|
|
100
|
+
cleanupStaleNodeConnections(): number;
|
|
82
101
|
}
|
|
83
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,19 +24,31 @@ export class oNodeConnectionManager extends oConnectionManager {
|
|
|
22
24
|
if (!connection) {
|
|
23
25
|
return;
|
|
24
26
|
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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);
|
|
38
52
|
}
|
|
39
53
|
});
|
|
40
54
|
}
|
|
@@ -105,8 +119,7 @@ export class oNodeConnectionManager extends oConnectionManager {
|
|
|
105
119
|
// Priority 2: Based on reuse context
|
|
106
120
|
if (reuseContext) {
|
|
107
121
|
// Prefer inbound connections
|
|
108
|
-
const inbound = openConnections.find((c) => c.direction === 'inbound' ||
|
|
109
|
-
!c.direction);
|
|
122
|
+
const inbound = openConnections.find((c) => c.direction === 'inbound' || !c.direction);
|
|
110
123
|
if (inbound) {
|
|
111
124
|
this.logger.debug('Selected inbound connection (reuse context)');
|
|
112
125
|
return inbound;
|
|
@@ -121,9 +134,32 @@ export class oNodeConnectionManager extends oConnectionManager {
|
|
|
121
134
|
}
|
|
122
135
|
}
|
|
123
136
|
// Priority 3: Return first open connection
|
|
124
|
-
this.logger.debug('Selected first available open connection');
|
|
137
|
+
this.logger.debug('Selected first available open connection', connectionsWithStreams);
|
|
125
138
|
return openConnections[0];
|
|
126
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
|
+
}
|
|
127
163
|
async getOrCreateConnection(nextHopAddress, address) {
|
|
128
164
|
if (!nextHopAddress) {
|
|
129
165
|
throw new Error('Invalid address passed');
|
|
@@ -137,7 +173,7 @@ export class oNodeConnectionManager extends oConnectionManager {
|
|
|
137
173
|
const cachedConnections = this.connectionsByAddress.get(addressKey) || [];
|
|
138
174
|
const bestConnection = this.selectBestConnection(cachedConnections, false);
|
|
139
175
|
if (bestConnection) {
|
|
140
|
-
this.logger.debug('Reusing cached connection for address:', nextHopAddress?.value);
|
|
176
|
+
this.logger.debug('Reusing cached connection for address:', nextHopAddress?.value, bestConnection.id);
|
|
141
177
|
return bestConnection;
|
|
142
178
|
}
|
|
143
179
|
// Clean up stale connections if they exist but are not open
|
|
@@ -210,6 +246,8 @@ export class oNodeConnectionManager extends oConnectionManager {
|
|
|
210
246
|
requestHandler: config.requestHandler ?? undefined,
|
|
211
247
|
reusePolicy: reuse ? 'reuse' : 'none',
|
|
212
248
|
});
|
|
249
|
+
// Cache the new connection by its p2p connection ID
|
|
250
|
+
this.cacheNodeConnection(connection);
|
|
213
251
|
const addressKey = this.getAddressKey(nextHopAddress);
|
|
214
252
|
if (addressKey) {
|
|
215
253
|
const connections = this.connectionsByAddress.get(addressKey) || [];
|
|
@@ -225,13 +263,21 @@ export class oNodeConnectionManager extends oConnectionManager {
|
|
|
225
263
|
return connection;
|
|
226
264
|
}
|
|
227
265
|
/**
|
|
228
|
-
* Connect to a given address, reusing libp2p connections when possible
|
|
266
|
+
* Connect to a given address, reusing oNodeConnection and libp2p connections when possible
|
|
229
267
|
* @param config - Connection configuration
|
|
230
268
|
* @returns The connection object
|
|
231
269
|
*/
|
|
232
270
|
async connect(config) {
|
|
233
271
|
const { address, nextHopAddress, callerAddress, readTimeoutMs, drainTimeoutMs, } = config;
|
|
272
|
+
// First get or create the underlying p2p connection
|
|
234
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
|
|
235
281
|
const connection = new oNodeConnection({
|
|
236
282
|
nextHopAddress: nextHopAddress,
|
|
237
283
|
address: address,
|
|
@@ -244,6 +290,8 @@ export class oNodeConnectionManager extends oConnectionManager {
|
|
|
244
290
|
runOnLimitedConnection: this.config.runOnLimitedConnection ?? false,
|
|
245
291
|
requestHandler: config.requestHandler ?? undefined,
|
|
246
292
|
});
|
|
293
|
+
// Cache the new connection by its p2p connection ID
|
|
294
|
+
this.cacheNodeConnection(connection);
|
|
247
295
|
return connection;
|
|
248
296
|
}
|
|
249
297
|
/**
|
|
@@ -342,7 +390,7 @@ export class oNodeConnectionManager extends oConnectionManager {
|
|
|
342
390
|
*/
|
|
343
391
|
getCacheStats() {
|
|
344
392
|
const allConnections = [];
|
|
345
|
-
for (const [addressKey, connections] of this.connectionsByAddress.entries()) {
|
|
393
|
+
for (const [addressKey, connections,] of this.connectionsByAddress.entries()) {
|
|
346
394
|
for (const conn of connections) {
|
|
347
395
|
allConnections.push({
|
|
348
396
|
peerId: conn.remotePeer?.toString() ?? 'unknown',
|
|
@@ -355,6 +403,7 @@ export class oNodeConnectionManager extends oConnectionManager {
|
|
|
355
403
|
cachedAddresses: this.connectionsByAddress.size,
|
|
356
404
|
totalCachedConnections: allConnections.length,
|
|
357
405
|
pendingDials: this.pendingDialsByAddress.size,
|
|
406
|
+
cachedNodeConnections: this.nodeConnectionByConnectionId.size,
|
|
358
407
|
connectionsByPeer: allConnections,
|
|
359
408
|
};
|
|
360
409
|
}
|
|
@@ -364,7 +413,7 @@ export class oNodeConnectionManager extends oConnectionManager {
|
|
|
364
413
|
*/
|
|
365
414
|
cleanupStaleConnections() {
|
|
366
415
|
let removed = 0;
|
|
367
|
-
for (const [addressKey, connections] of this.connectionsByAddress.entries()) {
|
|
416
|
+
for (const [addressKey, connections,] of this.connectionsByAddress.entries()) {
|
|
368
417
|
const openConnections = connections.filter((conn) => conn.status === 'open');
|
|
369
418
|
const staleCount = connections.length - openConnections.length;
|
|
370
419
|
if (staleCount > 0) {
|
|
@@ -384,4 +433,21 @@ export class oNodeConnectionManager extends oConnectionManager {
|
|
|
384
433
|
}
|
|
385
434
|
return removed;
|
|
386
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
|
+
}
|
|
387
453
|
}
|
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":"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;AAEtD,qBAAa,KAAM,SAAQ,SAAS;IAC3B,MAAM,EAAG,MAAM,CAAC;IAChB,OAAO,EAAG,MAAM,CAAC;IACjB,OAAO,EAAG,YAAY,CAAC;IACvB,MAAM,EAAE,WAAW,CAAC;IACpB,iBAAiB,EAAG,sBAAsB,CAAC;IAC3C,gBAAgB,EAAG,qBAAqB,CAAC;IACzC,0BAA0B,CAAC,EAAE,2BAA2B,CAAC;IAChE,SAAS,CAAC,mBAAmB,CAAC,EAAE,oBAAoB,CAAC;IACrD,SAAS,CAAC,WAAW,EAAE,OAAO,CAAS;IACvC,SAAS,CAAC,kBAAkB,EAAE,GAAG,EAAE,CAAM;IACzC,SAAS,CAAC,iBAAiB,EAAE,GAAG,EAAE,CAAM;IACxC,SAAS,CAAC,WAAW,EAAE,WAAW,CAAqB;gBAE3C,MAAM,EAAE,WAAW;IAK/B,IAAI,MAAM,IAAI,YAAY,GAAG,IAAI,CAEhC;IAED,IAAI,aAAa,IAAI,YAAY,CAKhC;IAED,IAAI,YAAY,IAAI,MAAM,GAAG,IAAI,CAOhC;IAED,mBAAmB,IAAI,GAAG,EAAE;IAItB,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC;IAIvC,SAAS,CAAC,yBAAyB,IAAI,oBAAoB;IAI3D,IAAI,aAAa,IAAI,YAAY,CAEhC;IAED,IAAI,gBAAgB,IAAI,cAAc,EAAE,CAEvC;IAED,IAAI,UAAU,IAAI,cAAc,EAAE,CAIjC;IAEK,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IA6D3B,eAAe,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAqCrD,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC;IA6C/B,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC;IAwB/B,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IA4B/B,aAAa,CAAC,OAAO,EAAE,YAAY,GAAG,MAAM;IAItC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAStB,mBAAmB,CAAC,OAAO,EAAE,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC;IAG1D;;;OAGG;IACG,SAAS,IAAI,OAAO,CAAC,YAAY,CAAC;cA8HxB,UAAU,IAAI,OAAO,CAAC,MAAM,CAAC;IAMvC,OAAO,CAAC,MAAM,EAAE,qBAAqB,GAAG,OAAO,CAAC,eAAe,CAAC;IAsBhE,qBAAqB,IAAI,OAAO,CAAC,IAAI,CAAC;cAU5B,sBAAsB,IAAI,OAAO,CAAC,IAAI,CAAC;IAavD,cAAc,CAAC,EAAE,EAAE,QAAQ;IAI3B,eAAe,CAAC,EAAE,EAAE,QAAQ;cAIZ,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC;IAgClD;;;;OAIG;YACW,wBAAwB;IA2BtC;;;;;;OAMG;cACa,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAInC,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IA6DjC;;OAEG;IAiBG,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAgB/B;;;OAGG;IACH,SAAS,CAAC,UAAU,IAAI,IAAI;IAmC5B,UAAU,IAAI,YAAY,EAAE;IAI5B,UAAU,IAAI,YAAY,EAAE;IAI5B,WAAW,IAAI,YAAY,EAAE;IAI7B,WAAW,CAAC,YAAY,EAAE,YAAY,GAAG,IAAI;IAI7C;;;OAGG;IACH,cAAc,IAAI,MAAM;IAUxB;;;OAGG;IACG,wBAAwB,CAAC,OAAO,EAAE,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC;CAwEhE"}
|
package/dist/src/o-node.js
CHANGED
|
@@ -11,12 +11,14 @@ import { oMethodResolver, oToolBase } from '@olane/o-tool';
|
|
|
11
11
|
import { oLeaderResolverFallback } from './router/index.js';
|
|
12
12
|
import { oNodeNotificationManager } from './o-node.notification-manager.js';
|
|
13
13
|
import { oReconnectionManager } from './managers/o-reconnection.manager.js';
|
|
14
|
+
import { LockManager } from './utils/lock-manager.js';
|
|
14
15
|
export class oNode extends oToolBase {
|
|
15
16
|
constructor(config) {
|
|
16
17
|
super(config);
|
|
17
18
|
this.didRegister = false;
|
|
18
19
|
this.hooksStartFinished = [];
|
|
19
20
|
this.hooksInitFinished = [];
|
|
21
|
+
this.lockManager = new LockManager();
|
|
20
22
|
this.config = config;
|
|
21
23
|
}
|
|
22
24
|
get leader() {
|
|
@@ -522,6 +524,8 @@ export class oNode extends oToolBase {
|
|
|
522
524
|
resetState() {
|
|
523
525
|
// Reset registration flag
|
|
524
526
|
this.didRegister = false;
|
|
527
|
+
// Clear all locks
|
|
528
|
+
this.lockManager.clearAll();
|
|
525
529
|
// Clear peer references
|
|
526
530
|
this.peerId = undefined;
|
|
527
531
|
this.p2pNode = undefined;
|
|
@@ -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.47",
|
|
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.47",
|
|
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.47",
|
|
64
|
+
"@olane/o-core": "0.7.47",
|
|
65
|
+
"@olane/o-protocol": "0.7.47",
|
|
66
|
+
"@olane/o-tool": "0.7.47",
|
|
67
67
|
"debug": "^4.4.1",
|
|
68
68
|
"dotenv": "^16.5.0",
|
|
69
69
|
"json5": "^2.2.3"
|
|
70
70
|
},
|
|
71
|
-
"gitHead": "
|
|
71
|
+
"gitHead": "2730d1ccdfb58549ebd253ded9479156086cc066"
|
|
72
72
|
}
|