Skip to content
Merged
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
6 changes: 3 additions & 3 deletions src/library_pthread.js
Original file line number Diff line number Diff line change
Expand Up @@ -351,17 +351,17 @@ var LibraryPThread = {
if (detached) {
PThread.returnWorkerToPool(worker);
}
#if EXIT_RUNTIME // If building with -s EXIT_RUNTIME=0, no thread will post this message, so don't even compile it in.
} else if (cmd === 'exitProcess') {
// A pthread has requested to exit the whole application process (runtime).
noExitRuntime = false;
#if ASSERTIONS
err("exitProcess requested by worker");
#endif
try {
exit(d['returnCode']);
} catch (e) {
if (e instanceof ExitStatus) return;
throw e;
}
#endif
} else if (cmd === 'cancelDone') {
PThread.returnWorkerToPool(worker);
} else if (cmd === 'objectTransfer') {
Expand Down
34 changes: 32 additions & 2 deletions src/postamble.js
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,26 @@ function exit(status, implicit) {
return;
}

#if USE_PTHREADS
if (!implicit) {
if (ENVIRONMENT_IS_PTHREAD) {
#if ASSERTIONS
err('Pthread 0x' + _pthread_self().toString(16) + ' called exit(), posting exitProcess.');
#endif
// When running in a pthread we propagate the exit back to the main thread
// where it can decide if the whole process should be shut down or not.
// The pthread may have decided not to exit its own runtime, for example
// because it runs a main loop, but that doesn't affect the main thread.
postMessage({ 'cmd': 'exitProcess', 'returnCode': status });
throw new ExitStatus(status);
} else {
#if ASSERTIONS
err('main thead called exit: noExitRuntime=' + noExitRuntime);
#endif
}
}
#endif

if (noExitRuntime) {
#if ASSERTIONS
// if exit() was called, we may warn the user if the runtime isn't actually being shut down
Expand Down Expand Up @@ -436,9 +456,19 @@ if (Module['noInitialRun']) shouldRunNow = false;

#if EXIT_RUNTIME == 0
#if USE_PTHREADS
if (!ENVIRONMENT_IS_PTHREAD) // EXIT_RUNTIME=0 only applies to default behavior of the main browser thread
// EXIT_RUNTIME=0 only applies to the default behavior of the main browser
// thread.
// The default behaviour for pthreads is always to exit once they return
// from their entry point (or call pthread_exit). If we set noExitRuntime
// to true here on pthreads they would never complete and attempt to
// pthread_join to them would block forever.
// pthreads can still choose to set `noExitRuntime` explicitly, or
// call emscripten_unwind_to_js_event_loop to extend their lifetime beyond
// their main function. See comment in src/worker.js for more.
noExitRuntime = !ENVIRONMENT_IS_PTHREAD;
#else
noExitRuntime = true;
#endif
noExitRuntime = true;
#endif

#if USE_PTHREADS
Expand Down
32 changes: 24 additions & 8 deletions src/worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -193,8 +193,12 @@ this.onmessage = function(e) {
#if STACK_OVERFLOW_CHECK
Module['checkStackCookie']();
#endif
#if !MINIMAL_RUNTIME // In MINIMAL_RUNTIME the noExitRuntime concept does not apply to pthreads. To exit a pthread with live runtime, use the function emscripten_unwind_to_js_event_loop() in the pthread body.
// The thread might have finished without calling pthread_exit(). If so, then perform the exit operation ourselves.
#if !MINIMAL_RUNTIME
// In MINIMAL_RUNTIME the noExitRuntime concept does not apply to
// pthreads. To exit a pthread with live runtime, use the function
// emscripten_unwind_to_js_event_loop() in the pthread body.
// The thread might have finished without calling pthread_exit(). If so,
// then perform the exit operation ourselves.
// (This is a no-op if explicit pthread_exit() had been called prior.)
if (!Module['getNoExitRuntime']())
#endif
Expand All @@ -213,14 +217,26 @@ this.onmessage = function(e) {
throw ex;
}
#endif
#if MINIMAL_RUNTIME
// ExitStatus not present in MINIMAL_RUNTIME
Module['PThread'].threadExit(-2);
throw ex; // ExitStatus not present in MINIMAL_RUNTIME
#else
Module['PThread'].threadExit((ex instanceof Module['ExitStatus']) ? ex.status : -2);
if (!(ex instanceof Module['ExitStatus'])) throw ex;
#if !MINIMAL_RUNTIME
if (ex instanceof Module['ExitStatus']) {
if (Module['getNoExitRuntime']()) {
#if ASSERTIONS
err('Pthread 0x' + _pthread_self().toString(16) + ' called exit(), staying alive due to noExitRuntime.');
#endif
} else {
#if ASSERTIONS
err('Pthread 0x' + _pthread_self().toString(16) + ' called exit(), calling threadExit.');
#endif
Module['PThread'].threadExit(ex.status);
}
}
else
#endif
{
Module['PThread'].threadExit(-2);
throw ex;
}
#if ASSERTIONS
} else {
// else e == 'unwind', and we should fall through here and keep the pthread alive for asynchronous events.
Expand Down
32 changes: 32 additions & 0 deletions tests/core/pthread/test_pthread_exit_runtime.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#include <assert.h>
#include <pthread.h>
#include <stdlib.h>
#include <stdio.h>

pthread_t t;

void* thread_main_exit(void* arg) {
printf("calling exit\n");
exit(42);
}

int main() {
printf("main\n");
int rc = pthread_create(&t, NULL, thread_main_exit, NULL);
assert(rc == 0);
void* thread_rtn = 0;
rc = pthread_join(t, &thread_rtn);
assert(rc == 0);
#if EXIT_RUNTIME
printf("done join -- should never get here\n");
return 1;
#else
// Since EXIT_RUNTIME is not set the exit() in the thread is not expected to
// bring down the whole process, only itself.
printf("done join -- thread exited with %ld\n", (intptr_t)thread_rtn);
#ifdef REPORT_RESULT
REPORT_RESULT(43);
#endif
return 43;
#endif
}
1 change: 1 addition & 0 deletions tests/core/pthread/test_pthread_exit_runtime.out
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
onExit status: 42
8 changes: 8 additions & 0 deletions tests/core/pthread/test_pthread_exit_runtime.pre.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Module.preRun = function() {
Module['onExit'] = function(status) {
out('onExit status: ' + status);
if (typeof reportResultToServer !== 'undefined') {
reportResultToServer('onExit status: ' + status);
}
};
}
22 changes: 22 additions & 0 deletions tests/test_browser.py
Original file line number Diff line number Diff line change
Expand Up @@ -3963,6 +3963,28 @@ def test_pthread_asan(self, name, args=[]):
def test_pthread_asan_use_after_free(self):
self.btest(path_from_root('tests', 'pthread', 'test_pthread_asan_use_after_free.cpp'), expected='1', args=['-fsanitize=address', '-s', 'INITIAL_MEMORY=256MB', '-s', 'USE_PTHREADS', '-s', 'PROXY_TO_PTHREAD', '--pre-js', path_from_root('tests', 'pthread', 'test_pthread_asan_use_after_free.js')])

@requires_threads
def test_pthread_exit_process(self):
args = ['-s', 'USE_PTHREADS=1',
'-s', 'PROXY_TO_PTHREAD',
'-s', 'PTHREAD_POOL_SIZE=2',
'-s', 'EXIT_RUNTIME',
'-DEXIT_RUNTIME',
'-O0']
args += ['--pre-js', path_from_root('tests', 'core', 'pthread', 'test_pthread_exit_runtime.pre.js')]
self.btest(path_from_root('tests', 'core', 'pthread', 'test_pthread_exit_runtime.c'), expected='onExit status: 42', args=args)

@requires_threads
def test_pthread_no_exit_process(self):
# Same as above but without EXIT_RUNTIME. In this case we don't expect onExit to
# ever be called.
args = ['-s', 'USE_PTHREADS=1',
'-s', 'PROXY_TO_PTHREAD',
'-s', 'PTHREAD_POOL_SIZE=2',
'-O0']
args += ['--pre-js', path_from_root('tests', 'core', 'pthread', 'test_pthread_exit_runtime.pre.js')]
self.btest(path_from_root('tests', 'core', 'pthread', 'test_pthread_exit_runtime.c'), expected='43', args=args)

# Tests MAIN_THREAD_EM_ASM_INT() function call signatures.
def test_main_thread_em_asm_signatures(self):
self.btest(path_from_root('tests', 'core', 'test_em_asm_signatures.cpp'), expected='121', args=[])
Expand Down
17 changes: 17 additions & 0 deletions tests/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -8090,6 +8090,23 @@ def test_pthread_exceptions(self):
self.emcc_args += ['-fexceptions']
self.do_run_in_out_file_test('tests', 'core', 'pthread', 'exceptions.cpp')

@node_pthreads
def test_pthread_exit_process(self):
self.set_setting('PROXY_TO_PTHREAD')
self.set_setting('PTHREAD_POOL_SIZE', '2')
self.set_setting('EXIT_RUNTIME')
self.emcc_args += ['--pre-js', path_from_root('tests', 'core', 'pthread', 'test_pthread_exit_runtime.pre.js')]
self.do_run_in_out_file_test('tests', 'core', 'pthread', 'test_pthread_exit_runtime.c', assert_returncode=42)

@node_pthreads
@disabled('https://github.com/emscripten-core/emscripten/issues/12945')
def test_pthread_no_exit_process(self):
# Same as above but without EXIT_RUNTIME
self.set_setting('PROXY_TO_PTHREAD')
self.set_setting('PTHREAD_POOL_SIZE', '2')
self.emcc_args += ['--pre-js', path_from_root('tests', 'core', 'pthread', 'test_pthread_exit_runtime.pre.js')]
self.do_run_in_out_file_test('tests', 'core', 'pthread', 'test_pthread_exit_runtime.c', assert_returncode=43)

def test_emscripten_atomics_stub(self):
self.do_run_in_out_file_test('tests', 'core', 'pthread', 'emscripten_atomics.c')

Expand Down