Repository Public MIT Selected
1/**2 * ─────────────────────────────────────────────3 * SPB Git — Personal Git Platform4 * ─────────────────────────────────────────────5 * Author : Simon-Pierre Boucher6 * Contact : contact@spboucher.ai7 * File : src/lib/semaphore.mjs8 * Purpose : Tiny counting semaphore — bounds concurrent git subprocesses so crawlers cannot exhaust memory9 * License : MIT © Simon-Pierre Boucher10 * ─────────────────────────────────────────────11 */1213/**14 * Counting semaphore. `acquire()` resolves with a release function; callers15 * must release in a `finally`. Waiters are served in FIFO order.16 */17export class Semaphore {18 /** @param {number} limit maximum concurrent holders */19 constructor(limit) {20 this.limit = Math.max(1, limit | 0);21 this.active = 0;22 this.queue = [];23 }2425 /** @returns {Promise<() => void>} release function */26 acquire() {27 return new Promise((resolve) => {28 const grant = () => {29 this.active += 1;30 let released = false;31 resolve(() => {32 if (released) return;33 released = true;34 this.active -= 1;35 const next = this.queue.shift();36 if (next) next();37 });38 };39 if (this.active < this.limit) grant();40 else this.queue.push(grant);41 });42 }4344 /**45 * Run `fn` while holding one slot.46 * @template T47 * @param {() => Promise<T>} fn48 * @returns {Promise<T>}49 */50 async run(fn) {51 const release = await this.acquire();52 try {53 return await fn();54 } finally {55 release();56 }57 }5859 /** @returns {{active: number, waiting: number, limit: number}} */60 stats() {61 return { active: this.active, waiting: this.queue.length, limit: this.limit };62 }63}64