Skip to content

Commit 56217db

Browse files
committed
[threads][test][js-api] Add worker-based tests for Wait/Notify
Extend JS API tests for Atomics.wait and Atomics.notify with worker-based scenarios. These tests verify that workers can be correctly woken up by notify calls on shared WebAssembly.Memory. Specifically: - Add worker-path-helper.js to handle worker script path resolution in different environments (WPT vs JS shells). - Add wait-notify-worker.js to execute the wait operation in a background thread. - Add test cases to wait-notify-shared.any.js covering single waiter, sequential multiple waiters, and simultaneous multiple waiters.
1 parent 17ca624 commit 56217db

3 files changed

Lines changed: 174 additions & 0 deletions

File tree

test/js-api/memory/wait-notify-shared.any.js

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// META: global=window,dedicatedworker,jsshell
22
// META: script=/wasm/jsapi/wasm-module-builder.js
3+
// META: script=/wasm/jsapi/memory/worker-path-helper.js
34

45
function createModule() {
56
const builder = new WasmModuleBuilder();
@@ -59,3 +60,132 @@ test(() => {
5960
assert_equals(
6061
result, 0, 'Notify should return 0 (number of waiters notified)');
6162
}, 'Notify on shared memory (0 waiters)');
63+
64+
function waitForWorker(worker) {
65+
const msg = worker.getMessage();
66+
if (msg.type === 'error') {
67+
throw new Error('Worker error: ' + msg.message);
68+
}
69+
return msg.value;
70+
}
71+
72+
function assert_within_timeout(start, seconds, message) {
73+
if (Date.now() - start > seconds * 1000) {
74+
throw new Error(message);
75+
}
76+
}
77+
78+
// Async tests using workers.
79+
if (typeof Worker !== 'undefined') {
80+
test(() => {
81+
const memory =
82+
new WebAssembly.Memory({initial: 1, maximum: 1, shared: true});
83+
const view = new Int32Array(memory.buffer);
84+
const worker = new Worker(getWorkerPath('wait-notify-worker.js'));
85+
86+
view[0] = 0;
87+
view[1] = 0; // ready index
88+
89+
worker.postMessage({
90+
module: module,
91+
memory: memory,
92+
address: 0,
93+
expected: 0,
94+
timeout: -1n,
95+
readyIndex: 1
96+
});
97+
98+
while (Atomics.load(view, 1) === 0);
99+
100+
const instance = createInstance(module, memory);
101+
let notifyResult;
102+
const start = Date.now();
103+
while ((notifyResult = instance.exports.notify(0, 1)) === 0) {
104+
assert_within_timeout(
105+
start, 30, 'Worker should wake up within 30 seconds');
106+
}
107+
108+
assert_equals(notifyResult, 1, 'Notify should wake up 1 waiter');
109+
const waitResult = waitForWorker(worker);
110+
assert_equals(waitResult, 0, 'Wait32 should return 0 (ok) when woken up');
111+
worker.terminate();
112+
}, 'Wait32 and Notify wake up 1 waiter');
113+
114+
test(() => {
115+
const memory =
116+
new WebAssembly.Memory({initial: 1, maximum: 1, shared: true});
117+
const view = new Int32Array(memory.buffer);
118+
const worker1 = new Worker(getWorkerPath('wait-notify-worker.js'));
119+
const worker2 = new Worker(getWorkerPath('wait-notify-worker.js'));
120+
121+
view[0] = 0; // address 0
122+
view[1] = 0; // address 4
123+
view[2] = 0; // ready index 1
124+
view[3] = 0; // ready index 2
125+
126+
const msg = {module: module, memory: memory, timeout: -1n, expected: 0};
127+
worker1.postMessage({...msg, address: 0, readyIndex: 2});
128+
worker2.postMessage({...msg, address: 4, readyIndex: 3});
129+
130+
while (Atomics.load(view, 2) === 0);
131+
while (Atomics.load(view, 3) === 0);
132+
133+
const instance = createInstance(module, memory);
134+
135+
let notified1;
136+
let start = Date.now();
137+
while ((notified1 = instance.exports.notify(0, 1)) === 0) {
138+
assert_within_timeout(
139+
start, 30, 'Worker 1 should wake up within 30 seconds');
140+
}
141+
assert_equals(notified1, 1, 'Notify 1');
142+
assert_equals(waitForWorker(worker1), 0);
143+
144+
let notified2;
145+
start = Date.now();
146+
while ((notified2 = instance.exports.notify(4, 1)) === 0) {
147+
assert_within_timeout(
148+
start, 30, 'Worker 2 should wake up within 30 seconds');
149+
}
150+
assert_equals(notified2, 1, 'Notify 2');
151+
assert_equals(waitForWorker(worker2), 0);
152+
153+
worker1.terminate();
154+
worker2.terminate();
155+
}, 'Two waiters on different addresses woken up one after the other');
156+
157+
test(() => {
158+
const memory =
159+
new WebAssembly.Memory({initial: 1, maximum: 1, shared: true});
160+
const view = new Int32Array(memory.buffer);
161+
const worker1 = new Worker(getWorkerPath('wait-notify-worker.js'));
162+
const worker2 = new Worker(getWorkerPath('wait-notify-worker.js'));
163+
164+
view[0] = 0; // address 0
165+
view[2] = 0; // ready index 1
166+
view[3] = 0; // ready index 2
167+
168+
const msg = {module: module, memory: memory, timeout: -1n, expected: 0};
169+
worker1.postMessage({...msg, address: 0, readyIndex: 2});
170+
worker2.postMessage({...msg, address: 0, readyIndex: 3});
171+
172+
while (Atomics.load(view, 2) === 0);
173+
while (Atomics.load(view, 3) === 0);
174+
175+
const instance = createInstance(module, memory);
176+
177+
let notified = 0;
178+
const start = Date.now();
179+
while ((notified += instance.exports.notify(0, 2 - notified)) < 2) {
180+
assert_within_timeout(
181+
start, 30, 'Both workers should wake up within 30 seconds');
182+
}
183+
assert_equals(notified, 2, 'Notify 2 at once');
184+
185+
assert_equals(waitForWorker(worker1), 0);
186+
assert_equals(waitForWorker(worker2), 0);
187+
188+
worker1.terminate();
189+
worker2.terminate();
190+
}, 'Two waiters on same address woken up at once');
191+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
onmessage = function(event) {
2+
try {
3+
const {module, memory, address, expected, timeout, readyIndex} = event.data;
4+
const instance = new WebAssembly.Instance(module, {env: {memory: memory}});
5+
const view = new Int32Array(memory.buffer);
6+
7+
// Signal readiness.
8+
Atomics.store(view, readyIndex, 1);
9+
10+
// Wait.
11+
const result = instance.exports.wait(address, expected, timeout);
12+
13+
postMessage({type: 'result', value: result});
14+
} catch (e) {
15+
postMessage({type: 'error', message: e.toString()});
16+
}
17+
};
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/**
2+
* Resolves the worker script path.
3+
* In browsers (WPT), it uses a path relative to the current script URL.
4+
* In JS shells, it attempts to build a path relative to the script's location
5+
* provided in the command-line arguments, with a fallback for this repo.
6+
*/
7+
function getWorkerPath(scriptName) {
8+
// 1. Browser/WPT detection.
9+
if (typeof location !== 'undefined') {
10+
return scriptName;
11+
}
12+
13+
// 2. JS shell detection (using the script path from command-line arguments).
14+
try {
15+
if (typeof arguments !== 'undefined' && arguments.length > 0) {
16+
const lastArg = arguments[arguments.length - 1];
17+
if (lastArg.includes('/')) {
18+
return lastArg.substring(0, lastArg.lastIndexOf('/') + 1) + scriptName;
19+
}
20+
}
21+
} catch (e) {
22+
// Fallback if arguments is not available or mapping fails.
23+
}
24+
25+
// 3. Fallback for the current repo root.
26+
return 'test/js-api/memory/' + scriptName;
27+
}

0 commit comments

Comments
 (0)