| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent fd501b3 commit 347164a
16 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -183,6 +183,10 @@ added: v0.10 | |||
| 183 | 183 | Aborting instead of exiting causes a core file to be generated for post-mortem | |
| 184 | 184 | analysis using a debugger (such as `lldb`, `gdb`, and `mdb`). | |
| 185 | 185 | ||
| 186 | + *Note*: If this flag is passed, the behavior can still be set to not abort | ||
| 187 | + through [`process.setUncaughtExceptionCaptureCallback()`][] (and through usage | ||
| 188 | + of the `domain` module that uses it). | ||
| 189 | + | ||
| 186 | 190 | ### `--trace-warnings` | |
| 187 | 191 | <!-- YAML | |
| 188 | 192 | added: v6.0.0 | |
@@ -598,3 +602,4 @@ greater than `4` (its current default value). For more information, see the | |||
| 598 | 602 | [debugger]: debugger.html | |
| 599 | 603 | [emit_warning]: process.html#process_process_emitwarning_warning_type_code_ctor | |
| 600 | 604 | [libuv threadpool documentation]: http://docs.libuv.org/en/latest/threadpool.html | |
| 605 | + [`process.setUncaughtExceptionCaptureCallback()`]: process.html#process_process_setuncaughtexceptioncapturecallback_fn | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -701,6 +701,23 @@ A signing `key` was not provided to the [`sign.sign()`][] method. | |||
| 701 | 701 | ||
| 702 | 702 | `c-ares` failed to set the DNS server. | |
| 703 | 703 | ||
| 704 | + <a id="ERR_DOMAIN_CALLBACK_NOT_AVAILABLE"></a> | ||
| 705 | + ### ERR_DOMAIN_CALLBACK_NOT_AVAILABLE | ||
| 706 | + | ||
| 707 | + The `domain` module was not usable since it could not establish the required | ||
| 708 | + error handling hooks, because | ||
| 709 | + [`process.setUncaughtExceptionCaptureCallback()`][] had been called at an | ||
| 710 | + earlier point in time. | ||
| 711 | + | ||
| 712 | + <a id="ERR_DOMAIN_CANNOT_SET_UNCAUGHT_EXCEPTION_CAPTURE"></a> | ||
| 713 | + ### ERR_DOMAIN_CANNOT_SET_UNCAUGHT_EXCEPTION_CAPTURE | ||
| 714 | + | ||
| 715 | + [`process.setUncaughtExceptionCaptureCallback()`][] could not be called | ||
| 716 | + because the `domain` module has been loaded at an earlier point in time. | ||
| 717 | + | ||
| 718 | + The stack trace is extended to include the point in time at which the | ||
| 719 | + `domain` module had been loaded. | ||
| 720 | + | ||
| 704 | 721 | <a id="ERR_ENCODING_INVALID_ENCODED_DATA"></a> | |
| 705 | 722 | ### ERR_ENCODING_INVALID_ENCODED_DATA | |
| 706 | 723 | ||
@@ -1419,6 +1436,15 @@ A Transform stream finished while it was still transforming. | |||
| 1419 | 1436 | ||
| 1420 | 1437 | A Transform stream finished with data still in the write buffer. | |
| 1421 | 1438 | ||
| 1439 | + <a id="ERR_UNCAUGHT_EXCEPTION_CAPTURE_ALREADY_SET"></a> | ||
| 1440 | + ### ERR_UNCAUGHT_EXCEPTION_CAPTURE_ALREADY_SET | ||
| 1441 | + | ||
| 1442 | + [`process.setUncaughtExceptionCaptureCallback()`][] was called twice, | ||
| 1443 | + without first resetting the callback to `null`. | ||
| 1444 | + | ||
| 1445 | + This error is designed to prevent accidentally overwriting a callback registered | ||
| 1446 | + from another module. | ||
| 1447 | + | ||
| 1422 | 1448 | <a id="ERR_UNESCAPED_CHARACTERS"></a> | |
| 1423 | 1449 | ### ERR_UNESCAPED_CHARACTERS | |
| 1424 | 1450 | ||
@@ -1524,6 +1550,7 @@ Creation of a [`zlib`][] object failed due to incorrect configuration. | |||
| 1524 | 1550 | [`new URLSearchParams(iterable)`]: url.html#url_constructor_new_urlsearchparams_iterable | |
| 1525 | 1551 | [`process.on('uncaughtException')`]: process.html#process_event_uncaughtexception | |
| 1526 | 1552 | [`process.send()`]: process.html#process_process_send_message_sendhandle_options_callback | |
| 1553 | + [`process.setUncaughtExceptionCaptureCallback()`]: process.html#process_process_setuncaughtexceptioncapturecallback_fn | ||
| 1527 | 1554 | [`require('crypto').setEngine()`]: crypto.html#crypto_crypto_setengine_engine_flags | |
| 1528 | 1555 | [`server.listen()`]: net.html#net_server_listen | |
| 1529 | 1556 | [ES6 module]: esm.html | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1136,6 +1136,16 @@ if (process.getuid) { | |||
| 1136 | 1136 | *Note*: This function is only available on POSIX platforms (i.e. not Windows | |
| 1137 | 1137 | or Android). | |
| 1138 | 1138 | ||
| 1139 | + ## process.hasUncaughtExceptionCaptureCallback() | ||
| 1140 | + <!-- YAML | ||
| 1141 | + added: REPLACEME | ||
| 1142 | + --> | ||
| 1143 | + | ||
| 1144 | + * Returns: {boolean} | ||
| 1145 | + | ||
| 1146 | + Indicates whether a callback has been set using | ||
| 1147 | + [`process.setUncaughtExceptionCaptureCallback()`][]. | ||
| 1148 | + | ||
| 1139 | 1149 | ## process.hrtime([time]) | |
| 1140 | 1150 | <!-- YAML | |
| 1141 | 1151 | added: v0.7.6 | |
@@ -1637,6 +1647,29 @@ if (process.getuid && process.setuid) { | |||
| 1637 | 1647 | or Android). | |
| 1638 | 1648 | ||
| 1639 | 1649 | ||
| 1650 | + ## process.setUncaughtExceptionCaptureCallback(fn) | ||
| 1651 | + <!-- YAML | ||
| 1652 | + added: REPLACEME | ||
| 1653 | + --> | ||
| 1654 | + | ||
| 1655 | + * `fn` {Function|null} | ||
| 1656 | + | ||
| 1657 | + The `process.setUncaughtExceptionCapture` function sets a function that will | ||
| 1658 | + be invoked when an uncaught exception occurs, which will receive the exception | ||
| 1659 | + value itself as its first argument. | ||
| 1660 | + | ||
| 1661 | + If such a function is set, the [`process.on('uncaughtException')`][] event will | ||
| 1662 | + not be emitted. If `--abort-on-uncaught-exception` was passed from the | ||
| 1663 | + command line or set through [`v8.setFlagsFromString()`][], the process will | ||
| 1664 | + not abort. | ||
| 1665 | + | ||
| 1666 | + To unset the capture function, `process.setUncaughtExceptionCapture(null)` | ||
| 1667 | + may be used. Calling this method with a non-`null` argument while another | ||
| 1668 | + capture function is set will throw an error. | ||
| 1669 | + | ||
| 1670 | + *Note*: Using this function is mutually exclusive with using the | ||
| 1671 | + deprecated [`domain`][] built-in module. | ||
| 1672 | + | ||
| 1640 | 1673 | ## process.stderr | |
| 1641 | 1674 | ||
| 1642 | 1675 | * {Stream} | |
@@ -1921,6 +1954,7 @@ cases: | |||
| 1921 | 1954 | [`JSON.stringify` spec]: https://tc39.github.io/ecma262/#sec-json.stringify | |
| 1922 | 1955 | [`console.error()`]: console.html#console_console_error_data_args | |
| 1923 | 1956 | [`console.log()`]: console.html#console_console_log_data_args | |
| 1957 | + [`domain`]: domain.html | ||
| 1924 | 1958 | [`end()`]: stream.html#stream_writable_end_chunk_encoding_callback | |
| 1925 | 1959 | [`net.Server`]: net.html#net_class_net_server | |
| 1926 | 1960 | [`net.Socket`]: net.html#net_class_net_socket | |
@@ -1930,11 +1964,14 @@ cases: | |||
| 1930 | 1964 | [`process.exit()`]: #process_process_exit_code | |
| 1931 | 1965 | [`process.exitCode`]: #process_process_exitcode | |
| 1932 | 1966 | [`process.kill()`]: #process_process_kill_pid_signal | |
| 1967 | + [`process.on('uncaughtException')`]: process.html#process_event_uncaughtexception | ||
| 1968 | + [`process.setUncaughtExceptionCaptureCallback()`]: process.html#process_process_setuncaughtexceptioncapturecallback_fn | ||
| 1933 | 1969 | [`promise.catch()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/catch | |
| 1934 | 1970 | [`require()`]: globals.html#globals_require | |
| 1935 | 1971 | [`require.main`]: modules.html#modules_accessing_the_main_module | |
| 1936 | 1972 | [`require.resolve()`]: modules.html#modules_require_resolve_request_options | |
| 1937 | 1973 | [`setTimeout(fn, 0)`]: timers.html#timers_settimeout_callback_delay_args | |
| 1974 | + [`v8.setFlagsFromString()`]: v8.html#v8_v8_setflagsfromstring_flags | ||
| 1938 | 1975 | [Child Process]: child_process.html | |
| 1939 | 1976 | [Cluster]: cluster.html | |
| 1940 | 1977 | [Duplex]: stream.html#stream_duplex_and_transform_streams | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -28,6 +28,7 @@ | |||
| 28 | 28 | ||
| 29 | 29 | const util = require('util'); | |
| 30 | 30 | const EventEmitter = require('events'); | |
| 31 | + const errors = require('internal/errors'); | ||
| 31 | 32 | const { createHook } = require('async_hooks'); | |
| 32 | 33 | ||
| 33 | 34 | // communicate with events module, but don't require that | |
@@ -81,19 +82,77 @@ const asyncHook = createHook({ | |||
| 81 | 82 | } | |
| 82 | 83 | }); | |
| 83 | 84 | ||
| 85 | + // When domains are in use, they claim full ownership of the | ||
| 86 | + // uncaught exception capture callback. | ||
| 87 | + if (process.hasUncaughtExceptionCaptureCallback()) { | ||
| 88 | + throw new errors.Error('ERR_DOMAIN_CALLBACK_NOT_AVAILABLE'); | ||
| 89 | + } | ||
| 90 | + | ||
| 91 | + // Get the stack trace at the point where `domain` was required. | ||
| 92 | + const domainRequireStack = new Error('require(`domain`) at this point').stack; | ||
| 93 | + | ||
| 94 | + const { setUncaughtExceptionCaptureCallback } = process; | ||
| 95 | + process.setUncaughtExceptionCaptureCallback = function(fn) { | ||
| 96 | + const err = | ||
| 97 | + new errors.Error('ERR_DOMAIN_CANNOT_SET_UNCAUGHT_EXCEPTION_CAPTURE'); | ||
| 98 | + err.stack = err.stack + '\n' + '-'.repeat(40) + '\n' + domainRequireStack; | ||
| 99 | + throw err; | ||
| 100 | + }; | ||
| 101 | + | ||
| 84 | 102 | // It's possible to enter one domain while already inside | |
| 85 | 103 | // another one. The stack is each entered domain. | |
| 86 | 104 | const stack = []; | |
| 87 | 105 | exports._stack = stack; | |
| 88 | - process._setupDomainUse(stack); | ||
| 106 | + process._setupDomainUse(); | ||
| 89 | 107 | ||
| 90 | - class Domain extends EventEmitter { | ||
| 108 | + function updateExceptionCapture() { | ||
| 109 | + if (stack.every((domain) => domain.listenerCount('error') === 0)) { | ||
| 110 | + setUncaughtExceptionCaptureCallback(null); | ||
| 111 | + } else { | ||
| 112 | + setUncaughtExceptionCaptureCallback(null); | ||
| 113 | + setUncaughtExceptionCaptureCallback((er) => { | ||
| 114 | + return process.domain._errorHandler(er); | ||
| 115 | + }); | ||
| 116 | + } | ||
| 117 | + } | ||
| 118 | + | ||
| 119 | + | ||
| 120 | + process.on('newListener', (name, listener) => { | ||
| 121 | + if (name === 'uncaughtException' && | ||
| 122 | + listener !== domainUncaughtExceptionClear) { | ||
| 123 | + // Make sure the first listener for `uncaughtException` always clears | ||
| 124 | + // the domain stack. | ||
| 125 | + process.removeListener(name, domainUncaughtExceptionClear); | ||
| 126 | + process.prependListener(name, domainUncaughtExceptionClear); | ||
| 127 | + } | ||
| 128 | + }); | ||
| 129 | + | ||
| 130 | + process.on('removeListener', (name, listener) => { | ||
| 131 | + if (name === 'uncaughtException' && | ||
| 132 | + listener !== domainUncaughtExceptionClear) { | ||
| 133 | + // If the domain listener would be the only remaining one, remove it. | ||
| 134 | + const listeners = process.listeners('uncaughtException'); | ||
| 135 | + if (listeners.length === 1 && listeners[0] === domainUncaughtExceptionClear) | ||
| 136 | + process.removeListener(name, domainUncaughtExceptionClear); | ||
| 137 | + } | ||
| 138 | + }); | ||
| 91 | 139 | ||
| 140 | + function domainUncaughtExceptionClear() { | ||
| 141 | + stack.length = 0; | ||
| 142 | + exports.active = process.domain = null; | ||
| 143 | + updateExceptionCapture(); | ||
| 144 | + } | ||
| 145 | + | ||
| 146 | + | ||
| 147 | + class Domain extends EventEmitter { | ||
| 92 | 148 | constructor() { | |
| 93 | 149 | super(); | |
| 94 | 150 | ||
| 95 | 151 | this.members = []; | |
| 96 | 152 | asyncHook.enable(); | |
| 153 | + | ||
| 154 | + this.on('removeListener', updateExceptionCapture); | ||
| 155 | + this.on('newListener', updateExceptionCapture); | ||
| 97 | 156 | } | |
| 98 | 157 | } | |
| 99 | 158 | ||
@@ -131,14 +190,14 @@ Domain.prototype._errorHandler = function _errorHandler(er) { | |||
| 131 | 190 | // prevent the process 'uncaughtException' event from being emitted | |
| 132 | 191 | // if a listener is set. | |
| 133 | 192 | if (EventEmitter.listenerCount(this, 'error') > 0) { | |
| 193 | + // Clear the uncaughtExceptionCaptureCallback so that we know that, even | ||
| 194 | + // if technically the top-level domain is still active, it would | ||
| 195 | + // be ok to abort on an uncaught exception at this point | ||
| 196 | + setUncaughtExceptionCaptureCallback(null); | ||
| 134 | 197 | try { | |
| 135 | - // Set the _emittingTopLevelDomainError so that we know that, even | ||
| 136 | - // if technically the top-level domain is still active, it would | ||
| 137 | - // be ok to abort on an uncaught exception at this point | ||
| 138 | - process._emittingTopLevelDomainError = true; | ||
| 139 | 198 | caught = this.emit('error', er); | |
| 140 | 199 | } finally { | |
| 141 | - process._emittingTopLevelDomainError = false; | ||
| 200 | + updateExceptionCapture(); | ||
| 142 | 201 | } | |
| 143 | 202 | } | |
| 144 | 203 | } else { | |
@@ -161,20 +220,21 @@ Domain.prototype._errorHandler = function _errorHandler(er) { | |||
| 161 | 220 | if (this === exports.active) { | |
| 162 | 221 | stack.pop(); | |
| 163 | 222 | } | |
| 223 | + updateExceptionCapture(); | ||
| 164 | 224 | if (stack.length) { | |
| 165 | 225 | exports.active = process.domain = stack[stack.length - 1]; | |
| 166 | - caught = process._fatalException(er2); | ||
| 226 | + caught = process.domain._errorHandler(er2); | ||
| 167 | 227 | } else { | |
| 168 | - caught = false; | ||
| 228 | + // Pass on to the next exception handler. | ||
| 229 | + throw er2; | ||
| 169 | 230 | } | |
| 170 | 231 | } | |
| 171 | 232 | } | |
| 172 | 233 | ||
| 173 | 234 | // Exit all domains on the stack. Uncaught exceptions end the | |
| 174 | 235 | // current tick and no domains should be left on the stack | |
| 175 | 236 | // between ticks. | |
| 176 | - stack.length = 0; | ||
| 177 | - exports.active = process.domain = null; | ||
| 237 | + domainUncaughtExceptionClear(); | ||
| 178 | 238 | ||
| 179 | 239 | return caught; | |
| 180 | 240 | }; | |
@@ -185,6 +245,7 @@ Domain.prototype.enter = function() { | |||
| 185 | 245 | // to push it onto the stack so that we can pop it later. | |
| 186 | 246 | exports.active = process.domain = this; | |
| 187 | 247 | stack.push(this); | |
| 248 | + updateExceptionCapture(); | ||
| 188 | 249 | }; | |
| 189 | 250 | ||
| 190 | 251 | ||
@@ -198,6 +259,7 @@ Domain.prototype.exit = function() { | |||
| 198 | 259 | ||
| 199 | 260 | exports.active = stack[stack.length - 1]; | |
| 200 | 261 | process.domain = exports.active; | |
| 262 | + updateExceptionCapture(); | ||
| 201 | 263 | }; | |
| 202 | 264 | ||
| 203 | 265 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -9,6 +9,7 @@ | |||
| 9 | 9 | ||
| 10 | 10 | (function(process) { | |
| 11 | 11 | let internalBinding; | |
| 12 | + const exceptionHandlerState = { captureFn: null }; | ||
| 12 | 13 | ||
| 13 | 14 | function startup() { | |
| 14 | 15 | const EventEmitter = NativeModule.require('events'); | |
@@ -34,6 +35,7 @@ | |||
| 34 | 35 | const _process = NativeModule.require('internal/process'); | |
| 35 | 36 | _process.setupConfig(NativeModule._source); | |
| 36 | 37 | _process.setupSignalHandlers(); | |
| 38 | + _process.setupUncaughtExceptionCapture(exceptionHandlerState); | ||
| 37 | 39 | NativeModule.require('internal/process/warning').setup(); | |
| 38 | 40 | NativeModule.require('internal/process/next_tick').setup(); | |
| 39 | 41 | NativeModule.require('internal/process/stdio').setup(); | |
@@ -376,8 +378,10 @@ | |||
| 376 | 378 | // that threw and was never cleared. So clear it now. | |
| 377 | 379 | async_id_fields[kInitTriggerAsyncId] = 0; | |
| 378 | 380 | ||
| 379 | - if (process.domain && process.domain._errorHandler) | ||
| 380 | - caught = process.domain._errorHandler(er); | ||
| 381 | + if (exceptionHandlerState.captureFn !== null) { | ||
| 382 | + exceptionHandlerState.captureFn(er); | ||
| 383 | + caught = true; | ||
| 384 | + } | ||
| 381 | 385 | ||
| 382 | 386 | if (!caught) | |
| 383 | 387 | caught = process.emit('uncaughtException', er); | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -171,6 +171,13 @@ E('ERR_CRYPTO_SIGN_KEY_REQUIRED', 'No key provided to sign'); | |||
| 171 | 171 | E('ERR_CRYPTO_TIMING_SAFE_EQUAL_LENGTH', | |
| 172 | 172 | 'Input buffers must have the same length'); | |
| 173 | 173 | E('ERR_DNS_SET_SERVERS_FAILED', 'c-ares failed to set servers: "%s" [%s]'); | |
| 174 | + E('ERR_DOMAIN_CALLBACK_NOT_AVAILABLE', | ||
| 175 | + 'A callback was registered through ' + | ||
| 176 | + 'process.setUncaughtExceptionCaptureCallback(), which is mutually ' + | ||
| 177 | + 'exclusive with using the `domain` module'); | ||
| 178 | + E('ERR_DOMAIN_CANNOT_SET_UNCAUGHT_EXCEPTION_CAPTURE', | ||
| 179 | + 'The `domain` module is in use, which is mutually exclusive with calling ' + | ||
| 180 | + 'process.setUncaughtExceptionCaptureCallback()'); | ||
| 174 | 181 | E('ERR_ENCODING_INVALID_ENCODED_DATA', | |
| 175 | 182 | 'The encoded data was not valid for encoding %s'); | |
| 176 | 183 | E('ERR_ENCODING_NOT_SUPPORTED', 'The "%s" encoding is not supported'); | |
@@ -339,6 +346,9 @@ E('ERR_TRANSFORM_ALREADY_TRANSFORMING', | |||
| 339 | 346 | 'Calling transform done when still transforming'); | |
| 340 | 347 | E('ERR_TRANSFORM_WITH_LENGTH_0', | |
| 341 | 348 | 'Calling transform done when writableState.length != 0'); | |
| 349 | + E('ERR_UNCAUGHT_EXCEPTION_CAPTURE_ALREADY_SET', | ||
| 350 | + '`process.setupUncaughtExceptionCapture()` was called while a capture ' + | ||
| 351 | + 'callback was already active'); | ||
| 342 | 352 | E('ERR_UNESCAPED_CHARACTERS', '%s contains unescaped characters'); | |
| 343 | 353 | E('ERR_UNHANDLED_ERROR', | |
| 344 | 354 | (err) => { | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -247,6 +247,34 @@ function setupRawDebug() { | |||
| 247 | 247 | }; | |
| 248 | 248 | } | |
| 249 | 249 | ||
| 250 | + | ||
| 251 | + function setupUncaughtExceptionCapture(exceptionHandlerState) { | ||
| 252 | + // This is a typed array for faster communication with JS. | ||
| 253 | + const shouldAbortOnUncaughtToggle = process._shouldAbortOnUncaughtToggle; | ||
| 254 | + delete process._shouldAbortOnUncaughtToggle; | ||
| 255 | + | ||
| 256 | + process.setUncaughtExceptionCaptureCallback = function(fn) { | ||
| 257 | + if (fn === null) { | ||
| 258 | + exceptionHandlerState.captureFn = fn; | ||
| 259 | + shouldAbortOnUncaughtToggle[0] = 1; | ||
| 260 | + return; | ||
| 261 | + } | ||
| 262 | + if (typeof fn !== 'function') { | ||
| 263 | + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'fn', | ||
| 264 | + ['Function', 'null']); | ||
| 265 | + } | ||
| 266 | + if (exceptionHandlerState.captureFn !== null) { | ||
| 267 | + throw new errors.Error('ERR_UNCAUGHT_EXCEPTION_CAPTURE_ALREADY_SET'); | ||
| 268 | + } | ||
| 269 | + exceptionHandlerState.captureFn = fn; | ||
| 270 | + shouldAbortOnUncaughtToggle[0] = 0; | ||
| 271 | + }; | ||
| 272 | + | ||
| 273 | + process.hasUncaughtExceptionCaptureCallback = function() { | ||
| 274 | + return exceptionHandlerState.captureFn !== null; | ||
| 275 | + }; | ||
| 276 | + } | ||
| 277 | + | ||
| 250 | 278 | module.exports = { | |
| 251 | 279 | setup_performance, | |
| 252 | 280 | setup_cpuUsage, | |
@@ -256,5 +284,6 @@ module.exports = { | |||
| 256 | 284 | setupKillAndExit, | |
| 257 | 285 | setupSignalHandlers, | |
| 258 | 286 | setupChannel, | |
| 259 | - setupRawDebug | ||
| 287 | + setupRawDebug, | ||
| 288 | + setupUncaughtExceptionCapture | ||
| 260 | 289 | }; | |
| Back | FazBrowse Home | New Git URL |
0 commit comments