// simple counting semaphore, for blocking async ops // cribbed mostly from https://github.com/ComFreek/async-playground export class Semaphore { #counter = 0; #resolvers: ((taken: boolean) => void)[] = []; constructor(count = 0) { this.#counter = count; } take(signal?: AbortSignal) { return new Promise((resolve) => { if (signal?.aborted) return resolve(false); // if there's resources available, use them this.#counter--; if (this.#counter >= 0) return resolve(true); // otherwise add to pending // and explicitly remove the resolver from the list on abort this.#resolvers.push(resolve); signal?.addEventListener("abort", () => { const index = this.#resolvers.indexOf(resolve); if (index >= 0) { this.#resolvers.splice(index, 1); this.#counter++; } resolve(false); }); }); } poll() { if (this.#counter <= 0) return false; this.#counter--; return true; } free() { this.#counter++; if (this.#resolvers.length > 0) { const resolver = this.#resolvers.shift(); resolver && queueMicrotask(() => resolver(true)); } } }