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