process: add flag for uncaught exception abort
Introduce `process.shouldAbortOnUncaughtException` to control `--abort-on-uncaught-exception` behaviour, and implement some of the domains functionality on top of it. PR-URL: https://github.com/nodejs/node/pull/17159 Refs: https://github.com/nodejs/node/issues/17143 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Andreas Madsen <amwebdk@gmail.com>
This commit is contained in:
parent
04e3aa28bb
commit
4503da8a3a
@ -183,6 +183,10 @@ added: v0.10
|
|||||||
Aborting instead of exiting causes a core file to be generated for post-mortem
|
Aborting instead of exiting causes a core file to be generated for post-mortem
|
||||||
analysis using a debugger (such as `lldb`, `gdb`, and `mdb`).
|
analysis using a debugger (such as `lldb`, `gdb`, and `mdb`).
|
||||||
|
|
||||||
|
*Note*: If this flag is passed, the behavior can still be set to not abort
|
||||||
|
through [`process.setUncaughtExceptionCaptureCallback()`][] (and through usage
|
||||||
|
of the `domain` module that uses it).
|
||||||
|
|
||||||
### `--trace-warnings`
|
### `--trace-warnings`
|
||||||
<!-- YAML
|
<!-- YAML
|
||||||
added: v6.0.0
|
added: v6.0.0
|
||||||
@ -598,3 +602,4 @@ greater than `4` (its current default value). For more information, see the
|
|||||||
[debugger]: debugger.html
|
[debugger]: debugger.html
|
||||||
[emit_warning]: process.html#process_process_emitwarning_warning_type_code_ctor
|
[emit_warning]: process.html#process_process_emitwarning_warning_type_code_ctor
|
||||||
[libuv threadpool documentation]: http://docs.libuv.org/en/latest/threadpool.html
|
[libuv threadpool documentation]: http://docs.libuv.org/en/latest/threadpool.html
|
||||||
|
[`process.setUncaughtExceptionCaptureCallback()`]: process.html#process_process_setuncaughtexceptioncapturecallback_fn
|
||||||
|
@ -729,6 +729,23 @@ A signing `key` was not provided to the [`sign.sign()`][] method.
|
|||||||
|
|
||||||
`c-ares` failed to set the DNS server.
|
`c-ares` failed to set the DNS server.
|
||||||
|
|
||||||
|
<a id="ERR_DOMAIN_CALLBACK_NOT_AVAILABLE"></a>
|
||||||
|
### ERR_DOMAIN_CALLBACK_NOT_AVAILABLE
|
||||||
|
|
||||||
|
The `domain` module was not usable since it could not establish the required
|
||||||
|
error handling hooks, because
|
||||||
|
[`process.setUncaughtExceptionCaptureCallback()`][] had been called at an
|
||||||
|
earlier point in time.
|
||||||
|
|
||||||
|
<a id="ERR_DOMAIN_CANNOT_SET_UNCAUGHT_EXCEPTION_CAPTURE"></a>
|
||||||
|
### ERR_DOMAIN_CANNOT_SET_UNCAUGHT_EXCEPTION_CAPTURE
|
||||||
|
|
||||||
|
[`process.setUncaughtExceptionCaptureCallback()`][] could not be called
|
||||||
|
because the `domain` module has been loaded at an earlier point in time.
|
||||||
|
|
||||||
|
The stack trace is extended to include the point in time at which the
|
||||||
|
`domain` module had been loaded.
|
||||||
|
|
||||||
<a id="ERR_ENCODING_INVALID_ENCODED_DATA"></a>
|
<a id="ERR_ENCODING_INVALID_ENCODED_DATA"></a>
|
||||||
### ERR_ENCODING_INVALID_ENCODED_DATA
|
### ERR_ENCODING_INVALID_ENCODED_DATA
|
||||||
|
|
||||||
@ -1459,6 +1476,15 @@ A Transform stream finished while it was still transforming.
|
|||||||
|
|
||||||
A Transform stream finished with data still in the write buffer.
|
A Transform stream finished with data still in the write buffer.
|
||||||
|
|
||||||
|
<a id="ERR_UNCAUGHT_EXCEPTION_CAPTURE_ALREADY_SET"></a>
|
||||||
|
### ERR_UNCAUGHT_EXCEPTION_CAPTURE_ALREADY_SET
|
||||||
|
|
||||||
|
[`process.setUncaughtExceptionCaptureCallback()`][] was called twice,
|
||||||
|
without first resetting the callback to `null`.
|
||||||
|
|
||||||
|
This error is designed to prevent accidentally overwriting a callback registered
|
||||||
|
from another module.
|
||||||
|
|
||||||
<a id="ERR_UNESCAPED_CHARACTERS"></a>
|
<a id="ERR_UNESCAPED_CHARACTERS"></a>
|
||||||
### ERR_UNESCAPED_CHARACTERS
|
### ERR_UNESCAPED_CHARACTERS
|
||||||
|
|
||||||
@ -1565,6 +1591,7 @@ Creation of a [`zlib`][] object failed due to incorrect configuration.
|
|||||||
[`new URLSearchParams(iterable)`]: url.html#url_constructor_new_urlsearchparams_iterable
|
[`new URLSearchParams(iterable)`]: url.html#url_constructor_new_urlsearchparams_iterable
|
||||||
[`process.on('uncaughtException')`]: process.html#process_event_uncaughtexception
|
[`process.on('uncaughtException')`]: process.html#process_event_uncaughtexception
|
||||||
[`process.send()`]: process.html#process_process_send_message_sendhandle_options_callback
|
[`process.send()`]: process.html#process_process_send_message_sendhandle_options_callback
|
||||||
|
[`process.setUncaughtExceptionCaptureCallback()`]: process.html#process_process_setuncaughtexceptioncapturecallback_fn
|
||||||
[`require('crypto').setEngine()`]: crypto.html#crypto_crypto_setengine_engine_flags
|
[`require('crypto').setEngine()`]: crypto.html#crypto_crypto_setengine_engine_flags
|
||||||
[`server.listen()`]: net.html#net_server_listen
|
[`server.listen()`]: net.html#net_server_listen
|
||||||
[ES6 module]: esm.html
|
[ES6 module]: esm.html
|
||||||
|
@ -1136,6 +1136,16 @@ if (process.getuid) {
|
|||||||
*Note*: This function is only available on POSIX platforms (i.e. not Windows
|
*Note*: This function is only available on POSIX platforms (i.e. not Windows
|
||||||
or Android).
|
or Android).
|
||||||
|
|
||||||
|
## process.hasUncaughtExceptionCaptureCallback()
|
||||||
|
<!-- YAML
|
||||||
|
added: REPLACEME
|
||||||
|
-->
|
||||||
|
|
||||||
|
* Returns: {boolean}
|
||||||
|
|
||||||
|
Indicates whether a callback has been set using
|
||||||
|
[`process.setUncaughtExceptionCaptureCallback()`][].
|
||||||
|
|
||||||
## process.hrtime([time])
|
## process.hrtime([time])
|
||||||
<!-- YAML
|
<!-- YAML
|
||||||
added: v0.7.6
|
added: v0.7.6
|
||||||
@ -1637,6 +1647,29 @@ if (process.getuid && process.setuid) {
|
|||||||
or Android).
|
or Android).
|
||||||
|
|
||||||
|
|
||||||
|
## process.setUncaughtExceptionCaptureCallback(fn)
|
||||||
|
<!-- YAML
|
||||||
|
added: REPLACEME
|
||||||
|
-->
|
||||||
|
|
||||||
|
* `fn` {Function|null}
|
||||||
|
|
||||||
|
The `process.setUncaughtExceptionCapture` function sets a function that will
|
||||||
|
be invoked when an uncaught exception occurs, which will receive the exception
|
||||||
|
value itself as its first argument.
|
||||||
|
|
||||||
|
If such a function is set, the [`process.on('uncaughtException')`][] event will
|
||||||
|
not be emitted. If `--abort-on-uncaught-exception` was passed from the
|
||||||
|
command line or set through [`v8.setFlagsFromString()`][], the process will
|
||||||
|
not abort.
|
||||||
|
|
||||||
|
To unset the capture function, `process.setUncaughtExceptionCapture(null)`
|
||||||
|
may be used. Calling this method with a non-`null` argument while another
|
||||||
|
capture function is set will throw an error.
|
||||||
|
|
||||||
|
*Note*: Using this function is mutually exclusive with using the
|
||||||
|
deprecated [`domain`][] built-in module.
|
||||||
|
|
||||||
## process.stderr
|
## process.stderr
|
||||||
|
|
||||||
* {Stream}
|
* {Stream}
|
||||||
@ -1921,6 +1954,7 @@ cases:
|
|||||||
[`JSON.stringify` spec]: https://tc39.github.io/ecma262/#sec-json.stringify
|
[`JSON.stringify` spec]: https://tc39.github.io/ecma262/#sec-json.stringify
|
||||||
[`console.error()`]: console.html#console_console_error_data_args
|
[`console.error()`]: console.html#console_console_error_data_args
|
||||||
[`console.log()`]: console.html#console_console_log_data_args
|
[`console.log()`]: console.html#console_console_log_data_args
|
||||||
|
[`domain`]: domain.html
|
||||||
[`end()`]: stream.html#stream_writable_end_chunk_encoding_callback
|
[`end()`]: stream.html#stream_writable_end_chunk_encoding_callback
|
||||||
[`net.Server`]: net.html#net_class_net_server
|
[`net.Server`]: net.html#net_class_net_server
|
||||||
[`net.Socket`]: net.html#net_class_net_socket
|
[`net.Socket`]: net.html#net_class_net_socket
|
||||||
@ -1930,11 +1964,14 @@ cases:
|
|||||||
[`process.exit()`]: #process_process_exit_code
|
[`process.exit()`]: #process_process_exit_code
|
||||||
[`process.exitCode`]: #process_process_exitcode
|
[`process.exitCode`]: #process_process_exitcode
|
||||||
[`process.kill()`]: #process_process_kill_pid_signal
|
[`process.kill()`]: #process_process_kill_pid_signal
|
||||||
|
[`process.on('uncaughtException')`]: process.html#process_event_uncaughtexception
|
||||||
|
[`process.setUncaughtExceptionCaptureCallback()`]: process.html#process_process_setuncaughtexceptioncapturecallback_fn
|
||||||
[`promise.catch()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/catch
|
[`promise.catch()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/catch
|
||||||
[`require()`]: globals.html#globals_require
|
[`require()`]: globals.html#globals_require
|
||||||
[`require.main`]: modules.html#modules_accessing_the_main_module
|
[`require.main`]: modules.html#modules_accessing_the_main_module
|
||||||
[`require.resolve()`]: modules.html#modules_require_resolve_request_options
|
[`require.resolve()`]: modules.html#modules_require_resolve_request_options
|
||||||
[`setTimeout(fn, 0)`]: timers.html#timers_settimeout_callback_delay_args
|
[`setTimeout(fn, 0)`]: timers.html#timers_settimeout_callback_delay_args
|
||||||
|
[`v8.setFlagsFromString()`]: v8.html#v8_v8_setflagsfromstring_flags
|
||||||
[Child Process]: child_process.html
|
[Child Process]: child_process.html
|
||||||
[Cluster]: cluster.html
|
[Cluster]: cluster.html
|
||||||
[Duplex]: stream.html#stream_duplex_and_transform_streams
|
[Duplex]: stream.html#stream_duplex_and_transform_streams
|
||||||
|
@ -28,6 +28,7 @@
|
|||||||
|
|
||||||
const util = require('util');
|
const util = require('util');
|
||||||
const EventEmitter = require('events');
|
const EventEmitter = require('events');
|
||||||
|
const errors = require('internal/errors');
|
||||||
const { createHook } = require('async_hooks');
|
const { createHook } = require('async_hooks');
|
||||||
|
|
||||||
// communicate with events module, but don't require that
|
// communicate with events module, but don't require that
|
||||||
@ -81,19 +82,77 @@ const asyncHook = createHook({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// When domains are in use, they claim full ownership of the
|
||||||
|
// uncaught exception capture callback.
|
||||||
|
if (process.hasUncaughtExceptionCaptureCallback()) {
|
||||||
|
throw new errors.Error('ERR_DOMAIN_CALLBACK_NOT_AVAILABLE');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the stack trace at the point where `domain` was required.
|
||||||
|
const domainRequireStack = new Error('require(`domain`) at this point').stack;
|
||||||
|
|
||||||
|
const { setUncaughtExceptionCaptureCallback } = process;
|
||||||
|
process.setUncaughtExceptionCaptureCallback = function(fn) {
|
||||||
|
const err =
|
||||||
|
new errors.Error('ERR_DOMAIN_CANNOT_SET_UNCAUGHT_EXCEPTION_CAPTURE');
|
||||||
|
err.stack = err.stack + '\n' + '-'.repeat(40) + '\n' + domainRequireStack;
|
||||||
|
throw err;
|
||||||
|
};
|
||||||
|
|
||||||
// It's possible to enter one domain while already inside
|
// It's possible to enter one domain while already inside
|
||||||
// another one. The stack is each entered domain.
|
// another one. The stack is each entered domain.
|
||||||
const stack = [];
|
const stack = [];
|
||||||
exports._stack = stack;
|
exports._stack = stack;
|
||||||
process._setupDomainUse(stack);
|
process._setupDomainUse();
|
||||||
|
|
||||||
|
function updateExceptionCapture() {
|
||||||
|
if (stack.every((domain) => domain.listenerCount('error') === 0)) {
|
||||||
|
setUncaughtExceptionCaptureCallback(null);
|
||||||
|
} else {
|
||||||
|
setUncaughtExceptionCaptureCallback(null);
|
||||||
|
setUncaughtExceptionCaptureCallback((er) => {
|
||||||
|
return process.domain._errorHandler(er);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
process.on('newListener', (name, listener) => {
|
||||||
|
if (name === 'uncaughtException' &&
|
||||||
|
listener !== domainUncaughtExceptionClear) {
|
||||||
|
// Make sure the first listener for `uncaughtException` always clears
|
||||||
|
// the domain stack.
|
||||||
|
process.removeListener(name, domainUncaughtExceptionClear);
|
||||||
|
process.prependListener(name, domainUncaughtExceptionClear);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
process.on('removeListener', (name, listener) => {
|
||||||
|
if (name === 'uncaughtException' &&
|
||||||
|
listener !== domainUncaughtExceptionClear) {
|
||||||
|
// If the domain listener would be the only remaining one, remove it.
|
||||||
|
const listeners = process.listeners('uncaughtException');
|
||||||
|
if (listeners.length === 1 && listeners[0] === domainUncaughtExceptionClear)
|
||||||
|
process.removeListener(name, domainUncaughtExceptionClear);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function domainUncaughtExceptionClear() {
|
||||||
|
stack.length = 0;
|
||||||
|
exports.active = process.domain = null;
|
||||||
|
updateExceptionCapture();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class Domain extends EventEmitter {
|
class Domain extends EventEmitter {
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.members = [];
|
this.members = [];
|
||||||
asyncHook.enable();
|
asyncHook.enable();
|
||||||
|
|
||||||
|
this.on('removeListener', updateExceptionCapture);
|
||||||
|
this.on('newListener', updateExceptionCapture);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -131,14 +190,14 @@ Domain.prototype._errorHandler = function _errorHandler(er) {
|
|||||||
// prevent the process 'uncaughtException' event from being emitted
|
// prevent the process 'uncaughtException' event from being emitted
|
||||||
// if a listener is set.
|
// if a listener is set.
|
||||||
if (EventEmitter.listenerCount(this, 'error') > 0) {
|
if (EventEmitter.listenerCount(this, 'error') > 0) {
|
||||||
|
// Clear the uncaughtExceptionCaptureCallback so that we know that, even
|
||||||
|
// if technically the top-level domain is still active, it would
|
||||||
|
// be ok to abort on an uncaught exception at this point
|
||||||
|
setUncaughtExceptionCaptureCallback(null);
|
||||||
try {
|
try {
|
||||||
// Set the _emittingTopLevelDomainError so that we know that, even
|
|
||||||
// if technically the top-level domain is still active, it would
|
|
||||||
// be ok to abort on an uncaught exception at this point
|
|
||||||
process._emittingTopLevelDomainError = true;
|
|
||||||
caught = this.emit('error', er);
|
caught = this.emit('error', er);
|
||||||
} finally {
|
} finally {
|
||||||
process._emittingTopLevelDomainError = false;
|
updateExceptionCapture();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -161,11 +220,13 @@ Domain.prototype._errorHandler = function _errorHandler(er) {
|
|||||||
if (this === exports.active) {
|
if (this === exports.active) {
|
||||||
stack.pop();
|
stack.pop();
|
||||||
}
|
}
|
||||||
|
updateExceptionCapture();
|
||||||
if (stack.length) {
|
if (stack.length) {
|
||||||
exports.active = process.domain = stack[stack.length - 1];
|
exports.active = process.domain = stack[stack.length - 1];
|
||||||
caught = process._fatalException(er2);
|
caught = process.domain._errorHandler(er2);
|
||||||
} else {
|
} else {
|
||||||
caught = false;
|
// Pass on to the next exception handler.
|
||||||
|
throw er2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -173,8 +234,7 @@ Domain.prototype._errorHandler = function _errorHandler(er) {
|
|||||||
// Exit all domains on the stack. Uncaught exceptions end the
|
// Exit all domains on the stack. Uncaught exceptions end the
|
||||||
// current tick and no domains should be left on the stack
|
// current tick and no domains should be left on the stack
|
||||||
// between ticks.
|
// between ticks.
|
||||||
stack.length = 0;
|
domainUncaughtExceptionClear();
|
||||||
exports.active = process.domain = null;
|
|
||||||
|
|
||||||
return caught;
|
return caught;
|
||||||
};
|
};
|
||||||
@ -185,6 +245,7 @@ Domain.prototype.enter = function() {
|
|||||||
// to push it onto the stack so that we can pop it later.
|
// to push it onto the stack so that we can pop it later.
|
||||||
exports.active = process.domain = this;
|
exports.active = process.domain = this;
|
||||||
stack.push(this);
|
stack.push(this);
|
||||||
|
updateExceptionCapture();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@ -198,6 +259,7 @@ Domain.prototype.exit = function() {
|
|||||||
|
|
||||||
exports.active = stack[stack.length - 1];
|
exports.active = stack[stack.length - 1];
|
||||||
process.domain = exports.active;
|
process.domain = exports.active;
|
||||||
|
updateExceptionCapture();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
8
lib/internal/bootstrap_node.js
vendored
8
lib/internal/bootstrap_node.js
vendored
@ -9,6 +9,7 @@
|
|||||||
|
|
||||||
(function(process) {
|
(function(process) {
|
||||||
let internalBinding;
|
let internalBinding;
|
||||||
|
const exceptionHandlerState = { captureFn: null };
|
||||||
|
|
||||||
function startup() {
|
function startup() {
|
||||||
const EventEmitter = NativeModule.require('events');
|
const EventEmitter = NativeModule.require('events');
|
||||||
@ -35,6 +36,7 @@
|
|||||||
const _process = NativeModule.require('internal/process');
|
const _process = NativeModule.require('internal/process');
|
||||||
_process.setupConfig(NativeModule._source);
|
_process.setupConfig(NativeModule._source);
|
||||||
_process.setupSignalHandlers();
|
_process.setupSignalHandlers();
|
||||||
|
_process.setupUncaughtExceptionCapture(exceptionHandlerState);
|
||||||
NativeModule.require('internal/process/warning').setup();
|
NativeModule.require('internal/process/warning').setup();
|
||||||
NativeModule.require('internal/process/next_tick').setup();
|
NativeModule.require('internal/process/next_tick').setup();
|
||||||
NativeModule.require('internal/process/stdio').setup();
|
NativeModule.require('internal/process/stdio').setup();
|
||||||
@ -377,8 +379,10 @@
|
|||||||
// that threw and was never cleared. So clear it now.
|
// that threw and was never cleared. So clear it now.
|
||||||
async_id_fields[kInitTriggerAsyncId] = 0;
|
async_id_fields[kInitTriggerAsyncId] = 0;
|
||||||
|
|
||||||
if (process.domain && process.domain._errorHandler)
|
if (exceptionHandlerState.captureFn !== null) {
|
||||||
caught = process.domain._errorHandler(er);
|
exceptionHandlerState.captureFn(er);
|
||||||
|
caught = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (!caught)
|
if (!caught)
|
||||||
caught = process.emit('uncaughtException', er);
|
caught = process.emit('uncaughtException', er);
|
||||||
|
@ -291,6 +291,13 @@ E('ERR_CRYPTO_SIGN_KEY_REQUIRED', 'No key provided to sign');
|
|||||||
E('ERR_CRYPTO_TIMING_SAFE_EQUAL_LENGTH',
|
E('ERR_CRYPTO_TIMING_SAFE_EQUAL_LENGTH',
|
||||||
'Input buffers must have the same length');
|
'Input buffers must have the same length');
|
||||||
E('ERR_DNS_SET_SERVERS_FAILED', 'c-ares failed to set servers: "%s" [%s]');
|
E('ERR_DNS_SET_SERVERS_FAILED', 'c-ares failed to set servers: "%s" [%s]');
|
||||||
|
E('ERR_DOMAIN_CALLBACK_NOT_AVAILABLE',
|
||||||
|
'A callback was registered through ' +
|
||||||
|
'process.setUncaughtExceptionCaptureCallback(), which is mutually ' +
|
||||||
|
'exclusive with using the `domain` module');
|
||||||
|
E('ERR_DOMAIN_CANNOT_SET_UNCAUGHT_EXCEPTION_CAPTURE',
|
||||||
|
'The `domain` module is in use, which is mutually exclusive with calling ' +
|
||||||
|
'process.setUncaughtExceptionCaptureCallback()');
|
||||||
E('ERR_ENCODING_INVALID_ENCODED_DATA',
|
E('ERR_ENCODING_INVALID_ENCODED_DATA',
|
||||||
'The encoded data was not valid for encoding %s');
|
'The encoded data was not valid for encoding %s');
|
||||||
E('ERR_ENCODING_NOT_SUPPORTED', 'The "%s" encoding is not supported');
|
E('ERR_ENCODING_NOT_SUPPORTED', 'The "%s" encoding is not supported');
|
||||||
@ -461,6 +468,9 @@ E('ERR_TRANSFORM_ALREADY_TRANSFORMING',
|
|||||||
'Calling transform done when still transforming');
|
'Calling transform done when still transforming');
|
||||||
E('ERR_TRANSFORM_WITH_LENGTH_0',
|
E('ERR_TRANSFORM_WITH_LENGTH_0',
|
||||||
'Calling transform done when writableState.length != 0');
|
'Calling transform done when writableState.length != 0');
|
||||||
|
E('ERR_UNCAUGHT_EXCEPTION_CAPTURE_ALREADY_SET',
|
||||||
|
'`process.setupUncaughtExceptionCapture()` was called while a capture ' +
|
||||||
|
'callback was already active');
|
||||||
E('ERR_UNESCAPED_CHARACTERS', '%s contains unescaped characters');
|
E('ERR_UNESCAPED_CHARACTERS', '%s contains unescaped characters');
|
||||||
E('ERR_UNHANDLED_ERROR',
|
E('ERR_UNHANDLED_ERROR',
|
||||||
(err) => {
|
(err) => {
|
||||||
|
@ -247,6 +247,34 @@ function setupRawDebug() {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function setupUncaughtExceptionCapture(exceptionHandlerState) {
|
||||||
|
// This is a typed array for faster communication with JS.
|
||||||
|
const shouldAbortOnUncaughtToggle = process._shouldAbortOnUncaughtToggle;
|
||||||
|
delete process._shouldAbortOnUncaughtToggle;
|
||||||
|
|
||||||
|
process.setUncaughtExceptionCaptureCallback = function(fn) {
|
||||||
|
if (fn === null) {
|
||||||
|
exceptionHandlerState.captureFn = fn;
|
||||||
|
shouldAbortOnUncaughtToggle[0] = 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (typeof fn !== 'function') {
|
||||||
|
throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'fn',
|
||||||
|
['Function', 'null']);
|
||||||
|
}
|
||||||
|
if (exceptionHandlerState.captureFn !== null) {
|
||||||
|
throw new errors.Error('ERR_UNCAUGHT_EXCEPTION_CAPTURE_ALREADY_SET');
|
||||||
|
}
|
||||||
|
exceptionHandlerState.captureFn = fn;
|
||||||
|
shouldAbortOnUncaughtToggle[0] = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
process.hasUncaughtExceptionCaptureCallback = function() {
|
||||||
|
return exceptionHandlerState.captureFn !== null;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
setup_performance,
|
setup_performance,
|
||||||
setup_cpuUsage,
|
setup_cpuUsage,
|
||||||
@ -256,5 +284,6 @@ module.exports = {
|
|||||||
setupKillAndExit,
|
setupKillAndExit,
|
||||||
setupSignalHandlers,
|
setupSignalHandlers,
|
||||||
setupChannel,
|
setupChannel,
|
||||||
setupRawDebug
|
setupRawDebug,
|
||||||
|
setupUncaughtExceptionCapture
|
||||||
};
|
};
|
||||||
|
@ -268,6 +268,7 @@ inline Environment::Environment(IsolateData* isolate_data,
|
|||||||
emit_napi_warning_(true),
|
emit_napi_warning_(true),
|
||||||
makecallback_cntr_(0),
|
makecallback_cntr_(0),
|
||||||
scheduled_immediate_count_(isolate_, 1),
|
scheduled_immediate_count_(isolate_, 1),
|
||||||
|
should_abort_on_uncaught_toggle_(isolate_, 1),
|
||||||
#if HAVE_INSPECTOR
|
#if HAVE_INSPECTOR
|
||||||
inspector_agent_(new inspector::Agent(this)),
|
inspector_agent_(new inspector::Agent(this)),
|
||||||
#endif
|
#endif
|
||||||
@ -305,6 +306,9 @@ inline Environment::Environment(IsolateData* isolate_data,
|
|||||||
performance_state_->milestones[
|
performance_state_->milestones[
|
||||||
performance::NODE_PERFORMANCE_MILESTONE_V8_START] =
|
performance::NODE_PERFORMANCE_MILESTONE_V8_START] =
|
||||||
performance::performance_v8_start;
|
performance::performance_v8_start;
|
||||||
|
|
||||||
|
// By default, always abort when --abort-on-uncaught-exception was passed.
|
||||||
|
should_abort_on_uncaught_toggle_[0] = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline Environment::~Environment() {
|
inline Environment::~Environment() {
|
||||||
@ -399,6 +403,11 @@ inline void Environment::set_abort_on_uncaught_exception(bool value) {
|
|||||||
abort_on_uncaught_exception_ = value;
|
abort_on_uncaught_exception_ = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline AliasedBuffer<uint32_t, v8::Uint32Array>&
|
||||||
|
Environment::should_abort_on_uncaught_toggle() {
|
||||||
|
return should_abort_on_uncaught_toggle_;
|
||||||
|
}
|
||||||
|
|
||||||
inline std::vector<double>* Environment::destroy_async_id_list() {
|
inline std::vector<double>* Environment::destroy_async_id_list() {
|
||||||
return &destroy_async_id_list_;
|
return &destroy_async_id_list_;
|
||||||
}
|
}
|
||||||
|
10
src/env.h
10
src/env.h
@ -134,7 +134,6 @@ class ModuleWrap;
|
|||||||
V(dns_txt_string, "TXT") \
|
V(dns_txt_string, "TXT") \
|
||||||
V(domain_string, "domain") \
|
V(domain_string, "domain") \
|
||||||
V(emit_string, "emit") \
|
V(emit_string, "emit") \
|
||||||
V(emitting_top_level_domain_error_string, "_emittingTopLevelDomainError") \
|
|
||||||
V(exchange_string, "exchange") \
|
V(exchange_string, "exchange") \
|
||||||
V(enumerable_string, "enumerable") \
|
V(enumerable_string, "enumerable") \
|
||||||
V(idle_string, "idle") \
|
V(idle_string, "idle") \
|
||||||
@ -309,7 +308,6 @@ class ModuleWrap;
|
|||||||
V(internal_binding_cache_object, v8::Object) \
|
V(internal_binding_cache_object, v8::Object) \
|
||||||
V(buffer_prototype_object, v8::Object) \
|
V(buffer_prototype_object, v8::Object) \
|
||||||
V(context, v8::Context) \
|
V(context, v8::Context) \
|
||||||
V(domains_stack_array, v8::Array) \
|
|
||||||
V(http2ping_constructor_template, v8::ObjectTemplate) \
|
V(http2ping_constructor_template, v8::ObjectTemplate) \
|
||||||
V(http2stream_constructor_template, v8::ObjectTemplate) \
|
V(http2stream_constructor_template, v8::ObjectTemplate) \
|
||||||
V(inspector_console_api_object, v8::Object) \
|
V(inspector_console_api_object, v8::Object) \
|
||||||
@ -568,8 +566,15 @@ class Environment {
|
|||||||
void PrintSyncTrace() const;
|
void PrintSyncTrace() const;
|
||||||
inline void set_trace_sync_io(bool value);
|
inline void set_trace_sync_io(bool value);
|
||||||
|
|
||||||
|
// This stores whether the --abort-on-uncaught-exception flag was passed
|
||||||
|
// to Node.
|
||||||
inline bool abort_on_uncaught_exception() const;
|
inline bool abort_on_uncaught_exception() const;
|
||||||
inline void set_abort_on_uncaught_exception(bool value);
|
inline void set_abort_on_uncaught_exception(bool value);
|
||||||
|
// This is a pseudo-boolean that keeps track of whether an uncaught exception
|
||||||
|
// should abort the process or not if --abort-on-uncaught-exception was
|
||||||
|
// passed to Node. If the flag was not passed, it is ignored.
|
||||||
|
inline AliasedBuffer<uint32_t, v8::Uint32Array>&
|
||||||
|
should_abort_on_uncaught_toggle();
|
||||||
|
|
||||||
// The necessary API for async_hooks.
|
// The necessary API for async_hooks.
|
||||||
inline double new_async_id();
|
inline double new_async_id();
|
||||||
@ -717,6 +722,7 @@ class Environment {
|
|||||||
std::vector<double> destroy_async_id_list_;
|
std::vector<double> destroy_async_id_list_;
|
||||||
|
|
||||||
AliasedBuffer<uint32_t, v8::Uint32Array> scheduled_immediate_count_;
|
AliasedBuffer<uint32_t, v8::Uint32Array> scheduled_immediate_count_;
|
||||||
|
AliasedBuffer<uint32_t, v8::Uint32Array> should_abort_on_uncaught_toggle_;
|
||||||
|
|
||||||
performance::performance_state* performance_state_ = nullptr;
|
performance::performance_state* performance_state_ = nullptr;
|
||||||
std::map<std::string, uint64_t> performance_marks_;
|
std::map<std::string, uint64_t> performance_marks_;
|
||||||
|
67
src/node.cc
67
src/node.cc
@ -779,66 +779,13 @@ void* ArrayBufferAllocator::Allocate(size_t size) {
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
bool DomainHasErrorHandler(const Environment* env,
|
|
||||||
const Local<Object>& domain) {
|
|
||||||
HandleScope scope(env->isolate());
|
|
||||||
|
|
||||||
Local<Value> domain_event_listeners_v = domain->Get(env->events_string());
|
|
||||||
if (!domain_event_listeners_v->IsObject())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
Local<Object> domain_event_listeners_o =
|
|
||||||
domain_event_listeners_v.As<Object>();
|
|
||||||
|
|
||||||
Local<Value> domain_error_listeners_v =
|
|
||||||
domain_event_listeners_o->Get(env->error_string());
|
|
||||||
|
|
||||||
if (domain_error_listeners_v->IsFunction() ||
|
|
||||||
(domain_error_listeners_v->IsArray() &&
|
|
||||||
domain_error_listeners_v.As<Array>()->Length() > 0))
|
|
||||||
return true;
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DomainsStackHasErrorHandler(const Environment* env) {
|
|
||||||
HandleScope scope(env->isolate());
|
|
||||||
|
|
||||||
if (!env->using_domains())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
Local<Array> domains_stack_array = env->domains_stack_array().As<Array>();
|
|
||||||
if (domains_stack_array->Length() == 0)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
uint32_t domains_stack_length = domains_stack_array->Length();
|
|
||||||
for (uint32_t i = domains_stack_length; i > 0; --i) {
|
|
||||||
Local<Value> domain_v = domains_stack_array->Get(i - 1);
|
|
||||||
if (!domain_v->IsObject())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
Local<Object> domain = domain_v.As<Object>();
|
|
||||||
if (DomainHasErrorHandler(env, domain))
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
bool ShouldAbortOnUncaughtException(Isolate* isolate) {
|
bool ShouldAbortOnUncaughtException(Isolate* isolate) {
|
||||||
HandleScope scope(isolate);
|
HandleScope scope(isolate);
|
||||||
|
|
||||||
Environment* env = Environment::GetCurrent(isolate);
|
Environment* env = Environment::GetCurrent(isolate);
|
||||||
Local<Object> process_object = env->process_object();
|
return env->should_abort_on_uncaught_toggle()[0];
|
||||||
Local<String> emitting_top_level_domain_error_key =
|
|
||||||
env->emitting_top_level_domain_error_string();
|
|
||||||
bool isEmittingTopLevelDomainError =
|
|
||||||
process_object->Get(emitting_top_level_domain_error_key)->BooleanValue();
|
|
||||||
|
|
||||||
return isEmittingTopLevelDomainError || !DomainsStackHasErrorHandler(env);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Local<Value> GetDomainProperty(Environment* env, Local<Object> object) {
|
Local<Value> GetDomainProperty(Environment* env, Local<Object> object) {
|
||||||
Local<Value> domain_v =
|
Local<Value> domain_v =
|
||||||
object->GetPrivate(env->context(), env->domain_private_symbol())
|
object->GetPrivate(env->context(), env->domain_private_symbol())
|
||||||
@ -888,9 +835,6 @@ void SetupDomainUse(const FunctionCallbackInfo<Value>& args) {
|
|||||||
|
|
||||||
HandleScope scope(env->isolate());
|
HandleScope scope(env->isolate());
|
||||||
|
|
||||||
CHECK(args[0]->IsArray());
|
|
||||||
env->set_domains_stack_array(args[0].As<Array>());
|
|
||||||
|
|
||||||
// Do a little housekeeping.
|
// Do a little housekeeping.
|
||||||
env->process_object()->Delete(
|
env->process_object()->Delete(
|
||||||
env->context(),
|
env->context(),
|
||||||
@ -3161,6 +3105,13 @@ void SetupProcessObject(Environment* env,
|
|||||||
scheduled_immediate_count,
|
scheduled_immediate_count,
|
||||||
env->scheduled_immediate_count().GetJSArray()).FromJust());
|
env->scheduled_immediate_count().GetJSArray()).FromJust());
|
||||||
|
|
||||||
|
auto should_abort_on_uncaught_toggle =
|
||||||
|
FIXED_ONE_BYTE_STRING(env->isolate(), "_shouldAbortOnUncaughtToggle");
|
||||||
|
CHECK(process->Set(env->context(),
|
||||||
|
should_abort_on_uncaught_toggle,
|
||||||
|
env->should_abort_on_uncaught_toggle().GetJSArray())
|
||||||
|
.FromJust());
|
||||||
|
|
||||||
// -e, --eval
|
// -e, --eval
|
||||||
if (eval_string) {
|
if (eval_string) {
|
||||||
READONLY_PROPERTY(process,
|
READONLY_PROPERTY(process,
|
||||||
|
@ -0,0 +1,18 @@
|
|||||||
|
'use strict';
|
||||||
|
const common = require('../common');
|
||||||
|
const assert = require('assert');
|
||||||
|
|
||||||
|
process.setUncaughtExceptionCaptureCallback(common.mustNotCall());
|
||||||
|
|
||||||
|
common.expectsError(
|
||||||
|
() => require('domain'),
|
||||||
|
{
|
||||||
|
code: 'ERR_DOMAIN_CALLBACK_NOT_AVAILABLE',
|
||||||
|
type: Error,
|
||||||
|
message: /^A callback was registered.*with using the `domain` module/
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
process.setUncaughtExceptionCaptureCallback(null);
|
||||||
|
|
||||||
|
assert.doesNotThrow(() => require('domain'));
|
@ -0,0 +1,28 @@
|
|||||||
|
'use strict';
|
||||||
|
const common = require('../common');
|
||||||
|
const assert = require('assert');
|
||||||
|
|
||||||
|
(function foobar() {
|
||||||
|
require('domain');
|
||||||
|
})();
|
||||||
|
|
||||||
|
assert.throws(
|
||||||
|
() => process.setUncaughtExceptionCaptureCallback(common.mustNotCall()),
|
||||||
|
(err) => {
|
||||||
|
common.expectsError(
|
||||||
|
{
|
||||||
|
code: 'ERR_DOMAIN_CANNOT_SET_UNCAUGHT_EXCEPTION_CAPTURE',
|
||||||
|
type: Error,
|
||||||
|
message: /^The `domain` module is in use, which is mutually/
|
||||||
|
}
|
||||||
|
)(err);
|
||||||
|
|
||||||
|
assert(err.stack.includes('-'.repeat(40)),
|
||||||
|
`expected ${err.stack} to contain dashes`);
|
||||||
|
|
||||||
|
const location = `at foobar (${__filename}:`;
|
||||||
|
assert(err.stack.includes(location),
|
||||||
|
`expected ${err.stack} to contain ${location}`);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
);
|
22
test/parallel/test-process-exception-capture-errors.js
Normal file
22
test/parallel/test-process-exception-capture-errors.js
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
'use strict';
|
||||||
|
const common = require('../common');
|
||||||
|
|
||||||
|
common.expectsError(
|
||||||
|
() => process.setUncaughtExceptionCaptureCallback(42),
|
||||||
|
{
|
||||||
|
code: 'ERR_INVALID_ARG_TYPE',
|
||||||
|
type: TypeError,
|
||||||
|
message: 'The "fn" argument must be one of type Function or null'
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
process.setUncaughtExceptionCaptureCallback(common.mustNotCall());
|
||||||
|
|
||||||
|
common.expectsError(
|
||||||
|
() => process.setUncaughtExceptionCaptureCallback(common.mustNotCall()),
|
||||||
|
{
|
||||||
|
code: 'ERR_UNCAUGHT_EXCEPTION_CAPTURE_ALREADY_SET',
|
||||||
|
type: Error,
|
||||||
|
message: /setupUncaughtExceptionCapture.*called while a capture callback/
|
||||||
|
}
|
||||||
|
);
|
@ -0,0 +1,13 @@
|
|||||||
|
'use strict';
|
||||||
|
const common = require('../common');
|
||||||
|
const assert = require('assert');
|
||||||
|
const v8 = require('v8');
|
||||||
|
|
||||||
|
assert.strictEqual(process.hasUncaughtExceptionCaptureCallback(), false);
|
||||||
|
|
||||||
|
v8.setFlagsFromString('--abort-on-uncaught-exception');
|
||||||
|
// This should make the process not crash even though the flag was passed.
|
||||||
|
process.setUncaughtExceptionCaptureCallback(common.mustCall((err) => {
|
||||||
|
assert.strictEqual(err.message, 'foo');
|
||||||
|
}));
|
||||||
|
throw new Error('foo');
|
@ -0,0 +1,12 @@
|
|||||||
|
// Flags: --abort-on-uncaught-exception
|
||||||
|
'use strict';
|
||||||
|
const common = require('../common');
|
||||||
|
const assert = require('assert');
|
||||||
|
|
||||||
|
assert.strictEqual(process.hasUncaughtExceptionCaptureCallback(), false);
|
||||||
|
|
||||||
|
// This should make the process not crash even though the flag was passed.
|
||||||
|
process.setUncaughtExceptionCaptureCallback(common.mustCall((err) => {
|
||||||
|
assert.strictEqual(err.message, 'foo');
|
||||||
|
}));
|
||||||
|
throw new Error('foo');
|
13
test/parallel/test-process-exception-capture.js
Normal file
13
test/parallel/test-process-exception-capture.js
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
// Flags: --abort-on-uncaught-exception
|
||||||
|
'use strict';
|
||||||
|
const common = require('../common');
|
||||||
|
const assert = require('assert');
|
||||||
|
|
||||||
|
assert.strictEqual(process.hasUncaughtExceptionCaptureCallback(), false);
|
||||||
|
|
||||||
|
// This should make the process not crash even though the flag was passed.
|
||||||
|
process.setUncaughtExceptionCaptureCallback(common.mustCall((err) => {
|
||||||
|
assert.strictEqual(err.message, 'foo');
|
||||||
|
}));
|
||||||
|
process.on('uncaughtException', common.mustNotCall());
|
||||||
|
throw new Error('foo');
|
Loading…
x
Reference in New Issue
Block a user