@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
JavaScript
;
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