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