| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 62d2cd4 commit 67b854d
26 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -736,6 +736,44 @@ generate a core file. | |||
| 736 | 736 | ||
| 737 | 737 | This feature is not available in [`Worker`][] threads. | |
| 738 | 738 | ||
| 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 | + | ||
| 739 | 777 | ## `process.allowedNodeEnvironmentFlags` | |
| 740 | 778 | ||
| 741 | 779 | <!-- YAML | |
@@ -4015,6 +4053,11 @@ This implies calling `module.setSourceMapsSupport()` with an option | |||
| 4015 | 4053 | ||
| 4016 | 4054 | <!-- YAML | |
| 4017 | 4055 | 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. | ||
| 4018 | 4061 | --> | |
| 4019 | 4062 | ||
| 4020 | 4063 | * `fn` {Function|null} | |
@@ -4034,8 +4077,8 @@ To unset the capture function, | |||
| 4034 | 4077 | method with a non-`null` argument while another capture function is set will | |
| 4035 | 4078 | throw an error. | |
| 4036 | 4079 | ||
| 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. | ||
| 4039 | 4082 | ||
| 4040 | 4083 | ## `process.sourceMapsEnabled` | |
| 4041 | 4084 | ||
@@ -4567,6 +4610,7 @@ cases: | |||
| 4567 | 4610 | [`net.Socket`]: net.md#class-netsocket | |
| 4568 | 4611 | [`os.constants.dlopen`]: os.md#dlopen-constants | |
| 4569 | 4612 | [`postMessageToThread()`]: worker_threads.md#worker_threadspostmessagetothreadthreadid-value-transferlist-timeout | |
| 4613 | + [`process.addUncaughtExceptionCaptureCallback()`]: #processadduncaughtexceptioncapturecallbackfn | ||
| 4570 | 4614 | [`process.argv`]: #processargv | |
| 4571 | 4615 | [`process.config`]: #processconfig | |
| 4572 | 4616 | [`process.execPath`]: #processexecpath | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -40,14 +40,11 @@ const { | |||
| 40 | 40 | ReflectApply, | |
| 41 | 41 | SafeMap, | |
| 42 | 42 | SafeWeakMap, | |
| 43 | - StringPrototypeRepeat, | ||
| 44 | 43 | Symbol, | |
| 45 | 44 | } = primordials; | |
| 46 | 45 | ||
| 47 | 46 | const EventEmitter = require('events'); | |
| 48 | 47 | const { | |
| 49 | - ERR_DOMAIN_CALLBACK_NOT_AVAILABLE, | ||
| 50 | - ERR_DOMAIN_CANNOT_SET_UNCAUGHT_EXCEPTION_CAPTURE, | ||
| 51 | 48 | ERR_UNHANDLED_ERROR, | |
| 52 | 49 | } = require('internal/errors').codes; | |
| 53 | 50 | const { createHook } = require('async_hooks'); | |
@@ -119,22 +116,9 @@ const asyncHook = createHook({ | |||
| 119 | 116 | }, | |
| 120 | 117 | }); | |
| 121 | 118 | ||
| 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). | ||
| 132 | 121 | 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 | - }; | ||
| 138 | 122 | ||
| 139 | 123 | ||
| 140 | 124 | let sendMakeCallbackDeprecation = false; | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -307,6 +307,7 @@ ObjectDefineProperty(process, 'features', { | |||
| 307 | 307 | const { | |
| 308 | 308 | onGlobalUncaughtException, | |
| 309 | 309 | setUncaughtExceptionCaptureCallback, | |
| 310 | + addUncaughtExceptionCaptureCallback, | ||
| 310 | 311 | hasUncaughtExceptionCaptureCallback, | |
| 311 | 312 | } = require('internal/process/execution'); | |
| 312 | 313 | ||
@@ -319,6 +320,8 @@ ObjectDefineProperty(process, 'features', { | |||
| 319 | 320 | process._fatalException = onGlobalUncaughtException; | |
| 320 | 321 | process.setUncaughtExceptionCaptureCallback = | |
| 321 | 322 | setUncaughtExceptionCaptureCallback; | |
| 323 | + process.addUncaughtExceptionCaptureCallback = | ||
| 324 | + addUncaughtExceptionCaptureCallback; | ||
| 322 | 325 | process.hasUncaughtExceptionCaptureCallback = | |
| 323 | 326 | hasUncaughtExceptionCaptureCallback; | |
| 324 | 327 | } | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,6 +1,7 @@ | |||
| 1 | 1 | 'use strict'; | |
| 2 | 2 | ||
| 3 | 3 | const { | |
| 4 | + ArrayPrototypePush, | ||
| 4 | 5 | RegExpPrototypeExec, | |
| 5 | 6 | StringPrototypeIndexOf, | |
| 6 | 7 | StringPrototypeSlice, | |
@@ -17,6 +18,7 @@ const { | |||
| 17 | 18 | ERR_UNCAUGHT_EXCEPTION_CAPTURE_ALREADY_SET, | |
| 18 | 19 | }, | |
| 19 | 20 | } = require('internal/errors'); | |
| 21 | + const { validateFunction } = require('internal/validators'); | ||
| 20 | 22 | const { pathToFileURL } = require('internal/url'); | |
| 21 | 23 | const { exitCodes: { kGenericUserError } } = internalBinding('errors'); | |
| 22 | 24 | const { | |
@@ -105,15 +107,18 @@ function evalScript(name, body, breakFirstLine, print, shouldLoadESM = false) { | |||
| 105 | 107 | } | |
| 106 | 108 | ||
| 107 | 109 | const exceptionHandlerState = { | |
| 108 | - captureFn: null, | ||
| 110 | + captureFn: null, // Primary callback (for domain's exclusive use) | ||
| 111 | + auxiliaryCallbacks: [], // Auxiliary callbacks (for REPL, etc.) - always called | ||
| 109 | 112 | reportFlag: false, | |
| 110 | 113 | }; | |
| 111 | 114 | ||
| 112 | 115 | function setUncaughtExceptionCaptureCallback(fn) { | |
| 113 | 116 | if (fn === null) { | |
| 114 | 117 | 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 | + } | ||
| 117 | 122 | return; | |
| 118 | 123 | } | |
| 119 | 124 | if (typeof fn !== 'function') { | |
@@ -129,6 +134,21 @@ function setUncaughtExceptionCaptureCallback(fn) { | |||
| 129 | 134 | process.report.reportOnUncaughtException = false; | |
| 130 | 135 | } | |
| 131 | 136 | ||
| 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 | + | ||
| 132 | 152 | function hasUncaughtExceptionCaptureCallback() { | |
| 133 | 153 | return exceptionHandlerState.captureFn !== null; | |
| 134 | 154 | } | |
@@ -154,21 +174,33 @@ function createOnGlobalUncaughtException() { | |||
| 154 | 174 | ||
| 155 | 175 | const type = fromPromise ? 'unhandledRejection' : 'uncaughtException'; | |
| 156 | 176 | process.emit('uncaughtExceptionMonitor', er, type); | |
| 177 | + // Primary callback (e.g., domain) has priority and always handles the exception | ||
| 157 | 178 | if (exceptionHandlerState.captureFn !== null) { | |
| 158 | 179 | 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. | ||
| 167 | 201 | } | |
| 168 | - } catch { | ||
| 169 | - // Nothing to be done about it at this point. | ||
| 202 | + return false; | ||
| 170 | 203 | } | |
| 171 | - return false; | ||
| 172 | 204 | } | |
| 173 | 205 | ||
| 174 | 206 | // If we handled an error, then make sure any ticks get processed | |
@@ -477,5 +509,6 @@ module.exports = { | |||
| 477 | 509 | evalScript, | |
| 478 | 510 | onGlobalUncaughtException: createOnGlobalUncaughtException(), | |
| 479 | 511 | setUncaughtExceptionCaptureCallback, | |
| 512 | + addUncaughtExceptionCaptureCallback, | ||
| 480 | 513 | hasUncaughtExceptionCaptureCallback, | |
| 481 | 514 | }; | |
| Back | FazBrowse Home | New Git URL |
0 commit comments