From bfa8d67267a74ba601d712a162c8d34e536de72d 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 abd71117dc8c6..6bf98d93e49a2 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 c60aec74feb4a..e7493d5436e04 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 291a3a52fede4..99e0a2060f084 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.js": 7110, + "a.out.js.gz": 3522, "a.out.nodebug.wasm": 19037, "a.out.nodebug.wasm.gz": 8787, - "total": 26180, - "total_gz": 12329, + "total": 26147, + "total_gz": 12309, "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..4a30e85f4eadc 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.js": 7518, + "a.out.js.gz": 3725, "a.out.nodebug.wasm": 19038, "a.out.nodebug.wasm.gz": 8788, - "total": 26589, - "total_gz": 12533, + "total": 26556, + "total_gz": 12513, "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 <<