Skip to content

Commit

Permalink
node: implement unhandled rejection tracking
Browse files Browse the repository at this point in the history
Implement unhandled rejection tracking for promises as
specified in https://gist.github.com/benjamingr/0237932cee84712951a2

Fixes #256
PR-URL: #758
Reviewed-By: Trevor Norris <trev.norris@gmail.com>
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Reviewed-By: Domenic Denicola <domenic@domenicdenicola.com>
  • Loading branch information
petkaantonov authored and rvagg committed Feb 25, 2015
1 parent 8a1e22a commit 872702d
Show file tree
Hide file tree
Showing 4 changed files with 720 additions and 1 deletion.
1 change: 1 addition & 0 deletions src/env.h
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,7 @@ namespace node {
V(module_load_list_array, v8::Array) \
V(pipe_constructor_template, v8::FunctionTemplate) \
V(process_object, v8::Object) \
V(promise_reject_function, v8::Function) \
V(script_context_constructor_template, v8::FunctionTemplate) \
V(script_data_constructor_function, v8::Function) \
V(secure_context_constructor_template, v8::FunctionTemplate) \
Expand Down
56 changes: 56 additions & 0 deletions src/node.cc
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ using v8::Message;
using v8::Number;
using v8::Object;
using v8::ObjectTemplate;
using v8::Promise;
using v8::PromiseRejectMessage;
using v8::PropertyCallbackInfo;
using v8::String;
using v8::TryCatch;
Expand Down Expand Up @@ -982,6 +984,37 @@ void SetupNextTick(const FunctionCallbackInfo<Value>& args) {
FIXED_ONE_BYTE_STRING(args.GetIsolate(), "_setupNextTick"));
}

void PromiseRejectCallback(PromiseRejectMessage message) {
Local<Promise> promise = message.GetPromise();
Isolate* isolate = promise->GetIsolate();
Local<Value> value = message.GetValue();
Local<Integer> event = Integer::New(isolate, message.GetEvent());

Environment* env = Environment::GetCurrent(isolate);
Local<Function> callback = env->promise_reject_function();

if (value.IsEmpty())
value = Undefined(isolate);

Local<Value> args[] = { event, promise, value };
Local<Object> process = env->process_object();

callback->Call(process, ARRAY_SIZE(args), args);
}

void SetupPromises(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
Isolate* isolate = env->isolate();

CHECK(args[0]->IsFunction());

isolate->SetPromiseRejectCallback(PromiseRejectCallback);
env->set_promise_reject_function(args[0].As<Function>());

env->process_object()->Delete(
FIXED_ONE_BYTE_STRING(args.GetIsolate(), "_setupPromises"));
}


Handle<Value> MakeCallback(Environment* env,
Handle<Value> recv,
Expand Down Expand Up @@ -2572,6 +2605,14 @@ void StopProfilerIdleNotifier(const FunctionCallbackInfo<Value>& args) {
obj->ForceSet(OneByteString(env->isolate(), str), var, v8::ReadOnly); \
} while (0)

#define READONLY_DONT_ENUM_PROPERTY(obj, str, var) \
do { \
obj->ForceSet(OneByteString(env->isolate(), str), \
var, \
static_cast<v8::PropertyAttribute>(v8::ReadOnly | \
v8::DontEnum)); \
} while (0)


void SetupProcessObject(Environment* env,
int argc,
Expand Down Expand Up @@ -2632,6 +2673,20 @@ void SetupProcessObject(Environment* env,
"modules",
FIXED_ONE_BYTE_STRING(env->isolate(), node_modules_version));

// process._promiseRejectEvent
Local<Object> promiseRejectEvent = Object::New(env->isolate());
READONLY_DONT_ENUM_PROPERTY(process,
"_promiseRejectEvent",
promiseRejectEvent);
READONLY_PROPERTY(promiseRejectEvent,
"unhandled",
Integer::New(env->isolate(),
v8::kPromiseRejectWithNoHandler));
READONLY_PROPERTY(promiseRejectEvent,
"handled",
Integer::New(env->isolate(),
v8::kPromiseHandlerAddedAfterReject));

#if HAVE_OPENSSL
// Stupid code to slice out the version string.
{ // NOLINT(whitespace/braces)
Expand Down Expand Up @@ -2790,6 +2845,7 @@ void SetupProcessObject(Environment* env,
env->SetMethod(process, "_linkedBinding", LinkedBinding);

env->SetMethod(process, "_setupNextTick", SetupNextTick);
env->SetMethod(process, "_setupPromises", SetupPromises);
env->SetMethod(process, "_setupDomainUse", SetupDomainUse);

// pre-set _events object for faster emit checks
Expand Down
58 changes: 57 additions & 1 deletion src/node.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
startup.processAssert();
startup.processConfig();
startup.processNextTick();
startup.processPromises();
startup.processStdio();
startup.processKillAndExit();
startup.processSignalHandlers();
Expand Down Expand Up @@ -264,8 +265,11 @@
});
};

var addPendingUnhandledRejection;
var hasBeenNotifiedProperty = new WeakMap();
startup.processNextTick = function() {
var nextTickQueue = [];
var pendingUnhandledRejections = [];
var microtasksScheduled = false;

// Used to run V8's micro task queue.
Expand Down Expand Up @@ -318,7 +322,8 @@
microtasksScheduled = false;
_runMicrotasks();

if (tickInfo[kIndex] < tickInfo[kLength])
if (tickInfo[kIndex] < tickInfo[kLength] ||
emitPendingUnhandledRejections())
scheduleMicrotasks();
}

Expand Down Expand Up @@ -388,6 +393,57 @@
nextTickQueue.push(obj);
tickInfo[kLength]++;
}

function emitPendingUnhandledRejections() {
var hadListeners = false;
while (pendingUnhandledRejections.length > 0) {
var promise = pendingUnhandledRejections.shift();
var reason = pendingUnhandledRejections.shift();
if (hasBeenNotifiedProperty.get(promise) === false) {
hasBeenNotifiedProperty.set(promise, true);
if (!process.emit('unhandledRejection', reason, promise)) {
// Nobody is listening.
// TODO(petkaantonov) Take some default action, see #830
} else
hadListeners = true;
}
}
return hadListeners;
}

addPendingUnhandledRejection = function(promise, reason) {
pendingUnhandledRejections.push(promise, reason);
scheduleMicrotasks();
};
};

startup.processPromises = function() {
var promiseRejectEvent = process._promiseRejectEvent;

function unhandledRejection(promise, reason) {
hasBeenNotifiedProperty.set(promise, false);
addPendingUnhandledRejection(promise, reason);
}

function rejectionHandled(promise) {
var hasBeenNotified = hasBeenNotifiedProperty.get(promise);
if (hasBeenNotified !== undefined) {
hasBeenNotifiedProperty.delete(promise);
if (hasBeenNotified === true)
process.emit('rejectionHandled', promise);
}
}

process._setupPromises(function(event, promise, reason) {
if (event === promiseRejectEvent.unhandled)
unhandledRejection(promise, reason);
else if (event === promiseRejectEvent.handled)
process.nextTick(function() {
rejectionHandled(promise);
});
else
NativeModule.require('assert').fail('unexpected PromiseRejectEvent');
});
};

function evalScript(name) {
Expand Down
Loading

1 comment on commit 872702d

@jonathanong
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👏

Please sign in to comment.