UNPKG

@apiratorjs/locking

Version:

A lightweight library providing both local and distributed locking primitives (mutexes and semaphores) for managing concurrency in Node.js.

103 lines 3.12 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Semaphore = void 0; const node_assert_1 = __importDefault(require("node:assert")); const node_crypto_1 = __importDefault(require("node:crypto")); const constants_1 = require("./constants"); class Releaser { constructor(_onRelease, _token) { this._onRelease = _onRelease; this._token = _token; } async release() { await this._onRelease(); } getToken() { return this._token; } } class Semaphore { constructor(maxCount) { node_assert_1.default.ok(maxCount > 0, "maxCount must be greater than 0"); this.maxCount = maxCount; this._freeCount = maxCount; this._queue = []; } async runExclusive(...args) { let callback; let params; if (args.length === 1) { callback = args[0]; } else { params = args[0]; callback = args[1]; } const releaser = await this.acquire(params); try { return await callback(); } finally { await releaser.release(); } } async freeCount() { return this._freeCount; } async acquire(params, acquireToken) { const timeoutMs = params?.timeoutMs || constants_1.DEFAULT_TIMEOUT_IN_MS; const releaser = new Releaser(this.release.bind(this), acquireToken ?? node_crypto_1.default.randomUUID()); if (this._freeCount > 0) { this._freeCount--; return releaser; } return new Promise((resolve, reject) => { const deferred = { resolve: () => resolve(releaser), reject, timer: null }; deferred.timer = setTimeout(() => { const index = this._queue.indexOf(deferred); if (index !== -1) { this._queue.splice(index, 1); } reject(new Error("Timeout acquiring semaphore")); }, timeoutMs); this._queue.push(deferred); }); } async cancelAll(errMessage) { while (this._queue.length > 0) { const { timer, reject } = this._queue.shift(); if (timer) { clearTimeout(timer); } reject(new Error(errMessage ?? "Semaphore cancelled")); } this._freeCount = this.maxCount; } async isLocked() { return this._freeCount === 0; } async release() { if (this._freeCount === this.maxCount) { return; } if (this._queue.length > 0) { const { resolve, timer } = this._queue.shift(); if (timer) { clearTimeout(timer); } resolve(); } else { this._freeCount++; } } } exports.Semaphore = Semaphore; //# sourceMappingURL=semaphore.js.map