goroutine.mjs raw

   1  // TinyJS Runtime — Goroutine Scheduler
   2  // Cooperative async/await scheduler with microtask queue.
   3  
   4  const runQueue = [];
   5  let running = false;
   6  let goroutineId = 0;
   7  
   8  class Goroutine {
   9    constructor(id, fn) {
  10      this.id = id;
  11      this.fn = fn;
  12      this.done = false;
  13      this.error = null;
  14    }
  15  }
  16  
  17  // Spawn a new goroutine.
  18  export function spawn(fn) {
  19    const id = ++goroutineId;
  20    const g = new Goroutine(id, fn);
  21    runQueue.push(g);
  22    scheduleRun();
  23    return g;
  24  }
  25  
  26  let scheduled = false;
  27  
  28  function scheduleRun() {
  29    if (!scheduled) {
  30      scheduled = true;
  31      // Use queueMicrotask for tight scheduling, setTimeout(0) for fairness.
  32      queueMicrotask(tick);
  33    }
  34  }
  35  
  36  async function tick() {
  37    if (running) { scheduled = false; return; }
  38    running = true;
  39  
  40    while (runQueue.length > 0) {
  41      const g = runQueue.shift();
  42      if (g.done) continue;
  43  
  44      try {
  45        const result = g.fn();
  46        if (result instanceof Promise) {
  47          await result;
  48        }
  49        g.done = true;
  50      } catch (e) {
  51        g.done = true;
  52        g.error = e;
  53        // Unhandled panic in goroutine — print and exit like Go does.
  54        if (typeof e === 'object' && e !== null && e.name === 'GoPanic') {
  55          console.error(`goroutine ${g.id}: panic: ${e.message}`);
  56        } else {
  57          console.error(`goroutine ${g.id}: unhandled error:`, e);
  58        }
  59        throw e;
  60      }
  61    }
  62  
  63    running = false;
  64    scheduled = false;
  65  }
  66  
  67  // Yield current goroutine to let others run.
  68  export function gosched() {
  69    return new Promise(resolve => {
  70      queueMicrotask(resolve);
  71    });
  72  }
  73  
  74  // Sleep for a duration (nanoseconds, but we convert to ms).
  75  // ns may be BigInt (int64) or Number.
  76  export function sleep(ns) {
  77    const ms = Math.max(0, Number(ns) / 1_000_000);
  78    return new Promise(resolve => setTimeout(resolve, ms));
  79  }
  80  
  81  // Run the main function and wait for all goroutines.
  82  export async function runMain(mainFn) {
  83    try {
  84      const result = mainFn();
  85      if (result instanceof Promise) await result;
  86    } catch (e) {
  87      if (e && e.name === 'GoPanic') {
  88        console.error(`panic: ${e.message}`);
  89        console.error(e.stack);
  90      }
  91      throw e;
  92    }
  93  
  94    // Drain remaining goroutines.
  95    while (runQueue.length > 0) {
  96      await tick();
  97      // Small yield to let any newly-spawned goroutines enqueue.
  98      await new Promise(r => setTimeout(r, 0));
  99    }
 100  }
 101