Skip to content

Commit 872702d

Browse files
petkaantonovrvagg
authored andcommitted
node: implement unhandled rejection tracking
Implement unhandled rejection tracking for promises as specified in https://gist.github.com/benjamingr/0237932cee84712951a2 Fixes #256 PR-URL: #758 Reviewed-By: Trevor Norris <[email protected]> Reviewed-By: Ben Noordhuis <[email protected]> Reviewed-By: Domenic Denicola <[email protected]>
1 parent 8a1e22a commit 872702d

File tree

4 files changed

+720
-1
lines changed

4 files changed

+720
-1
lines changed

src/env.h

+1
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,7 @@ namespace node {
235235
V(module_load_list_array, v8::Array) \
236236
V(pipe_constructor_template, v8::FunctionTemplate) \
237237
V(process_object, v8::Object) \
238+
V(promise_reject_function, v8::Function) \
238239
V(script_context_constructor_template, v8::FunctionTemplate) \
239240
V(script_data_constructor_function, v8::Function) \
240241
V(secure_context_constructor_template, v8::FunctionTemplate) \

src/node.cc

+56
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,8 @@ using v8::Message;
9898
using v8::Number;
9999
using v8::Object;
100100
using v8::ObjectTemplate;
101+
using v8::Promise;
102+
using v8::PromiseRejectMessage;
101103
using v8::PropertyCallbackInfo;
102104
using v8::String;
103105
using v8::TryCatch;
@@ -982,6 +984,37 @@ void SetupNextTick(const FunctionCallbackInfo<Value>& args) {
982984
FIXED_ONE_BYTE_STRING(args.GetIsolate(), "_setupNextTick"));
983985
}
984986

987+
void PromiseRejectCallback(PromiseRejectMessage message) {
988+
Local<Promise> promise = message.GetPromise();
989+
Isolate* isolate = promise->GetIsolate();
990+
Local<Value> value = message.GetValue();
991+
Local<Integer> event = Integer::New(isolate, message.GetEvent());
992+
993+
Environment* env = Environment::GetCurrent(isolate);
994+
Local<Function> callback = env->promise_reject_function();
995+
996+
if (value.IsEmpty())
997+
value = Undefined(isolate);
998+
999+
Local<Value> args[] = { event, promise, value };
1000+
Local<Object> process = env->process_object();
1001+
1002+
callback->Call(process, ARRAY_SIZE(args), args);
1003+
}
1004+
1005+
void SetupPromises(const FunctionCallbackInfo<Value>& args) {
1006+
Environment* env = Environment::GetCurrent(args);
1007+
Isolate* isolate = env->isolate();
1008+
1009+
CHECK(args[0]->IsFunction());
1010+
1011+
isolate->SetPromiseRejectCallback(PromiseRejectCallback);
1012+
env->set_promise_reject_function(args[0].As<Function>());
1013+
1014+
env->process_object()->Delete(
1015+
FIXED_ONE_BYTE_STRING(args.GetIsolate(), "_setupPromises"));
1016+
}
1017+
9851018

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

2608+
#define READONLY_DONT_ENUM_PROPERTY(obj, str, var) \
2609+
do { \
2610+
obj->ForceSet(OneByteString(env->isolate(), str), \
2611+
var, \
2612+
static_cast<v8::PropertyAttribute>(v8::ReadOnly | \
2613+
v8::DontEnum)); \
2614+
} while (0)
2615+
25752616

25762617
void SetupProcessObject(Environment* env,
25772618
int argc,
@@ -2632,6 +2673,20 @@ void SetupProcessObject(Environment* env,
26322673
"modules",
26332674
FIXED_ONE_BYTE_STRING(env->isolate(), node_modules_version));
26342675

2676+
// process._promiseRejectEvent
2677+
Local<Object> promiseRejectEvent = Object::New(env->isolate());
2678+
READONLY_DONT_ENUM_PROPERTY(process,
2679+
"_promiseRejectEvent",
2680+
promiseRejectEvent);
2681+
READONLY_PROPERTY(promiseRejectEvent,
2682+
"unhandled",
2683+
Integer::New(env->isolate(),
2684+
v8::kPromiseRejectWithNoHandler));
2685+
READONLY_PROPERTY(promiseRejectEvent,
2686+
"handled",
2687+
Integer::New(env->isolate(),
2688+
v8::kPromiseHandlerAddedAfterReject));
2689+
26352690
#if HAVE_OPENSSL
26362691
// Stupid code to slice out the version string.
26372692
{ // NOLINT(whitespace/braces)
@@ -2790,6 +2845,7 @@ void SetupProcessObject(Environment* env,
27902845
env->SetMethod(process, "_linkedBinding", LinkedBinding);
27912846

27922847
env->SetMethod(process, "_setupNextTick", SetupNextTick);
2848+
env->SetMethod(process, "_setupPromises", SetupPromises);
27932849
env->SetMethod(process, "_setupDomainUse", SetupDomainUse);
27942850

27952851
// pre-set _events object for faster emit checks

src/node.js

+57-1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
startup.processAssert();
3232
startup.processConfig();
3333
startup.processNextTick();
34+
startup.processPromises();
3435
startup.processStdio();
3536
startup.processKillAndExit();
3637
startup.processSignalHandlers();
@@ -264,8 +265,11 @@
264265
});
265266
};
266267

268+
var addPendingUnhandledRejection;
269+
var hasBeenNotifiedProperty = new WeakMap();
267270
startup.processNextTick = function() {
268271
var nextTickQueue = [];
272+
var pendingUnhandledRejections = [];
269273
var microtasksScheduled = false;
270274

271275
// Used to run V8's micro task queue.
@@ -318,7 +322,8 @@
318322
microtasksScheduled = false;
319323
_runMicrotasks();
320324

321-
if (tickInfo[kIndex] < tickInfo[kLength])
325+
if (tickInfo[kIndex] < tickInfo[kLength] ||
326+
emitPendingUnhandledRejections())
322327
scheduleMicrotasks();
323328
}
324329

@@ -388,6 +393,57 @@
388393
nextTickQueue.push(obj);
389394
tickInfo[kLength]++;
390395
}
396+
397+
function emitPendingUnhandledRejections() {
398+
var hadListeners = false;
399+
while (pendingUnhandledRejections.length > 0) {
400+
var promise = pendingUnhandledRejections.shift();
401+
var reason = pendingUnhandledRejections.shift();
402+
if (hasBeenNotifiedProperty.get(promise) === false) {
403+
hasBeenNotifiedProperty.set(promise, true);
404+
if (!process.emit('unhandledRejection', reason, promise)) {
405+
// Nobody is listening.
406+
// TODO(petkaantonov) Take some default action, see #830
407+
} else
408+
hadListeners = true;
409+
}
410+
}
411+
return hadListeners;
412+
}
413+
414+
addPendingUnhandledRejection = function(promise, reason) {
415+
pendingUnhandledRejections.push(promise, reason);
416+
scheduleMicrotasks();
417+
};
418+
};
419+
420+
startup.processPromises = function() {
421+
var promiseRejectEvent = process._promiseRejectEvent;
422+
423+
function unhandledRejection(promise, reason) {
424+
hasBeenNotifiedProperty.set(promise, false);
425+
addPendingUnhandledRejection(promise, reason);
426+
}
427+
428+
function rejectionHandled(promise) {
429+
var hasBeenNotified = hasBeenNotifiedProperty.get(promise);
430+
if (hasBeenNotified !== undefined) {
431+
hasBeenNotifiedProperty.delete(promise);
432+
if (hasBeenNotified === true)
433+
process.emit('rejectionHandled', promise);
434+
}
435+
}
436+
437+
process._setupPromises(function(event, promise, reason) {
438+
if (event === promiseRejectEvent.unhandled)
439+
unhandledRejection(promise, reason);
440+
else if (event === promiseRejectEvent.handled)
441+
process.nextTick(function() {
442+
rejectionHandled(promise);
443+
});
444+
else
445+
NativeModule.require('assert').fail('unexpected PromiseRejectEvent');
446+
});
391447
};
392448

393449
function evalScript(name) {

0 commit comments

Comments
 (0)