All files sema.js

92.5% Statements 37/40
77.27% Branches 17/22
100% Functions 8/8
100% Lines 33/33

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 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103              67x 64x 64x 64x               12x 12x 12x                 4x 4x 4x     1x 1x   4x 4x 4x 4x   4x                 5x 5x 5x 5x 5x 5x 5x   5x               64x                   10x 8x 8x 8x 8x               7x               1x          
class Sema {
  /**
   * Create a new Semaphore object
   * @constructor
   * @param {number} [init=1] - The initial value of the semaphore
   */
  constructor(init = 1) {
    if (typeof init !== 'number' || init < 0 || init >= 2 ** 31) throw new SyntaxError('Invalid initial value');
    this.buffer = new SharedArrayBuffer(4);
    this.sema = new Int32Array(this.buffer);
    this.sema[0] = init;
  }
 
  /**
   * Increases the counter of the semaphore; V operation
   * @returns {boolean} true
   */
  up() {
    Atomics.add(this.sema, 0, 1);
    Atomics.notify(this.sema, 0, 1);
    return true;
  }
 
  /**
   * Asynchronously decreases the counter of the semaphore
   * & waits if not possible; P operation
   * @returns {Promise<boolean>} A promise that resolves to true
   */
  async down() {
    for (;;) {
      let val = Atomics.load(this.sema, 0);
      if (val === 0) {
        const {
          value
        } = Atomics.waitAsync(this.sema, 0, 0);
        await value;
      }
      val = Atomics.load(this.sema, 0);
      Iif (val === 0) continue;
      const old = Atomics.compareExchange(this.sema, 0, val, val - 1);
      Eif (old === val) break;
    }
    return true;
  }
 
  /**
   * Synchronously decreases the counter of the semaphore
   * & waits if not possible; P operation
   * @returns {boolean} true
   */
  downSync() {
    for (;;) {
      let val = Atomics.load(this.sema, 0);
      Iif (val === 0) Atomics.wait(this.sema, 0, 0);
      val = Atomics.load(this.sema, 0);
      Iif (val === 0) continue;
      const old = Atomics.compareExchange(this.sema, 0, val, val - 1);
      Eif (old === val) break;
    }
    return true;
  }
 
  /**
   * Internal value of the semaphore
   * @returns {number} the current counter of the semaphore
   */
  value() {
    return Atomics.load(this.sema, 0);
  }
 
  /**
   * Creates a shared clone of the given semaphore
   * Usually used within a thread after passing the structuredCloned semaphore
   * @param {Sema} sema - Semaphore object
   * @returns {Sema} shared clone version of the given semaphore
   */
  static from({ buffer }) {
    if (!(buffer instanceof SharedArrayBuffer) || buffer.byteLength !== 4) throw new SyntaxError('Invalid semaphore');
    const sema = new Sema();
    sema.buffer = buffer;
    sema.sema = new Int32Array(sema.buffer);
    return sema;
  }
 
  /**
   * Creates a shared clone of the current semaphore
   * @returns {Sema} shared clone version of the current semaphore
   */
  clone() {
    return Sema.from({ buffer: this.buffer });
  }
 
  /**
   * Stringified semaphore
   * @returns {string} string representation of a semaphore
   */
  toString() {
    return `Sema(${this.sema[0]})`;
  }
}
 
export default Sema;