All files / src/classes CoreSemaphor.ts

100% Statements 20/20
85.71% Branches 6/7
100% Functions 7/7
100% Lines 20/20

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                                                                2x 2x 2x 2x       15x       4x 4x 4x 4x         12x 8x 8x 4x 4x 4x 4x   4x           4x 4x   4x      
import crypto from 'crypto';
 
export type SemElement = [string, (value: any | PromiseLike<any>) => void];
 
/**
 * A semaphore implementation that limits the number of concurrent operations.
 * <p>
 * The semaphore maintains an internal queue of pending requests and a set of
 * active tokens. When the number of active tokens is below the configured
 * maximum, the next request in the queue is granted immediately. Each granted
 * request receives a release callback; calling this callback frees a token
 * and triggers the next pending request.
 * <p>
 * Internally the semaphore tracks active tokens in a {@link Map} and pending
 * requests in an array. The {@link request} method returns a {@link Promise}
 * that resolves to a release function. The release function removes the
 * token from the active set and potentially starts the next pending request.
 * <p>
 * The semaphore is thread‑safe with respect to the event loop, as all state
 * mutations occur synchronously within the call stack of the {@link request}
 * and {@link release} methods.
 *
 * @param {number} [max=1] - The maximum number of concurrent operations allowed.
 */
export class CoreSemaphor {
  private curQueue: Map<string, SemElement>;
 
  private readonly max: number;
 
  private callbackQueue: SemElement[];
 
  constructor(max = 1) {
    this.curQueue = new Map();
    this.callbackQueue = [];
    this.max = max;
    this.release = this.release.bind(this);
  }
 
  hasFree() {
    return this.curQueue.size < this.max;
  }
 
  async request(): Promise<() => void> {
    const uuid = crypto.randomUUID();
    return new Promise((resolve) => {
      this.callbackQueue.push([uuid, resolve]);
      this.startNext();
    });
  }
 
  private startNext() {
    if (this.hasFree()) {
      const el = this.callbackQueue.shift();
      if (el) {
        const [uuid, resolve] = el;
        this.curQueue.set(uuid, el);
        resolve(() => {
          this.release(uuid);
        });
        this.startNext();
      }
    }
  }
 
  private release(uuid: string) {
    Eif (this.curQueue.has(uuid)) {
      this.curQueue.delete(uuid);
    }
    this.startNext();
  }
}