diff --git a/src/lib/libpthread.js b/src/lib/libpthread.js index 9b2807e48d42a..360d5f4f77478 100644 --- a/src/lib/libpthread.js +++ b/src/lib/libpthread.js @@ -1219,6 +1219,12 @@ var LibraryPThread = { }, $markAsFinished: (pthread_ptr) => { + // When this is reached via a message from the exiting thread, the thread + // may already have been joined and cleaned up, and its pthread_t reused by + // a new thread that is still running, which must not be marked. + if (!PThread.pthreads[pthread_ptr]) return; + var state = {{{ makeGetValue('pthread_ptr', C_STRUCTS.pthread.detach_state, 'i32') }}}; + if (state != {{{ cDefs.DT_EXITING }}} && state != {{{ cDefs.DT_EXITED }}}) return; #if PTHREADS_DEBUG dbg(`markAsFinished: ${ptrToString(pthread_ptr)}`); #endif diff --git a/src/struct_info_generated.json b/src/struct_info_generated.json index 2785d909cddc2..705f21c7efad3 100644 --- a/src/struct_info_generated.json +++ b/src/struct_info_generated.json @@ -55,6 +55,8 @@ "CSIZE": 48, "DT_CHR": 2, "DT_DIR": 4, + "DT_EXITED": 0, + "DT_EXITING": 1, "DT_LNK": 10, "DT_REG": 8, "E2BIG": 1, @@ -1078,6 +1080,7 @@ }, "pthread": { "__size__": 120, + "detach_state": 28, "profilerBlock": 96, "stack": 48, "stack_size": 52, diff --git a/src/struct_info_generated_wasm64.json b/src/struct_info_generated_wasm64.json index 6e6037da5af95..af3c8a30c9747 100644 --- a/src/struct_info_generated_wasm64.json +++ b/src/struct_info_generated_wasm64.json @@ -55,6 +55,8 @@ "CSIZE": 48, "DT_CHR": 2, "DT_DIR": 4, + "DT_EXITED": 0, + "DT_EXITING": 1, "DT_LNK": 10, "DT_REG": 8, "E2BIG": 1, @@ -1078,6 +1080,7 @@ }, "pthread": { "__size__": 216, + "detach_state": 52, "profilerBlock": 176, "stack": 80, "stack_size": 88, diff --git a/src/struct_info_internal.json b/src/struct_info_internal.json index de28e7a0b6c4c..10f3941636ddf 100644 --- a/src/struct_info_internal.json +++ b/src/struct_info_internal.json @@ -4,8 +4,13 @@ // =========================================== { "file": "pthread_impl.h", + "defines": [ + "DT_EXITED", + "DT_EXITING" + ], "structs": { "pthread": [ + "detach_state", "profilerBlock", "stack", "stack_size", diff --git a/test/core/pthread/test_pthread_dlsync_reused.c b/test/core/pthread/test_pthread_dlsync_reused.c new file mode 100644 index 0000000000000..72e55873dd7a4 --- /dev/null +++ b/test/core/pthread/test_pthread_dlsync_reused.c @@ -0,0 +1,70 @@ +// A joinable thread's "finished" message only reaches the main thread once it +// returns to the event loop. By then the main thread has joined that thread +// and created a new one that reuses its pthread_t. The late message must not +// stop the new, still running thread from taking part in dlopen()'s sync. + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +typedef int (*func_t)(); + +pthread_t waiter; +atomic_bool waiter_released; +func_t (*get_side_func)(); +int result; + +void* noop(void* arg) { return NULL; } + +void* wait_then_call(void* arg) { + // sched_yield() also runs any pending dlopen() sync for this thread. + while (!waiter_released) { + sched_yield(); + } + if (get_side_func) { + result = get_side_func()(); + } + return NULL; +} + +void release_waiter() { + waiter_released = true; + pthread_join(waiter, NULL); + waiter_released = false; +} + +void load_and_call(void* arg) { + void* handle = dlopen("libside.so", RTLD_NOW); + assert(handle); + get_side_func = dlsym(handle, "side_func_address"); + assert(get_side_func); + // The waiter traps here if dlopen() skipped it. + release_waiter(); + assert(result == 43); + printf("done\n"); + exit(0); +} + +int main() { + // The new thread almost always gets the joined thread's block back, but + // retry rather than depend on it. + for (int i = 0; i < 10; i++) { + pthread_t joined; + pthread_create(&joined, NULL, noop, NULL); + pthread_join(joined, NULL); + pthread_create(&waiter, NULL, wait_then_call, NULL); + if (pthread_equal(waiter, joined)) { + emscripten_set_timeout(load_and_call, 100, NULL); + return 0; + } + release_waiter(); + } + printf("no thread reused a joined thread's pthread_t\n"); + return 1; +} diff --git a/test/test_core.py b/test/test_core.py index 7d7785b77b47c..655406ec2a968 100644 --- a/test/test_core.py +++ b/test/test_core.py @@ -9394,6 +9394,15 @@ def test_pthread_dlopen(self, args): ['side module ctor', 'done\n', 'side module atexit'], assert_all=True, cflags=args) + @needs_dylink + @requires_pthreads + def test_pthread_dlsync_reused(self): + 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.do_runf('core/pthread/test_pthread_dlsync_reused.c', 'done\n', cflags=['-sEXIT_RUNTIME']) + @needs_dylink @requires_pthreads @flaky('https://github.com/emscripten-core/emscripten/issues/18887')