From b0993c4c651bad60b39957f046a129a31a235ca2 Mon Sep 17 00:00:00 2001 From: Sam Clegg Date: Fri, 22 May 2026 16:23:24 -0700 Subject: [PATCH] refactor(pthread): remove PThread.runningWorkers Remove `PThread.runningWorkers` and use `PThread.pthreads` instead to track running workers. `PThread.pthreads` already contains all the necessary information, and keeping both in sync was redundant. This also optimizes thread exit by replacing an O(N) array splice with an O(1) map deletion, at the cost of making count queries (mostly used in tests) O(N) instead of O(1). TAG=agy CONV=47f918e2-bd47-4e38-9a6d-af1765cf2f7e --- ChangeLog.md | 3 +++ src/lib/libpthread.js | 8 +------- test/codesize/test_codesize_minimal_pthreads.json | 8 ++++---- .../test_codesize_minimal_pthreads_memgrowth.json | 8 ++++---- test/other/test_pthread_reuse.c | 2 +- test/pthread/test_pthread_join.c | 6 +++--- test/pthread/test_pthread_preallocates_workers.c | 6 +++--- test/pthread/test_std_thread_detach.cpp | 2 +- 8 files changed, 20 insertions(+), 23 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index d92582ea2ae37..ae624ab3226b2 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -55,6 +55,9 @@ See docs/process.md for more on how version tagging works. implied). Also, if you include real dynamic libraries in your link command emscripten will now automatically produce a dynamically linked program (`-sMAIN_MODULE=2` is implied). (#25930) +- The `PThread.runningWorkers` field was removed from the `PThread` object. + If you have JS code that was depending on this you can transition to using the + `PThread.pthreads` object. (#26998) 5.0.7 - 04/30/26 ---------------- diff --git a/src/lib/libpthread.js b/src/lib/libpthread.js index 718b0bc5ff7e0..9d9e90523393b 100644 --- a/src/lib/libpthread.js +++ b/src/lib/libpthread.js @@ -108,8 +108,6 @@ var LibraryPThread = { // terminated, but is returned to this pool as an optimization so that // starting the next thread is faster. unusedWorkers: [], - // Contains all Workers that are currently hosting an active pthread. - runningWorkers: [], tlsInitFunctions: [], // Maps pthread_t pointers to the workers on which they are running. For // the reverse mapping, each worker has a `pthread_ptr` when its running a @@ -191,14 +189,13 @@ var LibraryPThread = { // pthreads will continue to be executing after `worker.terminate` has // returned. For this reason, we don't call `returnWorkerToPool` here or // free the underlying pthread data structures. - for (var worker of PThread.runningWorkers) { + for (var worker of Object.values(PThread.pthreads)) { terminateWorker(worker); } for (var worker of PThread.unusedWorkers) { terminateWorker(worker); } PThread.unusedWorkers = []; - PThread.runningWorkers = []; PThread.pthreads = {}; }, @@ -229,7 +226,6 @@ var LibraryPThread = { // Note: worker is intentionally not terminated so the pool can // dynamically grow. PThread.unusedWorkers.push(worker); - PThread.runningWorkers.splice(PThread.runningWorkers.indexOf(worker), 1); // Not a running Worker anymore // Detach the worker from the pthread object, and return it to the // worker pool as an unused worker. @@ -710,8 +706,6 @@ var LibraryPThread = { assert(!worker.pthread_ptr); #endif - PThread.runningWorkers.push(worker); - // Add to pthreads map PThread.pthreads[threadParams.pthread_ptr] = worker; diff --git a/test/codesize/test_codesize_minimal_pthreads.json b/test/codesize/test_codesize_minimal_pthreads.json index 57b8c99f35738..5e9412e1955ff 100644 --- a/test/codesize/test_codesize_minimal_pthreads.json +++ b/test/codesize/test_codesize_minimal_pthreads.json @@ -1,10 +1,10 @@ { - "a.out.js": 7154, - "a.out.js.gz": 3549, + "a.out.js": 7121, + "a.out.js.gz": 3528, "a.out.nodebug.wasm": 19037, "a.out.nodebug.wasm.gz": 8787, - "total": 26191, - "total_gz": 12336, + "total": 26158, + "total_gz": 12315, "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 770e5d5d29c11..cd4dd103bb6c3 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": 7562, - "a.out.js.gz": 3752, + "a.out.js": 7529, + "a.out.js.gz": 3732, "a.out.nodebug.wasm": 19038, "a.out.nodebug.wasm.gz": 8788, - "total": 26600, - "total_gz": 12540, + "total": 26567, + "total_gz": 12520, "sent": [ "a (memory)", "b (exit)", diff --git a/test/other/test_pthread_reuse.c b/test/other/test_pthread_reuse.c index 4bb52e1dc311e..8e4ca4b0c750d 100644 --- a/test/other/test_pthread_reuse.c +++ b/test/other/test_pthread_reuse.c @@ -24,7 +24,7 @@ void* thread_main(void* arg) { } void checkThreadPool() { - int running = EM_ASM_INT(return PThread.runningWorkers.length); + int running = EM_ASM_INT(return Object.keys(PThread.pthreads).length); int unused = EM_ASM_INT(return PThread.unusedWorkers.length); printf("running=%d unused=%d\n", running, unused); assert(running == 0); diff --git a/test/pthread/test_pthread_join.c b/test/pthread/test_pthread_join.c index 3916090dd2a82..3d0238d401327 100644 --- a/test/pthread/test_pthread_join.c +++ b/test/pthread/test_pthread_join.c @@ -34,7 +34,7 @@ int main() { pthread_t thr; - assert(EM_ASM_INT(return PThread.runningWorkers.length) == 0); + assert(EM_ASM_INT(return Object.keys(PThread.pthreads).length) == 0); assert(EM_ASM_INT(return PThread.unusedWorkers.length) == 8); // This test should be run with a prepopulated pool of size 8. intptr_t n = 20; @@ -44,14 +44,14 @@ int main() { emscripten_out("Main: Waiting for thread to join"); int result = 0; - assert(EM_ASM_INT(return PThread.runningWorkers.length) == 1); + assert(EM_ASM_INT(return Object.keys(PThread.pthreads).length) == 1); assert(EM_ASM_INT(return PThread.unusedWorkers.length) == 7); s = pthread_join(thr, (void**)&result); assert(s == 0); emscripten_outf("Main: Thread joined with result: %d", result); - assert(EM_ASM_INT(return PThread.runningWorkers.length) == 0); + assert(EM_ASM_INT(return Object.keys(PThread.pthreads).length) == 0); assert(EM_ASM_INT(return PThread.unusedWorkers.length) == 8); assert(result == 6765); diff --git a/test/pthread/test_pthread_preallocates_workers.c b/test/pthread/test_pthread_preallocates_workers.c index b112b806d195b..524761bd7319b 100644 --- a/test/pthread/test_pthread_preallocates_workers.c +++ b/test/pthread/test_pthread_preallocates_workers.c @@ -38,13 +38,13 @@ int main() // This test should be run with a prewarmed pool of size 4. None // of the threads are allocated yet. assert(EM_ASM_INT(return PThread.unusedWorkers.length) == 4); - assert(EM_ASM_INT(return PThread.runningWorkers.length) == 0); + assert(EM_ASM_INT(return Object.keys(PThread.pthreads).length) == 0); CreateThread(0); // We have one running thread, allocated on demand. assert(EM_ASM_INT(return PThread.unusedWorkers.length) == 3); - assert(EM_ASM_INT(return PThread.runningWorkers.length) == 1); + assert(EM_ASM_INT(return Object.keys(PThread.pthreads).length) == 1); for (int i = 1; i < 5; ++i) { CreateThread(i); @@ -56,7 +56,7 @@ int main() // solved in non-test cases by using PROXY_TO_PTHREAD, but we can't // do that here since we need to eval the length of the various pthread // arrays. - assert(EM_ASM_INT(return PThread.runningWorkers.length) == 5); + assert(EM_ASM_INT(return Object.keys(PThread.pthreads).length) == 5); assert(EM_ASM_INT(return PThread.unusedWorkers.length) == 0); return 0; diff --git a/test/pthread/test_std_thread_detach.cpp b/test/pthread/test_std_thread_detach.cpp index 71f864c4addd4..dc2b9c567b895 100644 --- a/test/pthread/test_std_thread_detach.cpp +++ b/test/pthread/test_std_thread_detach.cpp @@ -24,7 +24,7 @@ void EMSCRIPTEN_KEEPALIVE spawn_a_thread() { void EMSCRIPTEN_KEEPALIVE count_threads(int num_threads_spawned, int num_threads_spawned_extra) { num_threads_spawned += num_threads_spawned_extra; int num_workers = EM_ASM_INT({ - return PThread.runningWorkers.length + PThread.unusedWorkers.length; + return Object.keys(PThread.pthreads).length + PThread.unusedWorkers.length; }); std::cout <<