UNPKG

async-mutex

Version:
229 lines (223 loc) 8.86 kB
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._weightedQueues = []; this._weightedWaiters = []; } acquire(weight = 1) { if (weight <= 0) throw new Error(`invalid weight ${weight}: must be positive`); return new Promise((resolve, reject) => { if (!this._weightedQueues[weight - 1]) this._weightedQueues[weight - 1] = []; this._weightedQueues[weight - 1].push({ resolve, reject }); this._dispatch(); }); } runExclusive(callback, weight = 1) { return __awaiter$2(this, void 0, void 0, function* () { const [value, release] = yield this.acquire(weight); try { return yield callback(value); } finally { release(); } }); } waitForUnlock(weight = 1) { if (weight <= 0) throw new Error(`invalid weight ${weight}: must be positive`); return new Promise((resolve) => { if (!this._weightedWaiters[weight - 1]) this._weightedWaiters[weight - 1] = []; this._weightedWaiters[weight - 1].push(resolve); this._dispatch(); }); } isLocked() { return this._value <= 0; } getValue() { return this._value; } setValue(value) { this._value = value; this._dispatch(); } release(weight = 1) { if (weight <= 0) throw new Error(`invalid weight ${weight}: must be positive`); this._value += weight; this._dispatch(); } cancel() { this._weightedQueues.forEach((queue) => queue.forEach((entry) => entry.reject(this._cancelError))); this._weightedQueues = []; } _dispatch() { var _a; for (let weight = this._value; weight > 0; weight--) { const queueEntry = (_a = this._weightedQueues[weight - 1]) === null || _a === void 0 ? void 0 : _a.shift(); if (!queueEntry) continue; const previousValue = this._value; const previousWeight = weight; this._value -= weight; weight = this._value + 1; queueEntry.resolve([previousValue, this._newReleaser(previousWeight)]); } this._drainUnlockWaiters(); } _newReleaser(weight) { let called = false; return () => { if (called) return; called = true; this.release(weight); }; } _drainUnlockWaiters() { for (let weight = this._value; weight > 0; weight--) { if (!this._weightedWaiters[weight - 1]) continue; this._weightedWaiters[weight - 1].forEach((waiter) => waiter()); this._weightedWaiters[weight - 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, void 0, void 0, function* () { const [, releaser] = yield this._semaphore.acquire(); return releaser; }); } runExclusive(callback) { return this._semaphore.runExclusive(() => callback()); } isLocked() { return this._semaphore.isLocked(); } waitForUnlock() { return this._semaphore.waitForUnlock(); } 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: (weight) => { 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 sync.acquire(weight); 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) { return __awaiter(this, void 0, void 0, function* () { let release = () => undefined; try { const ticket = yield this.acquire(weight); 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: (weight) => { if (weight !== undefined && weight <= 0) { throw new Error(`invalid weight ${weight}: must be positive`); } return new Promise((resolve, reject) => { sync.waitForUnlock(weight).then(resolve); setTimeout(() => reject(timeoutError), timeout); }); }, isLocked: () => sync.isLocked(), getValue: () => sync.getValue(), setValue: (value) => sync.setValue(value), }; } // 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 };