Offload functions to worker threads with shared memory primitives for Node.js.
1import { setTimeout } from 'node:timers/promises';
2import { describe, it } from 'node:test';
3import assert from 'node:assert/strict';
4import { rwlock } from 'moroutine';
5
6describe('RwLock', () => {
7 it('read lock and unlock', async () => {
8 const rw = rwlock();
9 const guard = await rw.readLock();
10 assert.equal(typeof guard[Symbol.dispose], 'function');
11 rw.readUnlock();
12 });
13
14 it('write lock and unlock', async () => {
15 const rw = rwlock();
16 const guard = await rw.writeLock();
17 assert.equal(typeof guard[Symbol.dispose], 'function');
18 rw.writeUnlock();
19 });
20
21 it('multiple readers can hold the lock concurrently', async () => {
22 const rw = rwlock();
23 const g1 = await rw.readLock();
24 const g2 = await rw.readLock();
25 // Both acquired — success
26 rw.readUnlock();
27 rw.readUnlock();
28 });
29
30 it('writer excludes readers', async () => {
31 const rw = rwlock();
32 const order: string[] = [];
33
34 await rw.writeLock();
35 order.push('write-start');
36
37 const readerDone = (async () => {
38 const g = await rw.readLock();
39 order.push('read');
40 rw.readUnlock();
41 })();
42
43 await setTimeout(20);
44 order.push('write-end');
45 rw.writeUnlock();
46
47 await readerDone;
48 assert.deepEqual(order, ['write-start', 'write-end', 'read']);
49 });
50
51 it('writer excludes other writers', async () => {
52 const rw = rwlock();
53 const order: string[] = [];
54
55 await rw.writeLock();
56 order.push('w1-start');
57
58 const writer2Done = (async () => {
59 const g = await rw.writeLock();
60 order.push('w2');
61 rw.writeUnlock();
62 })();
63
64 await setTimeout(20);
65 order.push('w1-end');
66 rw.writeUnlock();
67
68 await writer2Done;
69 assert.deepEqual(order, ['w1-start', 'w1-end', 'w2']);
70 });
71
72 it('ReadGuard dispose calls readUnlock', async () => {
73 const rw = rwlock();
74 const guard = await rw.readLock();
75 guard[Symbol.dispose]();
76 const wg = await rw.writeLock();
77 rw.writeUnlock();
78 });
79
80 it('WriteGuard dispose calls writeUnlock', async () => {
81 const rw = rwlock();
82 const guard = await rw.writeLock();
83 guard[Symbol.dispose]();
84 const rg = await rw.readLock();
85 rw.readUnlock();
86 });
87
88 it('self-allocates', () => {
89 const rw = rwlock();
90 assert.ok(rw);
91 });
92
93 it('exposes byteSize of 4', () => {
94 assert.equal(rwlock.byteSize, 4);
95 });
96
97 it('wakes all waiting readers when writer unlocks', async () => {
98 const rw = rwlock();
99
100 await rw.writeLock();
101
102 const reader1Done = (async () => {
103 const g = await rw.readLock();
104 rw.readUnlock();
105 return 'r1';
106 })();
107
108 const reader2Done = (async () => {
109 const g = await rw.readLock();
110 rw.readUnlock();
111 return 'r2';
112 })();
113
114 // Let readers queue up
115 await setTimeout(20);
116 rw.writeUnlock();
117
118 const results = await Promise.all([reader1Done, reader2Done]);
119 assert.deepEqual(results.sort(), ['r1', 'r2']);
120 });
121});