build: define NODE_API_SWALLOW_UNTHROWABLE_EXCEPTIONS to stop teardown aborts - #954
Open
Amir (amirlehmam) wants to merge 1 commit into
Open
Conversation
An N-API call that fails with napi_pending_exception while the environment is tearing down currently reaches `throw Error::New(_env)` in Error::ThrowAsJavaScriptException, which has no handler above it in the ThreadSafeFunction dispatch path and terminates the host process. node-addon-api ships a guard for exactly this case, but only behind NODE_API_SWALLOW_UNTHROWABLE_EXCEPTIONS. node-pty depends on node_addon_api_except, whose except.gypi defines NAPI_CPP_EXCEPTIONS and nothing else, so the throwing path is what gets compiled into the prebuilds. Refs microsoft#951, microsoft#904.
This was referenced Aug 17, 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.
Fixes the root cause in #951 (same signature as #904).
The problem
Error::ThrowAsJavaScriptException()in node-addon-api ends with:In the ConPTY exit path that throw has no handler above it. The
ExitEventcallback insrc/win/conpty.ccruns on the main thread fromthe ThreadSafeFunction dispatcher — unlike every other
throw Napi::Error::New(...)in that file, it is not inside a functionregistered as an N-API method, so nothing converts it into a JS throw.
It reaches
UnhandledExceptionFilterand the host process is aborted(
0xc0000409,FAST_FAIL_FATAL_APP_EXIT).This only happens while the environment is terminating.
NAPI_PREAMBLEreturns
napi_pending_exceptionwhen!(last_exception.IsEmpty() && can_call_into_js()), whilenapi_is_exception_pendingreports only!last_exception.IsEmpty(). So a napi call failing withnapi_pending_exceptionandnapi_is_exception_pendingreturningfalse means
can_call_into_js() == false— nothing is wrong with theaddon's code, the isolate is simply going away.
The fix
node-addon-api already handles this, and its own comment describes the
situation precisely:
That guard is behind
NODE_API_SWALLOW_UNTHROWABLE_EXCEPTIONS. node-ptydepends on
node_addon_api_except, andexcept.gypidefinesNAPI_CPP_EXCEPTIONSand nothing else — so the#elsebranch is whatgoes into the shipped prebuilds. Defining the macro in
target_defaultscompiles the guard in.
There are two guarded sites in node-addon-api 7.1.1, not one:
ThrowAsJavaScriptException(napi-inl.h:3042) and theError(napi_env, napi_value)constructor'snapi_define_propertiescall(
napi-inl.h:2924), which otherwise hitsNAPI_FATAL_IF_FAILED. Bothare abort paths during teardown; both close with this one define.
The define only changes behaviour when the environment is already
terminating — an exception that can be thrown still is.
Verification
Built on Windows with VS 2022 BuildTools, Node 24.13.0, node-gyp 11.4.2:
npx node-gyp rebuildcompletes (gyp info ok), producingconpty.nodeandconpty_console_list.node.The define appears in the generated projects next to
NAPI_CPP_EXCEPTIONS, which is the pairing that matters:No new warnings; the only ones on
conpty.ccare the pre-existingC6387s at lines 414 and 448.
Two caveats about that build, stated rather than glossed: I disabled
SpectreMitigationlocally to compile, because this machine lacks theSpectre-mitigated libraries (
MSB8040) — unmodifiedmainfails thesame way on it, so that is environmental and is not part of this
diff. And the
pty(winpty) target was not generated becausedeps/winptywas not checked out. The affected target isconpty, andit built.
I do not have a machine that reproduces the crash on demand, so I cannot
offer a before/after soak. The reporter in #951 has thirteen crash dumps
with a byte-identical signature and can test a patched build.