Skip to content

Commit 563c359

Browse files
Julien GilliMyles Borins
authored andcommitted
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: #3887 PR-URL: #3887 Reviewed-By: James M Snell <jasnell@gmail.com>
1 parent 53bae2d commit 563c359

9 files changed

+878
-58
lines changed

lib/domain.js

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,19 +32,20 @@ var endMethods = ['end', 'abort', 'destroy', 'destroySoon'];
3232
// a few side effects.
3333
events.usingDomains = true;
3434

35+
// it's possible to enter one domain while already inside
36+
// another one. the stack is each entered domain.
37+
var stack = [];
38+
exports._stack = stack;
39+
3540
// let the process know we're using domains
36-
process._usingDomains();
41+
process._usingDomains(stack);
3742

3843
exports.Domain = Domain;
3944

4045
exports.create = exports.createDomain = function(cb) {
4146
return new Domain(cb);
4247
};
4348

44-
// it's possible to enter one domain while already inside
45-
// another one. the stack is each entered domain.
46-
var stack = [];
47-
exports._stack = stack;
4849
// the active domain is always the one that we're currently in.
4950
exports.active = null;
5051

src/node.cc

Lines changed: 69 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,8 @@ Persistent<String> domain_symbol;
106106
// declared in node_internals.h
107107
Persistent<Object> process;
108108

109+
static Persistent<Array> domains_stack;
110+
109111
static Persistent<Function> process_tickFromSpinner;
110112
static Persistent<Function> process_tickCallback;
111113

@@ -127,6 +129,8 @@ static Persistent<String> exit_symbol;
127129
static Persistent<String> disposed_symbol;
128130

129131
static Persistent<String> emitting_toplevel_domain_error_symbol;
132+
static Persistent<String> _events_symbol;
133+
static Persistent<String> error_symbol;
130134

131135
static bool print_eval = false;
132136
static bool force_repl = false;
@@ -905,25 +909,77 @@ Handle<Value> FromConstructorTemplate(Persistent<FunctionTemplate> t,
905909
return scope.Close(t->GetFunction()->NewInstance(argc, argv));
906910
}
907911

908-
static bool IsDomainActive() {
909-
if (domain_symbol.IsEmpty())
910-
domain_symbol = NODE_PSYMBOL("domain");
912+
static bool DomainHasErrorHandler(const Local<Object>& domain) {
913+
HandleScope scope;
914+
915+
if (_events_symbol.IsEmpty())
916+
_events_symbol = NODE_PSYMBOL("_events");
917+
918+
Local<Value> domain_event_listeners_v = domain->Get(_events_symbol);
919+
if (!domain_event_listeners_v->IsObject())
920+
return false;
921+
922+
Local<Object> domain_event_listeners_o =
923+
domain_event_listeners_v.As<Object>();
924+
925+
if (error_symbol.IsEmpty())
926+
error_symbol = NODE_PSYMBOL("error");
927+
928+
Local<Value> domain_error_listeners_v =
929+
domain_event_listeners_o->Get(error_symbol);
930+
931+
if (domain_error_listeners_v->IsFunction() ||
932+
(domain_error_listeners_v->IsArray() &&
933+
domain_error_listeners_v.As<Array>()->Length() > 0))
934+
return true;
935+
936+
return false;
937+
}
938+
939+
static bool TopDomainHasErrorHandler() {
940+
HandleScope scope;
941+
942+
if (!using_domains)
943+
return false;
911944

912-
Local<Value> domain_v = process->Get(domain_symbol);
945+
Local<Array> domains_stack_array = Local<Array>::New(domains_stack);
946+
if (domains_stack_array->Length() == 0)
947+
return false;
913948

914-
return domain_v->IsObject();
949+
uint32_t domains_stack_length = domains_stack_array->Length();
950+
if (domains_stack_length == 0)
951+
return false;
952+
953+
Local<Value> domain_v = domains_stack_array->Get(domains_stack_length - 1);
954+
if (!domain_v->IsObject())
955+
return false;
956+
957+
Local<Object> domain = domain_v.As<Object>();
958+
if (DomainHasErrorHandler(domain))
959+
return true;
960+
961+
return false;
915962
}
916963

917964
bool ShouldAbortOnUncaughtException() {
918965
Local<Value> emitting_toplevel_domain_error_v =
919966
process->Get(emitting_toplevel_domain_error_symbol);
920-
return !IsDomainActive() || emitting_toplevel_domain_error_v->BooleanValue();
967+
968+
return emitting_toplevel_domain_error_v->BooleanValue() ||
969+
!TopDomainHasErrorHandler();
921970
}
922971

923972
Handle<Value> UsingDomains(const Arguments& args) {
924973
HandleScope scope;
974+
925975
if (using_domains)
926976
return scope.Close(Undefined());
977+
978+
if (!args[0]->IsArray()) {
979+
fprintf(stderr, "domains stack must be an array\n");
980+
abort();
981+
}
982+
927983
using_domains = true;
928984
Local<Value> tdc_v = process->Get(String::New("_tickDomainCallback"));
929985
Local<Value> ndt_v = process->Get(String::New("_nextDomainTick"));
@@ -935,6 +991,9 @@ Handle<Value> UsingDomains(const Arguments& args) {
935991
fprintf(stderr, "process._nextDomainTick assigned to non-function\n");
936992
abort();
937993
}
994+
995+
domains_stack = Persistent<Array>::New(args[0].As<Array>());
996+
938997
Local<Function> tdc = tdc_v.As<Function>();
939998
Local<Function> ndt = ndt_v.As<Function>();
940999
process->Set(String::New("_tickCallback"), tdc);
@@ -2460,7 +2519,10 @@ Handle<Object> SetupProcessObject(int argc, char *argv[]) {
24602519
process->Set(String::NewSymbol("_tickInfoBox"), info_box);
24612520

24622521
// pre-set _events object for faster emit checks
2463-
process->Set(String::NewSymbol("_events"), Object::New());
2522+
if (_events_symbol.IsEmpty())
2523+
_events_symbol = NODE_PSYMBOL("_events");
2524+
2525+
process->Set(_events_symbol, Object::New());
24642526

24652527
if (emitting_toplevel_domain_error_symbol.IsEmpty())
24662528
emitting_toplevel_domain_error_symbol =

src/node.js

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,7 @@
229229
// and exit if there are no listeners.
230230
process._fatalException = function(er) {
231231
var caught = false;
232+
232233
if (process.domain) {
233234
var domain = process.domain;
234235
var domainModule = NativeModule.require('domain');
@@ -256,11 +257,17 @@
256257
// aborting in these cases.
257258
if (domainStack.length === 1) {
258259
try {
259-
// Set the _emittingTopLevelDomainError so that we know that, even
260-
// if technically the top-level domain is still active, it would
261-
// be ok to abort on an uncaught exception at this point
262-
process._emittingTopLevelDomainError = true;
263-
caught = domain.emit('error', er);
260+
// If there's no error handler, do not emit an 'error' event
261+
// as this would throw an error, make the process exit, and thus
262+
// prevent the process 'uncaughtException' event from being emitted
263+
// if a listener is set.
264+
if (process.EventEmitter.listenerCount(domain, 'error') > 0) {
265+
// Set the _emittingTopLevelDomainError so that we know that, even
266+
// if technically the top-level domain is still active, it would
267+
// be ok to abort on an uncaught exception at this point.
268+
process._emittingTopLevelDomainError = true;
269+
caught = domain.emit('error', er);
270+
}
264271
} finally {
265272
process._emittingTopLevelDomainError = false;
266273
}
@@ -297,9 +304,12 @@
297304
// current tick and no domains should be left on the stack
298305
// between ticks.
299306
_clearDomainsStack();
300-
} else {
307+
}
308+
309+
if (!caught) {
301310
caught = process.emit('uncaughtException', er);
302311
}
312+
303313
// if someone handled it, then great. otherwise, die in C++ land
304314
// since that means that we'll exit the process, emit the 'exit' event
305315
if (!caught) {

test/common.js

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,3 +226,37 @@ exports.busyLoop = function busyLoop(time) {
226226
;
227227
}
228228
};
229+
230+
// Returns true if the exit code "exitCode" and/or signal name "signal"
231+
// represent the exit code and/or signal name of a node process that aborted,
232+
// false otherwise.
233+
exports.nodeProcessAborted = function nodeProcessAborted(exitCode, signal) {
234+
// Depending on the compiler used, node will exit with either
235+
// exit code 132 (SIGILL), 133 (SIGTRAP) or 134 (SIGABRT).
236+
var expectedExitCodes = [132, 133, 134];
237+
238+
// On platforms using KSH as the default shell (like SmartOS),
239+
// when a process aborts, KSH exits with an exit code that is
240+
// greater than 256, and thus the exit code emitted with the 'exit'
241+
// event is null and the signal is set to either SIGILL, SIGTRAP,
242+
// or SIGABRT (depending on the compiler).
243+
var expectedSignals = ['SIGILL', 'SIGTRAP', 'SIGABRT'];
244+
245+
// On Windows, v8's base::OS::Abort triggers a debug breakpoint,
246+
// which makes the process exit with code -2147483645.
247+
if (process.platform === 'win32')
248+
expectedExitCodes = [-2147483645];
249+
250+
// When using --abort-on-uncaught-exception, V8 will use
251+
// base::OS::Abort to terminate the process.
252+
// Depending on the compiler used, the shell or other aspects of
253+
// the platform used to build the node binary, this will actually
254+
// make V8 exit by aborting or by raising a signal. In any case,
255+
// one of them (exit code or signal) needs to be set to one of
256+
// the expected exit codes or signals.
257+
if (signal !== null) {
258+
return expectedSignals.indexOf(signal) > -1;
259+
} else {
260+
return expectedExitCodes.indexOf(exitCode) > -1;
261+
}
262+
};

0 commit comments

Comments
 (0)