async_wrap,src: promise hook integration
This change provides unified tracking of asynchronous promise lifecycles for both domains and async hooks. PR-URL: https://github.com/nodejs/node/pull/13000 Reviewed-By: Andreas Madsen <amwebdk@gmail.com> Reviewed-By: Anna Henningsen <anna@addaleax.net>
This commit is contained in:
parent
410b141764
commit
920278bd37
@ -44,6 +44,8 @@ using v8::Local;
|
|||||||
using v8::MaybeLocal;
|
using v8::MaybeLocal;
|
||||||
using v8::Number;
|
using v8::Number;
|
||||||
using v8::Object;
|
using v8::Object;
|
||||||
|
using v8::Promise;
|
||||||
|
using v8::PromiseHookType;
|
||||||
using v8::RetainedObjectInfo;
|
using v8::RetainedObjectInfo;
|
||||||
using v8::Symbol;
|
using v8::Symbol;
|
||||||
using v8::TryCatch;
|
using v8::TryCatch;
|
||||||
@ -177,6 +179,143 @@ static void PushBackDestroyId(Environment* env, double id) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool DomainEnter(Environment* env, Local<Object> object) {
|
||||||
|
Local<Value> domain_v = object->Get(env->domain_string());
|
||||||
|
if (domain_v->IsObject()) {
|
||||||
|
Local<Object> domain = domain_v.As<Object>();
|
||||||
|
if (domain->Get(env->disposed_string())->IsTrue())
|
||||||
|
return true;
|
||||||
|
Local<Value> enter_v = domain->Get(env->enter_string());
|
||||||
|
if (enter_v->IsFunction()) {
|
||||||
|
if (enter_v.As<Function>()->Call(domain, 0, nullptr).IsEmpty()) {
|
||||||
|
FatalError("node::AsyncWrap::MakeCallback",
|
||||||
|
"domain enter callback threw, please report this");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool DomainExit(Environment* env, v8::Local<v8::Object> object) {
|
||||||
|
Local<Value> domain_v = object->Get(env->domain_string());
|
||||||
|
if (domain_v->IsObject()) {
|
||||||
|
Local<Object> domain = domain_v.As<Object>();
|
||||||
|
if (domain->Get(env->disposed_string())->IsTrue())
|
||||||
|
return true;
|
||||||
|
Local<Value> exit_v = domain->Get(env->exit_string());
|
||||||
|
if (exit_v->IsFunction()) {
|
||||||
|
if (exit_v.As<Function>()->Call(domain, 0, nullptr).IsEmpty()) {
|
||||||
|
FatalError("node::AsyncWrap::MakeCallback",
|
||||||
|
"domain exit callback threw, please report this");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static bool PreCallbackExecution(AsyncWrap* wrap, bool run_domain_cbs) {
|
||||||
|
AsyncHooks* async_hooks = wrap->env()->async_hooks();
|
||||||
|
|
||||||
|
if (wrap->env()->using_domains() && run_domain_cbs) {
|
||||||
|
bool is_disposed = DomainEnter(wrap->env(), wrap->object());
|
||||||
|
if (is_disposed)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (async_hooks->fields()[AsyncHooks::kBefore] > 0) {
|
||||||
|
Local<Value> uid = Number::New(wrap->env()->isolate(), wrap->get_id());
|
||||||
|
Local<Function> fn = wrap->env()->async_hooks_before_function();
|
||||||
|
TryCatch try_catch(wrap->env()->isolate());
|
||||||
|
MaybeLocal<Value> ar = fn->Call(
|
||||||
|
wrap->env()->context(), Undefined(wrap->env()->isolate()), 1, &uid);
|
||||||
|
if (ar.IsEmpty()) {
|
||||||
|
ClearFatalExceptionHandlers(wrap->env());
|
||||||
|
FatalException(wrap->env()->isolate(), try_catch);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static bool PostCallbackExecution(AsyncWrap* wrap, bool run_domain_cbs) {
|
||||||
|
AsyncHooks* async_hooks = wrap->env()->async_hooks();
|
||||||
|
|
||||||
|
// If the callback failed then the after() hooks will be called at the end
|
||||||
|
// of _fatalException().
|
||||||
|
if (async_hooks->fields()[AsyncHooks::kAfter] > 0) {
|
||||||
|
Local<Value> uid = Number::New(wrap->env()->isolate(), wrap->get_id());
|
||||||
|
Local<Function> fn = wrap->env()->async_hooks_after_function();
|
||||||
|
TryCatch try_catch(wrap->env()->isolate());
|
||||||
|
MaybeLocal<Value> ar = fn->Call(
|
||||||
|
wrap->env()->context(), Undefined(wrap->env()->isolate()), 1, &uid);
|
||||||
|
if (ar.IsEmpty()) {
|
||||||
|
ClearFatalExceptionHandlers(wrap->env());
|
||||||
|
FatalException(wrap->env()->isolate(), try_catch);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wrap->env()->using_domains() && run_domain_cbs) {
|
||||||
|
bool is_disposed = DomainExit(wrap->env(), wrap->object());
|
||||||
|
if (is_disposed)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
class PromiseWrap : public AsyncWrap {
|
||||||
|
public:
|
||||||
|
PromiseWrap(Environment* env, Local<Object> object)
|
||||||
|
: AsyncWrap(env, object, PROVIDER_PROMISE) {}
|
||||||
|
size_t self_size() const override { return sizeof(*this); }
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
static void PromiseHook(PromiseHookType type, Local<Promise> promise,
|
||||||
|
Local<Value> parent, void* arg) {
|
||||||
|
Local<Context> context = promise->CreationContext();
|
||||||
|
Environment* env = Environment::GetCurrent(context);
|
||||||
|
if (type == PromiseHookType::kInit) {
|
||||||
|
// Unfortunately, promises don't have internal fields. Need a surrogate that
|
||||||
|
// async wrap can wrap.
|
||||||
|
Local<Object> obj =
|
||||||
|
env->async_hooks_promise_object()->NewInstance(context).ToLocalChecked();
|
||||||
|
PromiseWrap* wrap = new PromiseWrap(env, obj);
|
||||||
|
v8::PropertyAttribute hidden =
|
||||||
|
static_cast<v8::PropertyAttribute>(v8::ReadOnly
|
||||||
|
| v8::DontDelete
|
||||||
|
| v8::DontEnum);
|
||||||
|
promise->DefineOwnProperty(context,
|
||||||
|
env->promise_wrap(),
|
||||||
|
v8::External::New(env->isolate(), wrap),
|
||||||
|
hidden).FromJust();
|
||||||
|
// The async tag will be destroyed at the same time as the promise as the
|
||||||
|
// only reference to it is held by the promise. This allows the promise
|
||||||
|
// wrap instance to be notified when the promise is destroyed.
|
||||||
|
promise->DefineOwnProperty(context,
|
||||||
|
env->promise_async_tag(),
|
||||||
|
obj, hidden).FromJust();
|
||||||
|
} else if (type == PromiseHookType::kResolve) {
|
||||||
|
// TODO(matthewloring): need to expose this through the async hooks api.
|
||||||
|
}
|
||||||
|
Local<v8::Value> external_wrap =
|
||||||
|
promise->Get(context, env->promise_wrap()).ToLocalChecked();
|
||||||
|
PromiseWrap* wrap =
|
||||||
|
static_cast<PromiseWrap*>(external_wrap.As<v8::External>()->Value());
|
||||||
|
CHECK_NE(wrap, nullptr);
|
||||||
|
if (type == PromiseHookType::kBefore) {
|
||||||
|
PreCallbackExecution(wrap, false);
|
||||||
|
} else if (type == PromiseHookType::kAfter) {
|
||||||
|
PostCallbackExecution(wrap, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static void SetupHooks(const FunctionCallbackInfo<Value>& args) {
|
static void SetupHooks(const FunctionCallbackInfo<Value>& args) {
|
||||||
Environment* env = Environment::GetCurrent(args);
|
Environment* env = Environment::GetCurrent(args);
|
||||||
|
|
||||||
@ -201,6 +340,7 @@ static void SetupHooks(const FunctionCallbackInfo<Value>& args) {
|
|||||||
SET_HOOK_FN(before);
|
SET_HOOK_FN(before);
|
||||||
SET_HOOK_FN(after);
|
SET_HOOK_FN(after);
|
||||||
SET_HOOK_FN(destroy);
|
SET_HOOK_FN(destroy);
|
||||||
|
env->AddPromiseHook(PromiseHook, nullptr);
|
||||||
#undef SET_HOOK_FN
|
#undef SET_HOOK_FN
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -262,6 +402,11 @@ void AsyncWrap::Initialize(Local<Object> target,
|
|||||||
env->SetMethod(target, "clearIdStack", ClearIdStack);
|
env->SetMethod(target, "clearIdStack", ClearIdStack);
|
||||||
env->SetMethod(target, "addIdToDestroyList", QueueDestroyId);
|
env->SetMethod(target, "addIdToDestroyList", QueueDestroyId);
|
||||||
|
|
||||||
|
Local<v8::ObjectTemplate> promise_object_template =
|
||||||
|
v8::ObjectTemplate::New(env->isolate());
|
||||||
|
promise_object_template->SetInternalFieldCount(1);
|
||||||
|
env->set_async_hooks_promise_object(promise_object_template);
|
||||||
|
|
||||||
v8::PropertyAttribute ReadOnlyDontDelete =
|
v8::PropertyAttribute ReadOnlyDontDelete =
|
||||||
static_cast<v8::PropertyAttribute>(v8::ReadOnly | v8::DontDelete);
|
static_cast<v8::PropertyAttribute>(v8::ReadOnly | v8::DontDelete);
|
||||||
|
|
||||||
@ -416,87 +561,30 @@ Local<Value> AsyncWrap::MakeCallback(const Local<Function> cb,
|
|||||||
Local<Value>* argv) {
|
Local<Value>* argv) {
|
||||||
CHECK(env()->context() == env()->isolate()->GetCurrentContext());
|
CHECK(env()->context() == env()->isolate()->GetCurrentContext());
|
||||||
|
|
||||||
AsyncHooks* async_hooks = env()->async_hooks();
|
|
||||||
Local<Object> context = object();
|
|
||||||
Local<Object> domain;
|
|
||||||
Local<Value> uid;
|
|
||||||
bool has_domain = false;
|
|
||||||
|
|
||||||
Environment::AsyncCallbackScope callback_scope(env());
|
Environment::AsyncCallbackScope callback_scope(env());
|
||||||
|
|
||||||
if (env()->using_domains()) {
|
Environment::AsyncHooks::ExecScope exec_scope(env(),
|
||||||
Local<Value> domain_v = context->Get(env()->domain_string());
|
get_id(),
|
||||||
has_domain = domain_v->IsObject();
|
get_trigger_id());
|
||||||
if (has_domain) {
|
|
||||||
domain = domain_v.As<Object>();
|
if (!PreCallbackExecution(this, true)) {
|
||||||
if (domain->Get(env()->disposed_string())->IsTrue())
|
|
||||||
return Local<Value>();
|
return Local<Value>();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (has_domain) {
|
|
||||||
Local<Value> enter_v = domain->Get(env()->enter_string());
|
|
||||||
if (enter_v->IsFunction()) {
|
|
||||||
if (enter_v.As<Function>()->Call(domain, 0, nullptr).IsEmpty()) {
|
|
||||||
FatalError("node::AsyncWrap::MakeCallback",
|
|
||||||
"domain enter callback threw, please report this");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Want currentId() to return the correct value from the callbacks.
|
|
||||||
AsyncHooks::ExecScope exec_scope(env(), get_id(), get_trigger_id());
|
|
||||||
|
|
||||||
if (async_hooks->fields()[AsyncHooks::kBefore] > 0) {
|
|
||||||
uid = Number::New(env()->isolate(), get_id());
|
|
||||||
Local<Function> fn = env()->async_hooks_before_function();
|
|
||||||
TryCatch try_catch(env()->isolate());
|
|
||||||
MaybeLocal<Value> ar = fn->Call(
|
|
||||||
env()->context(), Undefined(env()->isolate()), 1, &uid);
|
|
||||||
if (ar.IsEmpty()) {
|
|
||||||
ClearFatalExceptionHandlers(env());
|
|
||||||
FatalException(env()->isolate(), try_catch);
|
|
||||||
return Local<Value>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Finally... Get to running the user's callback.
|
// Finally... Get to running the user's callback.
|
||||||
MaybeLocal<Value> ret = cb->Call(env()->context(), context, argc, argv);
|
MaybeLocal<Value> ret = cb->Call(env()->context(), object(), argc, argv);
|
||||||
|
|
||||||
Local<Value> ret_v;
|
Local<Value> ret_v;
|
||||||
if (!ret.ToLocal(&ret_v)) {
|
if (!ret.ToLocal(&ret_v)) {
|
||||||
return Local<Value>();
|
return Local<Value>();
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the callback failed then the after() hooks will be called at the end
|
if (!PostCallbackExecution(this, true)) {
|
||||||
// of _fatalException().
|
|
||||||
if (async_hooks->fields()[AsyncHooks::kAfter] > 0) {
|
|
||||||
if (uid.IsEmpty())
|
|
||||||
uid = Number::New(env()->isolate(), get_id());
|
|
||||||
Local<Function> fn = env()->async_hooks_after_function();
|
|
||||||
TryCatch try_catch(env()->isolate());
|
|
||||||
MaybeLocal<Value> ar = fn->Call(
|
|
||||||
env()->context(), Undefined(env()->isolate()), 1, &uid);
|
|
||||||
if (ar.IsEmpty()) {
|
|
||||||
ClearFatalExceptionHandlers(env());
|
|
||||||
FatalException(env()->isolate(), try_catch);
|
|
||||||
return Local<Value>();
|
return Local<Value>();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// The execution scope of the id and trigger_id only go this far.
|
|
||||||
exec_scope.Dispose();
|
exec_scope.Dispose();
|
||||||
|
|
||||||
if (has_domain) {
|
|
||||||
Local<Value> exit_v = domain->Get(env()->exit_string());
|
|
||||||
if (exit_v->IsFunction()) {
|
|
||||||
if (exit_v.As<Function>()->Call(domain, 0, nullptr).IsEmpty()) {
|
|
||||||
FatalError("node::AsyncWrap::MakeCallback",
|
|
||||||
"domain exit callback threw, please report this");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (callback_scope.in_makecallback()) {
|
if (callback_scope.in_makecallback()) {
|
||||||
return ret_v;
|
return ret_v;
|
||||||
}
|
}
|
||||||
|
@ -44,6 +44,7 @@ namespace node {
|
|||||||
V(PIPECONNECTWRAP) \
|
V(PIPECONNECTWRAP) \
|
||||||
V(PIPEWRAP) \
|
V(PIPEWRAP) \
|
||||||
V(PROCESSWRAP) \
|
V(PROCESSWRAP) \
|
||||||
|
V(PROMISE) \
|
||||||
V(QUERYWRAP) \
|
V(QUERYWRAP) \
|
||||||
V(SHUTDOWNWRAP) \
|
V(SHUTDOWNWRAP) \
|
||||||
V(SIGNALWRAP) \
|
V(SIGNALWRAP) \
|
||||||
@ -132,6 +133,9 @@ class AsyncWrap : public BaseObject {
|
|||||||
|
|
||||||
void LoadAsyncWrapperInfo(Environment* env);
|
void LoadAsyncWrapperInfo(Environment* env);
|
||||||
|
|
||||||
|
bool DomainEnter(Environment* env, v8::Local<v8::Object> object);
|
||||||
|
bool DomainExit(Environment* env, v8::Local<v8::Object> object);
|
||||||
|
|
||||||
} // namespace node
|
} // namespace node
|
||||||
|
|
||||||
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
|
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
|
||||||
|
@ -195,6 +195,8 @@ namespace node {
|
|||||||
V(preference_string, "preference") \
|
V(preference_string, "preference") \
|
||||||
V(priority_string, "priority") \
|
V(priority_string, "priority") \
|
||||||
V(produce_cached_data_string, "produceCachedData") \
|
V(produce_cached_data_string, "produceCachedData") \
|
||||||
|
V(promise_wrap, "_promise_async_wrap") \
|
||||||
|
V(promise_async_tag, "_promise_async_wrap_tag") \
|
||||||
V(raw_string, "raw") \
|
V(raw_string, "raw") \
|
||||||
V(read_host_object_string, "_readHostObject") \
|
V(read_host_object_string, "_readHostObject") \
|
||||||
V(readable_string, "readable") \
|
V(readable_string, "readable") \
|
||||||
@ -256,6 +258,7 @@ namespace node {
|
|||||||
V(async_hooks_init_function, v8::Function) \
|
V(async_hooks_init_function, v8::Function) \
|
||||||
V(async_hooks_before_function, v8::Function) \
|
V(async_hooks_before_function, v8::Function) \
|
||||||
V(async_hooks_after_function, v8::Function) \
|
V(async_hooks_after_function, v8::Function) \
|
||||||
|
V(async_hooks_promise_object, v8::ObjectTemplate) \
|
||||||
V(binding_cache_object, v8::Object) \
|
V(binding_cache_object, v8::Object) \
|
||||||
V(buffer_constructor_function, v8::Function) \
|
V(buffer_constructor_function, v8::Function) \
|
||||||
V(buffer_prototype_object, v8::Object) \
|
V(buffer_prototype_object, v8::Object) \
|
||||||
|
35
src/node.cc
35
src/node.cc
@ -1130,7 +1130,6 @@ void DomainPromiseHook(PromiseHookType type,
|
|||||||
Environment* env = static_cast<Environment*>(arg);
|
Environment* env = static_cast<Environment*>(arg);
|
||||||
Local<Context> context = env->context();
|
Local<Context> context = env->context();
|
||||||
|
|
||||||
if (type == PromiseHookType::kResolve) return;
|
|
||||||
if (type == PromiseHookType::kInit && env->in_domain()) {
|
if (type == PromiseHookType::kInit && env->in_domain()) {
|
||||||
promise->Set(context,
|
promise->Set(context,
|
||||||
env->domain_string(),
|
env->domain_string(),
|
||||||
@ -1139,38 +1138,10 @@ void DomainPromiseHook(PromiseHookType type,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Loosely based on node::MakeCallback().
|
|
||||||
Local<Value> domain_v =
|
|
||||||
promise->Get(context, env->domain_string()).ToLocalChecked();
|
|
||||||
if (!domain_v->IsObject())
|
|
||||||
return;
|
|
||||||
|
|
||||||
Local<Object> domain = domain_v.As<Object>();
|
|
||||||
if (domain->Get(context, env->disposed_string())
|
|
||||||
.ToLocalChecked()->IsTrue()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type == PromiseHookType::kBefore) {
|
if (type == PromiseHookType::kBefore) {
|
||||||
Local<Value> enter_v =
|
DomainEnter(env, promise);
|
||||||
domain->Get(context, env->enter_string()).ToLocalChecked();
|
} else if (type == PromiseHookType::kAfter) {
|
||||||
if (enter_v->IsFunction()) {
|
DomainExit(env, promise);
|
||||||
if (enter_v.As<Function>()->Call(context, domain, 0, nullptr).IsEmpty()) {
|
|
||||||
FatalError("node::PromiseHook",
|
|
||||||
"domain enter callback threw, please report this "
|
|
||||||
"as a bug in Node.js");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Local<Value> exit_v =
|
|
||||||
domain->Get(context, env->exit_string()).ToLocalChecked();
|
|
||||||
if (exit_v->IsFunction()) {
|
|
||||||
if (exit_v.As<Function>()->Call(context, domain, 0, nullptr).IsEmpty()) {
|
|
||||||
FatalError("node::MakeCallback",
|
|
||||||
"domain exit callback threw, please report this "
|
|
||||||
"as a bug in Node.js");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
52
test/async-hooks/test-promise.js
Normal file
52
test/async-hooks/test-promise.js
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const common = require('../common');
|
||||||
|
const assert = require('assert');
|
||||||
|
const initHooks = require('./init-hooks');
|
||||||
|
const { checkInvocations } = require('./hook-checks');
|
||||||
|
|
||||||
|
const hooks = initHooks();
|
||||||
|
|
||||||
|
hooks.enable();
|
||||||
|
|
||||||
|
const p = (new Promise(common.mustCall(executor)));
|
||||||
|
p.then(afterresolution);
|
||||||
|
|
||||||
|
function executor(resolve, reject) {
|
||||||
|
const as = hooks.activitiesOfTypes('PROMISE');
|
||||||
|
assert.strictEqual(as.length, 1, 'one activities');
|
||||||
|
const a = as[0];
|
||||||
|
checkInvocations(a, { init: 1 }, 'while in promise executor');
|
||||||
|
resolve(5);
|
||||||
|
}
|
||||||
|
|
||||||
|
function afterresolution(val) {
|
||||||
|
assert.strictEqual(val, 5);
|
||||||
|
const as = hooks.activitiesOfTypes('PROMISE');
|
||||||
|
assert.strictEqual(as.length, 2, 'two activities');
|
||||||
|
checkInvocations(as[0], { init: 1 }, 'after resolution parent promise');
|
||||||
|
checkInvocations(as[1], { init: 1, before: 1 },
|
||||||
|
'after resolution child promise');
|
||||||
|
}
|
||||||
|
|
||||||
|
process.on('exit', onexit);
|
||||||
|
function onexit() {
|
||||||
|
hooks.disable();
|
||||||
|
hooks.sanityCheck('PROMISE');
|
||||||
|
|
||||||
|
const as = hooks.activitiesOfTypes('PROMISE');
|
||||||
|
assert.strictEqual(as.length, 2, 'two activities');
|
||||||
|
|
||||||
|
const a0 = as[0];
|
||||||
|
assert.strictEqual(a0.type, 'PROMISE', 'promise request');
|
||||||
|
assert.strictEqual(typeof a0.uid, 'number', 'uid is a number');
|
||||||
|
assert.strictEqual(a0.triggerId, 1, 'parent uid 1');
|
||||||
|
checkInvocations(a0, { init: 1 }, 'when process exits');
|
||||||
|
|
||||||
|
const a1 = as[1];
|
||||||
|
assert.strictEqual(a1.type, 'PROMISE', 'promise request');
|
||||||
|
assert.strictEqual(typeof a1.uid, 'number', 'uid is a number');
|
||||||
|
assert.strictEqual(a1.triggerId, 1, 'parent uid 1');
|
||||||
|
// We expect a destroy hook as well but we cannot guarentee predictable gc.
|
||||||
|
checkInvocations(a1, { init: 1, before: 1, after: 1 }, 'when process exits');
|
||||||
|
}
|
@ -66,6 +66,14 @@ function testInitialized(req, ctor_name) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
{
|
||||||
|
// We don't want to expose getAsyncId for promises but we need to construct
|
||||||
|
// one so that the cooresponding provider type is removed from the
|
||||||
|
// providers list.
|
||||||
|
new Promise((res) => res(5));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
if (common.hasCrypto) {
|
if (common.hasCrypto) {
|
||||||
const tls = require('tls');
|
const tls = require('tls');
|
||||||
// SecurePair
|
// SecurePair
|
||||||
@ -137,7 +145,6 @@ if (common.hasCrypto) {
|
|||||||
testInitialized(new Process(), 'Process');
|
testInitialized(new Process(), 'Process');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
{
|
{
|
||||||
const Signal = process.binding('signal_wrap').Signal;
|
const Signal = process.binding('signal_wrap').Signal;
|
||||||
testInitialized(new Signal(), 'Signal');
|
testInitialized(new Signal(), 'Signal');
|
||||||
|
Loading…
x
Reference in New Issue
Block a user