npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

async-shared-mutex

v2.2.2

Published

Lightweight async reader–writer (shared/exclusive) mutex

Readme

async-shared-mutex

npm Package

Lightweight shared (reader) / exclusive (writer) mutex for TypeScript / ESM. Two flavors:

  • SharedMutex – low‑level handle based API (you manage the critical section).
  • AsyncSharedMutex – convenience API that runs a function while holding the mutex.

Both support shared (concurrent) and exclusive (mutually exclusive) acquisition.

Quick start (task style)

import { AsyncSharedMutex } from 'async-shared-mutex'

const mtx = new AsyncSharedMutex()

// Exclusive (writer)
await mtx.lock(async () => {
  // only one task may run here
  await doWrite()
})

// Shared (reader) – many may run together
const [a, b, c] = await Promise.all([
  mtx.lockShared(() => readValue('a')),
  mtx.lockShared(() => readValue('b')),
  mtx.lockShared(() => readValue('c')),
])

Quick start (handle style)

import { SharedMutex } from 'async-shared-mutex'

const mtx = new SharedMutex()

// Exclusive
const exclusive = await mtx.lock()
try {
  await doWrite()
}
finally {
  exclusive.unlock()
}

// Shared
const shared = await mtx.lockShared()
try {
  const v = await readValue()
  console.log(v)
}
finally {
  shared.unlock()
}

With TypeScript using (TS 5.2+)

import { SharedMutex } from 'async-shared-mutex'
const mtx = new SharedMutex()

async function doStuff() {
  using lock = await mtx.lock() // unlocks automatically at end of scope
  await mutate()
}

If your runtime lacks native Symbol.dispose, add a small polyfill (see test/helpers/patchDisposable.ts for an example) or keep calling unlock() manually.

When to use

Use for coordinating access to a resource where:

  • Multiple readers may safely proceed concurrently.
  • Writers need full exclusivity (no readers or other writers).
  • Writers should not starve behind an ever‑arriving stream of readers.

Semantics

  • Shared acquisitions overlap with other shared acquisitions provided no earlier exclusive is pending / active.
  • An exclusive waits for all currently active (or already queued before it) shared holders to finish, then runs alone.
  • Shared acquisitions requested after an exclusive has queued must wait until that exclusive finishes.
  • Exclusives are serialized in request order.
  • Errors inside a task (or your critical section) propagate; the mutex is still unlocked.
  • try* variants attempt an instantaneous acquisition; they return null if not immediately possible (no waiting side effects).

This gives predictable writer progress (no writer starvation) while still batching readers that arrive before the next writer.

API

class SharedMutex

Low level; you get locks you must unlock.

| Method | Returns | Description | | ------ | ------- | ----------- | | lock() | Promise<LockHandle> | Await for an exclusive (writer) handle. | | tryLock() | LockHandle \| null | Immediate exclusive attempt. null if busy. | | lockShared() | Promise<LockHandle> | Await for a shared (reader) handle. | | tryLockShared() | LockHandle \| null | Immediate shared attempt (fails if an exclusive is active/pending). |

LockHandle:

  • unlock(): void – idempotent; may be called multiple times.
  • [Symbol.dispose]() – same as unlock() enabling using.

class AsyncSharedMutex

Wraps SharedMutex and runs a function while holding the mutex.

| Method | Returns | Description | | ------ | ------- | ----------- | | lock(task) | Promise<T> | Run task exclusively. | | tryLock(task) | Promise<T> \| null | Immediate exclusive attempt. If acquired, runs task; else null. | | lockShared(task) | Promise<T> | Run task under a shared lock. | | tryLockShared(task) | Promise<T> \| null | Immediate shared attempt. |

task signature: () => T | PromiseLike<T>

Error handling

If task throws / rejects, the mutex is unlocked and the error is re-thrown. No additional wrapping.

Ordering example

time →
S S S (queued)    E (queued after those S)  S S (queued after E)
|<--- overlap --->|<--- exclusive alone --->|<--- overlap --->|

Patterns

Debounce writes while permitting many simultaneous reads:

const stateMtx = new AsyncSharedMutex()
let state: Data

export const readState = () => stateMtx.lockShared(() => state)
export const updateState = (patch: Partial<Data>) => stateMtx.lock(async () => {
  state = { ...state, ...patch }
})

Attempt a fast read path that falls back to waiting if a writer is in flight:

const mtx = new SharedMutex()

export async function getSnapshot(): Promise<Snapshot> {
  const h = mtx.tryLockShared() || await mtx.lockShared()
  try {
    return snapshot()
  }
  finally {
    h.unlock()
  }
}

Target

Modern Node / browsers, ES2022.

Limitations / Notes

  • Not reentrant – calling lock methods from inside an already held lock will deadlock your logic (no detection performed).
  • Fairness beyond the described ordering is not attempted (e.g. readers arriving while a long queue of writers exists will wait until those writers finish).
  • No timeout / cancellation primitive provided. Compose with AbortController in your tasks if required.

Comparison

| | SharedMutex | AsyncSharedMutex | | - | - | - | | Style | Manual handles | Higher level task runner | | Cleanup | Call unlock() / using | Automatic around function | | Overhead | Slightly lower | Wrapper promise per task |

License

MIT


Feel free to open issues / PRs for ideas (timeouts, cancellation helpers, metrics, etc.).