Skip to content

Commit

Permalink
Handle thread crash/abort even when main thread is blocking (busy-wai…
Browse files Browse the repository at this point in the history
…ting). NFC

Previously if a thread crashed when the main thread was blocked on
something (such as pthread_join) it would never get the `onerror`
message because it would never return the event loop.
  • Loading branch information
sbc100 committed Feb 2, 2022
1 parent e002f7f commit 22f0d22
Show file tree
Hide file tree
Showing 12 changed files with 69 additions and 3 deletions.
1 change: 1 addition & 0 deletions emcc.py
Original file line number Diff line number Diff line change
Expand Up @@ -2052,6 +2052,7 @@ def default_setting(name, new_default):
worker_imports = [
'__emscripten_thread_init',
'__emscripten_thread_exit',
'__emscripten_thread_crashed',
'_emscripten_tls_init',
'_emscripten_current_thread_process_queued_calls',
'_pthread_self',
Expand Down
3 changes: 3 additions & 0 deletions src/worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,9 @@ self.onmessage = (e) => {
} catch(ex) {
err('worker.js onmessage() captured an uncaught exception: ' + ex);
if (ex && ex.stack) err(ex.stack);
if (Module['__emscripten_thread_crashed']) {
Module['__emscripten_thread_crashed']();
}
throw ex;
}
};
20 changes: 20 additions & 0 deletions system/lib/pthread/emscripten_futex_wait.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
#include <errno.h>
#include <math.h>
#include <emscripten/threading.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
#include "atomic.h"
#include "threading_internal.h"

Expand Down Expand Up @@ -106,11 +109,28 @@ static int futex_wait_busy(volatile void *addr, uint32_t val, double timeout) {
return 0;
}

static _Atomic bool thread_crashed = false;

void _emscripten_thread_crashed() {
thread_crashed = true;
}

int emscripten_futex_wait(volatile void *addr, uint32_t val, double max_wait_ms) {
if ((((intptr_t)addr)&3) != 0) {
return -EINVAL;
}

// When a secondary thread crashes, we need to be able to interrupt the main
// thread even if it's in a blocking/looping on a mutex. We want to avoid
// using the normal proxying mechanism to send this message since it can
// allocate (or otherwise itself crash) so use a low level atomic primitive
// for this signal.
if (emscripten_is_main_runtime_thread() && thread_crashed) {
// Return the event loop so we can handle the message from the crashed
// thread.
emscripten_unwind_to_js_event_loop();
}

int ret;
emscripten_conditional_set_current_thread_status(EM_THREAD_STATUS_RUNNING, EM_THREAD_STATUS_WAITFUTEX);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ F
G
H
I
o
J
K
p
q
r
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ $__wasm_call_ctors
$__wasm_init_memory
$_do_call
$_emscripten_do_dispatch_to_thread
$_emscripten_thread_crashed
$_emscripten_thread_exit
$_emscripten_thread_free_data
$_emscripten_thread_init
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ a.k
a.l
a.m
a.n
a.o
Original file line number Diff line number Diff line change
@@ -1 +1 @@
47185
47544
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ k
l
m
n
o
Original file line number Diff line number Diff line change
@@ -1 +1 @@
17498
17545
31 changes: 31 additions & 0 deletions tests/pthread/test_pthread_abort_interrupt.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright 2022 The Emscripten Authors. All rights reserved.
// Emscripten is available under two separate licenses, the MIT license and the
// University of Illinois/NCSA Open Source License. Both these licenses can be
// found in the LICENSE file.

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

// Test that when a thread aborts the whole application crashes
// even when the main thread is blocking in pthread_join and
// not returning the event loop.

void* thread_start(void* arg) {
puts("thread_start");
abort();
// Should never get here
__builtin_trap();
return NULL;
}

int main() {
puts("in main");
pthread_t t;
pthread_create(&t, NULL, thread_start, NULL);
puts("calling join");
pthread_join(t, NULL);
// Should never get here
__builtin_trap();
}

1 change: 1 addition & 0 deletions tests/pthread/test_pthread_abort_interrupt.out
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Aborted(native code called abort())
6 changes: 6 additions & 0 deletions tests/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -2560,6 +2560,12 @@ def test_pthread_abort(self):
self.add_pre_run("Module.onAbort = function() { console.log('onAbort called'); }")
self.do_run_in_out_file_test('pthread/test_pthread_abort.c', assert_returncode=NON_ZERO)

@node_pthreads
def test_pthread_abort_interrupt(self):
self.set_setting('EXIT_RUNTIME')
self.set_setting('PTHREAD_POOL_SIZE', 1)
self.do_run_in_out_file_test('pthread/test_pthread_abort_interrupt.c', assert_returncode=NON_ZERO)

@no_asan('ASan does not support custom memory allocators')
@no_lsan('LSan does not support custom memory allocators')
@node_pthreads
Expand Down

0 comments on commit 22f0d22

Please sign in to comment.