Skip to content

WIP: Fix deadlock when calling fflush during application shutdown #15190

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
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
35 changes: 12 additions & 23 deletions system/lib/pthread/library_pthread.c
Original file line number Diff line number Diff line change
Expand Up @@ -584,45 +584,34 @@ void emscripten_current_thread_process_queued_calls() {
//emscripten_current_thread_process_queued_calls(), ' + new Error().stack));
// #endif

static thread_local bool thread_is_processing_queued_calls = false;

// It is possible that when processing a queued call, the control flow leads back to calling this
// function in a nested fashion! Therefore this scenario must explicitly be detected, and
// processing the queue must be avoided if we are nesting, or otherwise the same queued calls
// would be processed again and again.
if (thread_is_processing_queued_calls)
return;
// This must be before pthread_mutex_lock(), since pthread_mutex_lock() can call back to this
// function.
thread_is_processing_queued_calls = true;

pthread_mutex_lock(&call_queue_lock);
CallQueue* q = GetQueue(pthread_self());
if (!q) {
pthread_mutex_unlock(&call_queue_lock);
thread_is_processing_queued_calls = false;
return;
}

int head = q->call_queue_head;
int tail = q->call_queue_tail;
while (head != tail) {
while (1) {
int head = q->call_queue_head;
int tail = q->call_queue_tail;
if (head == tail) {
break;
}

// Remove the item from the queue before running it.
em_queued_call* to_run = q->call_queue[head];
q->call_queue_head = (head + 1) % CALL_QUEUE_SIZE;

// Assume that the call is heavy, so unlock access to the call queue while it is being
// performed.
pthread_mutex_unlock(&call_queue_lock);
_do_call(q->call_queue[head]);
_do_call(to_run);
pthread_mutex_lock(&call_queue_lock);

head = (head + 1) % CALL_QUEUE_SIZE;
q->call_queue_head = head;
tail = q->call_queue_tail;
}
pthread_mutex_unlock(&call_queue_lock);

// If the queue was full and we had waiters pending to get to put data to queue, wake them up.
emscripten_futex_wake((void*)&q->call_queue_head, 0x7FFFFFFF);

thread_is_processing_queued_calls = false;
}

// At times when we disallow the main thread to process queued calls, this will
Expand Down
2 changes: 1 addition & 1 deletion tests/jsrun.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from tools import shared, utils

WORKING_ENGINES = {} # Holds all configured engines and whether they work: maps path -> True/False
DEFAULT_TIMEOUT = 5 * 60
DEFAULT_TIMEOUT = 5


def make_command(filename, engine, args=[]):
Expand Down
26 changes: 1 addition & 25 deletions tests/pthread/test_pthread_c11_threads.c
Original file line number Diff line number Diff line change
Expand Up @@ -17,21 +17,6 @@ void do_once(void) {
printf("in do_once\n");
}

// Because this thread is detached it can still be running
// when the main thread exits. And becasue of an emscripten
// bug (https://github.com/emscripten-core/emscripten/issues/15186).
// this means we can't write to stdout after the main thread
// exits. This means we can't use `thread_main` below because
// the destructor to the `tss_t key` writes to stdout.
int thread_main_detached(void* arg) {
printf("in thread_main_detached %p\n", (void*)thrd_current());
mtx_lock(&mutex);
thread_counter--;
cnd_signal(&cond);
mtx_unlock(&mutex);
return 0;
}

int thread_main(void* arg) {
printf("in thread_main %p\n", (void*)thrd_current());
tss_set(key, (void*)thrd_current());
Expand Down Expand Up @@ -102,18 +87,9 @@ int main(int argc, char* argv[]) {

// Test thrd_detach
thrd_t t6;
assert(thrd_create(&t6, thread_main_detached, NULL) == thrd_success);
assert(thrd_create(&t6, thread_main, NULL) == thrd_success);
assert(thrd_detach(t6) == thrd_success);

// Wait for the thread to at least be done printing before exiting
// the process.
// We shouldn't need to do this but there is a bug in emscripten
// where a deadlock can occur between main thread calling fflush()
// during exitRuntime and the detached thread calling print (and
// therefore holding the stdout lock).
// See https://github.com/emscripten-core/emscripten/issues/15186.
assert(cnd_wait(&cond, &mutex) == thrd_success);

printf("done!\n");
return 0;
}