Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 67b854d

Browse filesBrowse files
mcollinaaduh95
authored andcommitted
repl: remove dependency on domain module
Replace the domain-based error handling with AsyncLocalStorage and setUncaughtExceptionCaptureCallback. This removes the REPL's dependency on the deprecated domain module while preserving all existing behavior: - Synchronous errors during eval are caught and displayed - Async errors (setTimeout, promises, etc.) are caught via the uncaught exception capture callback - Top-level await errors are caught and displayed - The REPL continues operating after errors - Multiple REPL instances can coexist with errors routed correctly Changes: - Use AsyncLocalStorage to track which REPL instance owns an async context, replacing domain's automatic async tracking - Add setupExceptionCapture() to install setUncaughtExceptionCaptureCallback for catching async errors and routing them to the correct REPL - Extract error handling logic into REPLServer.prototype._handleError() - Wrap eval execution in replContext.run() for async context tracking - Update newListener protection to check AsyncLocalStorage context - Throw ERR_INVALID_ARG_VALUE if options.domain is passed PR-URL: #61227 Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com> Reviewed-By: Marco Ippolito <marcoippolito54@gmail.com> Reviewed-By: Rafael Gonzaga <rafael.nunu@hotmail.com>
1 parent 62d2cd4 commit 67b854d
Copy full SHA for 67b854d

26 files changed

+417-289Lines changed: 417 additions & 289 deletions
Expand file treeCollapse file tree
Open diff view settings
Collapse file

‎doc/api/process.md‎

Copy file name to clipboardExpand all lines: doc/api/process.md
+46-2Lines changed: 46 additions & 2 deletions
  • Display the source diff
  • Display the rich diff
Original file line numberDiff line numberDiff line change
@@ -736,6 +736,44 @@ generate a core file.
736736

737737
This feature is not available in [`Worker`][] threads.
738738

739+
## `process.addUncaughtExceptionCaptureCallback(fn)`
740+
741+
<!-- YAML
742+
added: REPLACEME
743+
-->
744+
745+
> Stability: 1 - Experimental
746+
747+
* `fn` {Function}
748+
749+
The `process.addUncaughtExceptionCaptureCallback()` function adds a callback
750+
that will be invoked when an uncaught exception occurs, receiving the exception
751+
value as its first argument.
752+
753+
Unlike [`process.setUncaughtExceptionCaptureCallback()`][], this function allows
754+
multiple callbacks to be registered and does not conflict with the
755+
[`domain`][] module. Callbacks are called in reverse order of registration
756+
(most recent first). If a callback returns `true`, subsequent callbacks
757+
and the default uncaught exception handling are skipped.
758+
759+
```mjs
760+
import process from 'node:process';
761+
762+
process.addUncaughtExceptionCaptureCallback((err) => {
763+
console.error('Caught exception:', err.message);
764+
return true; // Indicates exception was handled
765+
});
766+
```
767+
768+
```cjs
769+
const process = require('node:process');
770+
771+
process.addUncaughtExceptionCaptureCallback((err) => {
772+
console.error('Caught exception:', err.message);
773+
return true; // Indicates exception was handled
774+
});
775+
```
776+
739777
## `process.allowedNodeEnvironmentFlags`
740778

741779
<!-- YAML
@@ -4015,6 +4053,11 @@ This implies calling `module.setSourceMapsSupport()` with an option
40154053
40164054
<!-- YAML
40174055
added: v9.3.0
4056+
changes:
4057+
- version: REPLACEME
4058+
pr-url: https://github.com/nodejs/node/pull/61227
4059+
description: Use `process.addUncaughtExceptionCaptureCallback()` to
4060+
register multiple callbacks.
40184061
-->
40194062
40204063
* `fn` {Function|null}
@@ -4034,8 +4077,8 @@ To unset the capture function,
40344077
method with a non-`null` argument while another capture function is set will
40354078
throw an error.
40364079
4037-
Using this function is mutually exclusive with using the deprecated
4038-
[`domain`][] built-in module.
4080+
To register multiple callbacks that can coexist, use
4081+
[`process.addUncaughtExceptionCaptureCallback()`][] instead.
40394082
40404083
## `process.sourceMapsEnabled`
40414084
@@ -4567,6 +4610,7 @@ cases:
45674610
[`net.Socket`]: net.md#class-netsocket
45684611
[`os.constants.dlopen`]: os.md#dlopen-constants
45694612
[`postMessageToThread()`]: worker_threads.md#worker_threadspostmessagetothreadthreadid-value-transferlist-timeout
4613+
[`process.addUncaughtExceptionCaptureCallback()`]: #processadduncaughtexceptioncapturecallbackfn
45704614
[`process.argv`]: #processargv
45714615
[`process.config`]: #processconfig
45724616
[`process.execPath`]: #processexecpath
Collapse file

‎lib/domain.js‎

Copy file name to clipboardExpand all lines: lib/domain.js
+2-18Lines changed: 2 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -40,14 +40,11 @@ const {
4040
ReflectApply,
4141
SafeMap,
4242
SafeWeakMap,
43-
StringPrototypeRepeat,
4443
Symbol,
4544
} = primordials;
4645

4746
const EventEmitter = require('events');
4847
const {
49-
ERR_DOMAIN_CALLBACK_NOT_AVAILABLE,
50-
ERR_DOMAIN_CANNOT_SET_UNCAUGHT_EXCEPTION_CAPTURE,
5148
ERR_UNHANDLED_ERROR,
5249
} = require('internal/errors').codes;
5350
const { createHook } = require('async_hooks');
@@ -119,22 +116,9 @@ const asyncHook = createHook({
119116
},
120117
});
121118

122-
// When domains are in use, they claim full ownership of the
123-
// uncaught exception capture callback.
124-
if (process.hasUncaughtExceptionCaptureCallback()) {
125-
throw new ERR_DOMAIN_CALLBACK_NOT_AVAILABLE();
126-
}
127-
128-
// Get the stack trace at the point where `domain` was required.
129-
// eslint-disable-next-line no-restricted-syntax
130-
const domainRequireStack = new Error('require(`domain`) at this point').stack;
131-
119+
// Domain uses the stacking capability of setUncaughtExceptionCaptureCallback
120+
// to coexist with other callbacks (e.g., REPL).
132121
const { setUncaughtExceptionCaptureCallback } = process;
133-
process.setUncaughtExceptionCaptureCallback = function(fn) {
134-
const err = new ERR_DOMAIN_CANNOT_SET_UNCAUGHT_EXCEPTION_CAPTURE();
135-
err.stack += `\n${StringPrototypeRepeat('-', 40)}\n${domainRequireStack}`;
136-
throw err;
137-
};
138122

139123

140124
let sendMakeCallbackDeprecation = false;
Collapse file

‎lib/internal/bootstrap/node.js‎

Copy file name to clipboardExpand all lines: lib/internal/bootstrap/node.js
+3Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,7 @@ ObjectDefineProperty(process, 'features', {
307307
const {
308308
onGlobalUncaughtException,
309309
setUncaughtExceptionCaptureCallback,
310+
addUncaughtExceptionCaptureCallback,
310311
hasUncaughtExceptionCaptureCallback,
311312
} = require('internal/process/execution');
312313

@@ -319,6 +320,8 @@ ObjectDefineProperty(process, 'features', {
319320
process._fatalException = onGlobalUncaughtException;
320321
process.setUncaughtExceptionCaptureCallback =
321322
setUncaughtExceptionCaptureCallback;
323+
process.addUncaughtExceptionCaptureCallback =
324+
addUncaughtExceptionCaptureCallback;
322325
process.hasUncaughtExceptionCaptureCallback =
323326
hasUncaughtExceptionCaptureCallback;
324327
}
Collapse file

‎lib/internal/process/execution.js‎

Copy file name to clipboardExpand all lines: lib/internal/process/execution.js
+47-14Lines changed: 47 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'use strict';
22

33
const {
4+
ArrayPrototypePush,
45
RegExpPrototypeExec,
56
StringPrototypeIndexOf,
67
StringPrototypeSlice,
@@ -17,6 +18,7 @@ const {
1718
ERR_UNCAUGHT_EXCEPTION_CAPTURE_ALREADY_SET,
1819
},
1920
} = require('internal/errors');
21+
const { validateFunction } = require('internal/validators');
2022
const { pathToFileURL } = require('internal/url');
2123
const { exitCodes: { kGenericUserError } } = internalBinding('errors');
2224
const {
@@ -105,15 +107,18 @@ function evalScript(name, body, breakFirstLine, print, shouldLoadESM = false) {
105107
}
106108

107109
const exceptionHandlerState = {
108-
captureFn: null,
110+
captureFn: null, // Primary callback (for domain's exclusive use)
111+
auxiliaryCallbacks: [], // Auxiliary callbacks (for REPL, etc.) - always called
109112
reportFlag: false,
110113
};
111114

112115
function setUncaughtExceptionCaptureCallback(fn) {
113116
if (fn === null) {
114117
exceptionHandlerState.captureFn = fn;
115-
shouldAbortOnUncaughtToggle[0] = 1;
116-
process.report.reportOnUncaughtException = exceptionHandlerState.reportFlag;
118+
if (exceptionHandlerState.auxiliaryCallbacks.length === 0) {
119+
shouldAbortOnUncaughtToggle[0] = 1;
120+
process.report.reportOnUncaughtException = exceptionHandlerState.reportFlag;
121+
}
117122
return;
118123
}
119124
if (typeof fn !== 'function') {
@@ -129,6 +134,21 @@ function setUncaughtExceptionCaptureCallback(fn) {
129134
process.report.reportOnUncaughtException = false;
130135
}
131136

137+
// Add an auxiliary callback that coexists with the primary callback.
138+
// Auxiliary callbacks are called first; if any returns true, the error is handled.
139+
// Otherwise, the primary callback (if set) is called.
140+
function addUncaughtExceptionCaptureCallback(fn) {
141+
validateFunction(fn, 'fn');
142+
if (exceptionHandlerState.auxiliaryCallbacks.length === 0 &&
143+
exceptionHandlerState.captureFn === null) {
144+
exceptionHandlerState.reportFlag =
145+
process.report.reportOnUncaughtException === true;
146+
process.report.reportOnUncaughtException = false;
147+
shouldAbortOnUncaughtToggle[0] = 0;
148+
}
149+
ArrayPrototypePush(exceptionHandlerState.auxiliaryCallbacks, fn);
150+
}
151+
132152
function hasUncaughtExceptionCaptureCallback() {
133153
return exceptionHandlerState.captureFn !== null;
134154
}
@@ -154,21 +174,33 @@ function createOnGlobalUncaughtException() {
154174

155175
const type = fromPromise ? 'unhandledRejection' : 'uncaughtException';
156176
process.emit('uncaughtExceptionMonitor', er, type);
177+
// Primary callback (e.g., domain) has priority and always handles the exception
157178
if (exceptionHandlerState.captureFn !== null) {
158179
exceptionHandlerState.captureFn(er);
159-
} else if (!process.emit('uncaughtException', er, type)) {
160-
// If someone handled it, then great. Otherwise, die in C++ land
161-
// since that means that we'll exit the process, emit the 'exit' event.
162-
try {
163-
if (!process._exiting) {
164-
process._exiting = true;
165-
process.exitCode = kGenericUserError;
166-
process.emit('exit', kGenericUserError);
180+
} else {
181+
// If no primary callback, try auxiliary callbacks (e.g., REPL)
182+
// They must return true to indicate handling
183+
let handled = false;
184+
for (let i = exceptionHandlerState.auxiliaryCallbacks.length - 1; i >= 0; i--) {
185+
if (exceptionHandlerState.auxiliaryCallbacks[i](er) === true) {
186+
handled = true;
187+
break;
188+
}
189+
}
190+
if (!handled && !process.emit('uncaughtException', er, type)) {
191+
// If someone handled it, then great. Otherwise, die in C++ land
192+
// since that means that we'll exit the process, emit the 'exit' event.
193+
try {
194+
if (!process._exiting) {
195+
process._exiting = true;
196+
process.exitCode = kGenericUserError;
197+
process.emit('exit', kGenericUserError);
198+
}
199+
} catch {
200+
// Nothing to be done about it at this point.
167201
}
168-
} catch {
169-
// Nothing to be done about it at this point.
202+
return false;
170203
}
171-
return false;
172204
}
173205

174206
// If we handled an error, then make sure any ticks get processed
@@ -477,5 +509,6 @@ module.exports = {
477509
evalScript,
478510
onGlobalUncaughtException: createOnGlobalUncaughtException(),
479511
setUncaughtExceptionCaptureCallback,
512+
addUncaughtExceptionCaptureCallback,
480513
hasUncaughtExceptionCaptureCallback,
481514
};

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.