From d84c15d3ee54b0b275b9e8903afcbdd14cacabf9 Mon Sep 17 00:00:00 2001 From: Derek Schuff Date: Tue, 31 Mar 2026 16:47:42 +0000 Subject: [PATCH 1/4] Signal before unlocking proxy context mutex in emscripten_proxy_finish When finishing a proxied call, the following race condition can happen: thread 1 in emscripten_proxy_finish: pthread_mutex_lock(&ctx->sync.mutex); ctx->sync.state = DONE; remove_active_ctx(ctx); pthread_mutex_unlock(&ctx->sync.mutex); --> thread is preempted or suspends here <--- pthread_cond_signal(&ctx->sync.cond); thread 2 in emscripten_proxy_sync_with_ctx: (ctx is on this thread's stack) pthread_mutex_lock(&ctx.sync.mutex); <-- locks after unlock above while (ctx.sync.state == PENDING) { <--- reads sync.state == DONE pthread_cond_wait(&ctx.sync.cond, &ctx.sync.mutex); <-- doesn't run } pthread_mutex_unlock(&ctx.sync.mutex); int ret = ctx.sync.state == DONE; em_proxying_ctx_deinit(&ctx); <--- frees ctx and returns Then thread 1 tries to run pthread_cond_signal on the freed ctx. This may be what is causing flake in the pselect/ppoll tests on the CI waterfall. --- system/lib/pthread/proxying.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/lib/pthread/proxying.c b/system/lib/pthread/proxying.c index f18c4d156bad7..cc3cf52f7529e 100644 --- a/system/lib/pthread/proxying.c +++ b/system/lib/pthread/proxying.c @@ -339,8 +339,8 @@ void emscripten_proxy_finish(em_proxying_ctx* ctx) { pthread_mutex_lock(&ctx->sync.mutex); ctx->sync.state = DONE; remove_active_ctx(ctx); - pthread_mutex_unlock(&ctx->sync.mutex); pthread_cond_signal(&ctx->sync.cond); + pthread_mutex_unlock(&ctx->sync.mutex); } else { // Schedule the callback on the caller thread. If the caller thread has // already died or dies before the callback is executed, then at least make From c9b54a9a50a931d839544c919132616d2cbe2fb6 Mon Sep 17 00:00:00 2001 From: Derek Schuff Date: Tue, 31 Mar 2026 18:14:17 +0000 Subject: [PATCH 2/4] also use this order in cancel_ctx --- system/lib/pthread/proxying.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/lib/pthread/proxying.c b/system/lib/pthread/proxying.c index cc3cf52f7529e..62101df2b80eb 100644 --- a/system/lib/pthread/proxying.c +++ b/system/lib/pthread/proxying.c @@ -365,8 +365,8 @@ static void cancel_ctx(void* arg) { if (ctx->kind == SYNC) { pthread_mutex_lock(&ctx->sync.mutex); ctx->sync.state = CANCELED; - pthread_mutex_unlock(&ctx->sync.mutex); pthread_cond_signal(&ctx->sync.cond); + pthread_mutex_unlock(&ctx->sync.mutex); } else { if (ctx->cb.cancel == NULL || !do_proxy(ctx->cb.queue, From c9847a0bf2b2f67a4ee2cd87604425df2779ebad Mon Sep 17 00:00:00 2001 From: Derek Schuff Date: Wed, 1 Apr 2026 00:14:20 +0000 Subject: [PATCH 3/4] Add comment --- system/lib/pthread/proxying.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/system/lib/pthread/proxying.c b/system/lib/pthread/proxying.c index 62101df2b80eb..7625eba8ab3a6 100644 --- a/system/lib/pthread/proxying.c +++ b/system/lib/pthread/proxying.c @@ -339,6 +339,9 @@ void emscripten_proxy_finish(em_proxying_ctx* ctx) { pthread_mutex_lock(&ctx->sync.mutex); ctx->sync.state = DONE; remove_active_ctx(ctx); + // Signal must come before unlock to avoid emscripten_proxy_sync_with ctx + // seeing the state as DONE and freeing the ctx before we call unlock. + // See https://github.com/emscripten-core/emscripten/pull/26582 pthread_cond_signal(&ctx->sync.cond); pthread_mutex_unlock(&ctx->sync.mutex); } else { @@ -365,6 +368,7 @@ static void cancel_ctx(void* arg) { if (ctx->kind == SYNC) { pthread_mutex_lock(&ctx->sync.mutex); ctx->sync.state = CANCELED; + // Signal must be first, see comment in emscripten_proxy_finish. pthread_cond_signal(&ctx->sync.cond); pthread_mutex_unlock(&ctx->sync.mutex); } else { From 6235cab6cb285a651820c369a9b6a8f462d41679 Mon Sep 17 00:00:00 2001 From: Derek Schuff Date: Wed, 1 Apr 2026 00:16:42 +0000 Subject: [PATCH 4/4] Automatic rebaseline of codesize expectations. NFC This is an automatic change generated by tools/maint/rebaseline_tests.py. The following (2) test expectation files were updated by running the tests with `--rebaseline`: ``` codesize/test_codesize_minimal_pthreads.json: 26409 => 26409 [+0 bytes / +0.00%] codesize/test_codesize_minimal_pthreads_memgrowth.json: 26812 => 26812 [+0 bytes / +0.00%] Average change: +0.00% (+0.00% - +0.00%) ``` --- test/codesize/test_codesize_minimal_pthreads.json | 4 ++-- test/codesize/test_codesize_minimal_pthreads_memgrowth.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test/codesize/test_codesize_minimal_pthreads.json b/test/codesize/test_codesize_minimal_pthreads.json index 75383a2ac05a7..0357f3034a021 100644 --- a/test/codesize/test_codesize_minimal_pthreads.json +++ b/test/codesize/test_codesize_minimal_pthreads.json @@ -2,9 +2,9 @@ "a.out.js": 7363, "a.out.js.gz": 3604, "a.out.nodebug.wasm": 19046, - "a.out.nodebug.wasm.gz": 8822, + "a.out.nodebug.wasm.gz": 8821, "total": 26409, - "total_gz": 12426, + "total_gz": 12425, "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 4d941efd39106..cc47a8f9585d0 100644 --- a/test/codesize/test_codesize_minimal_pthreads_memgrowth.json +++ b/test/codesize/test_codesize_minimal_pthreads_memgrowth.json @@ -2,9 +2,9 @@ "a.out.js": 7765, "a.out.js.gz": 3810, "a.out.nodebug.wasm": 19047, - "a.out.nodebug.wasm.gz": 8823, + "a.out.nodebug.wasm.gz": 8822, "total": 26812, - "total_gz": 12633, + "total_gz": 12632, "sent": [ "a (memory)", "b (exit)",