sync.mjs raw

   1  // TinyJS Runtime — Sync Primitives
   2  // WaitGroup, Mutex, Once, RWMutex.
   3  
   4  export class WaitGroup {
   5    constructor() {
   6      this.counter = 0;
   7      this.waiters = [];
   8    }
   9  
  10    add(delta) {
  11      this.counter += delta;
  12      if (this.counter < 0) {
  13        throw new Error('sync: negative WaitGroup counter');
  14      }
  15      if (this.counter === 0) {
  16        const w = this.waiters.splice(0);
  17        for (const resolve of w) resolve();
  18      }
  19    }
  20  
  21    done() {
  22      this.add(-1);
  23    }
  24  
  25    async wait() {
  26      if (this.counter === 0) return;
  27      return new Promise(resolve => {
  28        this.waiters.push(resolve);
  29      });
  30    }
  31  }
  32  
  33  export class Mutex {
  34    constructor() {
  35      this.locked = false;
  36      this.waiters = [];
  37    }
  38  
  39    async lock() {
  40      if (!this.locked) {
  41        this.locked = true;
  42        return;
  43      }
  44      return new Promise(resolve => {
  45        this.waiters.push(resolve);
  46      });
  47    }
  48  
  49    unlock() {
  50      if (!this.locked) {
  51        throw new Error('sync: unlock of unlocked mutex');
  52      }
  53      if (this.waiters.length > 0) {
  54        const next = this.waiters.shift();
  55        queueMicrotask(next);
  56      } else {
  57        this.locked = false;
  58      }
  59    }
  60  }
  61  
  62  export class RWMutex {
  63    constructor() {
  64      this.readers = 0;
  65      this.writing = false;
  66      this.readWaiters = [];
  67      this.writeWaiters = [];
  68    }
  69  
  70    async rLock() {
  71      if (!this.writing && this.writeWaiters.length === 0) {
  72        this.readers++;
  73        return;
  74      }
  75      return new Promise(resolve => {
  76        this.readWaiters.push(resolve);
  77      });
  78    }
  79  
  80    rUnlock() {
  81      this.readers--;
  82      if (this.readers === 0 && this.writeWaiters.length > 0) {
  83        this.writing = true;
  84        const next = this.writeWaiters.shift();
  85        queueMicrotask(next);
  86      }
  87    }
  88  
  89    async lock() {
  90      if (!this.writing && this.readers === 0) {
  91        this.writing = true;
  92        return;
  93      }
  94      return new Promise(resolve => {
  95        this.writeWaiters.push(resolve);
  96      });
  97    }
  98  
  99    unlock() {
 100      if (!this.writing) {
 101        throw new Error('sync: unlock of unlocked RWMutex');
 102      }
 103      this.writing = false;
 104  
 105      // Wake waiting readers first (reader preference).
 106      if (this.readWaiters.length > 0) {
 107        const readers = this.readWaiters.splice(0);
 108        this.readers = readers.length;
 109        for (const resolve of readers) queueMicrotask(resolve);
 110      } else if (this.writeWaiters.length > 0) {
 111        this.writing = true;
 112        const next = this.writeWaiters.shift();
 113        queueMicrotask(next);
 114      }
 115    }
 116  }
 117  
 118  export class Once {
 119    constructor() {
 120      this.called = false;
 121      this.result = undefined;
 122    }
 123  
 124    do(fn) {
 125      if (this.called) return;
 126      this.called = true;
 127      this.result = fn();
 128    }
 129  }
 130