Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion system/lib/pthread/em_task_queue.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
46 changes: 46 additions & 0 deletions test/core/pthread/test_pthread_dlopen_exit.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#include <assert.h>
#include <dlfcn.h>
#include <pthread.h>
#include <stdio.h>

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;
}
3 changes: 3 additions & 0 deletions test/core/pthread/test_pthread_dlopen_exit_side.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
int side_func(void) {
return 42;
}
17 changes: 17 additions & 0 deletions test/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down
Loading