Skip to content

src/node_errors: add re-entrancy guard to TriggerUncaughtException#64327

Open
themuuln wants to merge 1 commit into
nodejs:mainfrom
themuuln:fix/reentrant-uncaught-exception-guard
Open

src/node_errors: add re-entrancy guard to TriggerUncaughtException#64327
themuuln wants to merge 1 commit into
nodejs:mainfrom
themuuln:fix/reentrant-uncaught-exception-guard

Conversation

@themuuln

@themuuln themuuln commented Jul 6, 2026

Copy link
Copy Markdown

Problem

When an uncaught exception triggers TriggerUncaughtException β†’ the JS handler (process._fatalException) β†’ console.error β†’ the inspector protocol (InspectorConsoleCall) throws again, V8 re-enters TriggerUncaughtException. This creates a tight infinite loop:

uv_run β†’ uv__async_io β†’ ThreadSafeFunction::AsyncCb β†’
CallbackScope::~CallbackScope β†’ Function::Call β†’
TriggerUncaughtException β†’ Function::Call β†’
InspectorConsoleCall β†’ [back to TriggerUncaughtException]

Each iteration:

  1. Widens the error (exception wrapped in a new exception string)
  2. Saturates a CPU core
  3. Compounds memory pressure (each error frame allocates)

This was observed in production with napi-rs plugins calling console.error during uncaught exception handling, but any JS handler that triggers inspector protocol activity during process._fatalException can hit it.

Fix

Add a thread_local re-entrancy guard to TriggerUncaughtException(). If the function is re-entered while already processing an uncaught exception, it:

  1. Formats the exception directly via FormatCaughtException (pure V8 string ops β€” no JS, no inspector)
  2. Prints the diagnostic to stderr with PrintToStderrAndFlush
  3. Calls ABORT() to terminate the process

An RAII scope guard resets the flag when TriggerUncaughtException returns normally (e.g., env->Exit() path).

The guard lives entirely in node_errors.cc β€” no header changes, no Environment fields.

Fixes: #64326

@nodejs-github-bot nodejs-github-bot added c++ Issues and PRs that require attention from people who are familiar with C++. needs-ci PRs that need a full CI run. labels Jul 6, 2026
…in TriggerUncaughtException

Add a thread_local re-entrancy guard to TriggerUncaughtException()
that detects when the JS-level exception handler (process._fatalException)
itself triggers another exception through the inspector protocol.

This prevents the infinite loop:
  TriggerUncaughtException -> InspectorConsoleCall ->
  TriggerUncaughtException -> InspectorConsoleCall -> ...

When re-entrancy is detected, the function prints a diagnostic with the
formatted exception to stderr using FormatCaughtException (which avoids
the inspector path entirely), then aborts the process.

Fixes: nodejs#64326
@themuuln themuuln force-pushed the fix/reentrant-uncaught-exception-guard branch from d6df9be to 955d7e4 Compare July 6, 2026 16:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

c++ Issues and PRs that require attention from people who are familiar with C++. needs-ci PRs that need a full CI run.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Infinite recursion between TriggerUncaughtException and InspectorConsoleCall when console.error throws

2 participants