UNPKG

@apiratorjs/locking

Version:

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

149 lines 5.39 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"); const errors_1 = require("./errors"); 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 = []; this._waitingForAnyUnlockListeners = []; this._waitingForFullyUnlockListeners = []; } async waitForFullyUnlock() { if (this.maxCount === this._freeCount) { return; } return new Promise((resolve, reject) => { this._waitingForFullyUnlockListeners.push({ resolve, reject }); }); } async waitForAnyUnlock() { if (this._freeCount > 0) { return; } return new Promise((resolve, reject) => { this._waitingForAnyUnlockListeners.push({ resolve, reject }); }); } 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 token = (acquireToken ?? node_crypto_1.default.randomUUID()); const releaser = new Releaser(this.release.bind(this), token); if (this._freeCount > 0) { this._freeCount--; return releaser; } return new Promise((resolve, reject) => { const timer = setTimeout(() => { const index = this._queue.indexOf(deferred); if (index !== -1) { this._queue.splice(index, 1); } reject(new errors_1.TimeoutLockingError("Timeout acquiring semaphore")); }, timeoutMs); const deferred = { resolve: () => { if (timer) { clearTimeout(timer); } resolve(releaser); }, reject: (err) => { if (timer) { clearTimeout(timer); } reject(err); } }; this._queue.push(deferred); }); } async cancelAll(errMessage) { const cancellationList = [...this._queue]; await Promise.all(cancellationList.map(deferred => deferred.reject(new errors_1.CancelledLockingError(errMessage ?? "Semaphore cancelled")))); this._queue = []; this._freeCount = this.maxCount; // Notify all waitingForAnyUnlockListeners listeners since we're resetting to unlocked state const waitingForAnyUnlockListeners = [...this._waitingForAnyUnlockListeners]; this._waitingForAnyUnlockListeners = []; waitingForAnyUnlockListeners.forEach(listener => listener.reject(new errors_1.CancelledLockingError(errMessage ?? "Semaphore cancelled"))); // Notify all waitingForFullyUnlockListeners listeners since we're resetting to unlocked state const fullyUnlockListeners = [...this._waitingForFullyUnlockListeners]; this._waitingForFullyUnlockListeners = []; fullyUnlockListeners.forEach(listener => listener.reject(new errors_1.CancelledLockingError(errMessage ?? "Semaphore cancelled"))); } async isLocked() { return this._freeCount === 0; } async release() { if (this._freeCount === this.maxCount) { return; } if (this._queue.length > 0) { const { resolve } = this._queue.shift(); resolve(); } else { this._freeCount++; if (this._freeCount > 0 && this._waitingForAnyUnlockListeners.length > 0) { const listeners = [...this._waitingForAnyUnlockListeners]; this._waitingForAnyUnlockListeners = []; listeners.forEach(listener => listener.resolve()); } if (this._freeCount === this.maxCount) { const listeners = [...this._waitingForFullyUnlockListeners]; this._waitingForFullyUnlockListeners = []; listeners.forEach(listener => listener.resolve()); } } } } exports.Semaphore = Semaphore; //# sourceMappingURL=semaphore.js.map