Files
2026-02-05 15:27:49 +08:00

97 lines
3.0 KiB
JavaScript

import { MultiKeyMap } from "./MultiKeyMap.js";
const locks = new MultiKeyMap();
export async function withLock(scope, acquireLockSignalOrCallback, callback) {
let acquireLockSignal = undefined;
if (acquireLockSignalOrCallback instanceof AbortSignal)
acquireLockSignal = acquireLockSignalOrCallback;
else if (acquireLockSignalOrCallback != null)
callback = acquireLockSignalOrCallback;
if (callback == null)
throw new Error("callback is required");
if (acquireLockSignal?.aborted)
throw acquireLockSignal.reason;
const scopeClone = scope.slice();
let [queue, onDelete] = locks.get(scopeClone) || [];
if (queue != null && onDelete != null)
await createQueuePromise(queue, acquireLockSignal);
else {
queue = [];
onDelete = [];
locks.set(scopeClone, [queue, onDelete]);
}
try {
return await callback();
}
finally {
if (queue.length > 0)
queue.shift()();
else {
locks.delete(scopeClone);
while (onDelete.length > 0)
onDelete.shift()();
}
}
}
/**
* Check if a lock is currently active for a given `scope` values.
*/
export function isLockActive(scope) {
return locks.has(scope) ?? false;
}
/**
* Acquire a lock for a given `scope` values.
*/
export function acquireLock(scope, acquireLockSignal) {
return new Promise((accept, reject) => {
const scopeClone = scope.slice();
void withLock(scopeClone, acquireLockSignal, () => {
let releaseLock;
const promise = new Promise((accept) => {
releaseLock = accept;
});
accept({
scope: scopeClone,
dispose() {
releaseLock();
},
[Symbol.dispose]() {
releaseLock();
}
});
return promise;
})
.catch(reject);
});
}
/**
* Wait for a lock to be released for a given `scope` values.
*/
export async function waitForLockRelease(scope, signal) {
if (signal?.aborted)
throw signal.reason;
const [queue, onDelete] = locks.get(scope) ?? [];
if (queue == null || onDelete == null)
return;
await createQueuePromise(onDelete, signal);
}
function createQueuePromise(queue, signal) {
if (signal == null)
return new Promise((accept) => void queue.push(accept));
return new Promise((accept, reject) => {
function onAcquireLock() {
signal.removeEventListener("abort", onAbort);
accept();
}
const queueLength = queue.length;
function onAbort() {
const itemIndex = queue.lastIndexOf(onAcquireLock, queueLength);
if (itemIndex >= 0)
queue.splice(itemIndex, 1);
signal.removeEventListener("abort", onAbort);
reject(signal.reason);
}
queue.push(onAcquireLock);
signal.addEventListener("abort", onAbort);
});
}
//# sourceMappingURL=withLock.js.map