better-lock 3.3.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 +57 -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 +157 -36
- 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 +1 -1
- package/spec/async_context.spec.ts +379 -0
- package/src/async_context.ts +86 -0
- package/src/better_lock.ts +174 -42
- 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/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,8 +237,27 @@ 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
262
|
if (
|
|
194
263
|
effectiveJobOptions.extend_stack_traces &&
|
|
@@ -199,10 +268,26 @@ export class BetterLock {
|
|
|
199
268
|
job.incoming_stack = tempErr.stack;
|
|
200
269
|
}
|
|
201
270
|
|
|
202
|
-
|
|
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>) {
|
|
203
288
|
const queuesToUpdate = [];
|
|
204
|
-
for (let i = 0; i < keys.length; i++) {
|
|
205
|
-
const queue = this.getQueue(keys[i]);
|
|
289
|
+
for (let i = 0; i < job.keys.length; i++) {
|
|
290
|
+
const queue = this.getQueue(job.keys[i]);
|
|
206
291
|
queue.jobs.push(job);
|
|
207
292
|
queuesToUpdate.push(queue);
|
|
208
293
|
}
|
|
@@ -224,8 +309,6 @@ export class BetterLock {
|
|
|
224
309
|
// Special case. We are given empty list of keys. We will immediately execute jobs like this.
|
|
225
310
|
this.executeJob(job);
|
|
226
311
|
}
|
|
227
|
-
|
|
228
|
-
return (callback as any).promise;
|
|
229
312
|
}
|
|
230
313
|
|
|
231
314
|
public acquireOr<TResult extends IValidResult>(
|
|
@@ -365,17 +448,30 @@ export class BetterLock {
|
|
|
365
448
|
return;
|
|
366
449
|
}
|
|
367
450
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
if (
|
|
372
|
-
//
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
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
|
+
}
|
|
379
475
|
}
|
|
380
476
|
}
|
|
381
477
|
|
|
@@ -395,30 +491,43 @@ export class BetterLock {
|
|
|
395
491
|
this.endJob(job, args);
|
|
396
492
|
};
|
|
397
493
|
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
494
|
+
const runExecutor = () => {
|
|
495
|
+
if (job.executor.length > 0) {
|
|
496
|
+
// Callback interface
|
|
497
|
+
job.executor(lockDone);
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
403
500
|
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
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
|
+
}
|
|
412
509
|
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
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);
|
|
419
528
|
} else {
|
|
420
|
-
//
|
|
421
|
-
|
|
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));
|
|
422
531
|
}
|
|
423
532
|
}, 0);
|
|
424
533
|
}
|
|
@@ -459,9 +568,15 @@ export class BetterLock {
|
|
|
459
568
|
|
|
460
569
|
job.ended_at = new Date();
|
|
461
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
|
+
|
|
462
577
|
const queuesToUpdate = [];
|
|
463
|
-
for (let i = 0; i <
|
|
464
|
-
const queue = this.getQueue(
|
|
578
|
+
for (let i = 0; i < keysToRelease.length; i++) {
|
|
579
|
+
const queue = this.getQueue(keysToRelease[i]);
|
|
465
580
|
queuesToUpdate.push(queue);
|
|
466
581
|
|
|
467
582
|
if (queue.active === job) {
|
|
@@ -484,7 +599,11 @@ export class BetterLock {
|
|
|
484
599
|
|
|
485
600
|
// Call the callback
|
|
486
601
|
try {
|
|
487
|
-
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
|
+
}
|
|
488
607
|
} finally {
|
|
489
608
|
// Whatever happens, schedule a queue update
|
|
490
609
|
setTimeout(() => this.update(queuesToUpdate), 0);
|
|
@@ -506,6 +625,19 @@ export class BetterLock {
|
|
|
506
625
|
// If we have a job holding this queue that hasn't been executed yet, abort it as well
|
|
507
626
|
this.endJob(queue.active, [new BetterLockJobAbortedError(this.options.name, queue.active)]);
|
|
508
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
|
+
}
|
|
509
641
|
}
|
|
510
642
|
|
|
511
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
|
*/
|