Skip to content

Commit 7e2a08c

Browse files
committed
lib,src: throw on unhanded promise rejections
Refs: nodejs#5292 Refs: nodejs/promises#26 Refs: nodejs#6355
1 parent 3ee68f7 commit 7e2a08c

16 files changed

+321
-5
lines changed

lib/internal/process/promises.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,18 @@ const pendingUnhandledRejections = [];
77
exports.setup = setupPromises;
88

99
function setupPromises(scheduleMicrotasks) {
10+
const promiseInternals = {};
11+
1012
process._setupPromises(function(event, promise, reason) {
1113
if (event === promiseRejectEvent.unhandled)
1214
unhandledRejection(promise, reason);
1315
else if (event === promiseRejectEvent.handled)
1416
rejectionHandled(promise);
1517
else
1618
require('assert').fail(null, null, 'unexpected PromiseRejectEvent');
17-
});
19+
}, function getPromiseReason(data) {
20+
return data[data.indexOf('[[PromiseValue]]') + 1];
21+
}, promiseInternals);
1822

1923
function unhandledRejection(promise, reason) {
2024
hasBeenNotifiedProperty.set(promise, false);
@@ -26,6 +30,7 @@ function setupPromises(scheduleMicrotasks) {
2630
if (hasBeenNotified !== undefined) {
2731
hasBeenNotifiedProperty.delete(promise);
2832
if (hasBeenNotified === true) {
33+
promiseInternals.untrackPromise(promise);
2934
process.nextTick(function() {
3035
process.emit('rejectionHandled', promise);
3136
});
@@ -43,7 +48,7 @@ function setupPromises(scheduleMicrotasks) {
4348
hasBeenNotifiedProperty.set(promise, true);
4449
if (!process.emit('unhandledRejection', reason, promise)) {
4550
// Nobody is listening.
46-
// TODO(petkaantonov) Take some default action, see #830
51+
promiseInternals.onPromiseGC(promise);
4752
} else {
4853
hadListeners = true;
4954
}

node.gyp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@
153153
'src/stream_wrap.cc',
154154
'src/tcp_wrap.cc',
155155
'src/timer_wrap.cc',
156+
'src/track-promise.cc',
156157
'src/tty_wrap.cc',
157158
'src/process_wrap.cc',
158159
'src/udp_wrap.cc',
@@ -181,6 +182,7 @@
181182
'src/node_revert.h',
182183
'src/node_i18n.h',
183184
'src/pipe_wrap.h',
185+
'src/track-promise.h',
184186
'src/tty_wrap.h',
185187
'src/tcp_wrap.h',
186188
'src/udp_wrap.h',

src/env.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ namespace node {
6363
V(address_string, "address") \
6464
V(args_string, "args") \
6565
V(argv_string, "argv") \
66+
V(array_class_string, "Array") \
67+
V(array_from_string, "from") \
6668
V(async, "async") \
6769
V(async_queue_string, "_asyncQueue") \
6870
V(atime_string, "atime") \
@@ -254,6 +256,7 @@ namespace node {
254256
V(zero_return_string, "ZERO_RETURN") \
255257

256258
#define ENVIRONMENT_STRONG_PERSISTENT_PROPERTIES(V) \
259+
V(array_from, v8::Function) \
257260
V(as_external, v8::External) \
258261
V(async_hooks_destroy_function, v8::Function) \
259262
V(async_hooks_init_function, v8::Function) \
@@ -272,6 +275,9 @@ namespace node {
272275
V(pipe_constructor_template, v8::FunctionTemplate) \
273276
V(process_object, v8::Object) \
274277
V(promise_reject_function, v8::Function) \
278+
V(promise_unhandled_rejection, v8::Function) \
279+
V(promise_unhandled_reject_map, v8::NativeWeakMap) \
280+
V(promise_unhandled_reject_keys, v8::Set) \
275281
V(push_values_to_array_function, v8::Function) \
276282
V(script_context_constructor_template, v8::FunctionTemplate) \
277283
V(script_data_constructor_function, v8::Function) \

src/node.cc

Lines changed: 102 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
#include "req-wrap.h"
3838
#include "req-wrap-inl.h"
3939
#include "string_bytes.h"
40+
#include "track-promise.h"
4041
#include "util.h"
4142
#include "uv.h"
4243
#include "libplatform/libplatform.h"
@@ -104,6 +105,7 @@ using v8::Array;
104105
using v8::ArrayBuffer;
105106
using v8::Boolean;
106107
using v8::Context;
108+
using v8::Debug;
107109
using v8::EscapableHandleScope;
108110
using v8::Exception;
109111
using v8::Function;
@@ -118,6 +120,7 @@ using v8::Locker;
118120
using v8::MaybeLocal;
119121
using v8::Message;
120122
using v8::Name;
123+
using v8::NativeWeakMap;
121124
using v8::Null;
122125
using v8::Number;
123126
using v8::Object;
@@ -127,6 +130,7 @@ using v8::PromiseRejectMessage;
127130
using v8::PropertyCallbackInfo;
128131
using v8::ScriptOrigin;
129132
using v8::SealHandleScope;
133+
using v8::Set;
130134
using v8::String;
131135
using v8::TryCatch;
132136
using v8::Uint32;
@@ -1136,14 +1140,77 @@ void PromiseRejectCallback(PromiseRejectMessage message) {
11361140
callback->Call(process, arraysize(args), args);
11371141
}
11381142

1143+
Local<Value> GetPromiseReason(Environment* env, Local<Value> promise) {
1144+
Local<Function> fn = env->promise_unhandled_rejection();
1145+
1146+
Local<Value> internalProps =
1147+
Debug::GetInternalProperties(env->isolate(),
1148+
promise).ToLocalChecked().As<Value>();
1149+
1150+
// If fn is empty we'll almost certainly have to panic anyways
1151+
return fn->Call(env->context(), Null(env->isolate()), 1,
1152+
&internalProps).ToLocalChecked();
1153+
}
1154+
1155+
void OnPromiseGC(const FunctionCallbackInfo<Value>& args) {
1156+
Environment* env = Environment::GetCurrent(args);
1157+
1158+
CHECK(args[0]->IsObject());
1159+
Local<Object> promise = args[0].As<Object>();
1160+
1161+
TrackPromise::New(env->isolate(), promise);
1162+
1163+
Local<Value> err = GetPromiseReason(env, promise);
1164+
Local<NativeWeakMap> unhandled_reject_map =
1165+
env->promise_unhandled_reject_map();
1166+
Local<Set> unhandled_reject_keys =
1167+
env->promise_unhandled_reject_keys();
1168+
1169+
if (unhandled_reject_keys->AsArray()->Length() > 1000) {
1170+
return;
1171+
}
1172+
1173+
if (!unhandled_reject_map->Has(err) && !err->IsUndefined()) {
1174+
unhandled_reject_map->Set(err, promise);
1175+
CHECK(!unhandled_reject_keys->Add(env->context(), err).IsEmpty());
1176+
}
1177+
}
1178+
1179+
void UntrackPromise(const FunctionCallbackInfo<Value>& args) {
1180+
Environment* env = Environment::GetCurrent(args);
1181+
1182+
CHECK(args[0]->IsObject());
1183+
Local<Value> promise = args[0].As<Value>();
1184+
1185+
Local<Value> err = GetPromiseReason(env, promise);
1186+
Local<NativeWeakMap> unhandled_reject_map =
1187+
env->promise_unhandled_reject_map();
1188+
Local<Set> unhandled_reject_keys =
1189+
env->promise_unhandled_reject_keys();
1190+
1191+
if (unhandled_reject_keys->Has(env->context(), err).IsJust()) {
1192+
CHECK(unhandled_reject_keys->Delete(env->context(), err).IsJust());
1193+
unhandled_reject_map->Delete(err);
1194+
}
1195+
}
1196+
11391197
void SetupPromises(const FunctionCallbackInfo<Value>& args) {
11401198
Environment* env = Environment::GetCurrent(args);
11411199
Isolate* isolate = env->isolate();
11421200

1201+
env->set_promise_unhandled_reject_map(NativeWeakMap::New(isolate));
1202+
env->set_promise_unhandled_reject_keys(Set::New(isolate));\
1203+
11431204
CHECK(args[0]->IsFunction());
1205+
CHECK(args[1]->IsFunction());
1206+
CHECK(args[2]->IsObject());
11441207

11451208
isolate->SetPromiseRejectCallback(PromiseRejectCallback);
11461209
env->set_promise_reject_function(args[0].As<Function>());
1210+
env->set_promise_unhandled_rejection(args[1].As<Function>());
1211+
1212+
env->SetMethod(args[2].As<Object>(), "onPromiseGC", OnPromiseGC);
1213+
env->SetMethod(args[2].As<Object>(), "untrackPromise", UntrackPromise);
11471214

11481215
env->process_object()->Delete(
11491216
env->context(),
@@ -1572,10 +1639,17 @@ void AppendExceptionLine(Environment* env,
15721639
PrintErrorString("\n%s", arrow);
15731640
}
15741641

1642+
void ReportPromiseRejection(Isolate* isolate, Local<Value> promise) {
1643+
Environment* env = Environment::GetCurrent(isolate);
1644+
1645+
Local<Value> err = GetPromiseReason(env, promise);
1646+
1647+
ReportException(env, err, Exception::CreateMessage(isolate, err));
1648+
}
15751649

1576-
static void ReportException(Environment* env,
1577-
Local<Value> er,
1578-
Local<Message> message) {
1650+
void ReportException(Environment* env,
1651+
Local<Value> er,
1652+
Local<Message> message) {
15791653
HandleScope scope(env->isolate());
15801654

15811655
AppendExceptionLine(env, er, message);
@@ -3307,6 +3381,11 @@ void LoadEnvironment(Environment* env) {
33073381
// Add a reference to the global object
33083382
Local<Object> global = env->context()->Global();
33093383

3384+
Local<Object> JSArray = global->Get(env->array_class_string()).As<Object>();
3385+
Local<Function> JSFrom =
3386+
JSArray->Get(env->array_from_string()).As<Function>();
3387+
env->set_array_from(JSFrom);
3388+
33103389
#if defined HAVE_DTRACE || defined HAVE_ETW
33113390
InitDTrace(env, global);
33123391
#endif
@@ -4322,6 +4401,26 @@ static void StartNodeInstance(void* arg) {
43224401
} while (more == true);
43234402
}
43244403

4404+
Local<Value> promise_keys_set =
4405+
env->promise_unhandled_reject_keys().As<Value>();
4406+
Local<Function> convert = env->array_from();
4407+
Local<Value> ret = convert->Call(env->context(),
4408+
Null(env->isolate()), 1, &promise_keys_set).ToLocalChecked();
4409+
Local<Array> promise_keys = ret.As<Array>();
4410+
uint32_t key_count = promise_keys->Length();
4411+
Local<NativeWeakMap> unhandled_reject_map =
4412+
env->promise_unhandled_reject_map();
4413+
4414+
for (uint32_t key_iter = 0; key_iter < key_count; key_iter++) {
4415+
Local<Value> key = promise_keys->Get(env->context(),
4416+
key_iter).ToLocalChecked();
4417+
4418+
if (unhandled_reject_map->Has(key)) {
4419+
ReportPromiseRejection(isolate, unhandled_reject_map->Get(key));
4420+
exit(1);
4421+
}
4422+
}
4423+
43254424
env->set_trace_sync_io(false);
43264425

43274426
int exit_code = EmitExit(env);

src/node_internals.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,12 @@ constexpr size_t arraysize(const T(&)[N]) { return N; }
128128

129129
bool IsExceptionDecorated(Environment* env, v8::Local<v8::Value> er);
130130

131+
void ReportPromiseRejection(v8::Isolate* isolate, v8::Local<v8::Value> promise);
132+
133+
void ReportException(Environment* env,
134+
v8::Local<v8::Value> er,
135+
v8::Local<v8::Message> message);
136+
131137
void AppendExceptionLine(Environment* env,
132138
v8::Local<v8::Value> er,
133139
v8::Local<v8::Message> message);

src/track-promise.cc

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
#include "track-promise.h"
2+
#include "env.h"
3+
#include "env-inl.h"
4+
#include "node_internals.h"
5+
6+
namespace node {
7+
8+
using v8::Function;
9+
using v8::Isolate;
10+
using v8::Local;
11+
using v8::Object;
12+
using v8::Persistent;
13+
using v8::Value;
14+
using v8::WeakCallbackData;
15+
16+
typedef void (*FreeCallback)(Local<Object> object, Local<Function> fn);
17+
18+
19+
TrackPromise* TrackPromise::New(Isolate* isolate,
20+
Local<Object> object) {
21+
return new TrackPromise(isolate, object);
22+
}
23+
24+
25+
Persistent<Object>* TrackPromise::persistent() {
26+
return &persistent_;
27+
}
28+
29+
30+
TrackPromise::TrackPromise(Isolate* isolate,
31+
Local<Object> object)
32+
: persistent_(isolate, object) {
33+
persistent_.SetWeak(this, WeakCallback);
34+
persistent_.MarkIndependent();
35+
}
36+
37+
38+
TrackPromise::~TrackPromise() {
39+
persistent_.Reset();
40+
}
41+
42+
43+
void TrackPromise::WeakCallback(
44+
const WeakCallbackData<Object, TrackPromise>& data) {
45+
data.GetParameter()->WeakCallback(data.GetIsolate(), data.GetValue());
46+
}
47+
48+
49+
void TrackPromise::WeakCallback(Isolate* isolate, Local<Object> object) {
50+
node::ReportPromiseRejection(isolate, object.As<Value>());
51+
exit(1);
52+
delete this;
53+
}
54+
55+
} // namespace node

src/track-promise.h

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#ifndef SRC_TRACK_PROMISE_H_
2+
#define SRC_TRACK_PROMISE_H_
3+
4+
#include "v8.h"
5+
6+
namespace node {
7+
8+
class Environment;
9+
10+
class TrackPromise {
11+
public:
12+
TrackPromise(v8::Isolate* isolate, v8::Local<v8::Object> object);
13+
virtual ~TrackPromise();
14+
15+
static TrackPromise* New(v8::Isolate* isolate,
16+
v8::Local<v8::Object> object);
17+
18+
inline v8::Persistent<v8::Object>* persistent();
19+
20+
static inline void WeakCallback(
21+
const v8::WeakCallbackData<v8::Object, TrackPromise>& data);
22+
23+
private:
24+
inline void WeakCallback(v8::Isolate* isolate, v8::Local<v8::Object> object);
25+
26+
v8::Persistent<v8::Object> persistent_;
27+
};
28+
29+
} // namespace node
30+
31+
#endif // SRC_TRACK_PROMISE_H_
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
'use strict';
2+
const common = require('../common');
3+
4+
const p1 = new Promise(function(res, rej) {
5+
consol.log('One'); // eslint-disable-line no-undef
6+
});
7+
8+
new Promise(function(res, rej) {
9+
consol.log('Two'); // eslint-disable-line no-undef
10+
});
11+
12+
const p3 = new Promise(function(res, rej) {
13+
consol.log('Three'); // eslint-disable-line no-undef
14+
});
15+
16+
new Promise(function(res, rej) {
17+
setTimeout(common.mustCall(() => {
18+
p1.catch(() => {});
19+
p3.catch(() => {});
20+
}));
21+
});
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
*test*message*promise_fast_handled_reject.js:*
2+
consol.log('Two'); // eslint-disable-line no-undef
3+
^
4+
5+
ReferenceError: consol is not defined
6+
at *test*message*promise_fast_handled_reject.js:*:*
7+
at Object.<anonymous> (*test*message*promise_fast_handled_reject.js:*:*)
8+
at Module._compile (module.js:*:*)
9+
at Object.Module._extensions..js (module.js:*:*)
10+
at Module.load (module.js:*:*)
11+
at tryModuleLoad (module.js:*:*)
12+
at Function.Module._load (module.js:*:*)
13+
at Function.Module.runMain (module.js:*:*)
14+
at startup (node.js:*:*)
15+
at node.js:*:*

test/message/promise_fast_reject.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
'use strict';
2+
require('../common');
3+
4+
new Promise(function(res, rej) {
5+
consol.log('One'); // eslint-disable-line no-undef
6+
});
7+
8+
new Promise(function(res, rej) {
9+
consol.log('Two'); // eslint-disable-line no-undef
10+
});

0 commit comments

Comments
 (0)