Skip to content

Don't mark a thread as joinable when it calls exit() #15105

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

Merged
merged 1 commit into from
Sep 24, 2021
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
3 changes: 3 additions & 0 deletions emcc.py
Original file line number Diff line number Diff line change
Expand Up @@ -1892,6 +1892,9 @@ def default_setting(name, new_default):
'_pthread_testcancel',
'_exit',
]
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += [
'$exitOnMainThread',
]
# Some of these symbols are using by worker.js but otherwise unreferenced.
# Because emitDCEGraph only considered the main js file, and not worker.js
# we have explicitly mark these symbols as user-exported so that they will
Expand Down
49 changes: 33 additions & 16 deletions src/library_pthread.js
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,12 @@ var LibraryPThread = {
#endif

terminateAllThreads: function() {
#if ASSERTIONS
assert(!ENVIRONMENT_IS_PTHREAD, 'Internal Error! terminateAllThreads() can only ever be called from main application thread!');
#endif
#if PTHREADS_DEBUG
err('terminateAllThreads');
#endif
for (var t in PThread.pthreads) {
var pthread = PThread.pthreads[t];
if (pthread && pthread.worker) {
Expand Down Expand Up @@ -277,20 +283,6 @@ var LibraryPThread = {
assert(detached);
#endif
PThread.returnWorkerToPool(worker);
} else if (cmd === 'exitProcess') {
// A pthread has requested to exit the whole application process (runtime).
#if ASSERTIONS
err("exitProcess requested by worker");
#endif
#if MINIMAL_RUNTIME
_exit(d['returnCode']);
#else
try {
_exit(d['returnCode']);
} catch (e) {
handleException(e);
}
#endif
} else if (cmd === 'cancelDone') {
PThread.returnWorkerToPool(worker);
} else if (e.data.target === 'setimmediate') {
Expand Down Expand Up @@ -458,6 +450,9 @@ var LibraryPThread = {

$killThread__desp: ['$freeThreadData'],
$killThread: function(pthread_ptr) {
#if PTHREADS_DEBUG
err('killThread 0x' + pthread_ptr.toString(16));
#endif
#if ASSERTIONS
assert(!ENVIRONMENT_IS_PTHREAD, 'Internal Error! killThread() can only ever be called from main application thread!');
assert(pthread_ptr, 'Internal Error! Null pthread_ptr in killThread!');
Expand Down Expand Up @@ -1191,6 +1186,7 @@ var LibraryPThread = {
return size <= 4 && (size & (size-1)) == 0 && (ptr&(size-1)) == 0;
},

__call_main__deps: ['exit', '$exitOnMainThread'],
__call_main: function(argc, argv) {
var returnCode = {{{ exportedAsmFunc('_main') }}}(argc, argv);
#if EXIT_RUNTIME
Expand All @@ -1199,8 +1195,7 @@ var LibraryPThread = {
#if PTHREADS_DEBUG
err('Proxied main thread 0x' + _pthread_self().toString(16) + ' finished with return code ' + returnCode + '. EXIT_RUNTIME=1 set, quitting process.');
#endif
postMessage({ 'cmd': 'exitProcess', 'returnCode': returnCode });
return returnCode;
exitOnMainThread(returnCode);
}
#else
// EXIT_RUNTIME==0 set on command line, keeping main thread alive.
Expand Down Expand Up @@ -1255,6 +1250,28 @@ var LibraryPThread = {
#endif
},

$exitOnMainThread__deps: ['exit',
#if !MINIMAL_RUNTIME
'$handleException',
#endif
],
$exitOnMainThread__proxy: 'async',
$exitOnMainThread: function(returnCode) {
// A pthread has requested to exit the whole application process (runtime).
#if PTHREADS_DEBUG
err('exitOnMainThread');
#endif
#if MINIMAL_RUNTIME
_exit(returnCode);
#else
try {
_exit(returnCode);
} catch (e) {
handleException(e);
}
#endif
},

emscripten_proxy_to_main_thread_js__deps: ['emscripten_run_in_main_runtime_thread_js'],
emscripten_proxy_to_main_thread_js__docs: '/** @type{function(number, (number|boolean), ...(number|boolean))} */',
emscripten_proxy_to_main_thread_js: function(index, sync) {
Expand Down
6 changes: 3 additions & 3 deletions src/postamble.js
Original file line number Diff line number Diff line change
Expand Up @@ -408,14 +408,14 @@ function exit(status, implicit) {
if (!implicit) {
if (ENVIRONMENT_IS_PTHREAD) {
#if PTHREADS_DEBUG
err('Pthread 0x' + _pthread_self().toString(16) + ' called exit(), posting exitProcess.');
err('Pthread 0x' + _pthread_self().toString(16) + ' called exit(), posting exitOnMainThread.');
#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);
exitOnMainThread(status);
throw 'unwind';
} else {
#if PTHREADS_DEBUG
err('main thread called exit: keepRuntimeAlive=' + keepRuntimeAlive() + ' (counter=' + runtimeKeepaliveCounter + ')');
Expand Down
4 changes: 4 additions & 0 deletions system/lib/libc/musl/include/unistd.h
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,11 @@ int eaccess(const char *, int);
#define _POSIX_THREAD_ATTR_STACKADDR _POSIX_VERSION
#define _POSIX_THREAD_ATTR_STACKSIZE _POSIX_VERSION
#define _POSIX_THREAD_PRIORITY_SCHEDULING _POSIX_VERSION
#ifdef __EMSCRIPTEN__
#define _POSIX_THREAD_CPUTIME -1
#else
#define _POSIX_THREAD_CPUTIME _POSIX_VERSION
#endif
#define _POSIX_TIMERS _POSIX_VERSION
#define _POSIX_TIMEOUTS _POSIX_VERSION
#define _POSIX_MONOTONIC_CLOCK _POSIX_VERSION
Expand Down
5 changes: 3 additions & 2 deletions system/lib/pthread/emscripten_proxy_main.c
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,13 @@
static int _main_argc;
static char** _main_argv;

extern int __call_main(int argc, char** argv);
extern void __call_main(int argc, char** argv);

static void* _main_thread(void* param) {
// This is the main runtime thread for the application.
emscripten_set_thread_name(pthread_self(), "Application main thread");
return (void*)__call_main(_main_argc, _main_argv);
__call_main(_main_argc, _main_argv);
return NULL;
}

int emscripten_proxy_main(int argc, char** argv) {
Expand Down
12 changes: 8 additions & 4 deletions tests/browser_reporting.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,15 @@ if (typeof window === 'object' && window) {
}

if (hasModule) {
Module['onExit'] = function(status) {
maybeReportResultToServer('exit:' + status);
if (!Module['onExit']) {
Module['onExit'] = function(status) {
maybeReportResultToServer('exit:' + status);
}
}

Module['onAbort'] = function(reason) {
maybeReportResultToServer('abort:' + reason);
if (!Module['onAbort']) {
Module['onAbort'] = function(reason) {
maybeReportResultToServer('abort:' + reason);
}
}
}
28 changes: 16 additions & 12 deletions tests/core/pthread/test_pthread_exit_runtime.c
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
#include <assert.h>
#include <stdatomic.h>
#include <stdbool.h>
#include <pthread.h>
#include <stdlib.h>
#include <stdio.h>
#include <emscripten/emscripten.h>

pthread_t t;

Expand All @@ -10,23 +13,24 @@ void* thread_main_exit(void* arg) {
exit(42);
}

// This location should never get set to true.
// We verify that it false from JS after the program exits.
atomic_bool join_returned = false;

EMSCRIPTEN_KEEPALIVE atomic_bool* join_returned_address() {
return &join_returned;
}

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
// pthread_join should never return becasue the runtime should
// exit first.
join_returned = true;
printf("done join %d -- should never get here\n", rc);
__builtin_trap();
}
23 changes: 16 additions & 7 deletions tests/core/pthread/test_pthread_exit_runtime.pre.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
Module.preRun = function() {
Module['onExit'] = function(status) {
out('onExit status: ' + status);
if (typeof reportResultToServer !== 'undefined') {
reportResultToServer('onExit status: ' + status);
}
};
var address = 0;

Module.onRuntimeInitialized = function() {
address = Module['_join_returned_address']();
assert(address);
assert(HEAP8[address] == 0);
}

Module.onExit = function(status) {
out('onExit status: ' + status);
// Verify that the join never returned
assert(address);
assert(HEAP8[address] == 0, 'pthread_join should not have returned!');
if (typeof reportResultToServer !== 'undefined') {
reportResultToServer('onExit status: ' + status);
}
};
11 changes: 0 additions & 11 deletions tests/test_browser.py
Original file line number Diff line number Diff line change
Expand Up @@ -4146,17 +4146,6 @@ def test_pthread_exit_process(self):
args += ['--pre-js', test_file('core/pthread/test_pthread_exit_runtime.pre.js')]
self.btest(test_file('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',
'-s', 'PROXY_TO_PTHREAD',
'-s', 'PTHREAD_POOL_SIZE=2',
'-O0']
args += ['--pre-js', test_file('core/pthread/test_pthread_exit_runtime.pre.js')]
self.btest(test_file('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_exit(test_file('core/test_em_asm_signatures.cpp'), assert_returncode=121, args=[])
Expand Down
8 changes: 0 additions & 8 deletions tests/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -8382,14 +8382,6 @@ def test_pthread_exit_process(self):
self.emcc_args += ['-DEXIT_RUNTIME', '--pre-js', test_file('core/pthread/test_pthread_exit_runtime.pre.js')]
self.do_run_in_out_file_test('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.emcc_args += ['--pre-js', test_file('core/pthread/test_pthread_exit_runtime.pre.js')]
self.do_run_in_out_file_test('core/pthread/test_pthread_exit_runtime.c', assert_returncode=43)

@node_pthreads
@no_wasm2js('wasm2js does not support PROXY_TO_PTHREAD (custom section support)')
def test_pthread_offset_converter(self):
Expand Down
9 changes: 9 additions & 0 deletions tests/test_posixtest.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ def get_pthread_tests():
}

unsupported = {
'test_pthread_attr_setinheritsched_2_2': 'scheduling policy/parameters are not supported',
'test_pthread_attr_setinheritsched_2_3': 'scheduling policy/parameters are not supported',
'test_pthread_attr_setinheritsched_2_3': 'scheduling policy/parameters are not supported',
'test_pthread_attr_setinheritsched_2_4': 'scheduling policy/parameters are not supported',
'test_pthread_attr_setschedparam_1_3': 'scheduling policy/parameters are not supported',
Expand All @@ -89,7 +91,10 @@ def get_pthread_tests():
'test_pthread_create_8_1': 'signals are not supported',
'test_pthread_create_8_2': 'signals are not supported',
'test_pthread_create_10_1': 'signals are not supported',
'test_pthread_create_11_1': '_POSIX_THREAD_CPUTIME not supported',
'test_pthread_getcpuclockid_1_1': 'pthread_getcpuclockid not supported',
'test_pthread_getschedparam_1_3': 'scheduling policy/parameters are not supported',
'test_pthread_getschedparam_1_2': 'scheduling policy/parameters are not supported',
'test_pthread_kill_1_2': 'signals are not supported',
'test_pthread_mutexattr_getprioceiling_1_2': 'pthread_mutexattr_setprioceiling is not supported',
'test_pthread_mutexattr_getprotocol_1_2': 'pthread_mutexattr_setprotocol is not supported',
Expand All @@ -111,6 +116,10 @@ def get_pthread_tests():
'test_pthread_rwlock_timedwrlock_6_1': 'signals are not supported',
'test_pthread_rwlock_timedwrlock_6_2': 'signals are not supported',
'test_pthread_rwlock_wrlock_2_1': 'signals are not supported',
'test_pthread_setschedparam_1_1': 'scheduling policy/parameters are not supported',
'test_pthread_setschedparam_1_2': 'scheduling policy/parameters are not supported',
'test_pthread_setschedparam_4_1': 'scheduling policy/parameters are not supported',
'test_pthread_setschedprio_1_1': 'scheduling policy/parameters are not supported',
'test_pthread_spin_init_2_1': 'shm_open and shm_unlink are not supported',
'test_pthread_spin_init_2_2': 'shm_open and shm_unlink are not supported',
}
Expand Down