From 227c45cb0b90cdc74e6868e708c38602835a878b Mon Sep 17 00:00:00 2001 From: Kleis Auke Wolthuizen Date: Sun, 10 May 2026 18:50:45 +0200 Subject: [PATCH 01/11] Add reproducer --- test/core/pthread/test_pthread_dlopen.c | 59 +++++++++++-------------- test/test_browser.py | 4 ++ test/test_core.py | 3 +- 3 files changed, 30 insertions(+), 36 deletions(-) diff --git a/test/core/pthread/test_pthread_dlopen.c b/test/core/pthread/test_pthread_dlopen.c index 564ac717042ba..be7ae30534486 100644 --- a/test/core/pthread/test_pthread_dlopen.c +++ b/test/core/pthread/test_pthread_dlopen.c @@ -7,6 +7,8 @@ #include #include +#define NUM_THREADS 8 + typedef int* (*sidey_data_type)(); typedef int (*func_t)(); typedef func_t (*sidey_func_type)(); @@ -16,18 +18,17 @@ static sidey_func_type p_side_func_address; static int* expected_data_addr; static func_t expected_func_addr; -static atomic_bool started = false; -static atomic_bool ready = false; +pthread_barrier_t started; static void* thread_main(void* arg) { - printf("in thread_main\n"); - started = true; + int id = (int)(intptr_t)arg; - while (!ready) { - printf("yielding ..\n"); - sched_yield(); - usleep(1000*100); - } + printf("thread %d: in thread_main\n", id); + + // Wait until all threads + main have reached the barrier + pthread_barrier_wait(&started); + + usleep(1000000); int* data_addr = p_side_data_address(); assert(data_addr == expected_data_addr); @@ -36,20 +37,24 @@ static void* thread_main(void* arg) { assert(expected_func_addr == func_addr); assert(func_addr() == 43); - printf("thread_main done\n"); + printf("thread %d: thread_main done\n", id); return 0; } int main() { printf("in main\n"); - // Start a thread before loading the shared library - pthread_t t; - int rc = pthread_create(&t, NULL, thread_main, NULL); - assert(rc == 0); + pthread_barrier_init(&started, NULL, NUM_THREADS + 1); + pthread_t threads[NUM_THREADS]; - // Spin until the thread has started - while (!started) {} + // Start threads before loading the shared library + for (int i = 0; i < NUM_THREADS; ++i) { + int rc = pthread_create(&threads[i], NULL, thread_main, (void*)(intptr_t)i); + assert(rc == 0); + + rc = pthread_detach(threads[i]); + assert(rc == 0); + } printf("loading dylib\n"); void* handle = dlopen("libside.so", RTLD_NOW|RTLD_GLOBAL); @@ -71,25 +76,11 @@ int main() { printf("p_side_func_address -> %p\n", expected_func_addr); assert(expected_func_addr() == 43); - ready = true; - - printf("joining\n"); - rc = pthread_join(t, NULL); - assert(rc == 0); - printf("done join\n"); - - printf("starting second & third thread\n"); - pthread_t t2, t3; - rc = pthread_create(&t2, NULL, thread_main, NULL); - assert(rc == 0); - rc = pthread_create(&t3, NULL, thread_main, NULL); - assert(rc == 0); - rc = pthread_join(t2, NULL); - assert(rc == 0); - rc = pthread_join(t3, NULL); - assert(rc == 0); - printf("starting second & third thread\n"); + // Wait until all threads execute their entry points + pthread_barrier_wait(&started); dlclose(handle); + + printf("done\n"); return 0; } diff --git a/test/test_browser.py b/test/test_browser.py index c3fe8de49d17a..fb0e879907292 100644 --- a/test/test_browser.py +++ b/test/test_browser.py @@ -3503,6 +3503,10 @@ def test_dlopen_blocking(self): # But with PROXY_TO_PTHEAD it does work, since we can do blocking and sync XHR in a worker. self.btest_exit('other/test_dlopen_blocking.c', cflags=['-sMAIN_MODULE=2', '-sPROXY_TO_PTHREAD', '-pthread', '-Wno-experimental', '-sAUTOLOAD_DYLIBS=0', 'libside.so']) + def test_pthread_dlopen(self): + self.emcc('core/pthread/test_pthread_dlopen_side.c', ['-o', 'libside.so', '-sSIDE_MODULE', '-pthread', '-Wno-experimental']) + self.btest_exit('core/pthread/test_pthread_dlopen.c', cflags=['-sMAIN_MODULE=2', '-sEXIT_RUNTIME', '-sPTHREAD_POOL_SIZE=8', '-pthread', '-Wno-experimental', 'libside.so']) + # verify that dynamic linking works in all kinds of in-browser environments. # don't mix different kinds in a single test. @parameterized({ diff --git a/test/test_core.py b/test/test_core.py index 1c437d8149c01..7165da6099f35 100644 --- a/test/test_core.py +++ b/test/test_core.py @@ -9309,9 +9309,8 @@ def test_pthread_dlopen(self): self.cflags += ['--embed-file', 'libside.so@libside.so'] self.prep_dlfcn_main() self.set_setting('EXIT_RUNTIME') - self.set_setting('PROXY_TO_PTHREAD') self.do_runf('core/pthread/test_pthread_dlopen.c', - ['side module ctor', 'done join', 'side module atexit'], + ['side module ctor', 'done', 'side module atexit'], assert_all=True) @needs_dylink From 31ea3277e7cdc29ec72bbe9b18800e3bf4d1ec26 Mon Sep 17 00:00:00 2001 From: Kleis Auke Wolthuizen Date: Mon, 11 May 2026 11:15:53 +0200 Subject: [PATCH 02/11] [dylink] Fix deadlock in `_emscripten_dlsync_threads()` This extends commit 662cb06 by ensuring `_emscripten_thread_notify()` is also called for the synchronous `_emscripten_dlsync_threads()` path. This ensures that threads process the dlopen catch-up queue promptly, even when blocked in `emscripten_futex_wait()`. To implement this cleanly, `dlopen_proxying_queue` is moved from a dynamic pointer in `dynlink.c` to a statically initialized queue in `proxying.c`. Fixes: #26913. --- system/lib/libc/dynlink.c | 27 +++++-------------- system/lib/pthread/emscripten_yield.c | 4 +-- system/lib/pthread/proxying.c | 36 ++++++++++++++++++++----- system/lib/pthread/threading_internal.h | 3 +++ 4 files changed, 42 insertions(+), 28 deletions(-) diff --git a/system/lib/libc/dynlink.c b/system/lib/libc/dynlink.c index e3b12483f3f2a..038d846c7dce8 100644 --- a/system/lib/libc/dynlink.c +++ b/system/lib/libc/dynlink.c @@ -356,19 +356,13 @@ static void thread_sync_done(void* arg) { free(info); } -// Proxying queue specially for handling code loading (dlopen) events. -// Initialized by the main thread on the first call to -// `_emscripten_proxy_dlsync` below, and processed by background threads -// that call `_emscripten_process_dlopen_queue` during futex_wait (i.e. whenever -// they block). -static em_proxying_queue * _Atomic dlopen_proxying_queue = NULL; static thread_local bool processing_queue = false; void _emscripten_process_dlopen_queue() { - if (dlopen_proxying_queue && !processing_queue) { + if (!processing_queue) { assert(!emscripten_is_main_runtime_thread()); processing_queue = true; - emscripten_proxy_execute_queue(dlopen_proxying_queue); + emscripten_proxy_execute_queue(_emscripten_proxy_dlopen_queue()); processing_queue = false; } } @@ -379,9 +373,6 @@ void _emscripten_process_dlopen_queue() { // manages the worker pool. int _emscripten_proxy_dlsync_async(pthread_t target_thread, em_promise_t promise) { assert(emscripten_is_main_runtime_thread()); - if (!dlopen_proxying_queue) { - dlopen_proxying_queue = em_proxying_queue_create(); - } struct promise_result* info = malloc(sizeof(struct promise_result)); if (!info) { @@ -391,7 +382,7 @@ int _emscripten_proxy_dlsync_async(pthread_t target_thread, em_promise_t promise .promise = promise, .result = false, }; - int rtn = emscripten_proxy_callback(dlopen_proxying_queue, + int rtn = emscripten_proxy_callback(_emscripten_proxy_dlopen_queue(), target_thread, do_thread_sync, thread_sync_done, @@ -403,21 +394,17 @@ int _emscripten_proxy_dlsync_async(pthread_t target_thread, em_promise_t promise emscripten_promise_resolve(promise, EM_PROMISE_FULFILL, NULL); emscripten_promise_destroy(promise); free(info); - } else { - // Wake up the target thread in case it's blocked in futex_wait - _emscripten_thread_notify(target_thread); } return rtn; } int _emscripten_proxy_dlsync(pthread_t target_thread) { assert(emscripten_is_main_runtime_thread()); - if (!dlopen_proxying_queue) { - dlopen_proxying_queue = em_proxying_queue_create(); - } int result; - if (!emscripten_proxy_sync( - dlopen_proxying_queue, target_thread, do_thread_sync_out, &result)) { + if (!emscripten_proxy_sync(_emscripten_proxy_dlopen_queue(), + target_thread, + do_thread_sync_out, + &result)) { return 0; } return result; diff --git a/system/lib/pthread/emscripten_yield.c b/system/lib/pthread/emscripten_yield.c index 6b85c56b199d1..f165b64727309 100644 --- a/system/lib/pthread/emscripten_yield.c +++ b/system/lib/pthread/emscripten_yield.c @@ -13,8 +13,8 @@ void _emscripten_thread_crashed() { // Notify the main thread that the calling thread has crashed. The will bring // down the whole program next time the main thread calls `_emscripten_yield`. crashed_thread_id = pthread_self(); - // Force the main runtime thread to wake up in case it is waiting in - // `_emscripten_thread_notify`. + // Force the main runtime thread to wake up in case it is blocked in + // `emscripten_futex_wait`. _emscripten_thread_notify(emscripten_main_runtime_thread_id()); } diff --git a/system/lib/pthread/proxying.c b/system/lib/pthread/proxying.c index 11aef2cdddb2b..5aa2f8e99dae6 100644 --- a/system/lib/pthread/proxying.c +++ b/system/lib/pthread/proxying.c @@ -41,6 +41,22 @@ em_proxying_queue* emscripten_proxy_get_system_queue(void) { return &system_proxying_queue; } +#ifdef EMSCRIPTEN_DYNAMIC_LINKING +// Proxying queue specially for handling code loading (dlopen) events. +// Processed by background threads that call `_emscripten_process_dlopen_queue` +// during futex_wait (i.e. whenever they block). +static em_proxying_queue dlopen_proxying_queue = { + .mutex = PTHREAD_MUTEX_INITIALIZER, + .task_queues = NULL, + .size = 0, + .capacity = 0, +}; + +em_proxying_queue* _emscripten_proxy_dlopen_queue(void) { + return &dlopen_proxying_queue; +} +#endif + em_proxying_queue* em_proxying_queue_create(void) { // Allocate the new queue. em_proxying_queue* q = malloc(sizeof(em_proxying_queue)); @@ -149,6 +165,9 @@ void emscripten_proxy_execute_queue(em_proxying_queue* q) { static bool do_proxy(em_proxying_queue* q, pthread_t target_thread, task t) { assert(q != NULL); pthread_mutex_lock(&q->mutex); +#ifdef EMSCRIPTEN_DYNAMIC_LINKING + bool is_dlopen_queue = q == &dlopen_proxying_queue; +#endif bool is_system_queue = q == &system_proxying_queue; if (is_system_queue) { system_queue_in_use = true; @@ -163,12 +182,17 @@ static bool do_proxy(em_proxying_queue* q, pthread_t target_thread, task t) { } bool ret = em_task_queue_send(tasks, t); - // When proxying work to the main thread using the system queue we have a - // special case in that we need to wake the target thread in case it is in - // `emscripten_futex_wait`. - if (ret && is_system_queue && - pthread_equal(target_thread, emscripten_main_runtime_thread_id())) { - DBG("waking main runtime thread using _emscripten_thread_notify"); + + // When proxying work to the dlopen or system queue we may have to wake the + // target thread in case it is blocked in `emscripten_futex_wait`. + bool needs_notify = +#ifdef EMSCRIPTEN_DYNAMIC_LINKING + is_dlopen_queue || +#endif + (is_system_queue && + pthread_equal(target_thread, emscripten_main_runtime_thread_id())); + if (ret && needs_notify) { + DBG("waking target thread using _emscripten_thread_notify"); _emscripten_thread_notify(target_thread); } return ret; diff --git a/system/lib/pthread/threading_internal.h b/system/lib/pthread/threading_internal.h index 91082a825d60d..0cf1fa0fd15d5 100644 --- a/system/lib/pthread/threading_internal.h +++ b/system/lib/pthread/threading_internal.h @@ -7,6 +7,8 @@ #pragma once +#include + #include #include #include @@ -82,6 +84,7 @@ int _emscripten_thread_is_valid(pthread_t thread); void _emscripten_thread_exit_joinable(pthread_t thread); void _emscripten_thread_exit(void* result); void _emscripten_process_dlopen_queue(void); +em_proxying_queue* _emscripten_proxy_dlopen_queue(void); #if !defined(__EMSCRIPTEN_PTHREADS__) || defined(NDEBUG) #define emscripten_set_current_thread_status(newStatus) From d7be9f431a14f10af339444f228593524bb01419 Mon Sep 17 00:00:00 2001 From: Kleis Auke Wolthuizen Date: Sat, 23 May 2026 20:41:04 +0000 Subject: [PATCH 03/11] 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: 26180 => 26179 [-1 bytes / -0.00%] codesize/test_codesize_minimal_pthreads_memgrowth.json: 26589 => 26588 [-1 bytes / -0.00%] Average change: -0.00% (-0.00% - -0.00%) ``` --- test/codesize/test_codesize_minimal_pthreads.json | 8 ++++---- .../test_codesize_minimal_pthreads_memgrowth.json | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/test/codesize/test_codesize_minimal_pthreads.json b/test/codesize/test_codesize_minimal_pthreads.json index 291a3a52fede4..d973a8f743253 100644 --- a/test/codesize/test_codesize_minimal_pthreads.json +++ b/test/codesize/test_codesize_minimal_pthreads.json @@ -1,10 +1,10 @@ { "a.out.js": 7143, "a.out.js.gz": 3542, - "a.out.nodebug.wasm": 19037, - "a.out.nodebug.wasm.gz": 8787, - "total": 26180, - "total_gz": 12329, + "a.out.nodebug.wasm": 19036, + "a.out.nodebug.wasm.gz": 8786, + "total": 26179, + "total_gz": 12328, "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 cca8bd65756ed..222acd0f61423 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": 7551, "a.out.js.gz": 3745, - "a.out.nodebug.wasm": 19038, - "a.out.nodebug.wasm.gz": 8788, - "total": 26589, - "total_gz": 12533, + "a.out.nodebug.wasm": 19037, + "a.out.nodebug.wasm.gz": 8787, + "total": 26588, + "total_gz": 12532, "sent": [ "a (memory)", "b (exit)", From 178727abd5e0da46d76c7fa453a5921349f11264 Mon Sep 17 00:00:00 2001 From: Kleis Auke Wolthuizen Date: Tue, 26 May 2026 10:56:01 +0200 Subject: [PATCH 04/11] Improve comment --- system/lib/pthread/proxying.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/system/lib/pthread/proxying.c b/system/lib/pthread/proxying.c index 5aa2f8e99dae6..8d8523c9bd4e9 100644 --- a/system/lib/pthread/proxying.c +++ b/system/lib/pthread/proxying.c @@ -183,8 +183,9 @@ static bool do_proxy(em_proxying_queue* q, pthread_t target_thread, task t) { bool ret = em_task_queue_send(tasks, t); - // When proxying work to the dlopen or system queue we may have to wake the - // target thread in case it is blocked in `emscripten_futex_wait`. + // Proxying via the dlopen or system queue may target a thread that is + // currently blocked in `emscripten_futex_wait`, so explicitly wake it + // after enqueueing the task. bool needs_notify = #ifdef EMSCRIPTEN_DYNAMIC_LINKING is_dlopen_queue || From f7e8ed70fe34e96b24c88ea8300b147e7dcdd32f Mon Sep 17 00:00:00 2001 From: Kleis Auke Wolthuizen Date: Tue, 26 May 2026 11:36:12 +0200 Subject: [PATCH 05/11] Improve test --- test/core/pthread/test_pthread_dlopen.c | 12 ++++-------- test/test_core.py | 2 ++ 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/test/core/pthread/test_pthread_dlopen.c b/test/core/pthread/test_pthread_dlopen.c index be7ae30534486..82b119f1d1c5e 100644 --- a/test/core/pthread/test_pthread_dlopen.c +++ b/test/core/pthread/test_pthread_dlopen.c @@ -1,11 +1,8 @@ #include -#include -#include #include #include #include #include -#include #define NUM_THREADS 8 @@ -28,8 +25,6 @@ static void* thread_main(void* arg) { // Wait until all threads + main have reached the barrier pthread_barrier_wait(&started); - usleep(1000000); - int* data_addr = p_side_data_address(); assert(data_addr == expected_data_addr); @@ -51,9 +46,6 @@ int main() { for (int i = 0; i < NUM_THREADS; ++i) { int rc = pthread_create(&threads[i], NULL, thread_main, (void*)(intptr_t)i); assert(rc == 0); - - rc = pthread_detach(threads[i]); - assert(rc == 0); } printf("loading dylib\n"); @@ -78,6 +70,10 @@ int main() { // Wait until all threads execute their entry points pthread_barrier_wait(&started); + for (int i = 0; i < NUM_THREADS; ++i) { + int rc = pthread_join(threads[i], NULL); + assert(rc == 0); + } dlclose(handle); diff --git a/test/test_core.py b/test/test_core.py index 7165da6099f35..7325997ddb15a 100644 --- a/test/test_core.py +++ b/test/test_core.py @@ -9309,6 +9309,8 @@ def test_pthread_dlopen(self): self.cflags += ['--embed-file', 'libside.so@libside.so'] self.prep_dlfcn_main() self.set_setting('EXIT_RUNTIME') + # Uncomment to test _emscripten_proxy_dlsync_async() + #self.set_setting('PROXY_TO_PTHREAD') self.do_runf('core/pthread/test_pthread_dlopen.c', ['side module ctor', 'done', 'side module atexit'], assert_all=True) From 2a440564f85d8397c3999aabaaf2bfc37faad29d Mon Sep 17 00:00:00 2001 From: Kleis Auke Wolthuizen Date: Tue, 26 May 2026 12:24:29 +0200 Subject: [PATCH 06/11] Inline bool --- system/lib/pthread/proxying.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/system/lib/pthread/proxying.c b/system/lib/pthread/proxying.c index 8d8523c9bd4e9..77b05c7f4dd3b 100644 --- a/system/lib/pthread/proxying.c +++ b/system/lib/pthread/proxying.c @@ -165,9 +165,6 @@ void emscripten_proxy_execute_queue(em_proxying_queue* q) { static bool do_proxy(em_proxying_queue* q, pthread_t target_thread, task t) { assert(q != NULL); pthread_mutex_lock(&q->mutex); -#ifdef EMSCRIPTEN_DYNAMIC_LINKING - bool is_dlopen_queue = q == &dlopen_proxying_queue; -#endif bool is_system_queue = q == &system_proxying_queue; if (is_system_queue) { system_queue_in_use = true; @@ -188,7 +185,7 @@ static bool do_proxy(em_proxying_queue* q, pthread_t target_thread, task t) { // after enqueueing the task. bool needs_notify = #ifdef EMSCRIPTEN_DYNAMIC_LINKING - is_dlopen_queue || + q == &dlopen_proxying_queue || #endif (is_system_queue && pthread_equal(target_thread, emscripten_main_runtime_thread_id())); From 6c04ce39dae3f242e435afd674151f9864b2fb47 Mon Sep 17 00:00:00 2001 From: Kleis Auke Wolthuizen Date: Tue, 26 May 2026 12:41:55 +0200 Subject: [PATCH 07/11] Appease ruff --- test/test_core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_core.py b/test/test_core.py index 7325997ddb15a..b6e352a489492 100644 --- a/test/test_core.py +++ b/test/test_core.py @@ -9310,7 +9310,7 @@ def test_pthread_dlopen(self): self.prep_dlfcn_main() self.set_setting('EXIT_RUNTIME') # Uncomment to test _emscripten_proxy_dlsync_async() - #self.set_setting('PROXY_TO_PTHREAD') + # self.set_setting('PROXY_TO_PTHREAD') self.do_runf('core/pthread/test_pthread_dlopen.c', ['side module ctor', 'done', 'side module atexit'], assert_all=True) From a5e67bb1999f1c3e49e719d8025d5187b5dc9227 Mon Sep 17 00:00:00 2001 From: Kleis Auke Wolthuizen Date: Wed, 27 May 2026 08:56:22 +0000 Subject: [PATCH 08/11] 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: 26158 => 26157 [-1 bytes / -0.00%] codesize/test_codesize_minimal_pthreads_memgrowth.json: 26567 => 26566 [-1 bytes / -0.00%] Average change: -0.00% (-0.00% - -0.00%) ``` --- test/codesize/test_codesize_minimal_pthreads.json | 8 ++++---- .../test_codesize_minimal_pthreads_memgrowth.json | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/test/codesize/test_codesize_minimal_pthreads.json b/test/codesize/test_codesize_minimal_pthreads.json index 5e9412e1955ff..ba2cb929c4caf 100644 --- a/test/codesize/test_codesize_minimal_pthreads.json +++ b/test/codesize/test_codesize_minimal_pthreads.json @@ -1,10 +1,10 @@ { "a.out.js": 7121, "a.out.js.gz": 3528, - "a.out.nodebug.wasm": 19037, - "a.out.nodebug.wasm.gz": 8787, - "total": 26158, - "total_gz": 12315, + "a.out.nodebug.wasm": 19036, + "a.out.nodebug.wasm.gz": 8786, + "total": 26157, + "total_gz": 12314, "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 cd4dd103bb6c3..5595b996969ca 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": 7529, "a.out.js.gz": 3732, - "a.out.nodebug.wasm": 19038, - "a.out.nodebug.wasm.gz": 8788, - "total": 26567, - "total_gz": 12520, + "a.out.nodebug.wasm": 19037, + "a.out.nodebug.wasm.gz": 8787, + "total": 26566, + "total_gz": 12519, "sent": [ "a (memory)", "b (exit)", From c9348e2ec7b7cd2e733fe2178f661882a1a6a36c Mon Sep 17 00:00:00 2001 From: Kleis Auke Wolthuizen Date: Thu, 28 May 2026 10:35:16 +0000 Subject: [PATCH 09/11] 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: 26147 => 26146 [-1 bytes / -0.00%] codesize/test_codesize_minimal_pthreads_memgrowth.json: 26556 => 26555 [-1 bytes / -0.00%] Average change: -0.00% (-0.00% - -0.00%) ``` --- test/codesize/test_codesize_minimal_pthreads.json | 8 ++++---- .../test_codesize_minimal_pthreads_memgrowth.json | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/test/codesize/test_codesize_minimal_pthreads.json b/test/codesize/test_codesize_minimal_pthreads.json index 851b17340ee79..99e67a4ec7798 100644 --- a/test/codesize/test_codesize_minimal_pthreads.json +++ b/test/codesize/test_codesize_minimal_pthreads.json @@ -1,10 +1,10 @@ { "a.out.js": 7110, "a.out.js.gz": 3524, - "a.out.nodebug.wasm": 19037, - "a.out.nodebug.wasm.gz": 8787, - "total": 26147, - "total_gz": 12311, + "a.out.nodebug.wasm": 19036, + "a.out.nodebug.wasm.gz": 8786, + "total": 26146, + "total_gz": 12310, "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 9dcfca60775f6..471be7b6511ac 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": 7518, "a.out.js.gz": 3728, - "a.out.nodebug.wasm": 19038, - "a.out.nodebug.wasm.gz": 8788, - "total": 26556, - "total_gz": 12516, + "a.out.nodebug.wasm": 19037, + "a.out.nodebug.wasm.gz": 8787, + "total": 26555, + "total_gz": 12515, "sent": [ "a (memory)", "b (exit)", From 4ef62dad3da4deafdc7eea188c42b0904bb0c87f Mon Sep 17 00:00:00 2001 From: Kleis Auke Wolthuizen Date: Thu, 28 May 2026 17:22:19 +0200 Subject: [PATCH 10/11] Incorporate feedback --- system/lib/libc/dynlink.c | 6 +++--- system/lib/pthread/proxying.c | 15 ++++++--------- system/lib/pthread/threading_internal.h | 5 ++++- test/test_core.py | 10 ++++++---- 4 files changed, 19 insertions(+), 17 deletions(-) diff --git a/system/lib/libc/dynlink.c b/system/lib/libc/dynlink.c index 038d846c7dce8..5270eac7c409b 100644 --- a/system/lib/libc/dynlink.c +++ b/system/lib/libc/dynlink.c @@ -362,7 +362,7 @@ void _emscripten_process_dlopen_queue() { if (!processing_queue) { assert(!emscripten_is_main_runtime_thread()); processing_queue = true; - emscripten_proxy_execute_queue(_emscripten_proxy_dlopen_queue()); + emscripten_proxy_execute_queue(&_dlopen_proxying_queue); processing_queue = false; } } @@ -382,7 +382,7 @@ int _emscripten_proxy_dlsync_async(pthread_t target_thread, em_promise_t promise .promise = promise, .result = false, }; - int rtn = emscripten_proxy_callback(_emscripten_proxy_dlopen_queue(), + int rtn = emscripten_proxy_callback(&_dlopen_proxying_queue, target_thread, do_thread_sync, thread_sync_done, @@ -401,7 +401,7 @@ int _emscripten_proxy_dlsync_async(pthread_t target_thread, em_promise_t promise int _emscripten_proxy_dlsync(pthread_t target_thread) { assert(emscripten_is_main_runtime_thread()); int result; - if (!emscripten_proxy_sync(_emscripten_proxy_dlopen_queue(), + if (!emscripten_proxy_sync(&_dlopen_proxying_queue, target_thread, do_thread_sync_out, &result)) { diff --git a/system/lib/pthread/proxying.c b/system/lib/pthread/proxying.c index 77b05c7f4dd3b..22d1e3fe6da69 100644 --- a/system/lib/pthread/proxying.c +++ b/system/lib/pthread/proxying.c @@ -45,16 +45,12 @@ em_proxying_queue* emscripten_proxy_get_system_queue(void) { // Proxying queue specially for handling code loading (dlopen) events. // Processed by background threads that call `_emscripten_process_dlopen_queue` // during futex_wait (i.e. whenever they block). -static em_proxying_queue dlopen_proxying_queue = { +em_proxying_queue _dlopen_proxying_queue = { .mutex = PTHREAD_MUTEX_INITIALIZER, .task_queues = NULL, .size = 0, .capacity = 0, }; - -em_proxying_queue* _emscripten_proxy_dlopen_queue(void) { - return &dlopen_proxying_queue; -} #endif em_proxying_queue* em_proxying_queue_create(void) { @@ -180,12 +176,13 @@ static bool do_proxy(em_proxying_queue* q, pthread_t target_thread, task t) { bool ret = em_task_queue_send(tasks, t); - // Proxying via the dlopen or system queue may target a thread that is - // currently blocked in `emscripten_futex_wait`, so explicitly wake it - // after enqueueing the task. + // When proxying work to the main thread using the system queue we have a + // special case in that we need to wake the target thread in case it is in + // `emscripten_futex_wait`. Additionally, the _dlopen_proxying_queue also + // requires a wakeup after enqueuing work. bool needs_notify = #ifdef EMSCRIPTEN_DYNAMIC_LINKING - q == &dlopen_proxying_queue || + q == &_dlopen_proxying_queue || #endif (is_system_queue && pthread_equal(target_thread, emscripten_main_runtime_thread_id())); diff --git a/system/lib/pthread/threading_internal.h b/system/lib/pthread/threading_internal.h index 0cf1fa0fd15d5..cf16c268269c8 100644 --- a/system/lib/pthread/threading_internal.h +++ b/system/lib/pthread/threading_internal.h @@ -83,8 +83,11 @@ int _emscripten_thread_is_valid(pthread_t thread); void _emscripten_thread_exit_joinable(pthread_t thread); void _emscripten_thread_exit(void* result); + +#ifdef EMSCRIPTEN_DYNAMIC_LINKING void _emscripten_process_dlopen_queue(void); -em_proxying_queue* _emscripten_proxy_dlopen_queue(void); +extern em_proxying_queue _dlopen_proxying_queue; +#endif #if !defined(__EMSCRIPTEN_PTHREADS__) || defined(NDEBUG) #define emscripten_set_current_thread_status(newStatus) diff --git a/test/test_core.py b/test/test_core.py index c58060b2c7043..e9a28f1d05b4b 100644 --- a/test/test_core.py +++ b/test/test_core.py @@ -9302,19 +9302,21 @@ def test_pthread_dylink_exceptions(self): self.dylink_testf(test_file('core/pthread/test_pthread_dylink_exceptions.cpp')) @needs_dylink + @parameterized({ + '': ([],), + 'proxied': (['-sPROXY_TO_PTHREAD'],), + }) @requires_pthreads - def test_pthread_dlopen(self): + def test_pthread_dlopen(self, args): self.cflags += ['-Wno-experimental', '-pthread'] self.build_dlfcn_lib(test_file('core/pthread/test_pthread_dlopen_side.c')) self.cflags += ['--embed-file', 'libside.so@libside.so'] self.prep_dlfcn_main() self.set_setting('EXIT_RUNTIME') - # Uncomment to test _emscripten_proxy_dlsync_async() - # self.set_setting('PROXY_TO_PTHREAD') self.do_runf('core/pthread/test_pthread_dlopen.c', ['side module ctor', 'done', 'side module atexit'], - assert_all=True) + assert_all=True, cflags=args) @needs_dylink @requires_pthreads From 4d7842b091ceffd5c3c2b723c149785ce89547b2 Mon Sep 17 00:00:00 2001 From: Kleis Auke Wolthuizen Date: Thu, 28 May 2026 15:23:34 +0000 Subject: [PATCH 11/11] 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: 26146 => 26179 [+33 bytes / +0.13%] codesize/test_codesize_minimal_pthreads_memgrowth.json: 26555 => 26588 [+33 bytes / +0.12%] Average change: +0.13% (+0.12% - +0.13%) ``` --- test/codesize/test_codesize_minimal_pthreads.json | 8 ++++---- .../test_codesize_minimal_pthreads_memgrowth.json | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/test/codesize/test_codesize_minimal_pthreads.json b/test/codesize/test_codesize_minimal_pthreads.json index 99e67a4ec7798..f90d55d58be60 100644 --- a/test/codesize/test_codesize_minimal_pthreads.json +++ b/test/codesize/test_codesize_minimal_pthreads.json @@ -1,10 +1,10 @@ { "a.out.js": 7110, "a.out.js.gz": 3524, - "a.out.nodebug.wasm": 19036, - "a.out.nodebug.wasm.gz": 8786, - "total": 26146, - "total_gz": 12310, + "a.out.nodebug.wasm": 19069, + "a.out.nodebug.wasm.gz": 8800, + "total": 26179, + "total_gz": 12324, "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 471be7b6511ac..6769ced1dfa15 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": 7518, "a.out.js.gz": 3728, - "a.out.nodebug.wasm": 19037, - "a.out.nodebug.wasm.gz": 8787, - "total": 26555, - "total_gz": 12515, + "a.out.nodebug.wasm": 19070, + "a.out.nodebug.wasm.gz": 8801, + "total": 26588, + "total_gz": 12529, "sent": [ "a (memory)", "b (exit)",