Skip to content

[browser][coreCLR] corerun: hard-exit Node on explicit process exit (#131937) - #132078

Merged
pavelsavara merged 2 commits into
dotnet:mainfrom
pavelsavara:fix-131937-corerun-node-exit
Aug 10, 2026
Merged

pavelsavara merged 2 commits into
dotnet:mainfrom
pavelsavara:fix-131937-corerun-node-exit

Conversation

@pavelsavara

Copy link
Copy Markdown
Member

Problem

JIT/Regression/CLR-x86-JIT/V1-M12-Beta2/b65423 fails on the browser-wasm CoreCLR checked leg (#131937). The test catches its expected TypeLoadException (the intended Expected: 100), but the wasm runtime then aborts during shutdown:

Caught TypeLoadException exception: 100
Aborted(Assertion failed: native function `strerror` called after runtime exit
        (use NO_EXIT_RUNTIME to keep it alive after main() exits))
   ... at _fd_write (corerun.js)
Expected: 100
Actual: 1

Root cause

The test ends with Environment.Exit(100). On browser-wasm that goes Environment_Exit → ForceEEShutdown → EEPolicy::HandleExitProcess → SafeExitProcess → ExitProcess → PROCEndProcess → Emscripten exit() (the host links -sEXIT_RUNTIME=1).

Emscripten's exitJS runs exitRuntime() — tearing down the Emscripten FS/TTY and setting runtimeExited=true — then _proc_exit throws the ExitStatus JS exception. That exception unwinds up through the still-live CLR interpreter frames (wasm EH). During the unwind, Frame::Pop(Thread*) calls Thread::GetFrame(), whose checked-only _ASSERTE (curSP <= m_pFrame && m_pFrame < m_CacheStackBase) fails because we are exiting mid-interpretation. The assert's fprintf(stderr, ...) then hits musl stdio → fd_write → the FS streams are already gone → getStreamChecked throws ErrnoError, and building its message calls strerror, tripping Emscripten's assert(!runtimeExited) → abort(). The abort turns the intended exit code 100 into 1.

This only reproduces on the checked leg because the triggering GetFrame() assert exists only under _DEBUG_IMPL.

Fix

In the corerun wasm glue, under Node, intercept _proc_exit and end the process immediately with process.exit(code) when the runtime is not kept alive — avoiding the ExitStatus throw and the unwind through live CLR frames. stdio was already flushed by exitRuntime() before _proc_exit is reached. The browser/keepalive/onExit path is untouched (Node-gated), and when the runtime is kept alive for async work it falls through to the original behavior.

_proc_exit is the correct interception point: wasmImports.exit binds the original exitJS by value (so reassigning exitJS would miss the wasm-initiated Environment.Exit), but exitJS's body calls _proc_exit by variable reference at call time.

Validation

Ran the assembled b65423.dll under corerun.js in Node, exactly as CI does:

corerun.js Node exit
before fix 1 (strerror abort — matches the issue)
after fix 100 (expected)

Regression checks also pass: normal return 7 from Main → exit 7; Environment.Exit(0) → exit 0.

Fixes #131937

Note

This PR description was generated with the assistance of GitHub Copilot.

Environment.Exit reaches _proc_exit from deep inside interpreted managed code. Letting Emscripten throw ExitStatus and unwind back through the live CLR interpreter frames runs native code (a checked-build Frame::Pop assert, stdio) after the runtime/FS teardown, aborting with 'strerror called after runtime exit' and turning the intended exit code into 1 (dotnet#131937).

Under Node, intercept _proc_exit and process.exit(code) immediately when the runtime is not kept alive, avoiding the unwind. stdio was already flushed by exitRuntime().

Fixes dotnet#131937
Copilot AI lite review requested due to automatic review settings August 10, 2026 13:46
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 3 pipeline(s).
13 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@pavelsavara pavelsavara added arch-wasm WebAssembly architecture os-browser Browser variant of arch-wasm labels Aug 10, 2026
@pavelsavara pavelsavara added this to the 11.0.0 milestone Aug 10, 2026
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to 'arch-wasm': @lewing, @pavelsavara
See info in area-owners.md if you want to be subscribed.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes a browser-wasm CoreCLR checked-leg test failure where Environment.Exit(...) triggers an Emscripten ExitStatus throw that unwinds through live interpreter frames and aborts with the wrong exit code under Node.

Changes:

  • Under Node, intercept _proc_exit to hard-exit via process.exit(code) when the runtime is not being kept alive.
  • Preserve existing behavior for non-Node environments and for keepalive/async scenarios by delegating to the original _proc_exit.

Comment thread src/coreclr/hosts/corerun/wasm/libCorerun.js
Copilot AI review requested due to automatic review settings August 10, 2026 13:50

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated no new comments.

Suppressed comments (1)

src/coreclr/hosts/corerun/wasm/libCorerun.js:43

  • Local variable original_proc_exit uses snake_case, which is inconsistent with the surrounding camelCase naming in this file. Consider renaming to originalProcExit while keeping the Emscripten-provided _proc_exit identifier as-is.
                    const original_proc_exit = _proc_exit;
                    _proc_exit = (code) => {
                        if (!keepRuntimeAlive()) {
                            process.exit(code);
                        }
                        return original_proc_exit(code);
                    };

@pavelsavara
pavelsavara merged commit 28c5e52 into dotnet:main Aug 10, 2026
130 checks passed
@pavelsavara
pavelsavara deleted the fix-131937-corerun-node-exit branch August 10, 2026 16:38
@dotnet-milestone-bot dotnet-milestone-bot Bot modified the milestones: 11.0.0, 11.0-rc1 Aug 11, 2026
radekdoulik added a commit to radekdoulik/runtime that referenced this pull request Aug 18, 2026
Remove the b65423 and fieldlayout suppressions now that dotnet#132078 and
dotnet#132172 fix their underlying CoreCLR browser failures.

Retain the EventPipe, ContextualReflection, and Server GC exclusions
that remain necessary on current main.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 808c6867-a1ea-4256-a003-46c17785ad04
radekdoulik added a commit that referenced this pull request Aug 19, 2026
)

## Summary

- Gate exactly four EventPipe events-validation projects on CoreCLR
browser because they use unsupported `Process.GetCurrentProcess` and
Process/PID-based managed self-process `DiagnosticsClient` collection;
browser diagnostics use JS/WebSocket clients instead.
- Temporarily suppress the CoreCLR-browser `ContextualReflection`
interpreter failure through its tracked #131925 ActiveIssue.
- Clarify that Server GC is disabled on CoreCLR browser by design.
- `b65423` and `readytorun/fieldlayout` are absent from this change:
current main includes the underlying fixes from #132078 and #132172, and
both restored tests now pass targeted browser validation.

Related issues: #131321, #131898, #131925.

## Validation

- Merged and validated current `origin/main` at
`663c457b86ccd35743374676a69de5ed97bc3b14`; the Checked browser
`clr+libs` baseline passed with 0 warnings and 0 errors.
- `b65423` passed 3/3: browser default, browser
`DOTNET_TieredCompilation=0`, and native macOS arm64 process-isolated
execution, each with expected/actual exit code 100.
- `fieldlayout` passed 2/2 in browser default and no-tiered modes after
producing and loading its composite Crossgen2/R2R WASM image.
- Removing the ContextualReflection ActiveIssue reproduced #131925 in
2/2 browser modes; the final gated browser runs passed 2/2, and native
ContextualReflection executed successfully.
- An ungated no-tiered `GCEvents` run reproduced the expected
`Process.GetCurrentProcess` PlatformNotSupportedException; all four
final EventPipe projects were gated from CoreCLR-browser builds.
- 14/14 representative property evaluations matched the intended scope
across CoreCLR browser, CoreCLR macOS, and browser Mono.
- Final delta: exactly 6 files, 23 insertions, 3 deletions. `git diff
--check origin/main..HEAD` passed.

> [!NOTE]
> This pull request description was generated by GitHub Copilot.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 808c6867-a1ea-4256-a003-46c17785ad04
@github-actions github-actions Bot locked and limited conversation to collaborators Sep 11, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

arch-wasm WebAssembly architecture area-Host os-browser Browser variant of arch-wasm

Projects

None yet

3 participants