Skip to content

Commit

Permalink
domains: fix no error handler & abort-on-uncaught
Browse files Browse the repository at this point in the history
Make the process abort if an error is thrown within a domain with no
error handler and `--abort-on-uncaught-exception` is used.

If the domain within which the error is thrown has no error handler,
but a domain further down the domains stack has one, the process will
not abort.

Fixes nodejs#3653.
  • Loading branch information
Julien Gilli committed Nov 4, 2015
1 parent 04b1302 commit e7e1dfd
Show file tree
Hide file tree
Showing 5 changed files with 508 additions and 15 deletions.
11 changes: 6 additions & 5 deletions lib/domain.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,19 +32,20 @@ var endMethods = ['end', 'abort', 'destroy', 'destroySoon'];
// a few side effects.
events.usingDomains = true;

// it's possible to enter one domain while already inside
// another one. the stack is each entered domain.
var stack = [];
exports._stack = stack;

// let the process know we're using domains
process._usingDomains();
process._usingDomains(stack);

exports.Domain = Domain;

exports.create = exports.createDomain = function(cb) {
return new Domain(cb);
};

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

Expand Down
81 changes: 74 additions & 7 deletions src/node.cc
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ Persistent<String> domain_symbol;
// declared in node_internals.h
Persistent<Object> process;

static Persistent<Array> domains_stack;

static Persistent<Function> process_tickFromSpinner;
static Persistent<Function> process_tickCallback;

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

static Persistent<String> emitting_toplevel_domain_error_symbol;
static Persistent<String> _events_symbol;
static Persistent<String> error_symbol;

static bool print_eval = false;
static bool force_repl = false;
Expand Down Expand Up @@ -904,25 +908,82 @@ Handle<Value> FromConstructorTemplate(Persistent<FunctionTemplate> t,
return scope.Close(t->GetFunction()->NewInstance(argc, argv));
}

static bool IsDomainActive() {
if (domain_symbol.IsEmpty())
domain_symbol = NODE_PSYMBOL("domain");
static bool domainHasErrorHandler(const Local<Object>& domain) {
HandleScope scope;

if (_events_symbol.IsEmpty())
_events_symbol = NODE_PSYMBOL("_events");

Local<Value> domain_event_listeners_v = domain->Get(_events_symbol);
if (!domain_event_listeners_v->IsObject())
return false;

Local<Object> domain_event_listeners_o =
domain_event_listeners_v->ToObject();

if (domain_event_listeners_o->IsNull())
return false;

if (error_symbol.IsEmpty())
error_symbol = NODE_PSYMBOL("error");

Local<Value> domain_error_listeners_v =
domain_event_listeners_o->Get(error_symbol);

if (domain_error_listeners_v->IsFunction() ||
(domain_error_listeners_v->IsArray() &&
domain_error_listeners_v.As<Array>()->Length() > 0))
return true;

return false;
}

static bool domainsStackHasErrorHandler() {
HandleScope scope;

if (!using_domains)
return false;

Local<Value> domain_v = process->Get(domain_symbol);
Local<Array> domains_stack_array = Local<Array>::New(domains_stack);
if (domains_stack_array->Length() == 0)
return false;

return domain_v->IsObject();
uint32_t domains_stack_length = domains_stack_array->Length();
for (int i = domains_stack_length - 1; i >= 0; --i) {
Local<Value> domain_v = domains_stack_array->Get(i);
if (domain_v->IsNull())
return false;

Local<Object> domain = domain_v->ToObject();
if (domain->IsNull())
return false;

if (domainHasErrorHandler(domain))
return true;
}

return false;
}

bool ShouldAbortOnUncaughtException() {
Local<Value> emitting_toplevel_domain_error_v =
process->Get(emitting_toplevel_domain_error_symbol);
return !IsDomainActive() || emitting_toplevel_domain_error_v->BooleanValue();

return emitting_toplevel_domain_error_v->BooleanValue() ||
!domainsStackHasErrorHandler();
}

Handle<Value> UsingDomains(const Arguments& args) {
HandleScope scope;

if (using_domains)
return scope.Close(Undefined());

if (!args[0]->IsArray()) {
fprintf(stderr, "domains stack must be an array\n");
abort();
}

using_domains = true;
Local<Value> tdc_v = process->Get(String::New("_tickDomainCallback"));
Local<Value> ndt_v = process->Get(String::New("_nextDomainTick"));
Expand All @@ -934,6 +995,9 @@ Handle<Value> UsingDomains(const Arguments& args) {
fprintf(stderr, "process._nextDomainTick assigned to non-function\n");
abort();
}

domains_stack = Persistent<Array>::New(args[0].As<Array>());

Local<Function> tdc = tdc_v.As<Function>();
Local<Function> ndt = ndt_v.As<Function>();
process->Set(String::New("_tickCallback"), tdc);
Expand Down Expand Up @@ -2449,7 +2513,10 @@ Handle<Object> SetupProcessObject(int argc, char *argv[]) {
process->Set(String::NewSymbol("_tickInfoBox"), info_box);

// pre-set _events object for faster emit checks
process->Set(String::NewSymbol("_events"), Object::New());
if (_events_symbol.IsEmpty())
_events_symbol = NODE_PSYMBOL("_events");

process->Set(_events_symbol, Object::New());

if (emitting_toplevel_domain_error_symbol.IsEmpty())
emitting_toplevel_domain_error_symbol =
Expand Down
Loading

0 comments on commit e7e1dfd

Please sign in to comment.