asyncmux
A utility for mutual exclusion of async operations. With two APIs — decorators and lock objects — it prevents concurrent access to the same resource.
asyncmux is a utility for mutual exclusion of async operations. It offers two approaches: decorating class methods, and acquiring lock objects. Both follow the same model of write locks and read locks, so you can choose whichever fits your situation.
Features
- Automatic locking with decorators — Methods decorated with
@asyncmuxrun exclusively, while methods decorated with@asyncmux.readonlyrun concurrently with other readers. Locks are acquired and released automatically along with the method invocation. - Manual locking with the function API —
await asyncmux(this)locks an arbitrary scope. Release it with theusingsyntax orrelease(). - Keyed locks — The
Asyncmuxclass manages an independent lock per key. When no key is given, the lock becomes a global lock that conflicts with every lock. - Abortable waits — Passing an
AbortSignallets you abort a lock acquisition that is still waiting. - Reentrancy detection — Requesting a lock on the same target while already holding it throws
ReentrantLockErrorto prevent deadlocks. In theAsyncmuxclass, enable it with thepreventReentrancyoption.
Quickstart
Excluding a method with a decorator
import { asyncmux } from "asyncmux";
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
class Counter {
#count = 0;
@asyncmux
async increment() {
const current = this.#count;
await sleep(10);
this.#count = current + 1;
}
}
Even when increment() is called concurrently, the second call waits for the first one to finish before running.
Excluding resources with keyed locks
import { Asyncmux } from "asyncmux";
const mux = new Asyncmux();
const cache = new Map<string, string>();
async function read(key: string) {
// Exclusive with writes on the same key, concurrent with other reads.
using _ = await mux.rLock(key);
return cache.get(key);
}
async function write(key: string, value: string) {
// Exclusive with reads and writes on the same key.
using _ = await mux.lock(key);
cache.set(key, value);
}
Requirements
- The decorator API requires a runtime or transpiler that supports Stage 3 decorators.
- The
usingsyntax can be used for automatic lock release, but it is optional. You can also release withrelease(). - Reentrancy detection accuracy varies by runtime. See Concurrency Model for details.