dflockd-client 1.8.3 → 1.9.1
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/README.md +4 -0
- package/dist/client.cjs +368 -338
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +97 -86
- package/dist/client.d.ts +97 -86
- package/dist/client.js +368 -338
- package/dist/client.js.map +1 -1
- package/package.json +1 -1
package/dist/client.cjs
CHANGED
|
@@ -71,6 +71,33 @@ var AuthError = class extends LockError {
|
|
|
71
71
|
this.name = "AuthError";
|
|
72
72
|
}
|
|
73
73
|
};
|
|
74
|
+
function validateKey(key) {
|
|
75
|
+
if (key === "") {
|
|
76
|
+
throw new LockError("key must not be empty");
|
|
77
|
+
}
|
|
78
|
+
if (/[\0\n\r]/.test(key)) {
|
|
79
|
+
throw new LockError(
|
|
80
|
+
"key must not contain NUL, newline, or carriage return characters"
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
function validateAuth(auth) {
|
|
85
|
+
if (/[\0\n\r]/.test(auth)) {
|
|
86
|
+
throw new LockError(
|
|
87
|
+
"auth token must not contain NUL, newline, or carriage return characters"
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
function validateToken(token) {
|
|
92
|
+
if (token === "") {
|
|
93
|
+
throw new LockError("token must not be empty");
|
|
94
|
+
}
|
|
95
|
+
if (/[\0\n\r]/.test(token)) {
|
|
96
|
+
throw new LockError(
|
|
97
|
+
"token must not contain NUL, newline, or carriage return characters"
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
74
101
|
function encodeLines(...lines) {
|
|
75
102
|
return Buffer.from(lines.map((l) => l + "\n").join(""), "utf-8");
|
|
76
103
|
}
|
|
@@ -82,7 +109,13 @@ function writeAll(sock, data) {
|
|
|
82
109
|
});
|
|
83
110
|
});
|
|
84
111
|
}
|
|
112
|
+
function parseLease(value, fallback = 30) {
|
|
113
|
+
if (value == null || value === "") return fallback;
|
|
114
|
+
const n = Number(value);
|
|
115
|
+
return Number.isFinite(n) && n >= 0 ? n : fallback;
|
|
116
|
+
}
|
|
85
117
|
var _readlineBuf = /* @__PURE__ */ new WeakMap();
|
|
118
|
+
var MAX_LINE_LENGTH = 1024 * 1024;
|
|
86
119
|
function readline(sock) {
|
|
87
120
|
return new Promise((resolve, reject) => {
|
|
88
121
|
let buf = _readlineBuf.get(sock) ?? "";
|
|
@@ -101,6 +134,10 @@ function readline(sock) {
|
|
|
101
134
|
const line = buf.slice(0, idx).replace(/\r$/, "");
|
|
102
135
|
_readlineBuf.set(sock, buf.slice(idx + 1));
|
|
103
136
|
resolve(line);
|
|
137
|
+
} else if (buf.length > MAX_LINE_LENGTH) {
|
|
138
|
+
cleanup();
|
|
139
|
+
_readlineBuf.delete(sock);
|
|
140
|
+
reject(new LockError("server response exceeded maximum line length"));
|
|
104
141
|
}
|
|
105
142
|
};
|
|
106
143
|
const onError = (err) => {
|
|
@@ -113,38 +150,83 @@ function readline(sock) {
|
|
|
113
150
|
_readlineBuf.delete(sock);
|
|
114
151
|
reject(new LockError("server closed connection"));
|
|
115
152
|
};
|
|
153
|
+
const onEnd = () => {
|
|
154
|
+
cleanup();
|
|
155
|
+
_readlineBuf.delete(sock);
|
|
156
|
+
reject(new LockError("server closed connection"));
|
|
157
|
+
};
|
|
116
158
|
const cleanup = () => {
|
|
117
159
|
sock.removeListener("data", onData);
|
|
118
160
|
sock.removeListener("error", onError);
|
|
119
161
|
sock.removeListener("close", onClose);
|
|
162
|
+
sock.removeListener("end", onEnd);
|
|
120
163
|
};
|
|
164
|
+
if (sock.readableEnded || sock.destroyed) {
|
|
165
|
+
_readlineBuf.delete(sock);
|
|
166
|
+
reject(new LockError("server closed connection"));
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
121
169
|
sock.on("data", onData);
|
|
122
170
|
sock.on("error", onError);
|
|
123
171
|
sock.on("close", onClose);
|
|
172
|
+
sock.on("end", onEnd);
|
|
124
173
|
});
|
|
125
174
|
}
|
|
126
|
-
async function connect2(host, port, tlsOptions, auth) {
|
|
175
|
+
async function connect2(host, port, tlsOptions, auth, connectTimeoutMs) {
|
|
127
176
|
const sock = await new Promise((resolve, reject) => {
|
|
177
|
+
let timer = null;
|
|
178
|
+
let settled = false;
|
|
179
|
+
const connectEvent = tlsOptions ? "secureConnect" : "connect";
|
|
180
|
+
const onConnect = () => {
|
|
181
|
+
if (settled) return;
|
|
182
|
+
settled = true;
|
|
183
|
+
if (timer) clearTimeout(timer);
|
|
184
|
+
s.removeListener("error", onError);
|
|
185
|
+
resolve(s);
|
|
186
|
+
};
|
|
187
|
+
const onError = (err) => {
|
|
188
|
+
if (settled) return;
|
|
189
|
+
settled = true;
|
|
190
|
+
if (timer) clearTimeout(timer);
|
|
191
|
+
s.removeListener(connectEvent, onConnect);
|
|
192
|
+
s.destroy();
|
|
193
|
+
reject(err);
|
|
194
|
+
};
|
|
195
|
+
let s;
|
|
128
196
|
if (tlsOptions) {
|
|
129
|
-
|
|
130
|
-
s.removeListener("error", reject);
|
|
131
|
-
resolve(s);
|
|
132
|
-
});
|
|
133
|
-
s.on("error", reject);
|
|
197
|
+
s = tls.connect({ ...tlsOptions, host, port }, onConnect);
|
|
134
198
|
} else {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
199
|
+
s = net.createConnection({ host, port }, onConnect);
|
|
200
|
+
}
|
|
201
|
+
s.on("error", onError);
|
|
202
|
+
if (connectTimeoutMs != null && connectTimeoutMs > 0) {
|
|
203
|
+
timer = setTimeout(() => {
|
|
204
|
+
if (settled) return;
|
|
205
|
+
settled = true;
|
|
206
|
+
s.removeListener("error", onError);
|
|
207
|
+
s.removeListener(connectEvent, onConnect);
|
|
208
|
+
s.destroy();
|
|
209
|
+
reject(
|
|
210
|
+
new LockError(
|
|
211
|
+
`connect timed out after ${connectTimeoutMs}ms to ${host}:${port}`
|
|
212
|
+
)
|
|
213
|
+
);
|
|
214
|
+
}, connectTimeoutMs);
|
|
140
215
|
}
|
|
141
216
|
});
|
|
142
217
|
sock.setNoDelay(true);
|
|
143
218
|
sock.on("error", () => {
|
|
144
219
|
});
|
|
145
220
|
if (auth != null && auth !== "") {
|
|
146
|
-
|
|
147
|
-
|
|
221
|
+
validateAuth(auth);
|
|
222
|
+
let resp;
|
|
223
|
+
try {
|
|
224
|
+
await writeAll(sock, encodeLines("auth", "_", auth));
|
|
225
|
+
resp = await readline(sock);
|
|
226
|
+
} catch (err) {
|
|
227
|
+
sock.destroy();
|
|
228
|
+
throw err;
|
|
229
|
+
}
|
|
148
230
|
if (resp === "ok") {
|
|
149
231
|
return sock;
|
|
150
232
|
}
|
|
@@ -172,135 +254,139 @@ function crc32(buf) {
|
|
|
172
254
|
return (crc ^ 4294967295) >>> 0;
|
|
173
255
|
}
|
|
174
256
|
function stableHashShard(key, numServers) {
|
|
175
|
-
|
|
257
|
+
if (numServers <= 0) {
|
|
258
|
+
throw new LockError("numServers must be greater than 0");
|
|
259
|
+
}
|
|
260
|
+
return crc32(Buffer.from(key, "utf-8")) % numServers;
|
|
176
261
|
}
|
|
177
|
-
async function
|
|
178
|
-
|
|
179
|
-
|
|
262
|
+
async function protoAcquire(sock, cmd, label, key, acquireTimeoutS, leaseTtlS, limit) {
|
|
263
|
+
validateKey(key);
|
|
264
|
+
if (!Number.isFinite(acquireTimeoutS) || acquireTimeoutS < 0) {
|
|
265
|
+
throw new LockError("acquireTimeoutS must be a finite number >= 0");
|
|
266
|
+
}
|
|
267
|
+
if (limit != null && (!Number.isInteger(limit) || limit < 1)) {
|
|
268
|
+
throw new LockError("limit must be an integer >= 1");
|
|
269
|
+
}
|
|
270
|
+
if (leaseTtlS != null && (!Number.isFinite(leaseTtlS) || leaseTtlS <= 0)) {
|
|
271
|
+
throw new LockError("leaseTtlS must be a finite number > 0");
|
|
272
|
+
}
|
|
273
|
+
const parts = [acquireTimeoutS];
|
|
274
|
+
if (limit != null) parts.push(limit);
|
|
275
|
+
if (leaseTtlS != null) parts.push(leaseTtlS);
|
|
276
|
+
await writeAll(sock, encodeLines(cmd, key, parts.join(" ")));
|
|
180
277
|
const resp = await readline(sock);
|
|
181
278
|
if (resp === "timeout") {
|
|
182
279
|
throw new AcquireTimeoutError(key);
|
|
183
280
|
}
|
|
184
281
|
if (!resp.startsWith("ok ")) {
|
|
185
|
-
throw new LockError(
|
|
282
|
+
throw new LockError(`${label} failed: '${resp}'`);
|
|
283
|
+
}
|
|
284
|
+
const respParts = resp.split(" ");
|
|
285
|
+
const token = respParts[1];
|
|
286
|
+
if (!token) {
|
|
287
|
+
throw new LockError(`${label}: server returned no token: '${resp}'`);
|
|
186
288
|
}
|
|
187
|
-
|
|
188
|
-
const token = parts[1];
|
|
189
|
-
const lease = parts.length >= 3 ? parseInt(parts[2], 10) : 30;
|
|
190
|
-
return { token, lease };
|
|
289
|
+
return { token, lease: parseLease(respParts[2]) };
|
|
191
290
|
}
|
|
192
|
-
async function
|
|
291
|
+
async function protoRenew(sock, cmd, label, key, token, leaseTtlS) {
|
|
292
|
+
validateKey(key);
|
|
293
|
+
validateToken(token);
|
|
294
|
+
if (leaseTtlS != null && (!Number.isFinite(leaseTtlS) || leaseTtlS <= 0)) {
|
|
295
|
+
throw new LockError("leaseTtlS must be a finite number > 0");
|
|
296
|
+
}
|
|
193
297
|
const arg = leaseTtlS == null ? token : `${token} ${leaseTtlS}`;
|
|
194
|
-
await writeAll(sock, encodeLines(
|
|
298
|
+
await writeAll(sock, encodeLines(cmd, key, arg));
|
|
195
299
|
const resp = await readline(sock);
|
|
196
300
|
if (resp !== "ok" && !resp.startsWith("ok ")) {
|
|
197
|
-
throw new LockError(
|
|
198
|
-
}
|
|
199
|
-
const parts = resp.split(" ");
|
|
200
|
-
if (parts.length >= 2 && /^\d+$/.test(parts[1])) {
|
|
201
|
-
return parseInt(parts[1], 10);
|
|
301
|
+
throw new LockError(`${label} failed: '${resp}'`);
|
|
202
302
|
}
|
|
203
|
-
|
|
303
|
+
if (resp === "ok") return leaseTtlS ?? 30;
|
|
304
|
+
return parseLease(resp.split(" ")[1]);
|
|
204
305
|
}
|
|
205
|
-
async function
|
|
206
|
-
|
|
207
|
-
|
|
306
|
+
async function protoEnqueue(sock, cmd, label, key, leaseTtlS, limit) {
|
|
307
|
+
validateKey(key);
|
|
308
|
+
if (limit != null && (!Number.isInteger(limit) || limit < 1)) {
|
|
309
|
+
throw new LockError("limit must be an integer >= 1");
|
|
310
|
+
}
|
|
311
|
+
if (leaseTtlS != null && (!Number.isFinite(leaseTtlS) || leaseTtlS <= 0)) {
|
|
312
|
+
throw new LockError("leaseTtlS must be a finite number > 0");
|
|
313
|
+
}
|
|
314
|
+
const parts = [];
|
|
315
|
+
if (limit != null) parts.push(limit);
|
|
316
|
+
if (leaseTtlS != null) parts.push(leaseTtlS);
|
|
317
|
+
await writeAll(sock, encodeLines(cmd, key, parts.join(" ")));
|
|
208
318
|
const resp = await readline(sock);
|
|
209
319
|
if (resp.startsWith("acquired ")) {
|
|
210
|
-
const
|
|
211
|
-
const token =
|
|
212
|
-
|
|
213
|
-
|
|
320
|
+
const respParts = resp.split(" ");
|
|
321
|
+
const token = respParts[1];
|
|
322
|
+
if (!token) {
|
|
323
|
+
throw new LockError(`${label}: server returned no token: '${resp}'`);
|
|
324
|
+
}
|
|
325
|
+
return { status: "acquired", token, lease: parseLease(respParts[2]) };
|
|
214
326
|
}
|
|
215
327
|
if (resp === "queued") {
|
|
216
328
|
return { status: "queued", token: null, lease: null };
|
|
217
329
|
}
|
|
218
|
-
throw new LockError(
|
|
330
|
+
throw new LockError(`${label} failed: '${resp}'`);
|
|
219
331
|
}
|
|
220
|
-
async function
|
|
221
|
-
|
|
332
|
+
async function protoWait(sock, cmd, label, key, waitTimeoutS) {
|
|
333
|
+
validateKey(key);
|
|
334
|
+
if (!Number.isFinite(waitTimeoutS) || waitTimeoutS < 0) {
|
|
335
|
+
throw new LockError("waitTimeoutS must be a finite number >= 0");
|
|
336
|
+
}
|
|
337
|
+
await writeAll(sock, encodeLines(cmd, key, String(waitTimeoutS)));
|
|
222
338
|
const resp = await readline(sock);
|
|
223
339
|
if (resp === "timeout") {
|
|
224
340
|
throw new AcquireTimeoutError(key);
|
|
225
341
|
}
|
|
226
342
|
if (!resp.startsWith("ok ")) {
|
|
227
|
-
throw new LockError(
|
|
343
|
+
throw new LockError(`${label} failed: '${resp}'`);
|
|
228
344
|
}
|
|
229
|
-
const
|
|
230
|
-
const token =
|
|
231
|
-
|
|
232
|
-
|
|
345
|
+
const respParts = resp.split(" ");
|
|
346
|
+
const token = respParts[1];
|
|
347
|
+
if (!token) {
|
|
348
|
+
throw new LockError(`${label}: server returned no token: '${resp}'`);
|
|
349
|
+
}
|
|
350
|
+
return { token, lease: parseLease(respParts[2]) };
|
|
233
351
|
}
|
|
234
|
-
async function
|
|
235
|
-
|
|
352
|
+
async function protoRelease(sock, cmd, label, key, token) {
|
|
353
|
+
validateKey(key);
|
|
354
|
+
validateToken(token);
|
|
355
|
+
await writeAll(sock, encodeLines(cmd, key, token));
|
|
236
356
|
const resp = await readline(sock);
|
|
237
357
|
if (resp !== "ok") {
|
|
238
|
-
throw new LockError(
|
|
358
|
+
throw new LockError(`${label} failed: '${resp}'`);
|
|
239
359
|
}
|
|
240
360
|
}
|
|
361
|
+
async function acquire(sock, key, acquireTimeoutS, leaseTtlS) {
|
|
362
|
+
return protoAcquire(sock, "l", "acquire", key, acquireTimeoutS, leaseTtlS);
|
|
363
|
+
}
|
|
364
|
+
async function renew(sock, key, token, leaseTtlS) {
|
|
365
|
+
return protoRenew(sock, "n", "renew", key, token, leaseTtlS);
|
|
366
|
+
}
|
|
367
|
+
async function enqueue(sock, key, leaseTtlS) {
|
|
368
|
+
return protoEnqueue(sock, "e", "enqueue", key, leaseTtlS);
|
|
369
|
+
}
|
|
370
|
+
async function waitForLock(sock, key, waitTimeoutS) {
|
|
371
|
+
return protoWait(sock, "w", "wait", key, waitTimeoutS);
|
|
372
|
+
}
|
|
373
|
+
async function release(sock, key, token) {
|
|
374
|
+
return protoRelease(sock, "r", "release", key, token);
|
|
375
|
+
}
|
|
241
376
|
async function semAcquire(sock, key, acquireTimeoutS, limit, leaseTtlS) {
|
|
242
|
-
|
|
243
|
-
await writeAll(sock, encodeLines("sl", key, arg));
|
|
244
|
-
const resp = await readline(sock);
|
|
245
|
-
if (resp === "timeout") {
|
|
246
|
-
throw new AcquireTimeoutError(key);
|
|
247
|
-
}
|
|
248
|
-
if (!resp.startsWith("ok ")) {
|
|
249
|
-
throw new LockError(`sem_acquire failed: '${resp}'`);
|
|
250
|
-
}
|
|
251
|
-
const parts = resp.split(" ");
|
|
252
|
-
const token = parts[1];
|
|
253
|
-
const lease = parts.length >= 3 ? parseInt(parts[2], 10) : 30;
|
|
254
|
-
return { token, lease };
|
|
377
|
+
return protoAcquire(sock, "sl", "sem_acquire", key, acquireTimeoutS, leaseTtlS, limit);
|
|
255
378
|
}
|
|
256
379
|
async function semRenew(sock, key, token, leaseTtlS) {
|
|
257
|
-
|
|
258
|
-
await writeAll(sock, encodeLines("sn", key, arg));
|
|
259
|
-
const resp = await readline(sock);
|
|
260
|
-
if (resp !== "ok" && !resp.startsWith("ok ")) {
|
|
261
|
-
throw new LockError(`sem_renew failed: '${resp}'`);
|
|
262
|
-
}
|
|
263
|
-
const parts = resp.split(" ");
|
|
264
|
-
if (parts.length >= 2 && /^\d+$/.test(parts[1])) {
|
|
265
|
-
return parseInt(parts[1], 10);
|
|
266
|
-
}
|
|
267
|
-
throw new LockError(`sem_renew: malformed response: '${resp}'`);
|
|
380
|
+
return protoRenew(sock, "sn", "sem_renew", key, token, leaseTtlS);
|
|
268
381
|
}
|
|
269
382
|
async function semEnqueue(sock, key, limit, leaseTtlS) {
|
|
270
|
-
|
|
271
|
-
await writeAll(sock, encodeLines("se", key, arg));
|
|
272
|
-
const resp = await readline(sock);
|
|
273
|
-
if (resp.startsWith("acquired ")) {
|
|
274
|
-
const parts = resp.split(" ");
|
|
275
|
-
const token = parts[1];
|
|
276
|
-
const lease = parts.length >= 3 ? parseInt(parts[2], 10) : 30;
|
|
277
|
-
return { status: "acquired", token, lease };
|
|
278
|
-
}
|
|
279
|
-
if (resp === "queued") {
|
|
280
|
-
return { status: "queued", token: null, lease: null };
|
|
281
|
-
}
|
|
282
|
-
throw new LockError(`sem_enqueue failed: '${resp}'`);
|
|
383
|
+
return protoEnqueue(sock, "se", "sem_enqueue", key, leaseTtlS, limit);
|
|
283
384
|
}
|
|
284
385
|
async function semWaitForLock(sock, key, waitTimeoutS) {
|
|
285
|
-
|
|
286
|
-
const resp = await readline(sock);
|
|
287
|
-
if (resp === "timeout") {
|
|
288
|
-
throw new AcquireTimeoutError(key);
|
|
289
|
-
}
|
|
290
|
-
if (!resp.startsWith("ok ")) {
|
|
291
|
-
throw new LockError(`sem_wait failed: '${resp}'`);
|
|
292
|
-
}
|
|
293
|
-
const parts = resp.split(" ");
|
|
294
|
-
const token = parts[1];
|
|
295
|
-
const lease = parts.length >= 3 ? parseInt(parts[2], 10) : 30;
|
|
296
|
-
return { token, lease };
|
|
386
|
+
return protoWait(sock, "sw", "sem_wait", key, waitTimeoutS);
|
|
297
387
|
}
|
|
298
388
|
async function semRelease(sock, key, token) {
|
|
299
|
-
|
|
300
|
-
const resp = await readline(sock);
|
|
301
|
-
if (resp !== "ok") {
|
|
302
|
-
throw new LockError(`sem_release failed: '${resp}'`);
|
|
303
|
-
}
|
|
389
|
+
return protoRelease(sock, "sr", "sem_release", key, token);
|
|
304
390
|
}
|
|
305
391
|
async function statsProto(sock) {
|
|
306
392
|
await writeAll(sock, encodeLines("stats", "_", ""));
|
|
@@ -318,14 +404,14 @@ async function statsProto(sock) {
|
|
|
318
404
|
async function stats(options) {
|
|
319
405
|
const host = options?.host ?? DEFAULT_HOST;
|
|
320
406
|
const port = options?.port ?? DEFAULT_PORT;
|
|
321
|
-
const sock = await connect2(host, port, options?.tls, options?.auth);
|
|
407
|
+
const sock = await connect2(host, port, options?.tls, options?.auth, options?.connectTimeoutMs);
|
|
322
408
|
try {
|
|
323
409
|
return await statsProto(sock);
|
|
324
410
|
} finally {
|
|
325
411
|
sock.destroy();
|
|
326
412
|
}
|
|
327
413
|
}
|
|
328
|
-
var
|
|
414
|
+
var DistributedPrimitive = class {
|
|
329
415
|
key;
|
|
330
416
|
acquireTimeoutS;
|
|
331
417
|
leaseTtlS;
|
|
@@ -335,18 +421,30 @@ var DistributedLock = class {
|
|
|
335
421
|
tls;
|
|
336
422
|
auth;
|
|
337
423
|
onLockLost;
|
|
424
|
+
connectTimeoutMs;
|
|
425
|
+
socketTimeoutMs;
|
|
338
426
|
token = null;
|
|
339
427
|
lease = 0;
|
|
340
428
|
sock = null;
|
|
341
429
|
renewTimer = null;
|
|
430
|
+
renewInFlight = null;
|
|
342
431
|
closed = false;
|
|
343
432
|
constructor(opts) {
|
|
433
|
+
validateKey(opts.key);
|
|
344
434
|
this.key = opts.key;
|
|
345
435
|
this.acquireTimeoutS = opts.acquireTimeoutS ?? 10;
|
|
346
436
|
this.leaseTtlS = opts.leaseTtlS;
|
|
347
437
|
this.tls = opts.tls;
|
|
348
438
|
this.auth = opts.auth;
|
|
349
439
|
this.onLockLost = opts.onLockLost;
|
|
440
|
+
this.connectTimeoutMs = opts.connectTimeoutMs;
|
|
441
|
+
this.socketTimeoutMs = opts.socketTimeoutMs;
|
|
442
|
+
if (!Number.isFinite(this.acquireTimeoutS) || this.acquireTimeoutS < 0) {
|
|
443
|
+
throw new LockError("acquireTimeoutS must be a finite number >= 0");
|
|
444
|
+
}
|
|
445
|
+
if (this.leaseTtlS != null && (!Number.isFinite(this.leaseTtlS) || this.leaseTtlS <= 0)) {
|
|
446
|
+
throw new LockError("leaseTtlS must be a finite number > 0");
|
|
447
|
+
}
|
|
350
448
|
if (opts.servers) {
|
|
351
449
|
if (opts.servers.length === 0) {
|
|
352
450
|
throw new LockError("servers list must not be empty");
|
|
@@ -356,7 +454,36 @@ var DistributedLock = class {
|
|
|
356
454
|
this.servers = [[opts.host ?? DEFAULT_HOST, opts.port ?? DEFAULT_PORT]];
|
|
357
455
|
}
|
|
358
456
|
this.shardingStrategy = opts.shardingStrategy ?? stableHashShard;
|
|
359
|
-
|
|
457
|
+
const renewRatio = opts.renewRatio ?? 0.5;
|
|
458
|
+
if (!Number.isFinite(renewRatio) || renewRatio <= 0 || renewRatio >= 1) {
|
|
459
|
+
throw new LockError("renewRatio must be a finite number between 0 and 1 (exclusive)");
|
|
460
|
+
}
|
|
461
|
+
this.renewRatio = renewRatio;
|
|
462
|
+
}
|
|
463
|
+
// -- public API --
|
|
464
|
+
async openConnection() {
|
|
465
|
+
const [host, port] = this.pickServer();
|
|
466
|
+
const sock = await connect2(host, port, this.tls, this.auth, this.connectTimeoutMs);
|
|
467
|
+
if (this.socketTimeoutMs != null && this.socketTimeoutMs > 0) {
|
|
468
|
+
sock.on("timeout", () => {
|
|
469
|
+
sock.destroy(new LockError("socket idle timeout"));
|
|
470
|
+
});
|
|
471
|
+
sock.setTimeout(this.socketTimeoutMs);
|
|
472
|
+
}
|
|
473
|
+
return sock;
|
|
474
|
+
}
|
|
475
|
+
/**
|
|
476
|
+
* Suspend or restore the socket idle timeout. Only has effect when
|
|
477
|
+
* `socketTimeoutMs` was set at construction time and a listener was
|
|
478
|
+
* registered in `openConnection`.
|
|
479
|
+
*/
|
|
480
|
+
suspendSocketTimeout(sock) {
|
|
481
|
+
sock.setTimeout(0);
|
|
482
|
+
}
|
|
483
|
+
restoreSocketTimeout(sock) {
|
|
484
|
+
if (this.socketTimeoutMs != null && this.socketTimeoutMs > 0) {
|
|
485
|
+
sock.setTimeout(this.socketTimeoutMs);
|
|
486
|
+
}
|
|
360
487
|
}
|
|
361
488
|
pickServer() {
|
|
362
489
|
const idx = this.shardingStrategy(this.key, this.servers.length);
|
|
@@ -368,26 +495,26 @@ var DistributedLock = class {
|
|
|
368
495
|
return this.servers[idx];
|
|
369
496
|
}
|
|
370
497
|
/**
|
|
371
|
-
* Acquire the lock
|
|
372
|
-
*
|
|
498
|
+
* Acquire the lock / semaphore slot.
|
|
499
|
+
* Returns `true` on success, `false` on timeout.
|
|
500
|
+
* @param opts.force - If `true`, silently close any existing connection
|
|
501
|
+
* before acquiring. Defaults to `false`, which throws if already connected.
|
|
373
502
|
*/
|
|
374
503
|
async acquire(opts) {
|
|
375
504
|
if (this.sock && !this.closed) {
|
|
376
505
|
if (!opts?.force) {
|
|
377
|
-
throw new LockError(
|
|
506
|
+
throw new LockError(
|
|
507
|
+
"already connected; call release() or close() first, or pass { force: true }"
|
|
508
|
+
);
|
|
378
509
|
}
|
|
379
510
|
this.close();
|
|
380
511
|
}
|
|
381
512
|
this.closed = false;
|
|
382
|
-
|
|
383
|
-
this.sock = await connect2(host, port, this.tls, this.auth);
|
|
513
|
+
this.sock = await this.openConnection();
|
|
384
514
|
try {
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
this.acquireTimeoutS,
|
|
389
|
-
this.leaseTtlS
|
|
390
|
-
);
|
|
515
|
+
this.suspendSocketTimeout(this.sock);
|
|
516
|
+
const result = await this.doAcquire(this.sock);
|
|
517
|
+
this.restoreSocketTimeout(this.sock);
|
|
391
518
|
this.token = result.token;
|
|
392
519
|
this.lease = result.lease;
|
|
393
520
|
} catch (err) {
|
|
@@ -398,12 +525,36 @@ var DistributedLock = class {
|
|
|
398
525
|
this.startRenew();
|
|
399
526
|
return true;
|
|
400
527
|
}
|
|
401
|
-
/**
|
|
528
|
+
/**
|
|
529
|
+
* Release the lock / semaphore slot and close the connection.
|
|
530
|
+
*
|
|
531
|
+
* Throws `LockError` if the instance is already closed (e.g. after a
|
|
532
|
+
* previous `release()` or `close()` call).
|
|
533
|
+
*
|
|
534
|
+
* The server-side release itself is best-effort: if the underlying
|
|
535
|
+
* connection is already dead the protocol-level release error is silently
|
|
536
|
+
* ignored so that the method doesn't throw on transient network failures.
|
|
537
|
+
*/
|
|
402
538
|
async release() {
|
|
539
|
+
if (this.closed) {
|
|
540
|
+
throw new LockError("not connected; nothing to release");
|
|
541
|
+
}
|
|
542
|
+
const tokenToRelease = this.token;
|
|
543
|
+
const sockToRelease = this.sock;
|
|
403
544
|
try {
|
|
404
545
|
this.stopRenew();
|
|
405
|
-
if (this.
|
|
406
|
-
await
|
|
546
|
+
if (this.renewInFlight) {
|
|
547
|
+
await Promise.race([
|
|
548
|
+
this.renewInFlight,
|
|
549
|
+
new Promise((r) => setTimeout(r, 5e3))
|
|
550
|
+
]);
|
|
551
|
+
this.stopRenew();
|
|
552
|
+
}
|
|
553
|
+
if (sockToRelease != null && tokenToRelease != null) {
|
|
554
|
+
try {
|
|
555
|
+
await this.doRelease(sockToRelease, tokenToRelease);
|
|
556
|
+
} catch {
|
|
557
|
+
}
|
|
407
558
|
}
|
|
408
559
|
} finally {
|
|
409
560
|
this.close();
|
|
@@ -411,27 +562,31 @@ var DistributedLock = class {
|
|
|
411
562
|
}
|
|
412
563
|
/**
|
|
413
564
|
* Two-phase step 1: connect and join the FIFO queue.
|
|
414
|
-
* Returns `"acquired"` (fast-path
|
|
565
|
+
* Returns `"acquired"` (fast-path) or `"queued"`.
|
|
415
566
|
* If acquired immediately, the renew loop starts automatically.
|
|
416
|
-
* @param opts.force - If `true`, silently close any existing connection
|
|
567
|
+
* @param opts.force - If `true`, silently close any existing connection
|
|
568
|
+
* before enqueuing. Defaults to `false`, which throws if already connected.
|
|
417
569
|
*/
|
|
418
570
|
async enqueue(opts) {
|
|
419
571
|
if (this.sock && !this.closed) {
|
|
420
572
|
if (!opts?.force) {
|
|
421
|
-
throw new LockError(
|
|
573
|
+
throw new LockError(
|
|
574
|
+
"already connected; call release() or close() first, or pass { force: true }"
|
|
575
|
+
);
|
|
422
576
|
}
|
|
423
577
|
this.close();
|
|
424
578
|
}
|
|
425
579
|
this.closed = false;
|
|
426
|
-
|
|
427
|
-
this.sock = await connect2(host, port, this.tls, this.auth);
|
|
580
|
+
this.sock = await this.openConnection();
|
|
428
581
|
try {
|
|
429
|
-
|
|
582
|
+
this.suspendSocketTimeout(this.sock);
|
|
583
|
+
const result = await this.doEnqueue(this.sock);
|
|
430
584
|
if (result.status === "acquired") {
|
|
431
585
|
this.token = result.token;
|
|
432
586
|
this.lease = result.lease ?? 0;
|
|
433
587
|
this.startRenew();
|
|
434
588
|
}
|
|
589
|
+
this.restoreSocketTimeout(this.sock);
|
|
435
590
|
return result.status;
|
|
436
591
|
} catch (err) {
|
|
437
592
|
this.close();
|
|
@@ -439,7 +594,7 @@ var DistributedLock = class {
|
|
|
439
594
|
}
|
|
440
595
|
}
|
|
441
596
|
/**
|
|
442
|
-
* Two-phase step 2: block until the lock is granted.
|
|
597
|
+
* Two-phase step 2: block until the lock / slot is granted.
|
|
443
598
|
* Returns `true` if granted, `false` on timeout.
|
|
444
599
|
* If already acquired during `enqueue()`, returns `true` immediately.
|
|
445
600
|
*/
|
|
@@ -447,12 +602,17 @@ var DistributedLock = class {
|
|
|
447
602
|
if (this.token !== null) {
|
|
448
603
|
return true;
|
|
449
604
|
}
|
|
605
|
+
if (this.closed) {
|
|
606
|
+
throw new LockError("connection closed; call enqueue() again");
|
|
607
|
+
}
|
|
450
608
|
if (!this.sock) {
|
|
451
609
|
throw new LockError("not connected; call enqueue() first");
|
|
452
610
|
}
|
|
453
611
|
const timeout = timeoutS ?? this.acquireTimeoutS;
|
|
454
612
|
try {
|
|
455
|
-
|
|
613
|
+
this.suspendSocketTimeout(this.sock);
|
|
614
|
+
const result = await this.doWait(this.sock, timeout);
|
|
615
|
+
this.restoreSocketTimeout(this.sock);
|
|
456
616
|
this.token = result.token;
|
|
457
617
|
this.lease = result.lease;
|
|
458
618
|
} catch (err) {
|
|
@@ -464,24 +624,28 @@ var DistributedLock = class {
|
|
|
464
624
|
return true;
|
|
465
625
|
}
|
|
466
626
|
/**
|
|
467
|
-
* Run `fn` while holding the lock, then release automatically.
|
|
627
|
+
* Run `fn` while holding the lock / slot, then release automatically.
|
|
468
628
|
*
|
|
469
|
-
*
|
|
470
|
-
*
|
|
471
|
-
* await lock.withLock(async () => {
|
|
472
|
-
* // critical section
|
|
473
|
-
* });
|
|
474
|
-
* ```
|
|
629
|
+
* If `fn()` throws, its error is always preserved — a concurrent
|
|
630
|
+
* release failure will not mask it.
|
|
475
631
|
*/
|
|
476
632
|
async withLock(fn) {
|
|
477
633
|
const ok = await this.acquire();
|
|
478
634
|
if (!ok) {
|
|
479
635
|
throw new AcquireTimeoutError(this.key);
|
|
480
636
|
}
|
|
637
|
+
let threw = false;
|
|
481
638
|
try {
|
|
482
639
|
return await fn();
|
|
640
|
+
} catch (err) {
|
|
641
|
+
threw = true;
|
|
642
|
+
throw err;
|
|
483
643
|
} finally {
|
|
484
|
-
|
|
644
|
+
try {
|
|
645
|
+
await this.release();
|
|
646
|
+
} catch (releaseErr) {
|
|
647
|
+
if (!threw) throw releaseErr;
|
|
648
|
+
}
|
|
485
649
|
}
|
|
486
650
|
}
|
|
487
651
|
/** Close the underlying socket (idempotent). */
|
|
@@ -489,29 +653,50 @@ var DistributedLock = class {
|
|
|
489
653
|
if (this.closed) return;
|
|
490
654
|
this.closed = true;
|
|
491
655
|
this.stopRenew();
|
|
656
|
+
this.renewInFlight = null;
|
|
492
657
|
if (this.sock) {
|
|
493
658
|
this.sock.destroy();
|
|
494
659
|
this.sock = null;
|
|
495
660
|
}
|
|
496
661
|
this.token = null;
|
|
662
|
+
this.lease = 0;
|
|
497
663
|
}
|
|
498
664
|
// -- internals --
|
|
499
665
|
startRenew() {
|
|
666
|
+
this.stopRenew();
|
|
500
667
|
const loop = async () => {
|
|
501
668
|
const savedToken = this.token;
|
|
502
|
-
|
|
669
|
+
const sock = this.sock;
|
|
670
|
+
if (!sock || !savedToken) return;
|
|
503
671
|
const start = Date.now();
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
672
|
+
const p = (async () => {
|
|
673
|
+
try {
|
|
674
|
+
const newLease = await this.doRenew(sock, savedToken);
|
|
675
|
+
if (this.token === savedToken && !this.closed) {
|
|
676
|
+
this.lease = newLease;
|
|
677
|
+
}
|
|
678
|
+
} catch {
|
|
679
|
+
if (this.token === savedToken) {
|
|
680
|
+
this.token = null;
|
|
681
|
+
if (this.onLockLost) {
|
|
682
|
+
try {
|
|
683
|
+
const result = this.onLockLost(this.key, savedToken);
|
|
684
|
+
if (result instanceof Promise) {
|
|
685
|
+
result.catch(() => {
|
|
686
|
+
});
|
|
687
|
+
}
|
|
688
|
+
} catch {
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
this.close();
|
|
511
692
|
}
|
|
693
|
+
return;
|
|
512
694
|
}
|
|
513
|
-
|
|
514
|
-
|
|
695
|
+
})();
|
|
696
|
+
this.renewInFlight = p;
|
|
697
|
+
await p;
|
|
698
|
+
this.renewInFlight = null;
|
|
699
|
+
if (this.closed || this.token !== savedToken) return;
|
|
515
700
|
const elapsed = Date.now() - start;
|
|
516
701
|
const interval2 = Math.max(1, this.lease * this.renewRatio) * 1e3;
|
|
517
702
|
this.renewTimer = setTimeout(loop, Math.max(0, interval2 - elapsed));
|
|
@@ -528,210 +713,55 @@ var DistributedLock = class {
|
|
|
528
713
|
}
|
|
529
714
|
}
|
|
530
715
|
};
|
|
531
|
-
var
|
|
532
|
-
key;
|
|
533
|
-
limit;
|
|
534
|
-
acquireTimeoutS;
|
|
535
|
-
leaseTtlS;
|
|
536
|
-
servers;
|
|
537
|
-
shardingStrategy;
|
|
538
|
-
renewRatio;
|
|
539
|
-
tls;
|
|
540
|
-
auth;
|
|
541
|
-
onLockLost;
|
|
542
|
-
token = null;
|
|
543
|
-
lease = 0;
|
|
544
|
-
sock = null;
|
|
545
|
-
renewTimer = null;
|
|
546
|
-
closed = false;
|
|
716
|
+
var DistributedLock = class extends DistributedPrimitive {
|
|
547
717
|
constructor(opts) {
|
|
548
|
-
|
|
549
|
-
this.limit = opts.limit;
|
|
550
|
-
this.acquireTimeoutS = opts.acquireTimeoutS ?? 10;
|
|
551
|
-
this.leaseTtlS = opts.leaseTtlS;
|
|
552
|
-
this.tls = opts.tls;
|
|
553
|
-
this.auth = opts.auth;
|
|
554
|
-
this.onLockLost = opts.onLockLost;
|
|
555
|
-
if (opts.servers) {
|
|
556
|
-
if (opts.servers.length === 0) {
|
|
557
|
-
throw new LockError("servers list must not be empty");
|
|
558
|
-
}
|
|
559
|
-
this.servers = [...opts.servers];
|
|
560
|
-
} else {
|
|
561
|
-
this.servers = [[opts.host ?? DEFAULT_HOST, opts.port ?? DEFAULT_PORT]];
|
|
562
|
-
}
|
|
563
|
-
this.shardingStrategy = opts.shardingStrategy ?? stableHashShard;
|
|
564
|
-
this.renewRatio = opts.renewRatio ?? 0.5;
|
|
718
|
+
super(opts);
|
|
565
719
|
}
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
if (!Number.isInteger(idx) || idx < 0 || idx >= this.servers.length) {
|
|
569
|
-
throw new LockError(
|
|
570
|
-
`shardingStrategy returned invalid index ${idx} for ${this.servers.length} server(s)`
|
|
571
|
-
);
|
|
572
|
-
}
|
|
573
|
-
return this.servers[idx];
|
|
720
|
+
doAcquire(sock) {
|
|
721
|
+
return acquire(sock, this.key, this.acquireTimeoutS, this.leaseTtlS);
|
|
574
722
|
}
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
* @param opts.force - If `true`, silently close any existing connection before acquiring. Defaults to `false`, which throws if already connected.
|
|
578
|
-
*/
|
|
579
|
-
async acquire(opts) {
|
|
580
|
-
if (this.sock && !this.closed) {
|
|
581
|
-
if (!opts?.force) {
|
|
582
|
-
throw new LockError("already connected; call release() or close() first, or pass { force: true }");
|
|
583
|
-
}
|
|
584
|
-
this.close();
|
|
585
|
-
}
|
|
586
|
-
this.closed = false;
|
|
587
|
-
const [host, port] = this.pickServer();
|
|
588
|
-
this.sock = await connect2(host, port, this.tls, this.auth);
|
|
589
|
-
try {
|
|
590
|
-
const result = await semAcquire(
|
|
591
|
-
this.sock,
|
|
592
|
-
this.key,
|
|
593
|
-
this.acquireTimeoutS,
|
|
594
|
-
this.limit,
|
|
595
|
-
this.leaseTtlS
|
|
596
|
-
);
|
|
597
|
-
this.token = result.token;
|
|
598
|
-
this.lease = result.lease;
|
|
599
|
-
} catch (err) {
|
|
600
|
-
this.close();
|
|
601
|
-
if (err instanceof AcquireTimeoutError) return false;
|
|
602
|
-
throw err;
|
|
603
|
-
}
|
|
604
|
-
this.startRenew();
|
|
605
|
-
return true;
|
|
723
|
+
doEnqueue(sock) {
|
|
724
|
+
return enqueue(sock, this.key, this.leaseTtlS);
|
|
606
725
|
}
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
try {
|
|
610
|
-
this.stopRenew();
|
|
611
|
-
if (this.sock && this.token) {
|
|
612
|
-
await semRelease(this.sock, this.key, this.token);
|
|
613
|
-
}
|
|
614
|
-
} finally {
|
|
615
|
-
this.close();
|
|
616
|
-
}
|
|
726
|
+
doWait(sock, timeoutS) {
|
|
727
|
+
return waitForLock(sock, this.key, timeoutS);
|
|
617
728
|
}
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
* Returns `"acquired"` (fast-path, slot granted immediately) or `"queued"`.
|
|
621
|
-
* If acquired immediately, the renew loop starts automatically.
|
|
622
|
-
* @param opts.force - If `true`, silently close any existing connection before enqueuing. Defaults to `false`, which throws if already connected.
|
|
623
|
-
*/
|
|
624
|
-
async enqueue(opts) {
|
|
625
|
-
if (this.sock && !this.closed) {
|
|
626
|
-
if (!opts?.force) {
|
|
627
|
-
throw new LockError("already connected; call release() or close() first, or pass { force: true }");
|
|
628
|
-
}
|
|
629
|
-
this.close();
|
|
630
|
-
}
|
|
631
|
-
this.closed = false;
|
|
632
|
-
const [host, port] = this.pickServer();
|
|
633
|
-
this.sock = await connect2(host, port, this.tls, this.auth);
|
|
634
|
-
try {
|
|
635
|
-
const result = await semEnqueue(this.sock, this.key, this.limit, this.leaseTtlS);
|
|
636
|
-
if (result.status === "acquired") {
|
|
637
|
-
this.token = result.token;
|
|
638
|
-
this.lease = result.lease ?? 0;
|
|
639
|
-
this.startRenew();
|
|
640
|
-
}
|
|
641
|
-
return result.status;
|
|
642
|
-
} catch (err) {
|
|
643
|
-
this.close();
|
|
644
|
-
throw err;
|
|
645
|
-
}
|
|
729
|
+
doRelease(sock, token) {
|
|
730
|
+
return release(sock, this.key, token);
|
|
646
731
|
}
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
* Returns `true` if granted, `false` on timeout.
|
|
650
|
-
* If already acquired during `enqueue()`, returns `true` immediately.
|
|
651
|
-
*/
|
|
652
|
-
async wait(timeoutS) {
|
|
653
|
-
if (this.token !== null) {
|
|
654
|
-
return true;
|
|
655
|
-
}
|
|
656
|
-
if (!this.sock) {
|
|
657
|
-
throw new LockError("not connected; call enqueue() first");
|
|
658
|
-
}
|
|
659
|
-
const timeout = timeoutS ?? this.acquireTimeoutS;
|
|
660
|
-
try {
|
|
661
|
-
const result = await semWaitForLock(this.sock, this.key, timeout);
|
|
662
|
-
this.token = result.token;
|
|
663
|
-
this.lease = result.lease;
|
|
664
|
-
} catch (err) {
|
|
665
|
-
this.close();
|
|
666
|
-
if (err instanceof AcquireTimeoutError) return false;
|
|
667
|
-
throw err;
|
|
668
|
-
}
|
|
669
|
-
this.startRenew();
|
|
670
|
-
return true;
|
|
732
|
+
doRenew(sock, token) {
|
|
733
|
+
return renew(sock, this.key, token, this.leaseTtlS);
|
|
671
734
|
}
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
* });
|
|
680
|
-
* ```
|
|
681
|
-
*/
|
|
682
|
-
async withLock(fn) {
|
|
683
|
-
const ok = await this.acquire();
|
|
684
|
-
if (!ok) {
|
|
685
|
-
throw new AcquireTimeoutError(this.key);
|
|
686
|
-
}
|
|
687
|
-
try {
|
|
688
|
-
return await fn();
|
|
689
|
-
} finally {
|
|
690
|
-
await this.release();
|
|
735
|
+
};
|
|
736
|
+
var DistributedSemaphore = class extends DistributedPrimitive {
|
|
737
|
+
limit;
|
|
738
|
+
constructor(opts) {
|
|
739
|
+
super(opts);
|
|
740
|
+
if (!Number.isInteger(opts.limit) || opts.limit < 1) {
|
|
741
|
+
throw new LockError("limit must be an integer >= 1");
|
|
691
742
|
}
|
|
743
|
+
this.limit = opts.limit;
|
|
692
744
|
}
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
this.
|
|
700
|
-
|
|
701
|
-
}
|
|
702
|
-
this.token = null;
|
|
745
|
+
doAcquire(sock) {
|
|
746
|
+
return semAcquire(
|
|
747
|
+
sock,
|
|
748
|
+
this.key,
|
|
749
|
+
this.acquireTimeoutS,
|
|
750
|
+
this.limit,
|
|
751
|
+
this.leaseTtlS
|
|
752
|
+
);
|
|
703
753
|
}
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
const loop = async () => {
|
|
707
|
-
const savedToken = this.token;
|
|
708
|
-
if (!this.sock || !savedToken) return;
|
|
709
|
-
const start = Date.now();
|
|
710
|
-
try {
|
|
711
|
-
this.lease = await semRenew(this.sock, this.key, savedToken, this.leaseTtlS);
|
|
712
|
-
} catch {
|
|
713
|
-
if (this.token === savedToken) {
|
|
714
|
-
this.token = null;
|
|
715
|
-
if (this.onLockLost) {
|
|
716
|
-
this.onLockLost(this.key, savedToken);
|
|
717
|
-
}
|
|
718
|
-
}
|
|
719
|
-
return;
|
|
720
|
-
}
|
|
721
|
-
const elapsed = Date.now() - start;
|
|
722
|
-
const interval2 = Math.max(1, this.lease * this.renewRatio) * 1e3;
|
|
723
|
-
this.renewTimer = setTimeout(loop, Math.max(0, interval2 - elapsed));
|
|
724
|
-
this.renewTimer.unref();
|
|
725
|
-
};
|
|
726
|
-
const interval = Math.max(1, this.lease * this.renewRatio) * 1e3;
|
|
727
|
-
this.renewTimer = setTimeout(loop, interval);
|
|
728
|
-
this.renewTimer.unref();
|
|
754
|
+
doEnqueue(sock) {
|
|
755
|
+
return semEnqueue(sock, this.key, this.limit, this.leaseTtlS);
|
|
729
756
|
}
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
757
|
+
doWait(sock, timeoutS) {
|
|
758
|
+
return semWaitForLock(sock, this.key, timeoutS);
|
|
759
|
+
}
|
|
760
|
+
doRelease(sock, token) {
|
|
761
|
+
return semRelease(sock, this.key, token);
|
|
762
|
+
}
|
|
763
|
+
doRenew(sock, token) {
|
|
764
|
+
return semRenew(sock, this.key, token, this.leaseTtlS);
|
|
735
765
|
}
|
|
736
766
|
};
|
|
737
767
|
// Annotate the CommonJS export names for ESM import in node:
|