Skip to content

Commit 9cae9b2

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: #3887 PR-URL: #3887 Reviewed-By: James M Snell <jasnell@gmail.com>
1 parent 28ab7b0 commit 9cae9b2

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
@@ -105,6 +105,8 @@ Persistent<String> domain_symbol;
105105
// declared in node_internals.h
106106
Persistent<Object> process;
107107

108+
static Persistent<Array> domains_stack;
109+
108110
static Persistent<Function> process_tickFromSpinner;
109111
static Persistent<Function> process_tickCallback;
110112

@@ -126,6 +128,8 @@ static Persistent<String> exit_symbol;
126128
static Persistent<String> disposed_symbol;
127129

128130
static Persistent<String> emitting_toplevel_domain_error_symbol;
131+
static Persistent<String> _events_symbol;
132+
static Persistent<String> error_symbol;
129133

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

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

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

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

916963
bool ShouldAbortOnUncaughtException() {
917964
Local<Value> emitting_toplevel_domain_error_v =
918965
process->Get(emitting_toplevel_domain_error_symbol);
919-
return !IsDomainActive() || emitting_toplevel_domain_error_v->BooleanValue();
966+
967+
return emitting_toplevel_domain_error_v->BooleanValue() ||
968+
!TopDomainHasErrorHandler();
920969
}
921970

922971
Handle<Value> UsingDomains(const Arguments& args) {
923972
HandleScope scope;
973+
924974
if (using_domains)
925975
return scope.Close(Undefined());
976+
977+
if (!args[0]->IsArray()) {
978+
fprintf(stderr, "domains stack must be an array\n");
979+
abort();
980+
}
981+
926982
using_domains = true;
927983
Local<Value> tdc_v = process->Get(String::New("_tickDomainCallback"));
928984
Local<Value> ndt_v = process->Get(String::New("_nextDomainTick"));
@@ -934,6 +990,9 @@ Handle<Value> UsingDomains(const Arguments& args) {
934990
fprintf(stderr, "process._nextDomainTick assigned to non-function\n");
935991
abort();
936992
}
993+
994+
domains_stack = Persistent<Array>::New(args[0].As<Array>());
995+
937996
Local<Function> tdc = tdc_v.As<Function>();
938997
Local<Function> ndt = ndt_v.As<Function>();
939998
process->Set(String::New("_tickCallback"), tdc);
@@ -2449,7 +2508,10 @@ Handle<Object> SetupProcessObject(int argc, char *argv[]) {
24492508
process->Set(String::NewSymbol("_tickInfoBox"), info_box);
24502509

24512510
// pre-set _events object for faster emit checks
2452-
process->Set(String::NewSymbol("_events"), Object::New());
2511+
if (_events_symbol.IsEmpty())
2512+
_events_symbol = NODE_PSYMBOL("_events");
2513+
2514+
process->Set(_events_symbol, Object::New());
24532515

24542516
if (emitting_toplevel_domain_error_symbol.IsEmpty())
24552517
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)