diff --git a/src/lib/libcore.js b/src/lib/libcore.js index 928777d174877..5be61f8984b79 100644 --- a/src/lib/libcore.js +++ b/src/lib/libcore.js @@ -113,6 +113,9 @@ addToLibrary({ #if PTHREADS '$exitOnMainThread', #endif +#if PROXY_TO_PTHREAD + '$proxiedMainDone', +#endif #if PTHREADS_DEBUG || ASSERTIONS '$runtimeKeepaliveCounter', #endif @@ -132,6 +135,10 @@ addToLibrary({ #endif #if PTHREADS_DEBUG dbg(`Pthread ${ptrToString(_pthread_self())} called exit(${status}), posting exitOnMainThread.`); +#endif +#if PROXY_TO_PTHREAD + // Forget a waiting main return. + proxiedMainDone = false; #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. @@ -2169,6 +2176,11 @@ addToLibrary({ #if PTHREADS '_emscripten_thread_exit', #endif +#if PROXY_TO_PTHREAD + '$proxiedMainDone', + '$proxiedMainExitCode', + '$exitOnMainThread', +#endif #if RUNTIME_DEBUG >= 2 '$runtimeKeepaliveCounter', #endif @@ -2192,6 +2204,14 @@ addToLibrary({ // exit the current thread, but only if there is one active. // TODO(https://github.com/emscripten-core/emscripten/issues/25076): // Unify this check with the runtimeExited check above +#if PROXY_TO_PTHREAD && EXIT_RUNTIME + // Run a waiting main return once. + if (proxiedMainDone) { + proxiedMainDone = false; + exitOnMainThread(proxiedMainExitCode); + return; + } +#endif if (_pthread_self()) __emscripten_thread_exit(EXITSTATUS); return; } diff --git a/src/lib/libpthread.js b/src/lib/libpthread.js index 7e68b6e14d7d8..38367fc030b87 100644 --- a/src/lib/libpthread.js +++ b/src/lib/libpthread.js @@ -986,6 +986,20 @@ var LibraryPThread = { _exit(returnCode); }, +#if PROXY_TO_PTHREAD + // Main's return, saved for maybeExit. + $proxiedMainDone__internal: true, + $proxiedMainDone: false, + $proxiedMainExitCode__internal: true, + $proxiedMainExitCode: 0, + + __emscripten_proxied_main_done__deps: ['$proxiedMainDone', '$proxiedMainExitCode'], + __emscripten_proxied_main_done: (status) => { + proxiedMainDone = true; + proxiedMainExitCode = status; + }, +#endif + #if MEMORY64 // Calls proxyToMainThread but returns a bigint rather than a number $proxyToMainThreadPtr__deps: ['$proxyToMainThread'], @@ -1144,6 +1158,10 @@ var LibraryPThread = { #if !MINIMAL_RUNTIME '$keepRuntimeAlive', '$runtimeKeepaliveCounter', +#endif +#if PROXY_TO_PTHREAD + '$proxiedMainDone', + '$proxiedMainExitCode', #endif ], $invokeEntryPoint: {{{ asyncIf(ASYNCIFY == 2) }}}(ptr, arg) => { @@ -1166,6 +1184,11 @@ var LibraryPThread = { noExitRuntime = 0; #endif #endif +#if PROXY_TO_PTHREAD + // No main return waiting yet. + proxiedMainDone = false; + proxiedMainExitCode = 0; +#endif #if MAIN_MODULE // Before we call the thread entry point, make sure any shared libraries diff --git a/system/lib/libc/crt1_proxy_main.c b/system/lib/libc/crt1_proxy_main.c index 2d178ca2e473a..e21c062dc7e11 100644 --- a/system/lib/libc/crt1_proxy_main.c +++ b/system/lib/libc/crt1_proxy_main.c @@ -32,6 +32,8 @@ static void* _main_thread(void* param) { if (!emscripten_runtime_keepalive_check()) { exit(rtn); } + // Wait for keepalives, then exit with main's status. + __emscripten_proxied_main_done(rtn); return NULL; } diff --git a/system/lib/pthread/threading_internal.h b/system/lib/pthread/threading_internal.h index 097595e3ba6b9..0e62c97081ac0 100644 --- a/system/lib/pthread/threading_internal.h +++ b/system/lib/pthread/threading_internal.h @@ -63,6 +63,8 @@ void _emscripten_init_main_thread_js(void* tb); void _emscripten_thread_profiler_enable(); void _emscripten_thread_cleanup(pthread_t thread); +void __emscripten_proxied_main_done(int status); + hidden void* _emscripten_tls_init(void); hidden void _emscripten_tls_free(void); diff --git a/test/other/test_proxied_main_keepalive_exit.c b/test/other/test_proxied_main_keepalive_exit.c new file mode 100644 index 0000000000000..949a89bde444a --- /dev/null +++ b/test/other/test_proxied_main_keepalive_exit.c @@ -0,0 +1,47 @@ +// Proxied main must exit with its status after keepalives run. +#include +#include +#include +#include + +void at_exit(void) { printf("done\n"); } + +#ifdef MODE_CLEARED +int long_id; + +void never(void* arg) { + printf("cleared callback ran\n"); + abort(); +} +#endif + +void fired(void* arg) { + printf("fired\n"); +#ifdef MODE_CLEARED + emscripten_clear_timeout(long_id); +#elif defined(MODE_FORCE_EXIT) + // exit() beats a waiting return. + emscripten_force_exit(7); +#elif defined(MODE_EXIT) + exit(7); +#endif +} + +int main(void) { +#ifdef MODE_NEGATIVE + // Check the value, not just the code. + MAIN_THREAD_EM_ASM({ Module['onExit'] = (c) => { out('exited:' + c); }; }); +#else + MAIN_THREAD_EM_ASM({ Module['onExit'] = () => { out('exited'); }; }); +#endif + atexit(at_exit); +#ifdef MODE_CLEARED + long_id = emscripten_set_timeout(never, 10000, NULL); +#endif + emscripten_set_timeout(fired, 10, NULL); +#ifdef MODE_NEGATIVE + return -1; +#else + return 3; +#endif +} diff --git a/test/test_other.py b/test/test_other.py index bd63374a7c13b..7eba2116a1916 100644 --- a/test/test_other.py +++ b/test/test_other.py @@ -11603,6 +11603,21 @@ def test_proxy_to_pthread_stack(self): '-sSTACK_SIZE=128kb', '-sEXIT_RUNTIME', '--profiling-funcs']) + @requires_pthreads + @parameterized({ + '': ([], 3, 'fired\ndone\nexited\n'), + 'cleared': (['-DMODE_CLEARED'], 3, 'fired\ndone\nexited\n'), + 'force_exit': (['-DMODE_FORCE_EXIT'], 7, 'fired\ndone\nexited\n'), + 'exit': (['-DMODE_EXIT'], 7, 'fired\ndone\nexited\n'), + 'negative': (['-DMODE_NEGATIVE'], NON_ZERO, 'fired\ndone\nexited:-1\n'), + }) + def test_proxied_main_keepalive_exit(self, cflags, returncode, expected): + # See https://github.com/emscripten-core/emscripten/issues/27721 + # Proxied main that returns with a keepalive must exit with its status. + self.do_runf('other/test_proxied_main_keepalive_exit.c', expected, + cflags=['-pthread', '-sPROXY_TO_PTHREAD', '-sEXIT_RUNTIME'] + cflags, + assert_returncode=returncode) + @crossplatform @no_windows('ptys and select are not available on windows') def test_color_diagnostics(self):