Manual locking
How to lock an arbitrary scope with asyncmux(this) and asyncmux.readonly(this), and the release rules of AsyncmuxLock.
Lock an arbitrary scope
When you want to lock only a specific scope or a conditional branch instead of a whole method, call asyncmux as a function. The object passed as the first argument is the lock target.
const lock = await asyncmux(this); // Write lock
const readLock = await asyncmux.readonly(this); // Read lock
The return value is an AsyncmuxLock. Until the lock is released, conflicting lock requests on the same object wait.
import { asyncmux } from "asyncmux";
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
class Runner {
isOpen = true;
async run(ms: number) {
if (!this.isOpen) {
return; // Exit before acquiring the lock.
}
using _ = await asyncmux(this);
await sleep(ms);
}
}
Auto-release with using
AsyncmuxLock implements Symbol.dispose, so the using syntax releases it automatically when the scope exits.
async function run() {
using _ = await asyncmux(this);
await sleep(10);
// Released automatically when leaving the scope.
}
Release explicitly with release()
When using is unavailable, call release() in a finally block.
const lock = await asyncmux(this);
try {
await task();
} finally {
lock.release();
}
Release rules
- Calling
release()twice throwsLockReleasedError. - Calling
Symbol.dispose(release viausing) on an already-released lock is a no-op and does not throw. - Even when the release handling throws, the lock is treated as released. A second
release()call throwsLockReleasedError. - Use the
releasedproperty to check whether the lock has been released.
Reentrancy notes
Unlike decorators, the function API does not register a lock context. Therefore, re-acquiring a lock on the same object does not throw ReentrantLockError unless a decorated method is running — it deadlocks instead.
// Deadlock example:
const lock1 = await asyncmux(target);
const lock2 = await asyncmux(target); // Waits forever.
While a method decorated with @asyncmux or @asyncmux.readonly is running, its context is subject to reentrancy detection. See Concurrency Model for details.
Abort with AbortSignal
Pass an AbortSignal as the second argument to abort a lock wait. See Aborting for details.
using _ = await asyncmux(this, signal);