Skip to content

Repository files navigation

using-mutex

A lightweight, zero-dependency mutex built on the Web Locks API, with keyed locks, cancellation, timeouts, and explicit resource management.

Features

  • Automatic release: Use the using keyword to release a lock when its scope ends.
  • Keyed locks: acquire() calls using the same Mutex instance and key are serialized in FIFO order, while different keys can run concurrently.
  • Timeouts and cancellation: Stop waiting with a timeout or an AbortSignal.
  • Web Locks options: Use Mutex.request() to pass LockOptions directly to navigator.locks.request().

Requirements

  • Node.js 24+
  • Chrome 134+
  • Firefox 141+

Other runtimes must support using, Symbol.dispose, Promise.withResolvers, crypto.randomUUID, AbortSignal, and navigator.locks.request.

Installation

npm install using-mutex

Usage

Automatic release

import { Mutex } from 'using-mutex'

const mutex = new Mutex()

async function updateResource() {
  // The lock is released automatically when this scope exits.
  using lock = await mutex.acquire()

  // Perform mutually exclusive work here.
}

await Promise.all([updateResource(), updateResource()])

MutexGuard.release() can also be used when the lock needs to be released before the scope ends. Calling it more than once has no effect.

const lock = await mutex.acquire()
try {
  // Perform mutually exclusive work here.
} finally {
  lock.release()
}

Keyed locks

async function updateUser(userId) {
  using lock = await mutex.acquire(`user:${userId}`)
  // Calls for the same user are serialized.
  // Calls for different users can run concurrently.
}

Keys are scoped to a Mutex instance. Two separate Mutex instances do not contend with each other, even when they use the same key.

Timeout and cancellation

async function withTimeout() {
  // Stop waiting after 1,000 milliseconds.
  using lock = await mutex.acquire('resource', 1_000)
  // The timeout only applies while waiting to acquire the lock.
}

async function withCancellation(signal) {
  // Or control cancellation with an AbortSignal.
  using lock = await mutex.acquire('resource', signal)
}

const controller = new AbortController()
await withCancellation(controller.signal)

A timed-out acquisition rejects with a DOMException named TimeoutError. An aborted acquisition rejects with the signal's reason.

Web Locks options

const controller = new AbortController()

using lock = await mutex.request('resource', {
  mode: 'exclusive',
  signal: controller.signal,
})

request() namespaces the key for the current Mutex instance, passes options to navigator.locks.request(), and holds the lock until the returned guard is released.

API

Mutex.acquire()

acquire(): Promise<MutexGuard>
acquire(timeout: number): Promise<MutexGuard>
acquire(signal: AbortSignal): Promise<MutexGuard>
acquire(key: string): Promise<MutexGuard>
acquire(key: string, timeout: number): Promise<MutexGuard>
acquire(key: string, signal: AbortSignal): Promise<MutexGuard>

Acquires an exclusive lock. Omitting key uses the instance's default key. A positive numeric timeout is interpreted in milliseconds.

Mutex.request(key, options?)

request(key: string, options?: LockOptions): Promise<MutexGuard>

Acquires a keyed lock using the supplied Web Locks API options.

MutexGuard

class MutexGuard implements Disposable {
  get key(): string
  release(): void
  [Symbol.dispose](): void
}

Represents an acquired lock. Release it with using, release(), or Symbol.dispose.

About

A lightweight, zero-dependency, mutex with built-in pool and resource management.

Topics

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Used by

Contributors

Languages