diff --git a/system/lib/pthread/em_task_queue.c b/system/lib/pthread/em_task_queue.c index d9c72f5384162..3a15d36c5b6e2 100644 --- a/system/lib/pthread/em_task_queue.c +++ b/system/lib/pthread/em_task_queue.c @@ -184,19 +184,24 @@ void em_task_queue_execute(em_task_queue* queue) { } void em_task_queue_cancel(em_task_queue* queue) { + queue->processing = 1; pthread_mutex_lock(&queue->mutex); while (!em_task_queue_is_empty(queue)) { task t = em_task_queue_dequeue(queue); if (t.cancel) { + pthread_mutex_unlock(&queue->mutex); t.cancel(t.arg); + pthread_mutex_lock(&queue->mutex); } } pthread_mutex_unlock(&queue->mutex); + queue->processing = 0; // Any subsequent messages to this queue (for example if a pthread struct is // reused for a future thread, potentially on a different worker) will require // a new notification. Clearing the flag is safe here because in both the // proxying queue and mailbox cases, there are no more outstanding references - // to the queue after thread shutdown. + // to the queue after thread shutdown. This must be the last access to the + // queue because cull_zombies may free it once the flag is cleared. queue->notification = NOTIFICATION_NONE; } diff --git a/test/core/pthread/test_pthread_dlopen_exit.c b/test/core/pthread/test_pthread_dlopen_exit.c new file mode 100644 index 0000000000000..37540afe1ff3b --- /dev/null +++ b/test/core/pthread/test_pthread_dlopen_exit.c @@ -0,0 +1,46 @@ +#include +#include +#include +#include + +static pthread_barrier_t barrier; + +static void* exiting_worker(void* arg) { + pthread_barrier_wait(&barrier); + // Race thread shutdown against dynamic link synchronization. + return NULL; +} + +static void* loader_thread(void* arg) { + for (int i = 0; i < NUM_ITERATIONS; ++i) { + char filename[32]; + snprintf(filename, sizeof(filename), "libside%d.so", i); + // Pair with every exiting worker, not just the first one. + pthread_barrier_wait(&barrier); + void* handle = dlopen(filename, RTLD_NOW | RTLD_GLOBAL); + assert(handle && "dlopen failed"); + int (*side_func)(void) = dlsym(handle, "side_func"); + assert(side_func); + assert(side_func() == 42); + assert(dlclose(handle) == 0); + } + return NULL; +} + +int main() { + assert(pthread_barrier_init(&barrier, NULL, 2) == 0); + + pthread_t loader; + assert(pthread_create(&loader, NULL, loader_thread, NULL) == 0); + + for (int i = 0; i < NUM_ITERATIONS; ++i) { + pthread_t worker; + assert(pthread_create(&worker, NULL, exiting_worker, NULL) == 0); + assert(pthread_join(worker, NULL) == 0); + } + + assert(pthread_join(loader, NULL) == 0); + assert(pthread_barrier_destroy(&barrier) == 0); + puts("PASSED"); + return 0; +} diff --git a/test/core/pthread/test_pthread_dlopen_exit_side.c b/test/core/pthread/test_pthread_dlopen_exit_side.c new file mode 100644 index 0000000000000..3ee9da24d9bbf --- /dev/null +++ b/test/core/pthread/test_pthread_dlopen_exit_side.c @@ -0,0 +1,3 @@ +int side_func(void) { + return 42; +} diff --git a/test/test_core.py b/test/test_core.py index 7d7785b77b47c..eac0f61fea3e8 100644 --- a/test/test_core.py +++ b/test/test_core.py @@ -9394,6 +9394,23 @@ 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_dlopen_exit(self): + iterations = 100 + self.cflags += ['-Wno-experimental', '-pthread'] + self.build_dlfcn_lib(test_file('core/pthread/test_pthread_dlopen_exit_side.c')) + # dlclose does not unload libraries, so use a fresh name each time to + # generate dynamic link synchronization work on every iteration. + for i in range(iterations): + shutil.copy('libside.so', f'libside{i}.so') + + self.prep_dlfcn_main() + self.set_setting('EXIT_RUNTIME') + self.set_setting('PROXY_TO_PTHREAD') + self.do_runf('core/pthread/test_pthread_dlopen_exit.c', 'PASSED', + cflags=[f'-DNUM_ITERATIONS={iterations}']) + @needs_dylink @requires_pthreads @flaky('https://github.com/emscripten-core/emscripten/issues/18887')