IMQLock class

In-process, promise-based locks used to collapse concurrent identical calls: the first caller executes the work while later callers for the same key wait and are then resolved with the first caller's result.

Signature:

export declare class IMQLock 

Remarks

These are not distributed locks. The lock table is a set of plain static objects held in memory, and nothing here touches Redis, the network or any shared store. Separate processes, cluster workers and service replicas each maintain their own independent locks and will run the guarded code concurrently. lock() inherits the same limitation. Use a Redis- or database-backed lock if you need mutual exclusion across processes.

Keys are used verbatim, with no prefixing or namespacing, so they are global to the process and unrelated call sites sharing a string share a lock.

Exclusion is not absolute, and IMQLock.deadlockTimeout is why. A waiter that times out frees the key so that a holder which never releases cannot poison it for the life of the process — but the holder is still running, so the next call acquires and runs alongside it. Pass the IMQLock.token() to IMQLock.release(), as the example does and as lock() does for you, and the damage stops there: the overtaken holder can no longer resolve the new holder's waiters or free a lock still in use. Set deadlockTimeout to 0 if you would rather have strict exclusion and let waiters wait forever.

Example

import { IMQLock, type AcquiredLock } from '@imqueue/rpc';

async function doSomething(): Promise<number | AcquiredLock<number>> {
    const lock: AcquiredLock<number> =
        await IMQLock.acquire<number>('doSomething');

    // locked() is the only reliable way to tell holder from waiter
    if (IMQLock.locked('doSomething')) {
        // read the token straight after acquiring and pass it to every
        // release, so a release cannot land on a later holder's lock
        const token = IMQLock.token('doSomething');

        // always wrap locked work in try/catch and release on both paths,
        // otherwise waiters hang until the deadlock timeout fires
        try {
            // runs only once across all concurrent calls; every waiter
            // resolves with this same value
            const res = Math.random();

            IMQLock.release('doSomething', res, undefined, token);

            return res;
        } catch (err) {
            // reject every waiter with the same error
            IMQLock.release('doSomething', null, err, token);
            throw err;
        }
    }

    return lock;
}

for (let i = 0; i < 10; ++i) {
    doSomething().then(res => console.log(res));
}

Properties

Property

Modifiers

Type

Description

deadlockTimeout

static

number

Deadlock timeout in milliseconds

logger

static

ILogger

Logger used to log errors that appear during locked calls

Methods

Method

Modifiers

Description

acquire(key, callback, metadata)

static

Acquires a lock for a given key.

locked(key)

static

Returns true if the given key is locked, false otherwise.

release(key, value, err, token)

static

Releases a previously acquired lock for a given key.

token(key)

static

Returns the token identifying the current holder of a given key, or undefined when the key is not locked.

Read this page as plain markdown — no HTML, no navigation. For pasting into an LLM, or for an agent to fetch.