Skip to content

Commit 6e4394f

Browse files
Matt Loringjasnell
Matt Loring
authored andcommitted
async_wrap,src: promise hook integration
This change provides unified tracking of asynchronous promise lifecycles for both domains and async hooks. PR-URL: #13000 Reviewed-By: Andreas Madsen <[email protected]> Reviewed-By: Anna Henningsen <[email protected]>
1 parent e554bb4 commit 6e4394f

File tree

6 files changed

+223
-98
lines changed

6 files changed

+223
-98
lines changed

src/async-wrap.cc

+153-65
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ using v8::Local;
4444
using v8::MaybeLocal;
4545
using v8::Number;
4646
using v8::Object;
47+
using v8::Promise;
48+
using v8::PromiseHookType;
4749
using v8::RetainedObjectInfo;
4850
using v8::Symbol;
4951
using v8::TryCatch;
@@ -177,6 +179,143 @@ static void PushBackDestroyId(Environment* env, double id) {
177179
}
178180

179181

182+
bool DomainEnter(Environment* env, Local<Object> object) {
183+
Local<Value> domain_v = object->Get(env->domain_string());
184+
if (domain_v->IsObject()) {
185+
Local<Object> domain = domain_v.As<Object>();
186+
if (domain->Get(env->disposed_string())->IsTrue())
187+
return true;
188+
Local<Value> enter_v = domain->Get(env->enter_string());
189+
if (enter_v->IsFunction()) {
190+
if (enter_v.As<Function>()->Call(domain, 0, nullptr).IsEmpty()) {
191+
FatalError("node::AsyncWrap::MakeCallback",
192+
"domain enter callback threw, please report this");
193+
}
194+
}
195+
}
196+
return false;
197+
}
198+
199+
200+
bool DomainExit(Environment* env, v8::Local<v8::Object> object) {
201+
Local<Value> domain_v = object->Get(env->domain_string());
202+
if (domain_v->IsObject()) {
203+
Local<Object> domain = domain_v.As<Object>();
204+
if (domain->Get(env->disposed_string())->IsTrue())
205+
return true;
206+
Local<Value> exit_v = domain->Get(env->exit_string());
207+
if (exit_v->IsFunction()) {
208+
if (exit_v.As<Function>()->Call(domain, 0, nullptr).IsEmpty()) {
209+
FatalError("node::AsyncWrap::MakeCallback",
210+
"domain exit callback threw, please report this");
211+
}
212+
}
213+
}
214+
return false;
215+
}
216+
217+
218+
static bool PreCallbackExecution(AsyncWrap* wrap, bool run_domain_cbs) {
219+
AsyncHooks* async_hooks = wrap->env()->async_hooks();
220+
221+
if (wrap->env()->using_domains() && run_domain_cbs) {
222+
bool is_disposed = DomainEnter(wrap->env(), wrap->object());
223+
if (is_disposed)
224+
return false;
225+
}
226+
227+
if (async_hooks->fields()[AsyncHooks::kBefore] > 0) {
228+
Local<Value> uid = Number::New(wrap->env()->isolate(), wrap->get_id());
229+
Local<Function> fn = wrap->env()->async_hooks_before_function();
230+
TryCatch try_catch(wrap->env()->isolate());
231+
MaybeLocal<Value> ar = fn->Call(
232+
wrap->env()->context(), Undefined(wrap->env()->isolate()), 1, &uid);
233+
if (ar.IsEmpty()) {
234+
ClearFatalExceptionHandlers(wrap->env());
235+
FatalException(wrap->env()->isolate(), try_catch);
236+
return false;
237+
}
238+
}
239+
240+
return true;
241+
}
242+
243+
244+
static bool PostCallbackExecution(AsyncWrap* wrap, bool run_domain_cbs) {
245+
AsyncHooks* async_hooks = wrap->env()->async_hooks();
246+
247+
// If the callback failed then the after() hooks will be called at the end
248+
// of _fatalException().
249+
if (async_hooks->fields()[AsyncHooks::kAfter] > 0) {
250+
Local<Value> uid = Number::New(wrap->env()->isolate(), wrap->get_id());
251+
Local<Function> fn = wrap->env()->async_hooks_after_function();
252+
TryCatch try_catch(wrap->env()->isolate());
253+
MaybeLocal<Value> ar = fn->Call(
254+
wrap->env()->context(), Undefined(wrap->env()->isolate()), 1, &uid);
255+
if (ar.IsEmpty()) {
256+
ClearFatalExceptionHandlers(wrap->env());
257+
FatalException(wrap->env()->isolate(), try_catch);
258+
return false;
259+
}
260+
}
261+
262+
if (wrap->env()->using_domains() && run_domain_cbs) {
263+
bool is_disposed = DomainExit(wrap->env(), wrap->object());
264+
if (is_disposed)
265+
return false;
266+
}
267+
268+
return true;
269+
}
270+
271+
class PromiseWrap : public AsyncWrap {
272+
public:
273+
PromiseWrap(Environment* env, Local<Object> object)
274+
: AsyncWrap(env, object, PROVIDER_PROMISE) {}
275+
size_t self_size() const override { return sizeof(*this); }
276+
};
277+
278+
279+
static void PromiseHook(PromiseHookType type, Local<Promise> promise,
280+
Local<Value> parent, void* arg) {
281+
Local<Context> context = promise->CreationContext();
282+
Environment* env = Environment::GetCurrent(context);
283+
if (type == PromiseHookType::kInit) {
284+
// Unfortunately, promises don't have internal fields. Need a surrogate that
285+
// async wrap can wrap.
286+
Local<Object> obj =
287+
env->async_hooks_promise_object()->NewInstance(context).ToLocalChecked();
288+
PromiseWrap* wrap = new PromiseWrap(env, obj);
289+
v8::PropertyAttribute hidden =
290+
static_cast<v8::PropertyAttribute>(v8::ReadOnly
291+
| v8::DontDelete
292+
| v8::DontEnum);
293+
promise->DefineOwnProperty(context,
294+
env->promise_wrap(),
295+
v8::External::New(env->isolate(), wrap),
296+
hidden).FromJust();
297+
// The async tag will be destroyed at the same time as the promise as the
298+
// only reference to it is held by the promise. This allows the promise
299+
// wrap instance to be notified when the promise is destroyed.
300+
promise->DefineOwnProperty(context,
301+
env->promise_async_tag(),
302+
obj, hidden).FromJust();
303+
} else if (type == PromiseHookType::kResolve) {
304+
// TODO(matthewloring): need to expose this through the async hooks api.
305+
}
306+
Local<v8::Value> external_wrap =
307+
promise->Get(context, env->promise_wrap()).ToLocalChecked();
308+
PromiseWrap* wrap =
309+
static_cast<PromiseWrap*>(external_wrap.As<v8::External>()->Value());
310+
CHECK_NE(wrap, nullptr);
311+
if (type == PromiseHookType::kBefore) {
312+
PreCallbackExecution(wrap, false);
313+
} else if (type == PromiseHookType::kAfter) {
314+
PostCallbackExecution(wrap, false);
315+
}
316+
}
317+
318+
180319
static void SetupHooks(const FunctionCallbackInfo<Value>& args) {
181320
Environment* env = Environment::GetCurrent(args);
182321

@@ -201,6 +340,7 @@ static void SetupHooks(const FunctionCallbackInfo<Value>& args) {
201340
SET_HOOK_FN(before);
202341
SET_HOOK_FN(after);
203342
SET_HOOK_FN(destroy);
343+
env->AddPromiseHook(PromiseHook, nullptr);
204344
#undef SET_HOOK_FN
205345
}
206346

@@ -262,6 +402,11 @@ void AsyncWrap::Initialize(Local<Object> target,
262402
env->SetMethod(target, "clearIdStack", ClearIdStack);
263403
env->SetMethod(target, "addIdToDestroyList", QueueDestroyId);
264404

405+
Local<v8::ObjectTemplate> promise_object_template =
406+
v8::ObjectTemplate::New(env->isolate());
407+
promise_object_template->SetInternalFieldCount(1);
408+
env->set_async_hooks_promise_object(promise_object_template);
409+
265410
v8::PropertyAttribute ReadOnlyDontDelete =
266411
static_cast<v8::PropertyAttribute>(v8::ReadOnly | v8::DontDelete);
267412

@@ -416,87 +561,30 @@ Local<Value> AsyncWrap::MakeCallback(const Local<Function> cb,
416561
Local<Value>* argv) {
417562
CHECK(env()->context() == env()->isolate()->GetCurrentContext());
418563

419-
AsyncHooks* async_hooks = env()->async_hooks();
420-
Local<Object> context = object();
421-
Local<Object> domain;
422-
Local<Value> uid;
423-
bool has_domain = false;
424-
425564
Environment::AsyncCallbackScope callback_scope(env());
426565

427-
if (env()->using_domains()) {
428-
Local<Value> domain_v = context->Get(env()->domain_string());
429-
has_domain = domain_v->IsObject();
430-
if (has_domain) {
431-
domain = domain_v.As<Object>();
432-
if (domain->Get(env()->disposed_string())->IsTrue())
433-
return Local<Value>();
434-
}
435-
}
566+
Environment::AsyncHooks::ExecScope exec_scope(env(),
567+
get_id(),
568+
get_trigger_id());
436569

437-
if (has_domain) {
438-
Local<Value> enter_v = domain->Get(env()->enter_string());
439-
if (enter_v->IsFunction()) {
440-
if (enter_v.As<Function>()->Call(domain, 0, nullptr).IsEmpty()) {
441-
FatalError("node::AsyncWrap::MakeCallback",
442-
"domain enter callback threw, please report this");
443-
}
444-
}
445-
}
446-
447-
// Want currentId() to return the correct value from the callbacks.
448-
AsyncHooks::ExecScope exec_scope(env(), get_id(), get_trigger_id());
449-
450-
if (async_hooks->fields()[AsyncHooks::kBefore] > 0) {
451-
uid = Number::New(env()->isolate(), get_id());
452-
Local<Function> fn = env()->async_hooks_before_function();
453-
TryCatch try_catch(env()->isolate());
454-
MaybeLocal<Value> ar = fn->Call(
455-
env()->context(), Undefined(env()->isolate()), 1, &uid);
456-
if (ar.IsEmpty()) {
457-
ClearFatalExceptionHandlers(env());
458-
FatalException(env()->isolate(), try_catch);
459-
return Local<Value>();
460-
}
570+
if (!PreCallbackExecution(this, true)) {
571+
return Local<Value>();
461572
}
462573

463574
// Finally... Get to running the user's callback.
464-
MaybeLocal<Value> ret = cb->Call(env()->context(), context, argc, argv);
575+
MaybeLocal<Value> ret = cb->Call(env()->context(), object(), argc, argv);
465576

466577
Local<Value> ret_v;
467578
if (!ret.ToLocal(&ret_v)) {
468579
return Local<Value>();
469580
}
470581

471-
// If the callback failed then the after() hooks will be called at the end
472-
// of _fatalException().
473-
if (async_hooks->fields()[AsyncHooks::kAfter] > 0) {
474-
if (uid.IsEmpty())
475-
uid = Number::New(env()->isolate(), get_id());
476-
Local<Function> fn = env()->async_hooks_after_function();
477-
TryCatch try_catch(env()->isolate());
478-
MaybeLocal<Value> ar = fn->Call(
479-
env()->context(), Undefined(env()->isolate()), 1, &uid);
480-
if (ar.IsEmpty()) {
481-
ClearFatalExceptionHandlers(env());
482-
FatalException(env()->isolate(), try_catch);
483-
return Local<Value>();
484-
}
582+
if (!PostCallbackExecution(this, true)) {
583+
return Local<Value>();
485584
}
486585

487-
// The execution scope of the id and trigger_id only go this far.
488586
exec_scope.Dispose();
489587

490-
if (has_domain) {
491-
Local<Value> exit_v = domain->Get(env()->exit_string());
492-
if (exit_v->IsFunction()) {
493-
if (exit_v.As<Function>()->Call(domain, 0, nullptr).IsEmpty()) {
494-
FatalError("node::AsyncWrap::MakeCallback",
495-
"domain exit callback threw, please report this");
496-
}
497-
}
498-
}
499-
500588
if (callback_scope.in_makecallback()) {
501589
return ret_v;
502590
}

src/async-wrap.h

+4
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ namespace node {
4444
V(PIPECONNECTWRAP) \
4545
V(PIPEWRAP) \
4646
V(PROCESSWRAP) \
47+
V(PROMISE) \
4748
V(QUERYWRAP) \
4849
V(SHUTDOWNWRAP) \
4950
V(SIGNALWRAP) \
@@ -132,6 +133,9 @@ class AsyncWrap : public BaseObject {
132133

133134
void LoadAsyncWrapperInfo(Environment* env);
134135

136+
bool DomainEnter(Environment* env, v8::Local<v8::Object> object);
137+
bool DomainExit(Environment* env, v8::Local<v8::Object> object);
138+
135139
} // namespace node
136140

137141
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS

src/env.h

+3
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,8 @@ namespace node {
195195
V(preference_string, "preference") \
196196
V(priority_string, "priority") \
197197
V(produce_cached_data_string, "produceCachedData") \
198+
V(promise_wrap, "_promise_async_wrap") \
199+
V(promise_async_tag, "_promise_async_wrap_tag") \
198200
V(raw_string, "raw") \
199201
V(read_host_object_string, "_readHostObject") \
200202
V(readable_string, "readable") \
@@ -256,6 +258,7 @@ namespace node {
256258
V(async_hooks_init_function, v8::Function) \
257259
V(async_hooks_before_function, v8::Function) \
258260
V(async_hooks_after_function, v8::Function) \
261+
V(async_hooks_promise_object, v8::ObjectTemplate) \
259262
V(binding_cache_object, v8::Object) \
260263
V(buffer_constructor_function, v8::Function) \
261264
V(buffer_prototype_object, v8::Object) \

src/node.cc

+3-32
Original file line numberDiff line numberDiff line change
@@ -1130,7 +1130,6 @@ void DomainPromiseHook(PromiseHookType type,
11301130
Environment* env = static_cast<Environment*>(arg);
11311131
Local<Context> context = env->context();
11321132

1133-
if (type == PromiseHookType::kResolve) return;
11341133
if (type == PromiseHookType::kInit && env->in_domain()) {
11351134
promise->Set(context,
11361135
env->domain_string(),
@@ -1139,38 +1138,10 @@ void DomainPromiseHook(PromiseHookType type,
11391138
return;
11401139
}
11411140

1142-
// Loosely based on node::MakeCallback().
1143-
Local<Value> domain_v =
1144-
promise->Get(context, env->domain_string()).ToLocalChecked();
1145-
if (!domain_v->IsObject())
1146-
return;
1147-
1148-
Local<Object> domain = domain_v.As<Object>();
1149-
if (domain->Get(context, env->disposed_string())
1150-
.ToLocalChecked()->IsTrue()) {
1151-
return;
1152-
}
1153-
11541141
if (type == PromiseHookType::kBefore) {
1155-
Local<Value> enter_v =
1156-
domain->Get(context, env->enter_string()).ToLocalChecked();
1157-
if (enter_v->IsFunction()) {
1158-
if (enter_v.As<Function>()->Call(context, domain, 0, nullptr).IsEmpty()) {
1159-
FatalError("node::PromiseHook",
1160-
"domain enter callback threw, please report this "
1161-
"as a bug in Node.js");
1162-
}
1163-
}
1164-
} else {
1165-
Local<Value> exit_v =
1166-
domain->Get(context, env->exit_string()).ToLocalChecked();
1167-
if (exit_v->IsFunction()) {
1168-
if (exit_v.As<Function>()->Call(context, domain, 0, nullptr).IsEmpty()) {
1169-
FatalError("node::MakeCallback",
1170-
"domain exit callback threw, please report this "
1171-
"as a bug in Node.js");
1172-
}
1173-
}
1142+
DomainEnter(env, promise);
1143+
} else if (type == PromiseHookType::kAfter) {
1144+
DomainExit(env, promise);
11741145
}
11751146
}
11761147

0 commit comments

Comments
 (0)