Async Mutex With FIFO Waiters
Serialize critical sections with an async mutex that queues waiters in FIFO order, supports acquisition timeouts and safe double release.
Code
export class AsyncMutex {
#locked = false;
#waiters = [];
get locked() {
return this.#locked;
}
get queued() {
return this.#waiters.length;
}
async acquire(options = {}) {
const { timeoutMs } = options;
if (timeoutMs !== undefined && (!Number.isFinite(timeoutMs) || timeoutMs <= 0)) {
throw new RangeError("timeoutMs must be a positive finite number");
}
if (!this.#locked) {
this.#locked = true;
return this.#createRelease();
}
return new Promise((resolve, reject) => {
const waiter = { resolve, reject, timer: undefined };
if (timeoutMs !== undefined) {
waiter.timer = setTimeout(() => {
const index = this.#waiters.indexOf(waiter);
if (index === -1) return;
this.#waiters.splice(index, 1);
reject(new Error("timed out waiting for mutex after " + timeoutMs + "ms"));
}, timeoutMs);
}
this.#waiters.push(waiter);
});
}
#createRelease() {
let released = false;
return () => {
if (released) return;
released = true;
const next = this.#waiters.shift();
if (next) {
if (next.timer !== undefined) clearTimeout(next.timer);
next.resolve(this.#createRelease());
} else {
this.#locked = false;
}
};
}
async runExclusive(task, options) {
const release = await this.acquire(options);
try {
return await task();
} finally {
release();
}
}
}Report a problem with this exampleOpens a pre-filled issue on GitHub.