openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
68 lines (67 loc) • 1.89 kB
JavaScript
//#region src/shared/permit-pool.ts
/**
* FIFO admission with caller-owned lifetime. Cancellation/deadlines only stop
* waiting: an acquired permit stays held until its idempotent release is called.
* acquire returns null on cancellation/expiry; tryAcquire returns null when busy.
*/
function createPermitPool(limit) {
let active = 0;
const waiters = [];
const createRelease = () => {
active += 1;
let released = false;
return () => {
if (released) return;
released = true;
active -= 1;
while (waiters.length > 0) {
const waiter = waiters.shift();
if (!waiter) break;
if (waiter.expired()) {
waiter.settle(null);
continue;
}
waiter.settle(createRelease());
break;
}
};
};
const tryAcquire = () => active < limit ? createRelease() : null;
return {
get pendingCount() {
return waiters.length;
},
tryAcquire,
async acquire({ signal, deadlineAtMs } = {}) {
const expired = () => signal?.aborted === true || deadlineAtMs !== void 0 && Date.now() >= deadlineAtMs;
if (expired()) return null;
const releasePermit = tryAcquire();
if (releasePermit) return releasePermit;
return await new Promise((resolve) => {
let settled = false;
let timer;
const cancel = () => waiter.settle(null);
const waiter = {
expired,
settle: (release) => {
if (settled) return;
settled = true;
clearTimeout(timer);
signal?.removeEventListener("abort", cancel);
const index = waiters.indexOf(waiter);
if (index >= 0) waiters.splice(index, 1);
resolve(release);
}
};
if (deadlineAtMs !== void 0) {
timer = setTimeout(cancel, Math.max(1, deadlineAtMs - Date.now()));
timer.unref();
}
signal?.addEventListener("abort", cancel, { once: true });
waiters.push(waiter);
});
}
};
}
//#endregion
export { createPermitPool as t };