All files lock.js

94.64% Statements 53/56
91.17% Branches 31/34
100% Functions 10/10
100% Lines 45/45

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 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127              79x 76x 76x 76x 76x 76x 76x                 11x 11x 9x 8x 8x 8x 8x 8x                 10x 10x 10x 9x 9x 9x 9x 9x 9x                   14x 14x 14x 13x 13x 13x 13x 13x                     11x 9x 6x 6x 6x 6x 6x 6x               3x               62x               10x               60x               2x          
class Lock {
  /**
   * Create a new Lock object
   * @constructor
   * @param {number} [tid=0] - ID of the current thread
   */
  constructor(tid = 0) {
    if (typeof tid !== 'number' || tid < 0 || tid >= 2 ** 31) throw new SyntaxError('Invalid tid');
    this.buffer = new SharedArrayBuffer(12);
    this.lock = new Int32Array(this.buffer);
    this.lock[0] = 1;
    this.lock[1] = -1;
    this.lock[2] = tid;
    this.internalTid = tid;
  }
 
  /**
   * Releases the lock
   * Returns false if the current tid does not hold the lock
   * @returns {boolean}
   */
  release() {
    const holder = Atomics.load(this.lock, 1);
    if (holder === -1) return false;
    if (holder !== this.internalTid) return false;
    Atomics.store(this.lock, 1, -1);
    const old = Atomics.compareExchange(this.lock, 0, 0, 1);
    Iif (old === 1) return false;
    Atomics.notify(this.lock, 0, 1);
    return true;
  }
 
  /**
   * Asynchronously acquires the lock & waits if not possible
   * Returns false if the lock is already held by the tid
   * @returns {boolean}
   */
  async acquire() {
    for (;;) {
      const holder = Atomics.load(this.lock, 1);
      if (holder === this.internalTid) return false;
      const { value } = Atomics.wait(this.lock, 0, 0);
      await value;
      const old = Atomics.compareExchange(this.lock, 0, 1, 0);
      Iif (old === 0) continue;
      Atomics.store(this.lock, 1, this.internalTid);
      return true;
    }
  }
 
  /**
   * Synchronously acquires the lock & waits if not possible
   * Returns false if the lock is already held by the tid
   * @returns {boolean}
   */
  acquireSync() {
    for (;;) {
      const holder = Atomics.load(this.lock, 1);
      if (holder === this.internalTid) return false;
      Atomics.wait(this.lock, 0, 0);
      const old = Atomics.compareExchange(this.lock, 0, 1, 0);
      Iif (old === 0) continue;
      Atomics.store(this.lock, 1, this.internalTid);
      return true;
    }
  }
 
  /**
   * Creates a shared clone of the given lock
   * @param {Lock} lock - Lock object to clone from
   * @param {number} [tid=] - tid to assign to the lock; a new tid is generated if left blank
   * @returns {Lock} shared clone version of the given lock
   */
  static from({ buffer }, tid) {
    if (!(buffer instanceof SharedArrayBuffer) || buffer.byteLength !== 12) throw new SyntaxError('Invalid Lock object');
    if (arguments.length !== 1 && (typeof tid !== 'number' || tid < 0 || tid >= 2 ** 31)) throw new SyntaxError('Invalid tid');
    const lock = new Lock();
    lock.buffer = buffer;
    lock.lock = new Int32Array(lock.buffer);
    if (arguments.length === 1) tid = Atomics.add(lock.lock, 2, 1) + 1;
    lock.internalTid = tid;
    return lock;
  }
 
  /**
   * Creates a shared clone of the current lock
   * @returns {Lock} shared clone version of the given lock
   */
  clone() {
    return Lock.from(this, this.internalTid);
  }
 
  /**
   * The current holder of the lock
   * @returns {number} tid
   */
  holder() {
    return Atomics.load(this.lock, 1);
  }
 
  /**
   * The tid of the lock
   * @returns {number} tid
   */
  tid() {
    return this.internalTid;
  }
 
  /**
   * Returns true if the lock is locked
   * @returns {boolean}
   */
  isLocked() {
    return Atomics.load(this.lock, 0) === 0;
  }
 
  /**
   * Stringified lock
   * @returns {string} string representation of a lock
   */
  toString() {
    return `[${this.lock[0] === 1 ? 'unlocked' : `LOCKED (${this.lock[1]})`}] Lock`;
  }
}
 
export default Lock;