Skip to content

Commit ff7ac7b

Browse files
Macilkripken
andauthored
Fix multithreading to work in Deno and Bun (#25947)
Projects using Emscripten with pthreads don't work in Deno or Bun because those runtimes implement Web Worker APIs that Node doesn't, and Emscripten attempts to re-implement those APIs when running in Node/Deno/Bun in a way that conflicts with Deno and Bun's own implementations. This PR feature-detects Deno and Bun's `postMessage` implementation and avoids setting up its own conflicting implementation in that case. Fixes: denoland/deno#17171 --------- Co-authored-by: Alon Zakai <[email protected]>
1 parent c9ade8c commit ff7ac7b

File tree

7 files changed

+48
-27
lines changed

7 files changed

+48
-27
lines changed

.circleci/config.yml

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -873,6 +873,8 @@ jobs:
873873
- upload-test-results
874874
test-bun:
875875
executor: linux-python
876+
environment:
877+
OVERRIDE_NODE_JS_TEST_VERSION: "v24.0.0"
876878
steps:
877879
- checkout
878880
- pip-install
@@ -881,10 +883,14 @@ jobs:
881883
name: install bun
882884
command: |
883885
curl -fsSL https://bun.com/install | bash
884-
echo "BUN_ENGINE = os.path.expanduser('~/.bun/bin/bun')" >> ~/emsdk/.emscripten
885-
echo "JS_ENGINES = [BUN_ENGINE]" >> ~/emsdk/.emscripten
886+
echo "NODE_JS_TEST = os.path.expanduser('~/.bun/bin/bun')" >> ~/emsdk/.emscripten
887+
echo "JS_ENGINES = [NODE_JS_TEST]" >> ~/emsdk/.emscripten
886888
- run-tests:
887-
test_targets: "core0.test_hello_world"
889+
test_targets: "
890+
core0.test_hello_world
891+
core0.test_hello_argc_pthreads
892+
core2.test_pthread_create
893+
"
888894
test-jsc:
889895
executor: linux-python
890896
steps:

src/pthread_esm_startup.mjs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,17 @@ console.log("Running pthread_esm_startup");
1616
#if ENVIRONMENT_MAY_BE_NODE
1717
if ({{{ nodeDetectionCode() }}}) {
1818
// Create as web-worker-like an environment as we can.
19+
globalThis.self = globalThis;
1920
var worker_threads = await import('worker_threads');
20-
global.Worker = worker_threads.Worker;
21+
globalThis.Worker = worker_threads.Worker;
2122
var parentPort = worker_threads['parentPort'];
22-
parentPort.on('message', (msg) => global.onmessage?.({ data: msg }));
23-
Object.assign(globalThis, {
24-
self: global,
25-
postMessage: (msg) => parentPort['postMessage'](msg),
26-
});
23+
// Deno and Bun already have `postMessage` defined on the global scope and
24+
// deliver messages to `globalThis.onmessage`, so we must not duplicate that
25+
// behavior here if `postMessage` is already present.
26+
if (!globalThis.postMessage) {
27+
parentPort.on('message', (msg) => globalThis.onmessage?.({ data: msg }));
28+
globalThis.postMessage = (msg) => parentPort['postMessage'](msg);
29+
}
2730
}
2831
#endif
2932

src/runtime_common.js

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,15 @@ var readyPromiseResolve, readyPromiseReject;
3838
#if (PTHREADS || WASM_WORKERS) && (ENVIRONMENT_MAY_BE_NODE && !WASM_ESM_INTEGRATION)
3939
if (ENVIRONMENT_IS_NODE && {{{ ENVIRONMENT_IS_WORKER_THREAD() }}}) {
4040
// Create as web-worker-like an environment as we can.
41+
globalThis.self = globalThis;
4142
var parentPort = worker_threads['parentPort'];
42-
parentPort.on('message', (msg) => global.onmessage?.({ data: msg }));
43-
Object.assign(globalThis, {
44-
self: global,
45-
postMessage: (msg) => parentPort['postMessage'](msg),
46-
});
43+
// Deno and Bun already have `postMessage` defined on the global scope and
44+
// deliver messages to `globalThis.onmessage`, so we must not duplicate that
45+
// behavior here if `postMessage` is already present.
46+
if (!globalThis.postMessage) {
47+
parentPort.on('message', (msg) => globalThis.onmessage?.({ data: msg }));
48+
globalThis.postMessage = (msg) => parentPort['postMessage'](msg);
49+
}
4750
// Node.js Workers do not pass postMessage()s and uncaught exception events to the parent
4851
// thread necessarily in the same order where they were generated in sequential program order.
4952
// See https://github.com/nodejs/node/issues/59617

test/codesize/test_codesize_minimal_pthreads.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
{
2-
"a.out.js": 7694,
3-
"a.out.js.gz": 3815,
2+
"a.out.js": 7722,
3+
"a.out.js.gz": 3812,
44
"a.out.nodebug.wasm": 19604,
55
"a.out.nodebug.wasm.gz": 9079,
6-
"total": 27298,
7-
"total_gz": 12894,
6+
"total": 27326,
7+
"total_gz": 12891,
88
"sent": [
99
"a (memory)",
1010
"b (emscripten_get_now)",

test/codesize/test_codesize_minimal_pthreads_memgrowth.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
{
2-
"a.out.js": 8116,
3-
"a.out.js.gz": 4015,
2+
"a.out.js": 8145,
3+
"a.out.js.gz": 4011,
44
"a.out.nodebug.wasm": 19605,
55
"a.out.nodebug.wasm.gz": 9080,
6-
"total": 27721,
7-
"total_gz": 13095,
6+
"total": 27750,
7+
"total_gz": 13091,
88
"sent": [
99
"a (memory)",
1010
"b (emscripten_get_now)",

test/common.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -390,6 +390,15 @@ def require_node(self):
390390
self.require_engine(nodejs)
391391
return nodejs
392392

393+
def get_node_test_version(self, nodejs):
394+
override = os.environ.get('OVERRIDE_NODE_JS_TEST_VERSION')
395+
if override:
396+
override = override.removeprefix('v')
397+
override = override.split('-')[0].split('.')
398+
override = tuple(int(v) for v in override)
399+
return override
400+
return shared.get_node_version(nodejs)
401+
393402
def node_is_canary(self, nodejs):
394403
return nodejs and nodejs[0] and ('canary' in nodejs[0] or 'nightly' in nodejs[0])
395404

@@ -432,7 +441,7 @@ def try_require_node_version(self, major, minor = 0, revision = 0):
432441
nodejs = self.get_nodejs()
433442
if not nodejs:
434443
self.skipTest('Test requires nodejs to run')
435-
version = shared.get_node_version(nodejs)
444+
version = self.get_node_test_version(nodejs)
436445
if version < (major, minor, revision):
437446
return False
438447

@@ -617,7 +626,7 @@ def setUp(self):
617626

618627
nodejs = self.get_nodejs()
619628
if nodejs:
620-
node_version = shared.get_node_version(nodejs)
629+
node_version = self.get_node_test_version(nodejs)
621630
if node_version < (13, 0, 0):
622631
self.node_args.append('--unhandled-rejections=strict')
623632
elif node_version < (15, 0, 0):

test/test_other.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12360,7 +12360,7 @@ def test_gen_struct_info(self):
1236012360
self.assertFileContents(path_from_root('src/struct_info_generated.json'), read_file('out.json'))
1236112361

1236212362
# Same again for wasm64
12363-
node_version = shared.get_node_version(self.get_nodejs())
12363+
node_version = self.get_node_test_version(self.get_nodejs())
1236412364
if node_version and node_version >= (14, 0, 0):
1236512365
self.run_process([PYTHON, path_from_root('tools/gen_struct_info.py'), '--wasm64', '-o', 'out.json'])
1236612366
self.assertFileContents(path_from_root('src/struct_info_generated_wasm64.json'), read_file('out.json'))
@@ -12873,7 +12873,7 @@ def test_node_unhandled_rejection(self):
1287312873
self.build('main.c', cflags=['--pre-js=pre.js', '-sNODEJS_CATCH_REJECTION=0'])
1287412874
self.assertNotContained('unhandledRejection', read_file('main.js'))
1287512875

12876-
if shared.get_node_version(self.get_nodejs())[0] >= 15:
12876+
if self.get_node_test_version(self.get_nodejs())[0] >= 15:
1287712877
self.skipTest('old behaviour of node JS cannot be tested on node v15 or above')
1287812878

1287912879
output = self.run_js('main.js')
@@ -13952,7 +13952,7 @@ def test_parseTools_legacy(self):
1395213952

1395313953
@requires_node
1395413954
def test_min_node_version(self):
13955-
node_version = shared.get_node_version(self.get_nodejs())
13955+
node_version = self.get_node_test_version(self.get_nodejs())
1395613956
node_version = '.'.join(str(x) for x in node_version)
1395713957
self.set_setting('MIN_NODE_VERSION', 300000)
1395813958
expected = 'This emscripten-generated code requires node v30.0.0 (detected v%s' % node_version

0 commit comments

Comments
 (0)