202 lines
7.7 KiB
JavaScript
202 lines
7.7 KiB
JavaScript
|
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(_maxConcurrency, _cancelError = E_CANCELED) {
|
||
|
this._maxConcurrency = _maxConcurrency;
|
||
|
this._cancelError = _cancelError;
|
||
|
this._queue = [];
|
||
|
this._waiters = [];
|
||
|
if (_maxConcurrency <= 0) {
|
||
|
throw new Error('semaphore must be initialized to a positive value');
|
||
|
}
|
||
|
this._value = _maxConcurrency;
|
||
|
}
|
||
|
acquire() {
|
||
|
const locked = this.isLocked();
|
||
|
const ticketPromise = new Promise((resolve, reject) => this._queue.push({ resolve, reject }));
|
||
|
if (!locked)
|
||
|
this._dispatch();
|
||
|
return ticketPromise;
|
||
|
}
|
||
|
runExclusive(callback) {
|
||
|
return __awaiter$2(this, void 0, void 0, function* () {
|
||
|
const [value, release] = yield this.acquire();
|
||
|
try {
|
||
|
return yield callback(value);
|
||
|
}
|
||
|
finally {
|
||
|
release();
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
waitForUnlock() {
|
||
|
return __awaiter$2(this, void 0, void 0, function* () {
|
||
|
if (!this.isLocked()) {
|
||
|
return Promise.resolve();
|
||
|
}
|
||
|
const waitPromise = new Promise((resolve) => this._waiters.push({ resolve }));
|
||
|
return waitPromise;
|
||
|
});
|
||
|
}
|
||
|
isLocked() {
|
||
|
return this._value <= 0;
|
||
|
}
|
||
|
/** @deprecated Deprecated in 0.3.0, will be removed in 0.4.0. Use runExclusive instead. */
|
||
|
release() {
|
||
|
if (this._maxConcurrency > 1) {
|
||
|
throw new Error('this method is unavailable on semaphores with concurrency > 1; use the scoped release returned by acquire instead');
|
||
|
}
|
||
|
if (this._currentReleaser) {
|
||
|
const releaser = this._currentReleaser;
|
||
|
this._currentReleaser = undefined;
|
||
|
releaser();
|
||
|
}
|
||
|
}
|
||
|
cancel() {
|
||
|
this._queue.forEach((ticket) => ticket.reject(this._cancelError));
|
||
|
this._queue = [];
|
||
|
}
|
||
|
_dispatch() {
|
||
|
const nextTicket = this._queue.shift();
|
||
|
if (!nextTicket)
|
||
|
return;
|
||
|
let released = false;
|
||
|
this._currentReleaser = () => {
|
||
|
if (released)
|
||
|
return;
|
||
|
released = true;
|
||
|
this._value++;
|
||
|
this._resolveWaiters();
|
||
|
this._dispatch();
|
||
|
};
|
||
|
nextTicket.resolve([this._value--, this._currentReleaser]);
|
||
|
}
|
||
|
_resolveWaiters() {
|
||
|
this._waiters.forEach((waiter) => waiter.resolve());
|
||
|
this._waiters = [];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
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();
|
||
|
}
|
||
|
/** @deprecated Deprecated in 0.3.0, will be removed in 0.4.0. Use runExclusive instead. */
|
||
|
release() {
|
||
|
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());
|
||
|
});
|
||
|
};
|
||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||
|
function withTimeout(sync, timeout, timeoutError = E_TIMEOUT) {
|
||
|
return {
|
||
|
acquire: () => 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();
|
||
|
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) {
|
||
|
return __awaiter(this, void 0, void 0, function* () {
|
||
|
let release = () => undefined;
|
||
|
try {
|
||
|
const ticket = yield this.acquire();
|
||
|
if (Array.isArray(ticket)) {
|
||
|
release = ticket[1];
|
||
|
return yield callback(ticket[0]);
|
||
|
}
|
||
|
else {
|
||
|
release = ticket;
|
||
|
return yield callback();
|
||
|
}
|
||
|
}
|
||
|
finally {
|
||
|
release();
|
||
|
}
|
||
|
});
|
||
|
},
|
||
|
/** @deprecated Deprecated in 0.3.0, will be removed in 0.4.0. Use runExclusive instead. */
|
||
|
release() {
|
||
|
sync.release();
|
||
|
},
|
||
|
cancel() {
|
||
|
return sync.cancel();
|
||
|
},
|
||
|
waitForUnlock: () => sync.waitForUnlock(),
|
||
|
isLocked: () => sync.isLocked(),
|
||
|
};
|
||
|
}
|
||
|
|
||
|
// 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 };
|