Skip to content

Commit 018e4e0

Browse files
Julien GilliMyles Borins
Julien Gilli
authored and
Myles Borins
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: #3885 PR-URL: #3885 Reviewed-By: James M Snell <[email protected]>
1 parent be39f30 commit 018e4e0

9 files changed

+854
-66
lines changed

lib/domain.js

+20-13
Original file line numberDiff line numberDiff line change
@@ -45,19 +45,20 @@ Object.defineProperty(process, 'domain', {
4545
// between js and c++ w/o much overhead
4646
var _domain_flag = {};
4747

48+
// it's possible to enter one domain while already inside
49+
// another one. the stack is each entered domain.
50+
var stack = [];
51+
exports._stack = stack;
52+
4853
// let the process know we're using domains
49-
process._setupDomainUse(_domain, _domain_flag);
54+
process._setupDomainUse(_domain, _domain_flag, stack);
5055

5156
exports.Domain = Domain;
5257

5358
exports.create = exports.createDomain = function() {
5459
return new Domain();
5560
};
5661

57-
// it's possible to enter one domain while already inside
58-
// another one. the stack is each entered domain.
59-
var stack = [];
60-
exports._stack = stack;
6162
// the active domain is always the one that we're currently in.
6263
exports.active = null;
6364

@@ -114,14 +115,20 @@ Domain.prototype._errorHandler = function errorHandler(er) {
114115
// that these exceptions are caught, and thus would prevent it from
115116
// aborting in these cases.
116117
if (stack.length === 1) {
117-
try {
118-
// Set the _emittingTopLevelDomainError so that we know that, even
119-
// if technically the top-level domain is still active, it would
120-
// be ok to abort on an uncaught exception at this point
121-
process._emittingTopLevelDomainError = true;
122-
caught = emitError();
123-
} finally {
124-
process._emittingTopLevelDomainError = false;
118+
// If there's no error handler, do not emit an 'error' event
119+
// as this would throw an error, make the process exit, and thus
120+
// prevent the process 'uncaughtException' event from being emitted
121+
// if a listener is set.
122+
if (EventEmitter.listenerCount(self, 'error') > 0) {
123+
try {
124+
// Set the _emittingTopLevelDomainError so that we know that, even
125+
// if technically the top-level domain is still active, it would
126+
// be ok to abort on an uncaught exception at this point
127+
process._emittingTopLevelDomainError = true;
128+
caught = emitError();
129+
} finally {
130+
process._emittingTopLevelDomainError = false;
131+
}
125132
}
126133
} else {
127134
// 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
@@ -258,6 +258,7 @@ namespace node {
258258
V(buffer_constructor_function, v8::Function) \
259259
V(context, v8::Context) \
260260
V(domain_array, v8::Array) \
261+
V(domains_stack_array, v8::Array) \
261262
V(fs_stats_constructor_function, v8::Function) \
262263
V(gc_info_callback_function, v8::Function) \
263264
V(module_load_list_array, v8::Array) \

src/node.cc

+45-9
Original file line numberDiff line numberDiff line change
@@ -910,19 +910,53 @@ Local<Value> WinapiErrnoException(Isolate* isolate,
910910
}
911911
#endif
912912

913+
static bool DomainHasErrorHandler(const Environment* env,
914+
const Local<Object>& domain) {
915+
HandleScope scope(env->isolate());
913916

914-
static bool IsDomainActive(const Environment* env) {
915-
if (!env->using_domains()) {
917+
Local<Value> domain_event_listeners_v = domain->Get(env->events_string());
918+
if (!domain_event_listeners_v->IsObject())
916919
return false;
917-
}
918920

919-
Local<Array> domain_array = env->domain_array().As<Array>();
920-
uint32_t domains_array_length = domain_array->Length();
921-
if (domains_array_length == 0)
921+
Local<Object> domain_event_listeners_o =
922+
domain_event_listeners_v.As<Object>();
923+
924+
Local<Value> domain_error_listeners_v =
925+
domain_event_listeners_o->Get(env->error_string());
926+
927+
if (domain_error_listeners_v->IsFunction() ||
928+
(domain_error_listeners_v->IsArray() &&
929+
domain_error_listeners_v.As<Array>()->Length() > 0))
930+
return true;
931+
932+
return false;
933+
}
934+
935+
static bool TopDomainHasErrorHandler(const Environment* env) {
936+
HandleScope scope(env->isolate());
937+
938+
if (!env->using_domains())
922939
return false;
923940

924-
Local<Value> domain_v = domain_array->Get(0);
925-
return !domain_v->IsNull();
941+
Local<Array> domains_stack_array = env->domains_stack_array().As<Array>();
942+
if (domains_stack_array->Length() == 0)
943+
return false;
944+
945+
uint32_t domains_stack_length = domains_stack_array->Length();
946+
if (domains_stack_length == 0)
947+
return false;
948+
949+
Local<Value> top_domain_v =
950+
domains_stack_array->Get(domains_stack_length - 1);
951+
952+
if (!top_domain_v->IsObject())
953+
return false;
954+
955+
Local<Object> top_domain = top_domain_v.As<Object>();
956+
if (DomainHasErrorHandler(env, top_domain))
957+
return true;
958+
959+
return false;
926960
}
927961

928962

@@ -934,7 +968,7 @@ bool ShouldAbortOnUncaughtException(v8::Isolate* isolate) {
934968
bool isEmittingTopLevelDomainError =
935969
process_object->Get(emitting_top_level_domain_error_key)->BooleanValue();
936970

937-
return !IsDomainActive(env) || isEmittingTopLevelDomainError;
971+
return isEmittingTopLevelDomainError || !TopDomainHasErrorHandler(env);
938972
}
939973

940974

@@ -962,8 +996,10 @@ void SetupDomainUse(const FunctionCallbackInfo<Value>& args) {
962996

963997
assert(args[0]->IsArray());
964998
assert(args[1]->IsObject());
999+
assert(args[2]->IsArray());
9651000

9661001
env->set_domain_array(args[0].As<Array>());
1002+
env->set_domains_stack_array(args[2].As<Array>());
9671003

9681004
Local<Object> domain_flag_obj = args[1].As<Object>();
9691005
Environment::DomainFlag* domain_flag = env->domain_flag();

test/common.js

+34
Original file line numberDiff line numberDiff line change
@@ -347,3 +347,37 @@ exports.busyLoop = function busyLoop(time) {
347347
;
348348
}
349349
};
350+
351+
// Returns true if the exit code "exitCode" and/or signal name "signal"
352+
// represent the exit code and/or signal name of a node process that aborted,
353+
// false otherwise.
354+
exports.nodeProcessAborted = function nodeProcessAborted(exitCode, signal) {
355+
// Depending on the compiler used, node will exit with either
356+
// exit code 132 (SIGILL), 133 (SIGTRAP) or 134 (SIGABRT).
357+
var expectedExitCodes = [132, 133, 134];
358+
359+
// On platforms using KSH as the default shell (like SmartOS),
360+
// when a process aborts, KSH exits with an exit code that is
361+
// greater than 256, and thus the exit code emitted with the 'exit'
362+
// event is null and the signal is set to either SIGILL, SIGTRAP,
363+
// or SIGABRT (depending on the compiler).
364+
var expectedSignals = ['SIGILL', 'SIGTRAP', 'SIGABRT'];
365+
366+
// On Windows, v8's base::OS::Abort triggers an access violation,
367+
// which corresponds to exit code 3221225477 (0xC0000005)
368+
if (process.platform === 'win32')
369+
expectedExitCodes = [3221225477];
370+
371+
// When using --abort-on-uncaught-exception, V8 will use
372+
// base::OS::Abort to terminate the process.
373+
// Depending on the compiler used, the shell or other aspects of
374+
// the platform used to build the node binary, this will actually
375+
// make V8 exit by aborting or by raising a signal. In any case,
376+
// one of them (exit code or signal) needs to be set to one of
377+
// the expected exit codes or signals.
378+
if (signal !== null) {
379+
return expectedSignals.indexOf(signal) > -1;
380+
} else {
381+
return expectedExitCodes.indexOf(exitCode) > -1;
382+
}
383+
};

0 commit comments

Comments
 (0)