export { IncompatibleHostnameMultiProcessMutexError, IncompatibleMultiProcessMutexError, IncompatiblePlatformMultiProcessMutexError, IncompatibleUidMultiProcessMutexError, InvalidMultiProcessMutexPathError, MultiProcessMutexError, MultiProcessMutexTimeoutError, StaleMultiProcessMutexError, } from "./errors/synchronization.js"; /** * A class that implements an inter-process mutex. * * This Mutex is implemented using hard-link-based atomic file creation. A * temporary file containing JSON metadata (PID, hostname, platform, uid, * session ID, and creation timestamp) is written first, then hard-linked to * the lock path via `fs.linkSync`. `linkSync` fails atomically with `EEXIST` * if the lock already exists, ensuring only one process can hold the lock at * a time. * * Staleness is determined by PID liveness only — timestamps are stored for * debugging purposes but are never used to determine staleness. This avoids the * clock-skew and long-running-task problems that time-based staleness detection * has (where a second process can break into a lock that's still legitimately * held). * * Incompatible locks — those created by a different hostname, platform, or * uid — are rejected immediately with specific subclasses of * `IncompatibleMultiProcessMutexError` * (`IncompatibleHostnameMultiProcessMutexError`, * `IncompatiblePlatformMultiProcessMutexError`, or * `IncompatibleUidMultiProcessMutexError`) because their PID liveness cannot * be verified or their lock file cannot be removed. These must be removed * manually. * * When the lock is held by a live process, the caller polls with exponential * backoff (default: 5ms → 10ms → ... → 160ms → 200ms cap) until the lock is * released or a timeout (default: 60s) is reached. * * If the filesystem does not support hard links (e.g., certain network * filesystems), acquisition fails fast with a `MultiProcessMutexError` rather * than degrading into timeout-based retries. * * ## Performance characteristics * * - **Uncontended acquisition:** One temp file write + one `linkSync` — takes * less than 1ms on most systems. * - **Stale lock recovery:** One `readFileSync` to read metadata, one * `process.kill(pid, 0)` liveness check, and one `unlinkSync` to remove the * stale lock file before retrying acquisition. The retry is immediate (no * sleep), so recovery adds sub-millisecond overhead. * - **Contended (live holder):** Polls with exponential backoff starting at * 5ms and doubling each iteration until capped at 200ms. Worst-case latency * after the lock is released is up to `MAX_POLL_INTERVAL_MS` (200ms). * - **Release:** A single `unlinkSync` call. * * ## Limitations * * - **Polling-based:** There is no filesystem notification; callers discover * that the lock is free only on the next poll, so there can be up to 200ms * of wasted wait time after the lock is released. * - **Not reentrant:** The same process (or even the same `MultiProcessMutex` * instance) calling `use()` while already holding the lock will deadlock * until the timeout fires. * - **Single-host, single-user only:** Encountering a lock from a different * hostname throws `IncompatibleHostnameMultiProcessMutexError`, a different * platform throws `IncompatiblePlatformMultiProcessMutexError`, and a * different uid throws `IncompatibleUidMultiProcessMutexError`. All extend * `IncompatibleMultiProcessMutexError`. This means the lock is not safe to * use on shared/networked filesystems (e.g., NFS) where multiple hosts or * users may access the same path. * - **Requires hard-link support:** The underlying filesystem must support * `linkSync`. If hard links are unsupported, acquisition fails immediately * with `MultiProcessMutexError`. * - **PID recycling:** If a process dies and the OS reassigns its PID to a new * unrelated process before the stale check runs, the lock is incorrectly * considered live. This is extremely unlikely in practice due to the large * PID space on modern systems. * - **No fairness guarantee:** Multiple waiters polling concurrently have no * guaranteed ordering — whichever one succeeds at `linkSync` first after the * lock is released wins. */ export declare class MultiProcessMutex { #private; /** * Creates an inter-process mutex given an absolute path. * * @param absolutePathToLock The absolute path of the mutex. * @param timeout The max amount of time to spend trying to acquire the lock * in milliseconds. Defaults to 60000. * @param initialPollInterval The initial poll interval in milliseconds. * Defaults to 5. */ constructor(absolutePathToLock: string, timeout?: number, initialPollInterval?: number); /** * Runs the function f while holding the mutex, returning its result. * * @param f The function to run. * @returns The result of the function. */ use(f: () => Promise): Promise; /** * Acquires the mutex, returning an async function to release it. * The function MUST be called after using the mutex. * * If this function throws, no cleanup is necessary — the lock was never * acquired. * * @returns The mutex's release function. */ acquire(): Promise<() => Promise>; } /** * A class that implements an asynchronous mutex (mutual exclusion) lock. * * The mutex ensures that only one asynchronous operation can be executed at a time, * providing exclusive access to a shared resource. */ export declare class AsyncMutex { #private; /** * Acquires the mutex, running the provided function exclusively, * and releasing it afterwards. * * @param f The function to run. * @returns The result of the function. */ exclusiveRun(f: () => ReturnT): Promise>; } /** * A class that deduplicates the concurrent computations of an asynchronous * operation based on a string key, by sharing the same Promise for all * concurrent calls. * * This class is useful when the operation is expensive, or you need to * guarantee that it's only executed once per key. * * For this cache to work correctly, the operation has to be either idempotent, * or virtually idempotent (e.g. reading the same file multiple times in a very * short time, in a context where it shouldn't be changed). * * The results of the first operation run per key are cached during the lifetime * of the class, or until the `delete` or `clear` methods are called. * * Note that you should always use the same function/operation per cache key. If * you call `getOrCompute` with the same key but different functions, the * first function will be used for all the calls. * * Notes on async stack traces: This class is designed to preserve as much as * possible of the producer async stack trace, including the place where the * first `getOrCompute` computation is started and where the producer throws. To * achieve this, you should provide an async lambda of the shape * `async () => await myFunction()`, instead of directly passing `myFunction`. * You should also try to immediately await the calls to `getOrCompute`. * * Concurrent callers for the same in-flight computation receive the same * original thrown value, so their own `getOrCompute` call site won't * necessarily be present in the error stack. * * Notes on concurrency: `getOrCompute` stores the in-flight promise in the * cache before invoking the producer function. Subsequent calls with the same * key, including synchronous same-key reentrant calls from the producer, * observe that promise and await the same computation instead of invoking their * own function. A producer must still not await a same-key reentrant call * before completing, because it would wait on its own in-flight result. * * This guarantee only applies while the cache entry remains installed. Calling * `delete` or `clear` during an in-flight computation lets later callers start * a new computation, and the older in-flight result won't repopulate the cache. */ export declare class SharedPromiseCache { #private; /** * Returns the cached value associated to the key, or computes it if needed, * guaranteeing that it's only computed once per key. * * @param key The cache key associated to the value. * @param fn The function that computes the value when it's not cached. Please * read the class docs to understand the requirements it should meet. * @returns The value. * @throws The original value thrown or rejected by the function. Concurrent * callers for the same in-flight computation observe the same thrown value, so * their stack reflects the original computation, not necessarily each * awaiting call site. */ getOrCompute(key: string, fn: () => Promise): Promise; /** * Returns the cached value associated to the key without invoking any * producer. If the entry is in-flight, the value is not yet available and * this method returns `undefined` exactly as it would for a missing key. * * Use this for synchronous fast-path lookups; if the result is `undefined` * and you still want to compute the value, fall through to `getOrCompute`. * * Note that if `ValueT` includes `undefined` as a valid value, you won't be * able to distinguish between a cached `undefined` and a missing/in-flight * entry, but that's an intentional tradeoff to keep this method simple. * * @param key The cache key. * @returns The cached value, or `undefined` if the key is missing or * in-flight. */ peek(key: string): ValueT | undefined; /** * Iterates over the entries that are successfully resolved, yielding * `[key, value]` pairs. In-flight entries are skipped because their value * is not yet known, and failed ones are removed from the cache. * * Producers are never invoked. */ resolvedEntries(): IterableIterator<[string, ValueT]>; /** * Deletes the cached value associated to the key, if any. Note that this does * not cancel any ongoing operation. Callers that already observed the * in-flight promise will still wait for the original operation to complete, * but callers that start after the deletion may start a new computation. * * @param key The cache key to delete. */ delete(key: string): void; /** * Clears the cache, removing all the stored values, but without cancelling * any ongoing operation. */ clear(): void; } //# sourceMappingURL=synchronization.d.ts.map