Skip to content
asyncmux
English
Esc
↑↓navigate↵open⌘Jpreview
On this page

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 throws LockReleasedError.
  • Calling Symbol.dispose (release via using) 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 throws LockReleasedError.
  • Use the released property 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);

Was this page helpful?