better-lock 3.2.0 → 4.0.0
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 +83 -10
- package/dist/async_context.d.ts +27 -0
- package/dist/async_context.js +58 -0
- package/dist/async_context.js.map +1 -0
- package/dist/better_lock.d.ts +21 -1
- package/dist/better_lock.js +162 -40
- package/dist/better_lock.js.map +1 -1
- package/dist/errors.d.ts +11 -0
- package/dist/errors.js +19 -1
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/internals.d.ts +10 -0
- package/dist/internals.js +9 -0
- package/dist/internals.js.map +1 -1
- package/dist/options.d.ts +21 -0
- package/package.json +3 -4
- package/spec/async_context.spec.ts +379 -0
- package/spec/better_lock.spec.ts +12 -0
- package/src/async_context.ts +86 -0
- package/src/better_lock.ts +182 -47
- package/src/errors.ts +28 -0
- package/src/index.ts +5 -1
- package/src/internals.ts +12 -0
- package/src/options.ts +24 -0
- package/tsconfig.build.json +6 -0
- package/tsconfig.json +1 -0
package/src/better_lock.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import { captureAsyncContext, getHeldJobs, runHoldingJob } from './async_context';
|
|
1
2
|
import {
|
|
2
3
|
BetterLockExecutionTimeoutError,
|
|
3
4
|
BetterLockInternalError,
|
|
4
5
|
BetterLockInvalidArgumentError,
|
|
5
6
|
BetterLockJobAbortedError,
|
|
6
7
|
BetterLockQueueOverflowError,
|
|
8
|
+
BetterLockReentrancyError,
|
|
7
9
|
BetterLockWaitTimeoutError,
|
|
8
10
|
isAcquisitionFailureError,
|
|
9
11
|
} from './errors';
|
|
@@ -22,12 +24,18 @@ export class BetterLock {
|
|
|
22
24
|
static DEFAULT_OPTIONS: IOptions = {
|
|
23
25
|
extend_stack_traces: true,
|
|
24
26
|
promise_tester: p => p && typeof p.then === 'function',
|
|
27
|
+
reentrancy: 'allow',
|
|
25
28
|
};
|
|
26
29
|
|
|
27
30
|
private readonly options: IOptions;
|
|
28
31
|
private readonly log: (msg: string) => void;
|
|
29
32
|
private readonly queues: { [key: string]: KeyQueue };
|
|
30
33
|
|
|
34
|
+
/**
|
|
35
|
+
* Reentrant jobs that haven't started executing yet. They are not in any key queue, so we track them here for abort().
|
|
36
|
+
*/
|
|
37
|
+
private readonly pendingReentrantJobs: LockJob<any>[] = [];
|
|
38
|
+
|
|
31
39
|
/**
|
|
32
40
|
* Create a better lock instance
|
|
33
41
|
*/
|
|
@@ -52,11 +60,49 @@ export class BetterLock {
|
|
|
52
60
|
}
|
|
53
61
|
|
|
54
62
|
/**
|
|
55
|
-
* Returns true if the caller can *immediately* acquire the lock. There is nothing holding the key and nothing in the queue
|
|
63
|
+
* Returns true if the caller can *immediately* acquire the lock. There is nothing holding the key and nothing in the queue,
|
|
64
|
+
* or the caller already holds the key and reentrancy is allowed.
|
|
56
65
|
*/
|
|
57
66
|
public canAcquire(key?: string): boolean {
|
|
58
67
|
const queue = this.getQueue(key);
|
|
59
|
-
|
|
68
|
+
if (!queue.active && !queue.jobs.length) {
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
return this.options.reentrancy !== 'error' && this.isHeldByCaller(queue, getHeldJobs());
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* True if the given queue's key is held by a job that is executing in the caller's async context.
|
|
76
|
+
* Ended jobs don't count, so detached work that outlives its job doesn't skip the lock.
|
|
77
|
+
*/
|
|
78
|
+
private isHeldByCaller(queue: KeyQueue, heldJobs: ReadonlyArray<LockJob<any>> | undefined) {
|
|
79
|
+
const holder = queue.active;
|
|
80
|
+
return !!(heldJobs && holder && !holder.ended_at && heldJobs.indexOf(holder) >= 0);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Find which of the given keys the caller already holds, and the jobs holding them.
|
|
85
|
+
* Returns null if the caller holds none of them.
|
|
86
|
+
*/
|
|
87
|
+
private findHeldKeys(
|
|
88
|
+
keys: Array<string | null>
|
|
89
|
+
): { holders: LockJob<any>[]; heldKeys: Array<string | null> } | null {
|
|
90
|
+
const heldJobs = getHeldJobs();
|
|
91
|
+
if (!heldJobs || !keys.length) {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const holders: LockJob<any>[] = [];
|
|
96
|
+
const heldKeys: Array<string | null> = [];
|
|
97
|
+
for (let i = 0; i < keys.length; i++) {
|
|
98
|
+
const queue = this.getQueue(keys[i]);
|
|
99
|
+
if (this.isHeldByCaller(queue, heldJobs)) {
|
|
100
|
+
holders.push(queue.active);
|
|
101
|
+
heldKeys.push(keys[i]);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return holders.length ? { holders, heldKeys } : null;
|
|
60
106
|
}
|
|
61
107
|
|
|
62
108
|
private normalizeAndValidateKey(key: ILockKey) {
|
|
@@ -114,6 +160,10 @@ export class BetterLock {
|
|
|
114
160
|
* Acquire the lock for given key or list of keys. If waiting on a list, keys will be acquired in order,
|
|
115
161
|
* as they become free. Any deadlock prevention must be handled by the caller.
|
|
116
162
|
*
|
|
163
|
+
* If the caller already holds all the keys (it runs inside an executor holding them), the executor runs
|
|
164
|
+
* without waiting. If it holds only some of the keys, or the "reentrancy" option is set to "error", the
|
|
165
|
+
* callback (or the promise) gets BetterLockReentrancyError. Reentrancy requires Node.js AsyncLocalStorage.
|
|
166
|
+
*
|
|
117
167
|
* Once the lock is acquired, we will call the executor function, with a "done" method.
|
|
118
168
|
* You must call "done" to release the lock. Alternatively, return a Promise; lock will be released
|
|
119
169
|
* once promise resolves or rejects.
|
|
@@ -187,19 +237,57 @@ export class BetterLock {
|
|
|
187
237
|
);
|
|
188
238
|
}
|
|
189
239
|
|
|
240
|
+
// Invalid arguments throw above. Reentrancy errors depend on where acquire is called from,
|
|
241
|
+
// so they are reported like other lock errors: through the callback or promise.
|
|
242
|
+
const held = this.findHeldKeys(keys);
|
|
243
|
+
if (
|
|
244
|
+
held &&
|
|
245
|
+
// Waiting for the remaining keys while holding these could deadlock
|
|
246
|
+
(held.heldKeys.length < keys.length || effectiveJobOptions.reentrancy === 'error')
|
|
247
|
+
) {
|
|
248
|
+
const err = new BetterLockReentrancyError(this.options.name, keys, held.heldKeys);
|
|
249
|
+
this.log(err.message);
|
|
250
|
+
// Never call back synchronously from acquire
|
|
251
|
+
setTimeout(() => callback(err), 0);
|
|
252
|
+
return (callback as any).promise;
|
|
253
|
+
}
|
|
254
|
+
const reentrantHolders = held ? held.holders : null;
|
|
255
|
+
|
|
190
256
|
const job = new LockJob(keys, executor, callback, effectiveJobOptions);
|
|
191
257
|
|
|
258
|
+
// Executor and callback will run in the caller's async context, not the context of whoever releases the lock
|
|
259
|
+
job.async_context = captureAsyncContext();
|
|
260
|
+
|
|
192
261
|
// Set incoming stack
|
|
193
|
-
if (
|
|
262
|
+
if (
|
|
263
|
+
effectiveJobOptions.extend_stack_traces &&
|
|
264
|
+
typeof (Error as any).captureStackTrace === 'function'
|
|
265
|
+
) {
|
|
194
266
|
const tempErr = new Error();
|
|
195
|
-
Error.captureStackTrace(tempErr, this.acquire);
|
|
267
|
+
(Error as any).captureStackTrace(tempErr, this.acquire);
|
|
196
268
|
job.incoming_stack = tempErr.stack;
|
|
197
269
|
}
|
|
198
270
|
|
|
199
|
-
|
|
271
|
+
if (reentrantHolders) {
|
|
272
|
+
// The caller holds all the keys. Run without entering the queues.
|
|
273
|
+
job.reentrant_holders = reentrantHolders;
|
|
274
|
+
this.pendingReentrantJobs.push(job);
|
|
275
|
+
this.log(`Reentering ${job}`);
|
|
276
|
+
this.executeJob(job);
|
|
277
|
+
} else {
|
|
278
|
+
this.enqueueJob(job);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return (callback as any).promise;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Add job to its key queues and start waiting for the keys
|
|
286
|
+
*/
|
|
287
|
+
private enqueueJob(job: LockJob<any>) {
|
|
200
288
|
const queuesToUpdate = [];
|
|
201
|
-
for (let i = 0; i < keys.length; i++) {
|
|
202
|
-
const queue = this.getQueue(keys[i]);
|
|
289
|
+
for (let i = 0; i < job.keys.length; i++) {
|
|
290
|
+
const queue = this.getQueue(job.keys[i]);
|
|
203
291
|
queue.jobs.push(job);
|
|
204
292
|
queuesToUpdate.push(queue);
|
|
205
293
|
}
|
|
@@ -221,8 +309,6 @@ export class BetterLock {
|
|
|
221
309
|
// Special case. We are given empty list of keys. We will immediately execute jobs like this.
|
|
222
310
|
this.executeJob(job);
|
|
223
311
|
}
|
|
224
|
-
|
|
225
|
-
return (callback as any).promise;
|
|
226
312
|
}
|
|
227
313
|
|
|
228
314
|
public acquireOr<TResult extends IValidResult>(
|
|
@@ -356,23 +442,36 @@ export class BetterLock {
|
|
|
356
442
|
// We want to do the rest of this in a separate context, because we don't want user executor code
|
|
357
443
|
// to ever interfere with the code calling acquire().
|
|
358
444
|
|
|
359
|
-
|
|
445
|
+
setTimeout(() => {
|
|
360
446
|
// Make sure job hasn't been ended in the meantime
|
|
361
447
|
if (job.executed_at || job.ended_at) {
|
|
362
448
|
return;
|
|
363
449
|
}
|
|
364
450
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
if (
|
|
369
|
-
//
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
451
|
+
if (job.reentrant_holders) {
|
|
452
|
+
this.removePendingReentrantJob(job);
|
|
453
|
+
|
|
454
|
+
if (job.reentrant_holders.some(holder => holder.ended_at)) {
|
|
455
|
+
// The holder released the keys before this job could start (eg. the reentrant acquire wasn't awaited).
|
|
456
|
+
// Wait for the keys like any other job.
|
|
457
|
+
this.log(`${job} is no longer reentrant because its holder has ended`);
|
|
458
|
+
job.reentrant_holders = null;
|
|
459
|
+
this.enqueueJob(job);
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
} else {
|
|
463
|
+
// Validate this job is holding all queues it needs
|
|
464
|
+
for (let i = 0; i < job.keys.length; i++) {
|
|
465
|
+
const queue = this.getQueue(job.keys[i]);
|
|
466
|
+
if (queue.active !== job) {
|
|
467
|
+
// This should never happen
|
|
468
|
+
return this.endJob(job, [
|
|
469
|
+
new BetterLockInternalError(
|
|
470
|
+
this.options.name,
|
|
471
|
+
`Corrupted wait queue state for ${queue.key}`
|
|
472
|
+
),
|
|
473
|
+
]);
|
|
474
|
+
}
|
|
376
475
|
}
|
|
377
476
|
}
|
|
378
477
|
|
|
@@ -392,32 +491,45 @@ export class BetterLock {
|
|
|
392
491
|
this.endJob(job, args);
|
|
393
492
|
};
|
|
394
493
|
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
494
|
+
const runExecutor = () => {
|
|
495
|
+
if (job.executor.length > 0) {
|
|
496
|
+
// Callback interface
|
|
497
|
+
job.executor(lockDone);
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
400
500
|
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
501
|
+
// Promise interface
|
|
502
|
+
let executorResult;
|
|
503
|
+
try {
|
|
504
|
+
executorResult = (job.executor as any)();
|
|
505
|
+
} catch (err) {
|
|
506
|
+
// Error thrown directly from executor
|
|
507
|
+
lockDone(err);
|
|
508
|
+
}
|
|
409
509
|
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
510
|
+
// Promise sniffing
|
|
511
|
+
if (this.options.promise_tester(executorResult)) {
|
|
512
|
+
executorResult.then(
|
|
513
|
+
res => lockDone(null, res),
|
|
514
|
+
err => lockDone(err)
|
|
515
|
+
);
|
|
516
|
+
} else {
|
|
517
|
+
// "promise" is just some random value. We don't have to wait
|
|
518
|
+
lockDone(null, executorResult);
|
|
519
|
+
}
|
|
520
|
+
};
|
|
521
|
+
|
|
522
|
+
if (!job.async_context) {
|
|
523
|
+
// No async context support in this runtime
|
|
524
|
+
runExecutor();
|
|
525
|
+
} else if (job.reentrant_holders) {
|
|
526
|
+
// The caller's context already records the holders
|
|
527
|
+
job.async_context.runInAsyncScope(runExecutor);
|
|
416
528
|
} else {
|
|
417
|
-
//
|
|
418
|
-
|
|
529
|
+
// Restore the caller's context first, then record this job as holding its keys inside it
|
|
530
|
+
job.async_context.runInAsyncScope(() => runHoldingJob(job, runExecutor));
|
|
419
531
|
}
|
|
420
|
-
});
|
|
532
|
+
}, 0);
|
|
421
533
|
}
|
|
422
534
|
|
|
423
535
|
private onWaitTimeout(job: LockJob<any>) {
|
|
@@ -456,9 +568,15 @@ export class BetterLock {
|
|
|
456
568
|
|
|
457
569
|
job.ended_at = new Date();
|
|
458
570
|
|
|
571
|
+
// Reentrant jobs never enter key queues, so they have nothing to release
|
|
572
|
+
const keysToRelease = job.reentrant_holders ? [] : job.keys;
|
|
573
|
+
if (job.reentrant_holders) {
|
|
574
|
+
this.removePendingReentrantJob(job);
|
|
575
|
+
}
|
|
576
|
+
|
|
459
577
|
const queuesToUpdate = [];
|
|
460
|
-
for (let i = 0; i <
|
|
461
|
-
const queue = this.getQueue(
|
|
578
|
+
for (let i = 0; i < keysToRelease.length; i++) {
|
|
579
|
+
const queue = this.getQueue(keysToRelease[i]);
|
|
462
580
|
queuesToUpdate.push(queue);
|
|
463
581
|
|
|
464
582
|
if (queue.active === job) {
|
|
@@ -481,10 +599,14 @@ export class BetterLock {
|
|
|
481
599
|
|
|
482
600
|
// Call the callback
|
|
483
601
|
try {
|
|
484
|
-
job.
|
|
602
|
+
if (job.async_context) {
|
|
603
|
+
job.async_context.runInAsyncScope(job.callback as any, null, ...callbackArgs);
|
|
604
|
+
} else {
|
|
605
|
+
job.callback.apply(null, callbackArgs);
|
|
606
|
+
}
|
|
485
607
|
} finally {
|
|
486
608
|
// Whatever happens, schedule a queue update
|
|
487
|
-
|
|
609
|
+
setTimeout(() => this.update(queuesToUpdate), 0);
|
|
488
610
|
}
|
|
489
611
|
}
|
|
490
612
|
|
|
@@ -503,6 +625,19 @@ export class BetterLock {
|
|
|
503
625
|
// If we have a job holding this queue that hasn't been executed yet, abort it as well
|
|
504
626
|
this.endJob(queue.active, [new BetterLockJobAbortedError(this.options.name, queue.active)]);
|
|
505
627
|
}
|
|
628
|
+
// Reentrant jobs for this key that haven't started yet
|
|
629
|
+
this.pendingReentrantJobs.slice().forEach(job => {
|
|
630
|
+
if (job.keys.some(jobKey => this.getQueue(jobKey) === queue)) {
|
|
631
|
+
this.endJob(job, [new BetterLockJobAbortedError(this.options.name, job)]);
|
|
632
|
+
}
|
|
633
|
+
});
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
private removePendingReentrantJob(job: LockJob<any>) {
|
|
637
|
+
const index = this.pendingReentrantJobs.indexOf(job);
|
|
638
|
+
if (index >= 0) {
|
|
639
|
+
this.pendingReentrantJobs.splice(index, 1);
|
|
640
|
+
}
|
|
506
641
|
}
|
|
507
642
|
|
|
508
643
|
/**
|
package/src/errors.ts
CHANGED
|
@@ -131,6 +131,34 @@ export class BetterLockJobAbortedError extends BetterLockError {
|
|
|
131
131
|
}
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
+
export class BetterLockReentrancyError extends BetterLockError {
|
|
135
|
+
/**
|
|
136
|
+
* Keys requested by the rejected acquire call
|
|
137
|
+
*/
|
|
138
|
+
public keys: string[];
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Requested keys that the caller already holds
|
|
142
|
+
*/
|
|
143
|
+
public held_keys: string[];
|
|
144
|
+
|
|
145
|
+
constructor(lockName, keys: string[], heldKeys: string[]) {
|
|
146
|
+
const describe = (list: string[]) =>
|
|
147
|
+
list.map(key => (key === null ? '<default>' : `"${key}"`)).join(', ');
|
|
148
|
+
const message =
|
|
149
|
+
heldKeys.length < keys.length
|
|
150
|
+
? `Caller holds only some of the requested keys (${describe(
|
|
151
|
+
heldKeys
|
|
152
|
+
)} of ${describe(keys)}). Waiting for the rest while holding these could deadlock`
|
|
153
|
+
: `Reentrant acquire of ${describe(keys)} is not allowed (reentrancy: "error")`;
|
|
154
|
+
|
|
155
|
+
super(lockName, message);
|
|
156
|
+
|
|
157
|
+
this.keys = keys;
|
|
158
|
+
this.held_keys = heldKeys;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
134
162
|
export function isAcquisitionFailureError(err) {
|
|
135
163
|
return (
|
|
136
164
|
(err && err instanceof BetterLockQueueOverflowError) ||
|
package/src/index.ts
CHANGED
|
@@ -6,9 +6,10 @@ import {
|
|
|
6
6
|
BetterLockInvalidArgumentError,
|
|
7
7
|
BetterLockJobAbortedError,
|
|
8
8
|
BetterLockQueueOverflowError,
|
|
9
|
+
BetterLockReentrancyError,
|
|
9
10
|
BetterLockWaitTimeoutError,
|
|
10
11
|
} from './errors';
|
|
11
|
-
import { ILockJobOptions as BetterLockJobOptions, IOptions as BetterLockOptions, IQueueEjectionStrategy as BetterLockQueueEjectionStrategy } from './options';
|
|
12
|
+
import { ILockJobOptions as BetterLockJobOptions, IOptions as BetterLockOptions, IQueueEjectionStrategy as BetterLockQueueEjectionStrategy, IReentrancyMode as BetterLockReentrancyMode } from './options';
|
|
12
13
|
import {
|
|
13
14
|
IErrorName as BetterLockErrorName,
|
|
14
15
|
IExecutor as BetterLockExecutor,
|
|
@@ -18,6 +19,7 @@ import {
|
|
|
18
19
|
export {
|
|
19
20
|
BetterLockOptions,
|
|
20
21
|
BetterLockQueueEjectionStrategy,
|
|
22
|
+
BetterLockReentrancyMode,
|
|
21
23
|
BetterLockJobOptions,
|
|
22
24
|
BetterLockExecutor,
|
|
23
25
|
BetterLockKey,
|
|
@@ -29,6 +31,7 @@ export {
|
|
|
29
31
|
BetterLockInvalidArgumentError,
|
|
30
32
|
BetterLockJobAbortedError,
|
|
31
33
|
BetterLockQueueOverflowError,
|
|
34
|
+
BetterLockReentrancyError,
|
|
32
35
|
BetterLockWaitTimeoutError,
|
|
33
36
|
};
|
|
34
37
|
|
|
@@ -50,6 +53,7 @@ export const BetterLock = attachExports(BetterLockUnadorned, {
|
|
|
50
53
|
ExecutionTimeoutError: BetterLockExecutionTimeoutError,
|
|
51
54
|
QueueOverflowError: BetterLockQueueOverflowError,
|
|
52
55
|
JobAbortedError: BetterLockJobAbortedError,
|
|
56
|
+
ReentrancyError: BetterLockReentrancyError,
|
|
53
57
|
});
|
|
54
58
|
|
|
55
59
|
export default BetterLock;
|
package/src/internals.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { IAsyncResource } from './async_context';
|
|
1
2
|
import { ILockJobOptions } from './options';
|
|
2
3
|
import { ICallback, IExecutor } from './types';
|
|
3
4
|
|
|
@@ -66,6 +67,17 @@ export class LockJob<TResult> {
|
|
|
66
67
|
*/
|
|
67
68
|
public incoming_stack: string = null;
|
|
68
69
|
|
|
70
|
+
/**
|
|
71
|
+
* Async context of the acquire() call. Executor and callback run in it. Undefined if the runtime doesn't support it.
|
|
72
|
+
*/
|
|
73
|
+
public async_context: IAsyncResource | undefined = undefined;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Set for reentrant jobs: the executing jobs (from the caller's async context) that hold this job's keys.
|
|
77
|
+
* A reentrant job doesn't enter key queues. It runs while these jobs hold the keys.
|
|
78
|
+
*/
|
|
79
|
+
public reentrant_holders: LockJob<any>[] | null = null;
|
|
80
|
+
|
|
69
81
|
constructor(
|
|
70
82
|
keys: Array<string | null>,
|
|
71
83
|
executor: IExecutor<TResult>,
|
package/src/options.ts
CHANGED
|
@@ -25,10 +25,21 @@ export interface ILockJobOptions {
|
|
|
25
25
|
* Overrides BetterLockOptions value.
|
|
26
26
|
*/
|
|
27
27
|
lock_condition?: (key: ILockKey | Array<ILockKey>) => boolean;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* What to do when acquire is called from code that already holds all the requested keys. Overrides BetterLockOptions value.
|
|
31
|
+
*/
|
|
32
|
+
reentrancy?: IReentrancyMode;
|
|
28
33
|
}
|
|
29
34
|
|
|
30
35
|
export type IQueueEjectionStrategy = 'oldest' | 'newest';
|
|
31
36
|
|
|
37
|
+
/**
|
|
38
|
+
* - 'allow': run the executor without waiting for the keys the caller already holds.
|
|
39
|
+
* - 'error': fail with BetterLockReentrancyError.
|
|
40
|
+
*/
|
|
41
|
+
export type IReentrancyMode = 'allow' | 'error';
|
|
42
|
+
|
|
32
43
|
/**
|
|
33
44
|
* Options that apply on BetterLock instance
|
|
34
45
|
*/
|
|
@@ -60,6 +71,19 @@ export interface IOptions extends ILockJobOptions {
|
|
|
60
71
|
*/
|
|
61
72
|
lock_condition?: (key: ILockKey | Array<ILockKey>) => boolean;
|
|
62
73
|
|
|
74
|
+
/**
|
|
75
|
+
* What to do when acquire is called from code that already holds all the requested keys (inside the executor, or
|
|
76
|
+
* in async work the executor started and that runs before it finishes). 'allow' runs the executor without
|
|
77
|
+
* waiting, 'error' fails with BetterLockReentrancyError. Defaults to 'allow'.
|
|
78
|
+
*
|
|
79
|
+
* Holding only some of the requested keys always fails with BetterLockReentrancyError, because waiting for the rest
|
|
80
|
+
* while holding these could deadlock.
|
|
81
|
+
*
|
|
82
|
+
* Requires Node.js AsyncLocalStorage, looked up through process.getBuiltinModule (Node.js 20.16+ or 22.3+).
|
|
83
|
+
* In other runtimes, this option has no effect and nested acquire calls wait in the queue like any other.
|
|
84
|
+
*/
|
|
85
|
+
reentrancy?: IReentrancyMode;
|
|
86
|
+
|
|
63
87
|
/**
|
|
64
88
|
* Lock name. This will be written in all logs and error messages, to help you distinguish between different locks
|
|
65
89
|
*/
|