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