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
|
||||
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`
|
||||
<!-- YAML
|
||||
added: v6.0.0
|
||||
@ -598,3 +602,4 @@ greater than `4` (its current default value). For more information, see the
|
||||
[debugger]: debugger.html
|
||||
[emit_warning]: process.html#process_process_emitwarning_warning_type_code_ctor
|
||||
[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.
|
||||
|
||||
<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>
|
||||
### 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 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>
|
||||
### 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
|
||||
[`process.on('uncaughtException')`]: process.html#process_event_uncaughtexception
|
||||
[`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
|
||||
[`server.listen()`]: net.html#net_server_listen
|
||||
[ES6 module]: esm.html
|
||||
|
@ -1136,6 +1136,16 @@ if (process.getuid) {
|
||||
*Note*: This function is only available on POSIX platforms (i.e. not Windows
|
||||
or Android).
|
||||
|
||||
## process.hasUncaughtExceptionCaptureCallback()
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
* Returns: {boolean}
|
||||
|
||||
Indicates whether a callback has been set using
|
||||
[`process.setUncaughtExceptionCaptureCallback()`][].
|
||||
|
||||
## process.hrtime([time])
|
||||
<!-- YAML
|
||||
added: v0.7.6
|
||||
@ -1637,6 +1647,29 @@ if (process.getuid && process.setuid) {
|
||||
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
|
||||
|
||||
* {Stream}
|
||||
@ -1921,6 +1954,7 @@ cases:
|
||||
[`JSON.stringify` spec]: https://tc39.github.io/ecma262/#sec-json.stringify
|
||||
[`console.error()`]: console.html#console_console_error_data_args
|
||||
[`console.log()`]: console.html#console_console_log_data_args
|
||||
[`domain`]: domain.html
|
||||
[`end()`]: stream.html#stream_writable_end_chunk_encoding_callback
|
||||
[`net.Server`]: net.html#net_class_net_server
|
||||
[`net.Socket`]: net.html#net_class_net_socket
|
||||
@ -1930,11 +1964,14 @@ cases:
|
||||
[`process.exit()`]: #process_process_exit_code
|
||||
[`process.exitCode`]: #process_process_exitcode
|
||||
[`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
|
||||
[`require()`]: globals.html#globals_require
|
||||
[`require.main`]: modules.html#modules_accessing_the_main_module
|
||||
[`require.resolve()`]: modules.html#modules_require_resolve_request_options
|
||||
[`setTimeout(fn, 0)`]: timers.html#timers_settimeout_callback_delay_args
|
||||
[`v8.setFlagsFromString()`]: v8.html#v8_v8_setflagsfromstring_flags
|
||||
[Child Process]: child_process.html
|
||||
[Cluster]: cluster.html
|
||||
[Duplex]: stream.html#stream_duplex_and_transform_streams
|
||||
|
@ -28,6 +28,7 @@
|
||||
|
||||
const util = require('util');
|
||||
const EventEmitter = require('events');
|
||||
const errors = require('internal/errors');
|
||||
const { createHook } = require('async_hooks');
|
||||
|
||||
// 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
|
||||
// another one. The stack is each entered domain.
|
||||
const 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 {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.members = [];
|
||||
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
|
||||
// if a listener is set.
|
||||
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 {
|
||||
// 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);
|
||||
} finally {
|
||||
process._emittingTopLevelDomainError = false;
|
||||
updateExceptionCapture();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@ -161,11 +220,13 @@ Domain.prototype._errorHandler = function _errorHandler(er) {
|
||||
if (this === exports.active) {
|
||||
stack.pop();
|
||||
}
|
||||
updateExceptionCapture();
|
||||
if (stack.length) {
|
||||
exports.active = process.domain = stack[stack.length - 1];
|
||||
caught = process._fatalException(er2);
|
||||
caught = process.domain._errorHandler(er2);
|
||||
} 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
|
||||
// current tick and no domains should be left on the stack
|
||||
// between ticks.
|
||||
stack.length = 0;
|
||||
exports.active = process.domain = null;
|
||||
domainUncaughtExceptionClear();
|
||||
|
||||
return caught;
|
||||
};
|
||||
@ -185,6 +245,7 @@ Domain.prototype.enter = function() {
|
||||
// to push it onto the stack so that we can pop it later.
|
||||
exports.active = process.domain = this;
|
||||
stack.push(this);
|
||||
updateExceptionCapture();
|
||||
};
|
||||
|
||||
|
||||
@ -198,6 +259,7 @@ Domain.prototype.exit = function() {
|
||||
|
||||
exports.active = stack[stack.length - 1];
|
||||
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) {
|
||||
let internalBinding;
|
||||
const exceptionHandlerState = { captureFn: null };
|
||||
|
||||
function startup() {
|
||||
const EventEmitter = NativeModule.require('events');
|
||||
@ -35,6 +36,7 @@
|
||||
const _process = NativeModule.require('internal/process');
|
||||
_process.setupConfig(NativeModule._source);
|
||||
_process.setupSignalHandlers();
|
||||
_process.setupUncaughtExceptionCapture(exceptionHandlerState);
|
||||
NativeModule.require('internal/process/warning').setup();
|
||||
NativeModule.require('internal/process/next_tick').setup();
|
||||
NativeModule.require('internal/process/stdio').setup();
|
||||
@ -377,8 +379,10 @@
|
||||
// that threw and was never cleared. So clear it now.
|
||||
async_id_fields[kInitTriggerAsyncId] = 0;
|
||||
|
||||
if (process.domain && process.domain._errorHandler)
|
||||
caught = process.domain._errorHandler(er);
|
||||
if (exceptionHandlerState.captureFn !== null) {
|
||||
exceptionHandlerState.captureFn(er);
|
||||
caught = true;
|
||||
}
|
||||
|
||||
if (!caught)
|
||||
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',
|
||||
'Input buffers must have the same length');
|
||||
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',
|
||||
'The encoded data was not valid for encoding %s');
|
||||
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');
|
||||
E('ERR_TRANSFORM_WITH_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_UNHANDLED_ERROR',
|
||||
(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 = {
|
||||
setup_performance,
|
||||
setup_cpuUsage,
|
||||
@ -256,5 +284,6 @@ module.exports = {
|
||||
setupKillAndExit,
|
||||
setupSignalHandlers,
|
||||
setupChannel,
|
||||
setupRawDebug
|
||||
setupRawDebug,
|
||||
setupUncaughtExceptionCapture
|
||||
};
|
||||
|
@ -268,6 +268,7 @@ inline Environment::Environment(IsolateData* isolate_data,
|
||||
emit_napi_warning_(true),
|
||||
makecallback_cntr_(0),
|
||||
scheduled_immediate_count_(isolate_, 1),
|
||||
should_abort_on_uncaught_toggle_(isolate_, 1),
|
||||
#if HAVE_INSPECTOR
|
||||
inspector_agent_(new inspector::Agent(this)),
|
||||
#endif
|
||||
@ -305,6 +306,9 @@ inline Environment::Environment(IsolateData* isolate_data,
|
||||
performance_state_->milestones[
|
||||
performance::NODE_PERFORMANCE_MILESTONE_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() {
|
||||
@ -399,6 +403,11 @@ inline void Environment::set_abort_on_uncaught_exception(bool 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() {
|
||||
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(domain_string, "domain") \
|
||||
V(emit_string, "emit") \
|
||||
V(emitting_top_level_domain_error_string, "_emittingTopLevelDomainError") \
|
||||
V(exchange_string, "exchange") \
|
||||
V(enumerable_string, "enumerable") \
|
||||
V(idle_string, "idle") \
|
||||
@ -309,7 +308,6 @@ class ModuleWrap;
|
||||
V(internal_binding_cache_object, v8::Object) \
|
||||
V(buffer_prototype_object, v8::Object) \
|
||||
V(context, v8::Context) \
|
||||
V(domains_stack_array, v8::Array) \
|
||||
V(http2ping_constructor_template, v8::ObjectTemplate) \
|
||||
V(http2stream_constructor_template, v8::ObjectTemplate) \
|
||||
V(inspector_console_api_object, v8::Object) \
|
||||
@ -568,8 +566,15 @@ class Environment {
|
||||
void PrintSyncTrace() const;
|
||||
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 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.
|
||||
inline double new_async_id();
|
||||
@ -717,6 +722,7 @@ class Environment {
|
||||
std::vector<double> destroy_async_id_list_;
|
||||
|
||||
AliasedBuffer<uint32_t, v8::Uint32Array> scheduled_immediate_count_;
|
||||
AliasedBuffer<uint32_t, v8::Uint32Array> should_abort_on_uncaught_toggle_;
|
||||
|
||||
performance::performance_state* performance_state_ = nullptr;
|
||||
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 {
|
||||
|
||||
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) {
|
||||
HandleScope scope(isolate);
|
||||
|
||||
Environment* env = Environment::GetCurrent(isolate);
|
||||
Local<Object> process_object = env->process_object();
|
||||
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);
|
||||
return env->should_abort_on_uncaught_toggle()[0];
|
||||
}
|
||||
|
||||
|
||||
Local<Value> GetDomainProperty(Environment* env, Local<Object> object) {
|
||||
Local<Value> domain_v =
|
||||
object->GetPrivate(env->context(), env->domain_private_symbol())
|
||||
@ -888,9 +835,6 @@ void SetupDomainUse(const FunctionCallbackInfo<Value>& args) {
|
||||
|
||||
HandleScope scope(env->isolate());
|
||||
|
||||
CHECK(args[0]->IsArray());
|
||||
env->set_domains_stack_array(args[0].As<Array>());
|
||||
|
||||
// Do a little housekeeping.
|
||||
env->process_object()->Delete(
|
||||
env->context(),
|
||||
@ -3161,6 +3105,13 @@ void SetupProcessObject(Environment* env,
|
||||
scheduled_immediate_count,
|
||||
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
|
||||
if (eval_string) {
|
||||
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