Skip to content

Commit 97602fb

Browse files
committed
Handle exit() from pthread by posting exitProcess back to main thread
1 parent bd9b271 commit 97602fb

File tree

6 files changed

+68
-10
lines changed

6 files changed

+68
-10
lines changed

src/library_pthread.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -351,17 +351,18 @@ var LibraryPThread = {
351351
if (detached) {
352352
PThread.returnWorkerToPool(worker);
353353
}
354-
#if EXIT_RUNTIME // If building with -s EXIT_RUNTIME=0, no thread will post this message, so don't even compile it in.
355354
} else if (cmd === 'exitProcess') {
356355
// A pthread has requested to exit the whole application process (runtime).
356+
#if ASSERTIONS
357+
err("exitProcess requested by worker");
358+
#endif
357359
noExitRuntime = false;
358360
try {
359361
exit(d['returnCode']);
360362
} catch (e) {
361363
if (e instanceof ExitStatus) return;
362364
throw e;
363365
}
364-
#endif
365366
} else if (cmd === 'cancelDone') {
366367
PThread.returnWorkerToPool(worker);
367368
} else if (cmd === 'objectTransfer') {

src/postamble.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,16 @@ function checkUnflushedContent() {
363363

364364
/** @param {boolean|number=} implicit */
365365
function exit(status, implicit) {
366+
#if USE_PTHREADS
367+
if (ENVIRONMENT_IS_PTHREAD) {
368+
// When running in a pthread we propagate the exit out the worker.js where
369+
// it gets tranlated in an 'exitProcess' message on the main thread.
370+
// This allows the main thread to decide if (or if not) it wants to bring
371+
// down the entire application, based on the main thread's noExitRuntime.
372+
throw new ExitStatus(status);
373+
}
374+
#endif
375+
366376
#if ASSERTIONS
367377
#if EXIT_RUNTIME == 0
368378
checkUnflushedContent();

src/worker.js

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -193,8 +193,12 @@ this.onmessage = function(e) {
193193
#if STACK_OVERFLOW_CHECK
194194
Module['checkStackCookie']();
195195
#endif
196-
#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.
197-
// The thread might have finished without calling pthread_exit(). If so, then perform the exit operation ourselves.
196+
#if !MINIMAL_RUNTIME
197+
// In MINIMAL_RUNTIME the noExitRuntime concept does not apply to
198+
// pthreads. To exit a pthread with live runtime, use the function
199+
// emscripten_unwind_to_js_event_loop() in the pthread body.
200+
// The thread might have finished without calling pthread_exit(). If so,
201+
// then perform the exit operation ourselves.
198202
// (This is a no-op if explicit pthread_exit() had been called prior.)
199203
if (!Module['getNoExitRuntime']())
200204
#endif
@@ -213,14 +217,28 @@ this.onmessage = function(e) {
213217
throw ex;
214218
}
215219
#endif
216-
#if MINIMAL_RUNTIME
217220
// ExitStatus not present in MINIMAL_RUNTIME
218-
Module['PThread'].threadExit(-2);
219-
throw ex; // ExitStatus not present in MINIMAL_RUNTIME
220-
#else
221-
Module['PThread'].threadExit((ex instanceof Module['ExitStatus']) ? ex.status : -2);
222-
if (!(ex instanceof Module['ExitStatus'])) throw ex;
221+
#if !MINIMAL_RUNTIME
222+
if (ex instanceof Module['ExitStatus']) {
223+
if (Module['getNoExitRuntime']()) {
224+
#if ASSERTIONS
225+
err('Pthread 0x' + _pthread_self().toString(16) + ' called exit(), ignoring due to noExitRuntime.');
223226
#endif
227+
} else {
228+
#if ASSERTIONS
229+
err('Pthread 0x' + _pthread_self().toString(16) + ' called exit(), quitting process.');
230+
#endif
231+
// Send the exitProcess message back to main thread.
232+
// its then up to the main
233+
postMessage({ 'cmd': 'exitProcess', 'returnCode': ex.status });
234+
}
235+
}
236+
else
237+
#endif
238+
{
239+
Module['PThread'].threadExit(-2);
240+
throw ex;
241+
}
224242
#if ASSERTIONS
225243
} else {
226244
// else e == 'unwind', and we should fall through here and keep the pthread alive for asynchronous events.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#include <pthread.h>
2+
#include <stdlib.h>
3+
#include <stdio.h>
4+
5+
pthread_t t;
6+
7+
void* thread_main_exit(void* arg) {
8+
printf("calling exit\n");
9+
exit(42);
10+
}
11+
12+
int main() {
13+
printf("main\n");
14+
pthread_create(&t, NULL, thread_main_exit, NULL);
15+
pthread_join(t, NULL);
16+
printf("done join\n");
17+
return 0;
18+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
onExit status: 42

tests/test_core.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8090,6 +8090,16 @@ def test_pthread_exceptions(self):
80908090
self.emcc_args += ['-fexceptions']
80918091
self.do_run_in_out_file_test('tests', 'core', 'pthread', 'exceptions.cpp')
80928092

8093+
@node_pthreads
8094+
def test_pthread_exit_runtime(self):
8095+
self.add_pre_run("Module['onExit'] = function(status) { out('onExit status: ' + status); };")
8096+
self.set_setting('PROXY_TO_PTHREAD')
8097+
self.set_setting('PTHREAD_POOL_SIZE', '2')
8098+
self.do_run_in_out_file_test('tests', 'core', 'pthread', 'test_pthread_exit_runtime.c', assert_returncode=42)
8099+
8100+
self.set_setting('EXIT_RUNTIME')
8101+
self.do_run_in_out_file_test('tests', 'core', 'pthread', 'test_pthread_exit_runtime.c', assert_returncode=42)
8102+
80938103
def test_emscripten_atomics_stub(self):
80948104
self.do_run_in_out_file_test('tests', 'core', 'pthread', 'emscripten_atomics.c')
80958105

0 commit comments

Comments
 (0)