All files / lib/util semaphore.ts

100% Statements 73/73
100% Branches 16/16
100% Functions 6/6
100% Lines 73/73

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 741x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 70x 70x 70x 1x 1x 1x 55x 55x 1x 1x 1x 1x 1x 1x 1x 86x 33x 33x 33x 86x 3x 3x 86x 86x 86x 86x 86x 50x 86x 1x 1x 1x 1x 1x 1x 1x 83x 83x 83x 1x 1x 83x 82x 83x 50x 50x 83x 32x 32x 83x 83x 1x  
"use strict";
/**
 * A counting semaphore with a bounded FIFO wait queue. A released slot is
 * handed directly to the longest-waiting acquirer, so the number of holders
 * never exceeds the limit and new callers cannot overtake queued ones.
 *
 * @module util/semaphore
 */
 
/**
 * Bounded concurrency gate.
 */
export class Semaphore {
  private active = 0;
  private readonly waiters: Array<() => void> = [];
 
  /**
   * @param {Number} max - concurrent holders
   * @param {Number} maxQueue - maximum waiting acquirers
   */
  constructor(
    private readonly max: number,
    private readonly maxQueue: number,
  ) {}
 
  /** Number of slots currently held. */
  get inUse(): number {
    return this.active;
  }
 
  /**
   * Acquire a slot.
   *
   * @return {Promise} release function, or undefined when the queue is full
   */
  async acquire(): Promise<(() => void) | undefined> {
    if (this.active < this.max) {
      this.active++;
      return this._release();
    }
    if (this.waiters.length >= this.maxQueue) {
      return undefined;
    }
    // The releasing holder hands its slot over directly (see _release), so
    // `active` already counts this waiter when it resumes. Incrementing
    // here instead would let a caller arriving between the release and the
    // resumption take the same slot.
    await new Promise<void>((resolve) => this.waiters.push(resolve));
    return this._release();
  }
 
  /**
   * Build a one-shot release function.
   *
   * @private
   */
  private _release(): () => void {
    let released = false;
    return () => {
      if (released) {
        return;
      }
      released = true;
      const next = this.waiters.shift();
      if (next) {
        // Transfer the slot: `active` is unchanged, FIFO order is kept.
        next();
      } else {
        this.active--;
      }
    };
  }
}