diff --git a/src/lib/libpthread.js b/src/lib/libpthread.js index 47c3b4eb6a919..e251f4508ba23 100644 --- a/src/lib/libpthread.js +++ b/src/lib/libpthread.js @@ -241,6 +241,9 @@ var LibraryPThread = { worker.onmessage = (e) => { var d = e['data']; var cmd = d.cmd; +#if PTHREADS_DEBUG + dbg(`main thread: received message '${cmd}' from worker. ${d}`); +#endif // If this message is intended to a recipient that is not the main // thread, forward it to the target thread. @@ -283,6 +286,13 @@ var LibraryPThread = { // Worker wants to postMessage() to itself to implement setImmediate() // emulation. worker.postMessage(d); +#if ENVIRONMENT_MAY_BE_NODE + } else if (cmd === 'uncaughtException') { + // Message handler for Node.js specific out-of-order behavior: + // https://github.com/nodejs/node/issues/59617 + // A pthread sent an uncaught exception event. Re-raise it on the main thread. + worker.onerror(d.error); +#endif } else if (cmd === 'callHandler') { Module[d.handler](...d.args); } else if (cmd) { @@ -308,6 +318,13 @@ var LibraryPThread = { if (ENVIRONMENT_IS_NODE) { worker.on('message', (data) => worker.onmessage({ data: data })); worker.on('error', (e) => worker.onerror(e)); + +#if PTHREADS_DEBUG + worker.on('exit', (code) => { + if (worker.pthread_ptr) dbg(`Worker hosting pthread ${ptrToString(worker.pthread_ptr)} has terminated with code ${code}.`); + else dbg(`Worker has terminated with code ${code}.`); + }); +#endif } #endif diff --git a/src/runtime_common.js b/src/runtime_common.js index 59f4d918bd927..9e00b0ac8b544 100644 --- a/src/runtime_common.js +++ b/src/runtime_common.js @@ -40,6 +40,21 @@ if (ENVIRONMENT_IS_NODE && {{{ ENVIRONMENT_IS_WORKER_THREAD() }}}) { self: global, postMessage: (msg) => parentPort['postMessage'](msg), }); + // Node.js Workers do not pass postMessage()s and uncaught exception events to the parent + // thread necessarily in the same order where they were generated in sequential program order. + // See https://github.com/nodejs/node/issues/59617 + // To remedy this, capture all uncaughtExceptions in the Worker, and sequentialize those over + // to the same postMessage pipe that other messages use. + process.on("uncaughtException", (err) => { +#if PTHREADS_DEBUG + dbg(`uncaughtException on worker thread: ${err.message}`); +#endif + postMessage({ cmd: 'uncaughtException', error: err }); + // Also shut down the Worker to match the same semantics as if this uncaughtException + // handler was not registered. + // (n.b. this will not shut down the whole Node.js app process, but just the Worker) + process.exit(1); + }); } #endif // (PTHREADS || WASM_WORKERS) && (ENVIRONMENT_MAY_BE_NODE && !WASM_ESM_INTEGRATION) diff --git a/test/code_size/test_codesize_minimal_pthreads.json b/test/code_size/test_codesize_minimal_pthreads.json index e277ff4d8f5fc..d2d287006e49b 100644 --- a/test/code_size/test_codesize_minimal_pthreads.json +++ b/test/code_size/test_codesize_minimal_pthreads.json @@ -1,10 +1,10 @@ { - "a.out.js": 7499, - "a.out.js.gz": 3721, + "a.out.js": 7649, + "a.out.js.gz": 3768, "a.out.nodebug.wasm": 19588, "a.out.nodebug.wasm.gz": 9025, - "total": 27087, - "total_gz": 12746, + "total": 27237, + "total_gz": 12793, "sent": [ "a (memory)", "b (emscripten_get_now)", diff --git a/test/code_size/test_codesize_minimal_pthreads_memgrowth.json b/test/code_size/test_codesize_minimal_pthreads_memgrowth.json index cbe886f575601..3f98de951b241 100644 --- a/test/code_size/test_codesize_minimal_pthreads_memgrowth.json +++ b/test/code_size/test_codesize_minimal_pthreads_memgrowth.json @@ -1,10 +1,10 @@ { - "a.out.js": 7926, - "a.out.js.gz": 3924, + "a.out.js": 8076, + "a.out.js.gz": 3974, "a.out.nodebug.wasm": 19589, "a.out.nodebug.wasm.gz": 9025, - "total": 27515, - "total_gz": 12949, + "total": 27665, + "total_gz": 12999, "sent": [ "a (memory)", "b (emscripten_get_now)", diff --git a/test/test_core.py b/test/test_core.py index cadc2f0c4fdcd..027aa33beee58 100644 --- a/test/test_core.py +++ b/test/test_core.py @@ -24,7 +24,7 @@ from tools import shared, building, config, utils, webassembly import common from common import RunnerCore, path_from_root, requires_native_clang, test_file, create_file -from common import skip_if, no_windows, no_mac, is_slow_test, parameterized, parameterize +from common import skip_if, no_windows, is_slow_test, parameterized, parameterize from common import env_modify, with_env_modify, disabled, flaky, node_pthreads, also_without_bigint from common import read_file, read_binary, requires_v8, requires_node, requires_dev_dependency, requires_wasm2js, requires_node_canary from common import compiler_for, crossplatform, no_4gb, no_2gb, also_with_minimal_runtime, also_with_modularize @@ -2669,8 +2669,6 @@ def test_pthread_attr_getstack(self): self.do_run_in_out_file_test('pthread/test_pthread_attr_getstack.c') @node_pthreads - @no_mac('https://github.com/emscripten-core/emscripten/issues/15014') - @flaky('https://github.com/emscripten-core/emscripten/issues/15014') def test_pthread_abort(self): self.set_setting('PROXY_TO_PTHREAD') # Add the onAbort handler at runtime during preRun. This means that onAbort