async-mutex

repository·master·Indexed 23 days ago

https://github.com/dirtyhairy/async-mutex

A JavaScript library providing synchronization primitives, including Mutex and Semaphore, to prevent race conditions in asynchronous workflows. Version 0.5.0 includes features for mutual exclusion, concurrency limiting, and utilities like withTimeout and tryAcquire to manage lock acquisition and timeouts.

Tokens
6.9K
Snippets
17
Records
46
Agent score
78%

What's inside async-mutex

  1. What is a Mutex and when to use it?

    master

    A Mutex (mutual exclusion) is a primitive used to synchronize asynchronous operations in JavaScript. While JavaScript is single-threaded, the asynchronous execution model allows for race conditions (e.g., when multiple async calls attempt to access or modify the same state during different event loop turns).

    Locking a mutex returns a promise that resolves once the mutex becomes available. Once the protected async process is complete, the lock must be released to allow the next scheduled task to execute.

  2. What is a Semaphore and when to use it?

    master

    A Semaphore is a data structure initialized with an integer value that allows controlling access to multiple instances of a shared resource.

    • As long as the semaphore value is positive, locking it decrements the value and allows execution to continue immediately.
    • Once the value reaches zero, subsequent attempts to acquire a lock will be suspended until another process releases a lock, incrementing the value again.

    Use a semaphore when you want to limit concurrency, such as limiting the number of parallel web crawler requests or worker processes.

  3. Import Mutex, Semaphore, or withTimeout

    master

    Depending on your environment, use the appropriate import syntax. The library is written in TypeScript and requires no external typings for TypeScript (version >= 2).

    // CommonJS
    const {Mutex, Semaphore, withTimeout} = require('async-mutex');
    // ES6
    import {Mutex, Semaphore, withTimeout} from 'async-mutex';
    // TypeScript
    import {Mutex, MutexInterface, Semaphore, SemaphoreInterface, withTimeout} from 'async-mutex';
  4. Import Mutex, Semaphore, and utilities

    master

    Depending on your environment, import the necessary primitives and utility functions as follows:

    CommonJS

    const { Mutex, Semaphore, withTimeout, tryAcquire } = require('async-mutex');

    ES6

    import { Mutex, Semaphore, withTimeout, tryAcquire } from 'async-mutex';

    TypeScript

    import { 
      Mutex, MutexInterface, 
      Semaphore, SemaphoreInterface, 
      withTimeout, tryAcquire,
      E_TIMEOUT, E_ALREADY_LOCKED, E_CANCELED 
    } from 'async-mutex';
    // CommonJS
    const { Mutex, Semaphore, withTimeout, tryAcquire } = require('async-mutex');
    
    // ES6
    import { Mutex, Semaphore, withTimeout, tryAcquire } from 'async-mutex';
    
    // TypeScript
    import { 
      Mutex, MutexInterface, 
      Semaphore, SemaphoreInterface, 
      withTimeout, tryAcquire,
      E_TIMEOUT, E_ALREADY_LOCKED, E_CANCELED 
    } from 'async-mutex';
  5. Cancel pending locks with E_CANCELED

    master

    You can cancel all pending locks (locks that are waiting to be acquired) by calling mutex.cancel(). This causes the pending promises to reject with the E_CANCELED error.

    Note that cancel() does not revoke a lock that is currently being held.

    You can customize the error thrown by passing a different error to the Mutex constructor.

    import {E_CANCELED} from 'async-mutex';
    
    try {
        await mutex.runExclusive(() => {
            // ...
        });
    } catch (e) {
        if (e === E_CANCELED) {
            // Handle cancellation
        }
    }
  6. Apply a timeout to a Mutex or Semaphore

    master

    Use the withTimeout decorator to prevent tasks from waiting indefinitely. If the timeout is exceeded, acquire and runExclusive will reject with E_TIMEOUT.

    Arguments:

    1. The Mutex or Semaphore instance.
    2. timeout: Time in milliseconds.
    3. customError (optional): A custom error to throw instead of the default E_TIMEOUT.
  7. Execute code within a Mutex using runExclusive

    master
    The runExclusive method is the recommended way to ensure code runs exclusively. It schedules the supplied callback to run once the mutex is unlocked. The mutex is automatically released when the callback's promise resolves or rejects (or immediately if a synchronous value is returned).
  8. Wait for a mutex to become available with waitForUnlock

    master

    If you want to wait until the mutex is free without actually acquiring the lock, use waitForUnlock(). This returns a promise that resolves once the mutex is available.

    Warning: This does not lock the mutex; there is no guarantee the mutex will still be available immediately after the promise resolves.

    await mutex.waitForUnlock();
    // ...
  9. Fail early if a Mutex or Semaphore is locked

    master

    Use the tryAcquire decorator to attempt to acquire a lock without waiting. If the lock is already held, it will immediately throw E_ALREADY_LOCKED.

    Arguments:

    1. The Mutex or Semaphore instance.
    2. customError (optional): A custom error to throw instead of the default E_ALREADY_LOCKED.
    import {tryAcquire, E_ALREADY_LOCKED} from 'async-mutex';
    
    try {
        await tryAcquire(semaphoreOrMutex).runExclusive(() => { /* ... */ });
    } catch (e) {
        if (e === E_ALREADY_LOCKED) {
            // Handle the case where it was already locked
        }
    }
  10. Cancel pending Semaphore locks

    master

    You can cancel all currently queued (pending) locks by calling semaphore.cancel().

    • All pending promises from acquire or runExclusive will reject with E_CANCELED.
    • Note: Currently held locks are not revoked. The semaphore may still be unavailable immediately after calling cancel() if locks are still active.
    import {E_CANCELED} from 'async-mutex';
    
    try {
        await semaphore.runExclusive(() => { /* ... */ });
    } catch (e) {
        if (e === E_CANCELED) {
            // Handle cancellation
        }
    }
  11. Use Mutex for mutual exclusion

    master

    A Mutex ensures that only one asynchronous operation can access a critical section at a time.

    Constructor

    const mutex = new Mutex(cancelError?: Error);

    • cancelError: Optional custom error used when canceling pending locks (default: E_CANCELED).

    Acquiring the Mutex

    You can acquire the mutex manually or use the runExclusive helper.

    Manual Acquisition (Async/Await)

    const release = await mutex.acquire();
    try {
      // Critical section
    } finally {
      release();
    }

    Using runExclusive (Recommended) This automatically handles acquisition and release.

    const result = await mutex.runExclusive(async () => {
      // Critical section
      return someValue;
    });

    Mutex Methods

    • acquire(priority?: number): Promise<MutexInterface.Releaser>: Acquires the mutex. Higher priority values are processed first.
    • runExclusive<T>(callback: () => Promise<T> | T, priority?: number): Promise<T>: Runs a callback exclusively.
    • release(): void: Releases the mutex if it's locked.
    • waitForUnlock(priority?: number): Promise<void>: Waits until the mutex is available without acquiring it.
    • isLocked(): boolean: Returns true if the mutex is currently locked.
    • cancel(): void: Cancels all pending lock requests.
    const mutex = new Mutex();
    
    // Async/await style
    const release = await mutex.acquire();
    try {
      // Critical section
    } finally {
      release();
    }
    
    // runExclusive style
    const result = await mutex.runExclusive(async () => {
      // Critical section
      return someValue;
    });