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 scheduled = false;
38 if (running) return;
39 running = true;
40
41 while (runQueue.length > 0) {
42 const g = runQueue.shift();
43 if (g.done) continue;
44
45 try {
46 const result = g.fn();
47 if (result instanceof Promise) {
48 result.then(
49 () => { g.done = true; },
50 (e) => {
51 g.done = true;
52 g.error = e;
53 if (typeof e === 'object' && e !== null && e.name === 'GoPanic') {
54 console.error(`goroutine ${g.id}: panic: ${e.message}`);
55 } else {
56 console.error(`goroutine ${g.id}: unhandled error:`, e);
57 }
58 if (typeof process !== 'undefined' && process.exit) process.exit(2);
59 }
60 );
61 } else {
62 g.done = true;
63 }
64 } catch (e) {
65 g.done = true;
66 g.error = e;
67 if (typeof e === 'object' && e !== null && e.name === 'GoPanic') {
68 console.error(`goroutine ${g.id}: panic: ${e.message}`);
69 } else {
70 console.error(`goroutine ${g.id}: unhandled error:`, e);
71 }
72 if (typeof process !== 'undefined' && process.exit) process.exit(2);
73 }
74 }
75
76 running = false;
77 }
78
79 // Yield current goroutine to let others run.
80 export function gosched() {
81 return new Promise(resolve => {
82 queueMicrotask(resolve);
83 });
84 }
85
86 // Sleep for a duration (nanoseconds, but we convert to ms).
87 export function sleep(ns) {
88 const ms = Math.max(0, ns / 1_000_000);
89 return new Promise(resolve => setTimeout(resolve, ms));
90 }
91
92 // Run the main function. Goroutines continue asynchronously.
93 export async function runMain(mainFn) {
94 try {
95 const result = mainFn();
96 if (result instanceof Promise) await result;
97 } catch (e) {
98 if (e && e.name === 'GoPanic') {
99 console.error(`panic: ${e.message}`);
100 console.error(e.stack);
101 if (typeof process !== 'undefined' && process.exit) process.exit(2);
102 }
103 throw e;
104 }
105
106 if (runQueue.length > 0) {
107 scheduleRun();
108 }
109 }
110