From 58a0ee789780b1f4cabdcefc7ad6a43b53145cb8 Mon Sep 17 00:00:00 2001 From: Sam Clegg Date: Mon, 13 Jul 2026 14:58:56 -0700 Subject: [PATCH] [pthreads] Fix relaying of mailbox notifications to main thread In #27018, `d.targetThread && d.targetThread != _pthread_self()` in `worker.onmessage` on the main thread was changed to only check `d.targetThread` before relaying or asserting. However, when `waitAsyncPolyfilled` is active or `Atomics.waitAsync` is unsupported (e.g., Firefox before 145), `thread->waiting_async` is not set when `_emscripten_thread_mailbox_await` runs. In this fallback mode, `emscripten_thread_mailbox_send` calls `_emscripten_notify_mailbox_postmessage`, which sends a `postMessage` from the worker with `targetThread` set to the main thread. When `worker.onmessage` on the main thread receives this message, `d.targetThread == _pthread_self()`. Without checking `d.targetThread != _pthread_self()`, the main thread either triggers an assertion failure (`assert(d.targetThread != _pthread_self())`) or drops the message when looking up `PThread.pthreads[targetThread]`. Restoring `d.targetThread != _pthread_self()` ensures that mailbox notifications addressed to the main thread fall through and are processed directly (`checkMailbox()`) instead of being forwarded. Also add a browser test (`browser.test_pthread_no_waitasync`) that disables `Atomics.waitAsync` in a `--pre-js` script to verify this fallback behavior. Fixes: #27037 --- src/lib/libpthread.js | 6 +----- test/codesize/test_codesize_minimal_pthreads.json | 8 ++++---- .../test_codesize_minimal_pthreads_memgrowth.json | 8 ++++---- test/test_browser.py | 6 ++++++ 4 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/lib/libpthread.js b/src/lib/libpthread.js index 6aeb5c409b185..d7007c7f1987e 100644 --- a/src/lib/libpthread.js +++ b/src/lib/libpthread.js @@ -286,11 +286,7 @@ var LibraryPThread = { // If this message is intended to a recipient that is not the main // thread, forward it to the target thread. This is currently only // used by `CMD_CHECK_MAILBOX`. - if (d.targetThread) { -#if ASSERTIONS - // pthreads should not be relaying messages to themselves. - assert(d.targetThread != _pthread_self()); -#endif + if (d.targetThread && d.targetThread != _pthread_self()) { var targetWorker = PThread.pthreads[d.targetThread]; #if ASSERTIONS if (!targetWorker) err(`worker sent message (${cmd}) to pthread (${d.targetThread}) that no longer exists`); diff --git a/test/codesize/test_codesize_minimal_pthreads.json b/test/codesize/test_codesize_minimal_pthreads.json index 7394016e2d40f..ab8c2d4354a6e 100644 --- a/test/codesize/test_codesize_minimal_pthreads.json +++ b/test/codesize/test_codesize_minimal_pthreads.json @@ -1,10 +1,10 @@ { - "a.out.js": 6945, - "a.out.js.gz": 3424, + "a.out.js": 6955, + "a.out.js.gz": 3431, "a.out.nodebug.wasm": 19108, "a.out.nodebug.wasm.gz": 8824, - "total": 26053, - "total_gz": 12248, + "total": 26063, + "total_gz": 12255, "sent": [ "a (memory)", "b (exit)", diff --git a/test/codesize/test_codesize_minimal_pthreads_memgrowth.json b/test/codesize/test_codesize_minimal_pthreads_memgrowth.json index 6b72597e5813f..56a3d321836d4 100644 --- a/test/codesize/test_codesize_minimal_pthreads_memgrowth.json +++ b/test/codesize/test_codesize_minimal_pthreads_memgrowth.json @@ -1,10 +1,10 @@ { - "a.out.js": 7379, - "a.out.js.gz": 3639, + "a.out.js": 7389, + "a.out.js.gz": 3647, "a.out.nodebug.wasm": 19109, "a.out.nodebug.wasm.gz": 8826, - "total": 26488, - "total_gz": 12465, + "total": 26498, + "total_gz": 12473, "sent": [ "a (memory)", "b (exit)", diff --git a/test/test_browser.py b/test/test_browser.py index aafa29e9dcb5b..f2b70ee312b66 100644 --- a/test/test_browser.py +++ b/test/test_browser.py @@ -3954,6 +3954,12 @@ def test_gauge_available_memory(self, args): def test_pthread_run_on_main_thread(self): self.btest_exit('pthread/test_pthread_run_on_main_thread.c', cflags=['-O3', '-pthread', '-sPTHREAD_POOL_SIZE']) + # Test that proxying operations work when Atomics.waitAsync is disabled, + # forcing the waitAsyncPolyfilled/postMessage fallback path. + def test_pthread_no_waitasync(self): + create_file('pre.js', 'delete Atomics.waitAsync;\n') + self.btest_exit('pthread/test_pthread_proxy_to_pthread.c', cflags=['-O3', '-pthread', '-sPROXY_TO_PTHREAD', '-sASSERTIONS', '--pre-js=pre.js']) + # Test how a lot of back-to-back called proxying operations behave. def test_pthread_run_on_main_thread_flood(self): self.btest_exit('pthread/test_pthread_run_on_main_thread_flood.c', cflags=['-O3', '-pthread', '-sPTHREAD_POOL_SIZE'])