Skip to content

Commit 25cded4

Browse files
author
Julien Gilli
committed
domains: fix handling of uncaught exceptions
Fix node exiting due to an exception being thrown rather than emitting an `'uncaughtException'` event on the process object when: 1. no error handler is set on the domain within which an error is thrown 2. an `'uncaughtException'` event listener is set on the process Also fix an issue where the process would not abort in the proper function call if an error is thrown within a domain with no error handler and `--abort-on-uncaught-exception` is used. Fixes #3607 and #3653. PR: #3884 PR-URL: #3884 Reviewed-By: James M Snell <[email protected]>
1 parent 0e269a7 commit 25cded4

9 files changed

+818
-129
lines changed

lib/domain.js

+20-13
Original file line numberDiff line numberDiff line change
@@ -27,19 +27,20 @@ Object.defineProperty(process, 'domain', {
2727
}
2828
});
2929

30+
// It's possible to enter one domain while already inside
31+
// another one. the stack is each entered domain.
32+
const stack = [];
33+
exports._stack = stack;
34+
3035
// let the process know we're using domains
31-
const _domain_flag = process._setupDomainUse(_domain);
36+
const _domain_flag = process._setupDomainUse(_domain, stack);
3237

3338
exports.Domain = Domain;
3439

3540
exports.create = exports.createDomain = function() {
3641
return new Domain();
3742
};
3843

39-
// it's possible to enter one domain while already inside
40-
// another one. the stack is each entered domain.
41-
var stack = [];
42-
exports._stack = stack;
4344
// the active domain is always the one that we're currently in.
4445
exports.active = null;
4546

@@ -96,14 +97,20 @@ Domain.prototype._errorHandler = function errorHandler(er) {
9697
// that these exceptions are caught, and thus would prevent it from
9798
// aborting in these cases.
9899
if (stack.length === 1) {
99-
try {
100-
// Set the _emittingTopLevelDomainError so that we know that, even
101-
// if technically the top-level domain is still active, it would
102-
// be ok to abort on an uncaught exception at this point
103-
process._emittingTopLevelDomainError = true;
104-
caught = emitError();
105-
} finally {
106-
process._emittingTopLevelDomainError = false;
100+
// If there's no error handler, do not emit an 'error' event
101+
// as this would throw an error, make the process exit, and thus
102+
// prevent the process 'uncaughtException' event from being emitted
103+
// if a listener is set.
104+
if (EventEmitter.listenerCount(self, 'error') > 0) {
105+
try {
106+
// Set the _emittingTopLevelDomainError so that we know that, even
107+
// if technically the top-level domain is still active, it would
108+
// be ok to abort on an uncaught exception at this point
109+
process._emittingTopLevelDomainError = true;
110+
caught = emitError();
111+
} finally {
112+
process._emittingTopLevelDomainError = false;
113+
}
107114
}
108115
} else {
109116
// wrap this in a try/catch so we don't get infinite throwing

src/env.h

+1
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,7 @@ namespace node {
236236
V(buffer_prototype_object, v8::Object) \
237237
V(context, v8::Context) \
238238
V(domain_array, v8::Array) \
239+
V(domains_stack_array, v8::Array) \
239240
V(fs_stats_constructor_function, v8::Function) \
240241
V(generic_internal_field_template, v8::ObjectTemplate) \
241242
V(jsstream_constructor_template, v8::FunctionTemplate) \

src/node.cc

+45-6
Original file line numberDiff line numberDiff line change
@@ -947,17 +947,53 @@ void* ArrayBufferAllocator::Allocate(size_t size) {
947947
return malloc(size);
948948
}
949949

950+
static bool DomainHasErrorHandler(const Environment* env,
951+
const Local<Object>& domain) {
952+
HandleScope scope(env->isolate());
953+
954+
Local<Value> domain_event_listeners_v = domain->Get(env->events_string());
955+
if (!domain_event_listeners_v->IsObject())
956+
return false;
957+
958+
Local<Object> domain_event_listeners_o =
959+
domain_event_listeners_v.As<Object>();
960+
961+
Local<Value> domain_error_listeners_v =
962+
domain_event_listeners_o->Get(env->error_string());
963+
964+
if (domain_error_listeners_v->IsFunction() ||
965+
(domain_error_listeners_v->IsArray() &&
966+
domain_error_listeners_v.As<Array>()->Length() > 0))
967+
return true;
968+
969+
return false;
970+
}
971+
972+
static bool TopDomainHasErrorHandler(const Environment* env) {
973+
HandleScope scope(env->isolate());
950974

951-
static bool IsDomainActive(const Environment* env) {
952975
if (!env->using_domains())
953976
return false;
954977

955-
Local<Array> domain_array = env->domain_array().As<Array>();
956-
if (domain_array->Length() == 0)
978+
Local<Array> domains_stack_array = env->domains_stack_array().As<Array>();
979+
if (domains_stack_array->Length() == 0)
980+
return false;
981+
982+
uint32_t domains_stack_length = domains_stack_array->Length();
983+
if (domains_stack_length == 0)
984+
return false;
985+
986+
Local<Value> top_domain_v =
987+
domains_stack_array->Get(domains_stack_length - 1);
988+
989+
if (!top_domain_v->IsObject())
957990
return false;
958991

959-
Local<Value> domain_v = domain_array->Get(0);
960-
return !domain_v->IsNull();
992+
Local<Object> top_domain = top_domain_v.As<Object>();
993+
if (DomainHasErrorHandler(env, top_domain))
994+
return true;
995+
996+
return false;
961997
}
962998

963999

@@ -971,7 +1007,7 @@ static bool ShouldAbortOnUncaughtException(Isolate* isolate) {
9711007
bool isEmittingTopLevelDomainError =
9721008
process_object->Get(emitting_top_level_domain_error_key)->BooleanValue();
9731009

974-
return !IsDomainActive(env) || isEmittingTopLevelDomainError;
1010+
return isEmittingTopLevelDomainError || !TopDomainHasErrorHandler(env);
9751011
}
9761012

9771013

@@ -1000,6 +1036,9 @@ void SetupDomainUse(const FunctionCallbackInfo<Value>& args) {
10001036
CHECK(args[0]->IsArray());
10011037
env->set_domain_array(args[0].As<Array>());
10021038

1039+
CHECK(args[1]->IsArray());
1040+
env->set_domains_stack_array(args[1].As<Array>());
1041+
10031042
// Do a little housekeeping.
10041043
env->process_object()->Delete(
10051044
FIXED_ONE_BYTE_STRING(args.GetIsolate(), "_setupDomainUse"));

test/common.js

+34
Original file line numberDiff line numberDiff line change
@@ -449,3 +449,37 @@ exports.fileExists = function(pathname) {
449449
exports.fail = function(msg) {
450450
assert.fail(null, null, msg);
451451
};
452+
453+
// Returns true if the exit code "exitCode" and/or signal name "signal"
454+
// represent the exit code and/or signal name of a node process that aborted,
455+
// false otherwise.
456+
exports.nodeProcessAborted = function nodeProcessAborted(exitCode, signal) {
457+
// Depending on the compiler used, node will exit with either
458+
// exit code 132 (SIGILL), 133 (SIGTRAP) or 134 (SIGABRT).
459+
var expectedExitCodes = [132, 133, 134];
460+
461+
// On platforms using KSH as the default shell (like SmartOS),
462+
// when a process aborts, KSH exits with an exit code that is
463+
// greater than 256, and thus the exit code emitted with the 'exit'
464+
// event is null and the signal is set to either SIGILL, SIGTRAP,
465+
// or SIGABRT (depending on the compiler).
466+
const expectedSignals = ['SIGILL', 'SIGTRAP', 'SIGABRT'];
467+
468+
// On Windows, v8's base::OS::Abort triggers an access violation,
469+
// which corresponds to exit code 3221225477 (0xC0000005)
470+
if (process.platform === 'win32')
471+
expectedExitCodes = [3221225477];
472+
473+
// When using --abort-on-uncaught-exception, V8 will use
474+
// base::OS::Abort to terminate the process.
475+
// Depending on the compiler used, the shell or other aspects of
476+
// the platform used to build the node binary, this will actually
477+
// make V8 exit by aborting or by raising a signal. In any case,
478+
// one of them (exit code or signal) needs to be set to one of
479+
// the expected exit codes or signals.
480+
if (signal !== null) {
481+
return expectedSignals.indexOf(signal) > -1;
482+
} else {
483+
return expectedExitCodes.indexOf(exitCode) > -1;
484+
}
485+
};

0 commit comments

Comments
 (0)