Repository navigation
[browser][coreCLR] corerun: exit Node naturally after main returns - #136897
Merged
Merged
Conversation
process.exit() joins V8's platform worker threads without disposing the isolate. When a background compile job is parked waiting for a main-thread GC, that join never returns and Node hangs after the test has finished (nodejs/node#64274). This intermittently times out runtime tests on the browser-wasm legs, for example JIT/jit64/regress/vsw/539509 after it prints "Passed.". Keep the hard process.exit() only for exits requested from inside wasm, such as Environment.Exit, which must not unwind ExitStatus through live CLR frames. Otherwise let Node exit once the event loop drains, which disposes the isolate and releases the parked worker. Ignore the late exit that SystemJS_ResolveMainPromise queues for corerun once the runtime has exited. Fixes #135042 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
|
Azure Pipelines: Successfully started running 3 pipeline(s). 12 pipeline(s) were filtered out due to trigger conditions. There may be pipelines that require an authorized user to comment /azp run to run. |
Contributor
|
Tagging subscribers to this area: @agocke |
Contributor
There was a problem hiding this comment.
🔵 Needs a closer look
Source-only reviews could not verify shutdown safety across V8 stack formatting and generated Emscripten exit paths, requiring browser-wasm maintainer validation of fully built Checked/Release hosts for exit-code preservation and safe synchronous and asynchronous exits.
No open issues 🎉
What changed in this PR
This PR adjusts CoreCLR’s WebAssembly corerun host to avoid the Node/V8 shutdown deadlock reported in #135042 while preserving immediate termination for exits requested from live WebAssembly frames.
Changes:
- Detects WebAssembly frames on the JavaScript stack to retain hard exits for calls such as
Environment.Exit. - Uses Emscripten’s normal exit path otherwise, allowing Node to exit naturally.
- Guards against a queued exit running after the runtime has already shut down.
| File | Description |
|---|---|
src/coreclr/hosts/corerun/wasm/libCorerun.js |
Refines Node exit handling and guards against repeated runtime shutdown. |
Review effort: Balanced
steveisok
approved these changes
Nov 10, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
JIT/jit64/regress/vsw/539509/test1_539509intermittently times out on theR2R-CG2 browser wasm Checkedleg. It printsPassed.and thennodenever exits (#135042, about 25 hits a month). Any runtime test run undercorerun.json Node can hit this; this test hits it most often.Root cause
This is a Node/V8 shutdown deadlock, nodejs/node#64274. The upstream fix, nodejs/node#66171, is not merged yet, so the Helix image's node v24.22.2 is affected.
corerun's
_proc_exitoverride (#132078) callsprocess.exit(). That joins V8's platform worker threads without disposing the isolate. If a background compile job is parked inCollectionBarrier::AwaitCollectionBackground, waiting for a GC that only the main thread can run, the join never returns.Evidence, using the exact payloads from the failing Helix job (crossgen2 +
corerun.js+ node v24.22.2) on Linux x64:Passed.in about 1 of every 1,000–2,000 runs.MainThread, oneV8WorkerandSignalInspectorleft, all infutex_wait_queuewith no CPU time. It doesn't answer--report-on-signal, which fits a main thread blocked in a join.--stress-concurrent-allocationthe hang rate rises to 18 of 170 runs.--single-threaded(0 of 60) and natural exit both remove it.Fix
In
libCorerun.js, on Node:process.exit()only when the exit is requested from inside wasm, i.e. a wasm frame is on the JS stack (for exampleEnvironment.Exit). That case must not unwindExitStatusthrough live CLR frames ([ci-scan] Test failure: JIT/Regression/CLR-x86-JIT/V1-M12-Beta2/b65423 crashes on browser-wasm withstrerrorcalled after runt [Content truncated due to length] #131937)._proc_exit. It setsprocess.exitCodeand lets Node exit when the event loop drains, which disposes the isolate and releases the parked worker._exitthatSystemJS_ResolveMainPromisequeues for corerun once the runtime has already exited. Previously it never ran becauseprocess.exit()ended the process first. Without the guard it would trip Emscripten'sexitRuntimeassertion for exit code 0.Validation
--stress-concurrent-allocationhad 0 hangs in 180 runs with the fix vs 3 in 60 with the current host. Without stress, the average run time is unchanged (656 vs 652 ms).Environment.Exit(42),Environment.Exit(0), async Main returning 3 and 0, an unhandled exception, and GC with finalizers. Exit codes and output were identical.-O0and-O2. Main returning exits naturally (beforeExitfires),exit()from wasm still hard-exits, and a late_exitafter runtime exit is silent.corerun.js, so this PR's browser legs provide the full build validation.The Windows x86 gcstress timeouts added to #135042 are a different mechanism (native corerun, no Node), and this PR does not address them.
Fixes #135042
Note
This PR description was generated with GitHub Copilot.