const E_TIMEOUT = new Error('timeout while waiting for mutex to become available'); const E_ALREADY_LOCKED = new Error('mutex already locked'); const E_CANCELED = new Error('request for lock canceled'); var __awaiter$2 = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; class Semaphore { constructor(_value, _cancelError = E_CANCELED) { this._value = _value; this._cancelError = _cancelError; this._queue = []; this._weightedWaiters = []; } acquire(weight = 1, priority = 0) { if (weight <= 0) throw new Error(`invalid weight ${weight}: must be positive`); return new Promise((resolve, reject) => { const task = { resolve, reject, weight, priority }; const i = findIndexFromEnd(this._queue, (other) => priority <= other.priority); if (i === -1 && weight <= this._value) { // Needs immediate dispatch, skip the queue this._dispatchItem(task); } else { this._queue.splice(i + 1, 0, task); } }); } runExclusive(callback_1) { return __awaiter$2(this, arguments, void 0, function* (callback, weight = 1, priority = 0) { const [value, release] = yield this.acquire(weight, priority); try { return yield callback(value); } finally { release(); } }); } waitForUnlock(weight = 1, priority = 0) { if (weight <= 0) throw new Error(`invalid weight ${weight}: must be positive`); if (this._couldLockImmediately(weight, priority)) { return Promise.resolve(); } else { return new Promise((resolve) => { if (!this._weightedWaiters[weight - 1]) this._weightedWaiters[weight - 1] = []; insertSorted(this._weightedWaiters[weight - 1], { resolve, priority }); }); } } isLocked() { return this._value <= 0; } getValue() { return this._value; } setValue(value) { this._value = value; this._dispatchQueue(); } release(weight = 1) { if (weight <= 0) throw new Error(`invalid weight ${weight}: must be positive`); this._value += weight; this._dispatchQueue(); } cancel() { this._queue.forEach((entry) => entry.reject(this._cancelError)); this._queue = []; } _dispatchQueue() { this._drainUnlockWaiters(); while (this._queue.length > 0 && this._queue[0].weight <= this._value) { this._dispatchItem(this._queue.shift()); this._drainUnlockWaiters(); } } _dispatchItem(item) { const previousValue = this._value; this._value -= item.weight; item.resolve([previousValue, this._newReleaser(item.weight)]); } _newReleaser(weight) { let called = false; return () => { if (called) return; called = true; this.release(weight); }; } _drainUnlockWaiters() { if (this._queue.length === 0) { for (let weight = this._value; weight > 0; weight--) { const waiters = this._weightedWaiters[weight - 1]; if (!waiters) continue; waiters.forEach((waiter) => waiter.resolve()); this._weightedWaiters[weight - 1] = []; } } else { const queuedPriority = this._queue[0].priority; for (let weight = this._value; weight > 0; weight--) { const waiters = this._weightedWaiters[weight - 1]; if (!waiters) continue; const i = waiters.findIndex((waiter) => waiter.priority <= queuedPriority); (i === -1 ? waiters : waiters.splice(0, i)) .forEach((waiter => waiter.resolve())); } } } _couldLockImmediately(weight, priority) { return (this._queue.length === 0 || this._queue[0].priority < priority) && weight <= this._value; } } function insertSorted(a, v) { const i = findIndexFromEnd(a, (other) => v.priority <= other.priority); a.splice(i + 1, 0, v); } function findIndexFromEnd(a, predicate) { for (let i = a.length - 1; i >= 0; i--) { if (predicate(a[i])) { return i; } } return -1; } var __awaiter$1 = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; class Mutex { constructor(cancelError) { this._semaphore = new Semaphore(1, cancelError); } acquire() { return __awaiter$1(this, arguments, void 0, function* (priority = 0) { const [, releaser] = yield this._semaphore.acquire(1, priority); return releaser; }); } runExclusive(callback, priority = 0) { return this._semaphore.runExclusive(() => callback(), 1, priority); } isLocked() { return this._semaphore.isLocked(); } waitForUnlock(priority = 0) { return this._semaphore.waitForUnlock(1, priority); } release() { if (this._semaphore.isLocked()) this._semaphore.release(); } cancel() { return this._semaphore.cancel(); } } var __awaiter = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; function withTimeout(sync, timeout, timeoutError = E_TIMEOUT) { return { acquire: (weightOrPriority, priority) => { let weight; if (isSemaphore(sync)) { weight = weightOrPriority; } else { weight = undefined; priority = weightOrPriority; } if (weight !== undefined && weight <= 0) { throw new Error(`invalid weight ${weight}: must be positive`); } return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () { let isTimeout = false; const handle = setTimeout(() => { isTimeout = true; reject(timeoutError); }, timeout); try { const ticket = yield (isSemaphore(sync) ? sync.acquire(weight, priority) : sync.acquire(priority)); if (isTimeout) { const release = Array.isArray(ticket) ? ticket[1] : ticket; release(); } else { clearTimeout(handle); resolve(ticket); } } catch (e) { if (!isTimeout) { clearTimeout(handle); reject(e); } } })); }, runExclusive(callback, weight, priority) { return __awaiter(this, void 0, void 0, function* () { let release = () => undefined; try { const ticket = yield this.acquire(weight, priority); if (Array.isArray(ticket)) { release = ticket[1]; return yield callback(ticket[0]); } else { release = ticket; return yield callback(); } } finally { release(); } }); }, release(weight) { sync.release(weight); }, cancel() { return sync.cancel(); }, waitForUnlock: (weightOrPriority, priority) => { let weight; if (isSemaphore(sync)) { weight = weightOrPriority; } else { weight = undefined; priority = weightOrPriority; } if (weight !== undefined && weight <= 0) { throw new Error(`invalid weight ${weight}: must be positive`); } return new Promise((resolve, reject) => { const handle = setTimeout(() => reject(timeoutError), timeout); (isSemaphore(sync) ? sync.waitForUnlock(weight, priority) : sync.waitForUnlock(priority)).then(() => { clearTimeout(handle); resolve(); }); }); }, isLocked: () => sync.isLocked(), getValue: () => sync.getValue(), setValue: (value) => sync.setValue(value), }; } function isSemaphore(sync) { return sync.getValue !== undefined; } // eslint-disable-next-lisne @typescript-eslint/explicit-module-boundary-types function tryAcquire(sync, alreadyAcquiredError = E_ALREADY_LOCKED) { // eslint-disable-next-line @typescript-eslint/no-explicit-any return withTimeout(sync, 0, alreadyAcquiredError); } export { E_ALREADY_LOCKED, E_CANCELED, E_TIMEOUT, Mutex, Semaphore, tryAcquire, withTimeout };