dflockd-client 1.8.0 → 1.8.2
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/client.cjs +108 -61
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +24 -8
- package/dist/client.d.ts +24 -8
- package/dist/client.js +108 -61
- package/dist/client.js.map +1 -1
- package/package.json +1 -1
package/dist/client.d.cts
CHANGED
|
@@ -118,16 +118,24 @@ declare class DistributedLock {
|
|
|
118
118
|
private closed;
|
|
119
119
|
constructor(opts: DistributedLockOptions);
|
|
120
120
|
private pickServer;
|
|
121
|
-
/**
|
|
122
|
-
|
|
121
|
+
/**
|
|
122
|
+
* Acquire the lock. Returns `true` on success, `false` on timeout.
|
|
123
|
+
* @param opts.force - If `true`, silently close any existing connection before acquiring. Defaults to `false`, which throws if already connected.
|
|
124
|
+
*/
|
|
125
|
+
acquire(opts?: {
|
|
126
|
+
force?: boolean;
|
|
127
|
+
}): Promise<boolean>;
|
|
123
128
|
/** Release the lock and close the connection. */
|
|
124
129
|
release(): Promise<void>;
|
|
125
130
|
/**
|
|
126
131
|
* Two-phase step 1: connect and join the FIFO queue.
|
|
127
132
|
* Returns `"acquired"` (fast-path, lock is already held) or `"queued"`.
|
|
128
133
|
* If acquired immediately, the renew loop starts automatically.
|
|
134
|
+
* @param opts.force - If `true`, silently close any existing connection before enqueuing. Defaults to `false`, which throws if already connected.
|
|
129
135
|
*/
|
|
130
|
-
enqueue(
|
|
136
|
+
enqueue(opts?: {
|
|
137
|
+
force?: boolean;
|
|
138
|
+
}): Promise<"acquired" | "queued">;
|
|
131
139
|
/**
|
|
132
140
|
* Two-phase step 2: block until the lock is granted.
|
|
133
141
|
* Returns `true` if granted, `false` on timeout.
|
|
@@ -146,7 +154,7 @@ declare class DistributedLock {
|
|
|
146
154
|
*/
|
|
147
155
|
withLock<T>(fn: () => T | Promise<T>): Promise<T>;
|
|
148
156
|
/** Close the underlying socket (idempotent). */
|
|
149
|
-
close():
|
|
157
|
+
close(): void;
|
|
150
158
|
private startRenew;
|
|
151
159
|
private stopRenew;
|
|
152
160
|
}
|
|
@@ -184,16 +192,24 @@ declare class DistributedSemaphore {
|
|
|
184
192
|
private closed;
|
|
185
193
|
constructor(opts: DistributedSemaphoreOptions);
|
|
186
194
|
private pickServer;
|
|
187
|
-
/**
|
|
188
|
-
|
|
195
|
+
/**
|
|
196
|
+
* Acquire a semaphore slot. Returns `true` on success, `false` on timeout.
|
|
197
|
+
* @param opts.force - If `true`, silently close any existing connection before acquiring. Defaults to `false`, which throws if already connected.
|
|
198
|
+
*/
|
|
199
|
+
acquire(opts?: {
|
|
200
|
+
force?: boolean;
|
|
201
|
+
}): Promise<boolean>;
|
|
189
202
|
/** Release the semaphore slot and close the connection. */
|
|
190
203
|
release(): Promise<void>;
|
|
191
204
|
/**
|
|
192
205
|
* Two-phase step 1: connect and join the FIFO queue.
|
|
193
206
|
* Returns `"acquired"` (fast-path, slot granted immediately) or `"queued"`.
|
|
194
207
|
* If acquired immediately, the renew loop starts automatically.
|
|
208
|
+
* @param opts.force - If `true`, silently close any existing connection before enqueuing. Defaults to `false`, which throws if already connected.
|
|
195
209
|
*/
|
|
196
|
-
enqueue(
|
|
210
|
+
enqueue(opts?: {
|
|
211
|
+
force?: boolean;
|
|
212
|
+
}): Promise<"acquired" | "queued">;
|
|
197
213
|
/**
|
|
198
214
|
* Two-phase step 2: block until a semaphore slot is granted.
|
|
199
215
|
* Returns `true` if granted, `false` on timeout.
|
|
@@ -212,7 +228,7 @@ declare class DistributedSemaphore {
|
|
|
212
228
|
*/
|
|
213
229
|
withLock<T>(fn: () => T | Promise<T>): Promise<T>;
|
|
214
230
|
/** Close the underlying socket (idempotent). */
|
|
215
|
-
close():
|
|
231
|
+
close(): void;
|
|
216
232
|
private startRenew;
|
|
217
233
|
private stopRenew;
|
|
218
234
|
}
|
package/dist/client.d.ts
CHANGED
|
@@ -118,16 +118,24 @@ declare class DistributedLock {
|
|
|
118
118
|
private closed;
|
|
119
119
|
constructor(opts: DistributedLockOptions);
|
|
120
120
|
private pickServer;
|
|
121
|
-
/**
|
|
122
|
-
|
|
121
|
+
/**
|
|
122
|
+
* Acquire the lock. Returns `true` on success, `false` on timeout.
|
|
123
|
+
* @param opts.force - If `true`, silently close any existing connection before acquiring. Defaults to `false`, which throws if already connected.
|
|
124
|
+
*/
|
|
125
|
+
acquire(opts?: {
|
|
126
|
+
force?: boolean;
|
|
127
|
+
}): Promise<boolean>;
|
|
123
128
|
/** Release the lock and close the connection. */
|
|
124
129
|
release(): Promise<void>;
|
|
125
130
|
/**
|
|
126
131
|
* Two-phase step 1: connect and join the FIFO queue.
|
|
127
132
|
* Returns `"acquired"` (fast-path, lock is already held) or `"queued"`.
|
|
128
133
|
* If acquired immediately, the renew loop starts automatically.
|
|
134
|
+
* @param opts.force - If `true`, silently close any existing connection before enqueuing. Defaults to `false`, which throws if already connected.
|
|
129
135
|
*/
|
|
130
|
-
enqueue(
|
|
136
|
+
enqueue(opts?: {
|
|
137
|
+
force?: boolean;
|
|
138
|
+
}): Promise<"acquired" | "queued">;
|
|
131
139
|
/**
|
|
132
140
|
* Two-phase step 2: block until the lock is granted.
|
|
133
141
|
* Returns `true` if granted, `false` on timeout.
|
|
@@ -146,7 +154,7 @@ declare class DistributedLock {
|
|
|
146
154
|
*/
|
|
147
155
|
withLock<T>(fn: () => T | Promise<T>): Promise<T>;
|
|
148
156
|
/** Close the underlying socket (idempotent). */
|
|
149
|
-
close():
|
|
157
|
+
close(): void;
|
|
150
158
|
private startRenew;
|
|
151
159
|
private stopRenew;
|
|
152
160
|
}
|
|
@@ -184,16 +192,24 @@ declare class DistributedSemaphore {
|
|
|
184
192
|
private closed;
|
|
185
193
|
constructor(opts: DistributedSemaphoreOptions);
|
|
186
194
|
private pickServer;
|
|
187
|
-
/**
|
|
188
|
-
|
|
195
|
+
/**
|
|
196
|
+
* Acquire a semaphore slot. Returns `true` on success, `false` on timeout.
|
|
197
|
+
* @param opts.force - If `true`, silently close any existing connection before acquiring. Defaults to `false`, which throws if already connected.
|
|
198
|
+
*/
|
|
199
|
+
acquire(opts?: {
|
|
200
|
+
force?: boolean;
|
|
201
|
+
}): Promise<boolean>;
|
|
189
202
|
/** Release the semaphore slot and close the connection. */
|
|
190
203
|
release(): Promise<void>;
|
|
191
204
|
/**
|
|
192
205
|
* Two-phase step 1: connect and join the FIFO queue.
|
|
193
206
|
* Returns `"acquired"` (fast-path, slot granted immediately) or `"queued"`.
|
|
194
207
|
* If acquired immediately, the renew loop starts automatically.
|
|
208
|
+
* @param opts.force - If `true`, silently close any existing connection before enqueuing. Defaults to `false`, which throws if already connected.
|
|
195
209
|
*/
|
|
196
|
-
enqueue(
|
|
210
|
+
enqueue(opts?: {
|
|
211
|
+
force?: boolean;
|
|
212
|
+
}): Promise<"acquired" | "queued">;
|
|
197
213
|
/**
|
|
198
214
|
* Two-phase step 2: block until a semaphore slot is granted.
|
|
199
215
|
* Returns `true` if granted, `false` on timeout.
|
|
@@ -212,7 +228,7 @@ declare class DistributedSemaphore {
|
|
|
212
228
|
*/
|
|
213
229
|
withLock<T>(fn: () => T | Promise<T>): Promise<T>;
|
|
214
230
|
/** Close the underlying socket (idempotent). */
|
|
215
|
-
close():
|
|
231
|
+
close(): void;
|
|
216
232
|
private startRenew;
|
|
217
233
|
private stopRenew;
|
|
218
234
|
}
|
package/dist/client.js
CHANGED
|
@@ -24,6 +24,14 @@ var AuthError = class extends LockError {
|
|
|
24
24
|
function encodeLines(...lines) {
|
|
25
25
|
return Buffer.from(lines.map((l) => l + "\n").join(""), "utf-8");
|
|
26
26
|
}
|
|
27
|
+
function writeAll(sock, data) {
|
|
28
|
+
return new Promise((resolve, reject) => {
|
|
29
|
+
sock.write(data, (err) => {
|
|
30
|
+
if (err) reject(err);
|
|
31
|
+
else resolve();
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
}
|
|
27
35
|
var _readlineBuf = /* @__PURE__ */ new WeakMap();
|
|
28
36
|
function readline(sock) {
|
|
29
37
|
return new Promise((resolve, reject) => {
|
|
@@ -47,10 +55,12 @@ function readline(sock) {
|
|
|
47
55
|
};
|
|
48
56
|
const onError = (err) => {
|
|
49
57
|
cleanup();
|
|
58
|
+
_readlineBuf.delete(sock);
|
|
50
59
|
reject(err);
|
|
51
60
|
};
|
|
52
61
|
const onClose = () => {
|
|
53
62
|
cleanup();
|
|
63
|
+
_readlineBuf.delete(sock);
|
|
54
64
|
reject(new LockError("server closed connection"));
|
|
55
65
|
};
|
|
56
66
|
const cleanup = () => {
|
|
@@ -79,10 +89,11 @@ async function connect2(host, port, tlsOptions, auth) {
|
|
|
79
89
|
s.on("error", reject);
|
|
80
90
|
}
|
|
81
91
|
});
|
|
92
|
+
sock.setNoDelay(true);
|
|
82
93
|
sock.on("error", () => {
|
|
83
94
|
});
|
|
84
95
|
if (auth != null && auth !== "") {
|
|
85
|
-
sock
|
|
96
|
+
await writeAll(sock, encodeLines("auth", "_", auth));
|
|
86
97
|
const resp = await readline(sock);
|
|
87
98
|
if (resp === "ok") {
|
|
88
99
|
return sock;
|
|
@@ -115,7 +126,7 @@ function stableHashShard(key, numServers) {
|
|
|
115
126
|
}
|
|
116
127
|
async function acquire(sock, key, acquireTimeoutS, leaseTtlS) {
|
|
117
128
|
const arg = leaseTtlS == null ? String(acquireTimeoutS) : `${acquireTimeoutS} ${leaseTtlS}`;
|
|
118
|
-
sock
|
|
129
|
+
await writeAll(sock, encodeLines("l", key, arg));
|
|
119
130
|
const resp = await readline(sock);
|
|
120
131
|
if (resp === "timeout") {
|
|
121
132
|
throw new AcquireTimeoutError(key);
|
|
@@ -124,18 +135,15 @@ async function acquire(sock, key, acquireTimeoutS, leaseTtlS) {
|
|
|
124
135
|
throw new LockError(`acquire failed: '${resp}'`);
|
|
125
136
|
}
|
|
126
137
|
const parts = resp.split(" ");
|
|
127
|
-
if (parts.length < 2) {
|
|
128
|
-
throw new LockError(`bad ok response: '${resp}'`);
|
|
129
|
-
}
|
|
130
138
|
const token = parts[1];
|
|
131
139
|
const lease = parts.length >= 3 ? parseInt(parts[2], 10) : 30;
|
|
132
140
|
return { token, lease };
|
|
133
141
|
}
|
|
134
142
|
async function renew(sock, key, token, leaseTtlS) {
|
|
135
143
|
const arg = leaseTtlS == null ? token : `${token} ${leaseTtlS}`;
|
|
136
|
-
sock
|
|
144
|
+
await writeAll(sock, encodeLines("n", key, arg));
|
|
137
145
|
const resp = await readline(sock);
|
|
138
|
-
if (!resp.startsWith("ok")) {
|
|
146
|
+
if (resp !== "ok" && !resp.startsWith("ok ")) {
|
|
139
147
|
throw new LockError(`renew failed: '${resp}'`);
|
|
140
148
|
}
|
|
141
149
|
const parts = resp.split(" ");
|
|
@@ -146,12 +154,12 @@ async function renew(sock, key, token, leaseTtlS) {
|
|
|
146
154
|
}
|
|
147
155
|
async function enqueue(sock, key, leaseTtlS) {
|
|
148
156
|
const arg = leaseTtlS == null ? "" : String(leaseTtlS);
|
|
149
|
-
sock
|
|
157
|
+
await writeAll(sock, encodeLines("e", key, arg));
|
|
150
158
|
const resp = await readline(sock);
|
|
151
159
|
if (resp.startsWith("acquired ")) {
|
|
152
160
|
const parts = resp.split(" ");
|
|
153
161
|
const token = parts[1];
|
|
154
|
-
const lease = parts.length >= 3 ? parseInt(parts[2], 10) :
|
|
162
|
+
const lease = parts.length >= 3 ? parseInt(parts[2], 10) : 30;
|
|
155
163
|
return { status: "acquired", token, lease };
|
|
156
164
|
}
|
|
157
165
|
if (resp === "queued") {
|
|
@@ -160,7 +168,7 @@ async function enqueue(sock, key, leaseTtlS) {
|
|
|
160
168
|
throw new LockError(`enqueue failed: '${resp}'`);
|
|
161
169
|
}
|
|
162
170
|
async function waitForLock(sock, key, waitTimeoutS) {
|
|
163
|
-
sock
|
|
171
|
+
await writeAll(sock, encodeLines("w", key, String(waitTimeoutS)));
|
|
164
172
|
const resp = await readline(sock);
|
|
165
173
|
if (resp === "timeout") {
|
|
166
174
|
throw new AcquireTimeoutError(key);
|
|
@@ -170,11 +178,11 @@ async function waitForLock(sock, key, waitTimeoutS) {
|
|
|
170
178
|
}
|
|
171
179
|
const parts = resp.split(" ");
|
|
172
180
|
const token = parts[1];
|
|
173
|
-
const lease = parts.length >= 3 ? parseInt(parts[2], 10) :
|
|
181
|
+
const lease = parts.length >= 3 ? parseInt(parts[2], 10) : 30;
|
|
174
182
|
return { token, lease };
|
|
175
183
|
}
|
|
176
184
|
async function release(sock, key, token) {
|
|
177
|
-
sock
|
|
185
|
+
await writeAll(sock, encodeLines("r", key, token));
|
|
178
186
|
const resp = await readline(sock);
|
|
179
187
|
if (resp !== "ok") {
|
|
180
188
|
throw new LockError(`release failed: '${resp}'`);
|
|
@@ -182,7 +190,7 @@ async function release(sock, key, token) {
|
|
|
182
190
|
}
|
|
183
191
|
async function semAcquire(sock, key, acquireTimeoutS, limit, leaseTtlS) {
|
|
184
192
|
const arg = leaseTtlS == null ? `${acquireTimeoutS} ${limit}` : `${acquireTimeoutS} ${limit} ${leaseTtlS}`;
|
|
185
|
-
sock
|
|
193
|
+
await writeAll(sock, encodeLines("sl", key, arg));
|
|
186
194
|
const resp = await readline(sock);
|
|
187
195
|
if (resp === "timeout") {
|
|
188
196
|
throw new AcquireTimeoutError(key);
|
|
@@ -191,18 +199,15 @@ async function semAcquire(sock, key, acquireTimeoutS, limit, leaseTtlS) {
|
|
|
191
199
|
throw new LockError(`sem_acquire failed: '${resp}'`);
|
|
192
200
|
}
|
|
193
201
|
const parts = resp.split(" ");
|
|
194
|
-
if (parts.length < 2) {
|
|
195
|
-
throw new LockError(`bad ok response: '${resp}'`);
|
|
196
|
-
}
|
|
197
202
|
const token = parts[1];
|
|
198
203
|
const lease = parts.length >= 3 ? parseInt(parts[2], 10) : 30;
|
|
199
204
|
return { token, lease };
|
|
200
205
|
}
|
|
201
206
|
async function semRenew(sock, key, token, leaseTtlS) {
|
|
202
207
|
const arg = leaseTtlS == null ? token : `${token} ${leaseTtlS}`;
|
|
203
|
-
sock
|
|
208
|
+
await writeAll(sock, encodeLines("sn", key, arg));
|
|
204
209
|
const resp = await readline(sock);
|
|
205
|
-
if (!resp.startsWith("ok")) {
|
|
210
|
+
if (resp !== "ok" && !resp.startsWith("ok ")) {
|
|
206
211
|
throw new LockError(`sem_renew failed: '${resp}'`);
|
|
207
212
|
}
|
|
208
213
|
const parts = resp.split(" ");
|
|
@@ -213,7 +218,7 @@ async function semRenew(sock, key, token, leaseTtlS) {
|
|
|
213
218
|
}
|
|
214
219
|
async function semEnqueue(sock, key, limit, leaseTtlS) {
|
|
215
220
|
const arg = leaseTtlS == null ? String(limit) : `${limit} ${leaseTtlS}`;
|
|
216
|
-
sock
|
|
221
|
+
await writeAll(sock, encodeLines("se", key, arg));
|
|
217
222
|
const resp = await readline(sock);
|
|
218
223
|
if (resp.startsWith("acquired ")) {
|
|
219
224
|
const parts = resp.split(" ");
|
|
@@ -227,7 +232,7 @@ async function semEnqueue(sock, key, limit, leaseTtlS) {
|
|
|
227
232
|
throw new LockError(`sem_enqueue failed: '${resp}'`);
|
|
228
233
|
}
|
|
229
234
|
async function semWaitForLock(sock, key, waitTimeoutS) {
|
|
230
|
-
sock
|
|
235
|
+
await writeAll(sock, encodeLines("sw", key, String(waitTimeoutS)));
|
|
231
236
|
const resp = await readline(sock);
|
|
232
237
|
if (resp === "timeout") {
|
|
233
238
|
throw new AcquireTimeoutError(key);
|
|
@@ -241,20 +246,24 @@ async function semWaitForLock(sock, key, waitTimeoutS) {
|
|
|
241
246
|
return { token, lease };
|
|
242
247
|
}
|
|
243
248
|
async function semRelease(sock, key, token) {
|
|
244
|
-
sock
|
|
249
|
+
await writeAll(sock, encodeLines("sr", key, token));
|
|
245
250
|
const resp = await readline(sock);
|
|
246
251
|
if (resp !== "ok") {
|
|
247
252
|
throw new LockError(`sem_release failed: '${resp}'`);
|
|
248
253
|
}
|
|
249
254
|
}
|
|
250
255
|
async function statsProto(sock) {
|
|
251
|
-
sock
|
|
256
|
+
await writeAll(sock, encodeLines("stats", "_", ""));
|
|
252
257
|
const resp = await readline(sock);
|
|
253
258
|
if (!resp.startsWith("ok ")) {
|
|
254
259
|
throw new LockError(`stats failed: '${resp}'`);
|
|
255
260
|
}
|
|
256
261
|
const json = resp.slice(3);
|
|
257
|
-
|
|
262
|
+
try {
|
|
263
|
+
return JSON.parse(json);
|
|
264
|
+
} catch {
|
|
265
|
+
throw new LockError(`stats: malformed JSON response: '${json}'`);
|
|
266
|
+
}
|
|
258
267
|
}
|
|
259
268
|
async function stats(options) {
|
|
260
269
|
const host = options?.host ?? DEFAULT_HOST;
|
|
@@ -292,7 +301,7 @@ var DistributedLock = class {
|
|
|
292
301
|
if (opts.servers.length === 0) {
|
|
293
302
|
throw new LockError("servers list must not be empty");
|
|
294
303
|
}
|
|
295
|
-
this.servers = opts.servers;
|
|
304
|
+
this.servers = [...opts.servers];
|
|
296
305
|
} else {
|
|
297
306
|
this.servers = [[opts.host ?? DEFAULT_HOST, opts.port ?? DEFAULT_PORT]];
|
|
298
307
|
}
|
|
@@ -308,9 +317,17 @@ var DistributedLock = class {
|
|
|
308
317
|
}
|
|
309
318
|
return this.servers[idx];
|
|
310
319
|
}
|
|
311
|
-
/**
|
|
312
|
-
|
|
313
|
-
|
|
320
|
+
/**
|
|
321
|
+
* Acquire the lock. Returns `true` on success, `false` on timeout.
|
|
322
|
+
* @param opts.force - If `true`, silently close any existing connection before acquiring. Defaults to `false`, which throws if already connected.
|
|
323
|
+
*/
|
|
324
|
+
async acquire(opts) {
|
|
325
|
+
if (this.sock && !this.closed) {
|
|
326
|
+
if (!opts?.force) {
|
|
327
|
+
throw new LockError("already connected; call release() or close() first, or pass { force: true }");
|
|
328
|
+
}
|
|
329
|
+
this.close();
|
|
330
|
+
}
|
|
314
331
|
this.closed = false;
|
|
315
332
|
const [host, port] = this.pickServer();
|
|
316
333
|
this.sock = await connect2(host, port, this.tls, this.auth);
|
|
@@ -324,7 +341,7 @@ var DistributedLock = class {
|
|
|
324
341
|
this.token = result.token;
|
|
325
342
|
this.lease = result.lease;
|
|
326
343
|
} catch (err) {
|
|
327
|
-
|
|
344
|
+
this.close();
|
|
328
345
|
if (err instanceof AcquireTimeoutError) return false;
|
|
329
346
|
throw err;
|
|
330
347
|
}
|
|
@@ -339,16 +356,22 @@ var DistributedLock = class {
|
|
|
339
356
|
await release(this.sock, this.key, this.token);
|
|
340
357
|
}
|
|
341
358
|
} finally {
|
|
342
|
-
|
|
359
|
+
this.close();
|
|
343
360
|
}
|
|
344
361
|
}
|
|
345
362
|
/**
|
|
346
363
|
* Two-phase step 1: connect and join the FIFO queue.
|
|
347
364
|
* Returns `"acquired"` (fast-path, lock is already held) or `"queued"`.
|
|
348
365
|
* If acquired immediately, the renew loop starts automatically.
|
|
366
|
+
* @param opts.force - If `true`, silently close any existing connection before enqueuing. Defaults to `false`, which throws if already connected.
|
|
349
367
|
*/
|
|
350
|
-
async enqueue() {
|
|
351
|
-
|
|
368
|
+
async enqueue(opts) {
|
|
369
|
+
if (this.sock && !this.closed) {
|
|
370
|
+
if (!opts?.force) {
|
|
371
|
+
throw new LockError("already connected; call release() or close() first, or pass { force: true }");
|
|
372
|
+
}
|
|
373
|
+
this.close();
|
|
374
|
+
}
|
|
352
375
|
this.closed = false;
|
|
353
376
|
const [host, port] = this.pickServer();
|
|
354
377
|
this.sock = await connect2(host, port, this.tls, this.auth);
|
|
@@ -361,7 +384,7 @@ var DistributedLock = class {
|
|
|
361
384
|
}
|
|
362
385
|
return result.status;
|
|
363
386
|
} catch (err) {
|
|
364
|
-
|
|
387
|
+
this.close();
|
|
365
388
|
throw err;
|
|
366
389
|
}
|
|
367
390
|
}
|
|
@@ -383,7 +406,7 @@ var DistributedLock = class {
|
|
|
383
406
|
this.token = result.token;
|
|
384
407
|
this.lease = result.lease;
|
|
385
408
|
} catch (err) {
|
|
386
|
-
|
|
409
|
+
this.close();
|
|
387
410
|
if (err instanceof AcquireTimeoutError) return false;
|
|
388
411
|
throw err;
|
|
389
412
|
}
|
|
@@ -412,7 +435,7 @@ var DistributedLock = class {
|
|
|
412
435
|
}
|
|
413
436
|
}
|
|
414
437
|
/** Close the underlying socket (idempotent). */
|
|
415
|
-
|
|
438
|
+
close() {
|
|
416
439
|
if (this.closed) return;
|
|
417
440
|
this.closed = true;
|
|
418
441
|
this.stopRenew();
|
|
@@ -424,21 +447,26 @@ var DistributedLock = class {
|
|
|
424
447
|
}
|
|
425
448
|
// -- internals --
|
|
426
449
|
startRenew() {
|
|
427
|
-
const interval = Math.max(1, this.lease * this.renewRatio) * 1e3;
|
|
428
450
|
const loop = async () => {
|
|
429
|
-
|
|
451
|
+
const savedToken = this.token;
|
|
452
|
+
if (!this.sock || !savedToken) return;
|
|
453
|
+
const start = Date.now();
|
|
430
454
|
try {
|
|
431
|
-
await renew(this.sock, this.key,
|
|
455
|
+
this.lease = await renew(this.sock, this.key, savedToken, this.leaseTtlS);
|
|
432
456
|
} catch {
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
457
|
+
if (this.token === savedToken) {
|
|
458
|
+
this.token = null;
|
|
459
|
+
if (this.onLockLost) {
|
|
460
|
+
this.onLockLost(this.key, savedToken);
|
|
461
|
+
}
|
|
437
462
|
}
|
|
438
463
|
return;
|
|
439
464
|
}
|
|
440
|
-
|
|
465
|
+
const elapsed = Date.now() - start;
|
|
466
|
+
const interval2 = Math.max(1, this.lease * this.renewRatio) * 1e3;
|
|
467
|
+
this.renewTimer = setTimeout(loop, Math.max(0, interval2 - elapsed));
|
|
441
468
|
};
|
|
469
|
+
const interval = Math.max(1, this.lease * this.renewRatio) * 1e3;
|
|
442
470
|
this.renewTimer = setTimeout(loop, interval);
|
|
443
471
|
}
|
|
444
472
|
stopRenew() {
|
|
@@ -476,7 +504,7 @@ var DistributedSemaphore = class {
|
|
|
476
504
|
if (opts.servers.length === 0) {
|
|
477
505
|
throw new LockError("servers list must not be empty");
|
|
478
506
|
}
|
|
479
|
-
this.servers = opts.servers;
|
|
507
|
+
this.servers = [...opts.servers];
|
|
480
508
|
} else {
|
|
481
509
|
this.servers = [[opts.host ?? DEFAULT_HOST, opts.port ?? DEFAULT_PORT]];
|
|
482
510
|
}
|
|
@@ -492,9 +520,17 @@ var DistributedSemaphore = class {
|
|
|
492
520
|
}
|
|
493
521
|
return this.servers[idx];
|
|
494
522
|
}
|
|
495
|
-
/**
|
|
496
|
-
|
|
497
|
-
|
|
523
|
+
/**
|
|
524
|
+
* Acquire a semaphore slot. Returns `true` on success, `false` on timeout.
|
|
525
|
+
* @param opts.force - If `true`, silently close any existing connection before acquiring. Defaults to `false`, which throws if already connected.
|
|
526
|
+
*/
|
|
527
|
+
async acquire(opts) {
|
|
528
|
+
if (this.sock && !this.closed) {
|
|
529
|
+
if (!opts?.force) {
|
|
530
|
+
throw new LockError("already connected; call release() or close() first, or pass { force: true }");
|
|
531
|
+
}
|
|
532
|
+
this.close();
|
|
533
|
+
}
|
|
498
534
|
this.closed = false;
|
|
499
535
|
const [host, port] = this.pickServer();
|
|
500
536
|
this.sock = await connect2(host, port, this.tls, this.auth);
|
|
@@ -509,7 +545,7 @@ var DistributedSemaphore = class {
|
|
|
509
545
|
this.token = result.token;
|
|
510
546
|
this.lease = result.lease;
|
|
511
547
|
} catch (err) {
|
|
512
|
-
|
|
548
|
+
this.close();
|
|
513
549
|
if (err instanceof AcquireTimeoutError) return false;
|
|
514
550
|
throw err;
|
|
515
551
|
}
|
|
@@ -524,16 +560,22 @@ var DistributedSemaphore = class {
|
|
|
524
560
|
await semRelease(this.sock, this.key, this.token);
|
|
525
561
|
}
|
|
526
562
|
} finally {
|
|
527
|
-
|
|
563
|
+
this.close();
|
|
528
564
|
}
|
|
529
565
|
}
|
|
530
566
|
/**
|
|
531
567
|
* Two-phase step 1: connect and join the FIFO queue.
|
|
532
568
|
* Returns `"acquired"` (fast-path, slot granted immediately) or `"queued"`.
|
|
533
569
|
* If acquired immediately, the renew loop starts automatically.
|
|
570
|
+
* @param opts.force - If `true`, silently close any existing connection before enqueuing. Defaults to `false`, which throws if already connected.
|
|
534
571
|
*/
|
|
535
|
-
async enqueue() {
|
|
536
|
-
|
|
572
|
+
async enqueue(opts) {
|
|
573
|
+
if (this.sock && !this.closed) {
|
|
574
|
+
if (!opts?.force) {
|
|
575
|
+
throw new LockError("already connected; call release() or close() first, or pass { force: true }");
|
|
576
|
+
}
|
|
577
|
+
this.close();
|
|
578
|
+
}
|
|
537
579
|
this.closed = false;
|
|
538
580
|
const [host, port] = this.pickServer();
|
|
539
581
|
this.sock = await connect2(host, port, this.tls, this.auth);
|
|
@@ -546,7 +588,7 @@ var DistributedSemaphore = class {
|
|
|
546
588
|
}
|
|
547
589
|
return result.status;
|
|
548
590
|
} catch (err) {
|
|
549
|
-
|
|
591
|
+
this.close();
|
|
550
592
|
throw err;
|
|
551
593
|
}
|
|
552
594
|
}
|
|
@@ -568,7 +610,7 @@ var DistributedSemaphore = class {
|
|
|
568
610
|
this.token = result.token;
|
|
569
611
|
this.lease = result.lease;
|
|
570
612
|
} catch (err) {
|
|
571
|
-
|
|
613
|
+
this.close();
|
|
572
614
|
if (err instanceof AcquireTimeoutError) return false;
|
|
573
615
|
throw err;
|
|
574
616
|
}
|
|
@@ -597,7 +639,7 @@ var DistributedSemaphore = class {
|
|
|
597
639
|
}
|
|
598
640
|
}
|
|
599
641
|
/** Close the underlying socket (idempotent). */
|
|
600
|
-
|
|
642
|
+
close() {
|
|
601
643
|
if (this.closed) return;
|
|
602
644
|
this.closed = true;
|
|
603
645
|
this.stopRenew();
|
|
@@ -609,21 +651,26 @@ var DistributedSemaphore = class {
|
|
|
609
651
|
}
|
|
610
652
|
// -- internals --
|
|
611
653
|
startRenew() {
|
|
612
|
-
const interval = Math.max(1, this.lease * this.renewRatio) * 1e3;
|
|
613
654
|
const loop = async () => {
|
|
614
|
-
|
|
655
|
+
const savedToken = this.token;
|
|
656
|
+
if (!this.sock || !savedToken) return;
|
|
657
|
+
const start = Date.now();
|
|
615
658
|
try {
|
|
616
|
-
await semRenew(this.sock, this.key,
|
|
659
|
+
this.lease = await semRenew(this.sock, this.key, savedToken, this.leaseTtlS);
|
|
617
660
|
} catch {
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
661
|
+
if (this.token === savedToken) {
|
|
662
|
+
this.token = null;
|
|
663
|
+
if (this.onLockLost) {
|
|
664
|
+
this.onLockLost(this.key, savedToken);
|
|
665
|
+
}
|
|
622
666
|
}
|
|
623
667
|
return;
|
|
624
668
|
}
|
|
625
|
-
|
|
669
|
+
const elapsed = Date.now() - start;
|
|
670
|
+
const interval2 = Math.max(1, this.lease * this.renewRatio) * 1e3;
|
|
671
|
+
this.renewTimer = setTimeout(loop, Math.max(0, interval2 - elapsed));
|
|
626
672
|
};
|
|
673
|
+
const interval = Math.max(1, this.lease * this.renewRatio) * 1e3;
|
|
627
674
|
this.renewTimer = setTimeout(loop, interval);
|
|
628
675
|
}
|
|
629
676
|
stopRenew() {
|