mirror of
https://github.com/nodejs/node.git
synced 2024-11-29 15:06:33 +01:00
async_hooks: use resource objects for Promises
Use `PromiseWrap` resource objects whose lifetimes are tied to the `Promise` instances themselves to track promises, and have a `.promise` getter that points to the `Promise` and a `.parent` property that points to the parent Promise’s resource object, if there is any. The properties are implemented as getters for internal fields rather than normal properties in the hope that it helps keep performance for the common case that async_hooks users will often not inspect them. PR-URL: https://github.com/nodejs/node/pull/13452 Reviewed-By: Andreas Madsen <amwebdk@gmail.com> Reviewed-By: Trevor Norris <trev.norris@gmail.com>
This commit is contained in:
parent
02aea0690d
commit
8f39881b74
@ -203,13 +203,16 @@ UDPSENDWRAP, UDPWRAP, WRITEWRAP, ZLIB, SSLCONNECTION, PBKDF2REQUEST,
|
||||
RANDOMBYTESREQUEST, TLSWRAP
|
||||
```
|
||||
|
||||
There is also the `PROMISE` resource type, which is used to track `Promise`
|
||||
instances and asynchronous work scheduled by them.
|
||||
|
||||
Users are be able to define their own `type` when using the public embedder API.
|
||||
|
||||
*Note:* It is possible to have type name collisions. Embedders are encouraged
|
||||
to use a unique prefixes, such as the npm package name, to prevent collisions
|
||||
when listening to the hooks.
|
||||
|
||||
###### `triggerid`
|
||||
###### `triggerId`
|
||||
|
||||
`triggerId` is the `asyncId` of the resource that caused (or "triggered") the
|
||||
new resource to initialize and that caused `init` to call. This is different
|
||||
@ -258,7 +261,13 @@ considered public, but using the Embedder API users can provide and document
|
||||
their own resource objects. Such as resource object could for example contain
|
||||
the SQL query being executed.
|
||||
|
||||
*Note:* In some cases the resource object is reused for performance reasons,
|
||||
In the case of Promises, the `resource` object will have `promise` property
|
||||
that refers to the Promise that is being initialized, and a `parentId` property
|
||||
that equals the `asyncId` of a parent Promise, if there is one, and
|
||||
`undefined` otherwise. For example, in the case of `b = a.then(handler)`,
|
||||
`a` is considered a parent Promise of `b`.
|
||||
|
||||
*Note*: In some cases the resource object is reused for performance reasons,
|
||||
it is thus not safe to use it as a key in a `WeakMap` or add properties to it.
|
||||
|
||||
###### asynchronous context example
|
||||
|
@ -36,6 +36,7 @@ using v8::Context;
|
||||
using v8::Float64Array;
|
||||
using v8::Function;
|
||||
using v8::FunctionCallbackInfo;
|
||||
using v8::FunctionTemplate;
|
||||
using v8::HandleScope;
|
||||
using v8::HeapProfiler;
|
||||
using v8::Integer;
|
||||
@ -44,8 +45,10 @@ using v8::Local;
|
||||
using v8::MaybeLocal;
|
||||
using v8::Number;
|
||||
using v8::Object;
|
||||
using v8::ObjectTemplate;
|
||||
using v8::Promise;
|
||||
using v8::PromiseHookType;
|
||||
using v8::PropertyCallbackInfo;
|
||||
using v8::RetainedObjectInfo;
|
||||
using v8::String;
|
||||
using v8::Symbol;
|
||||
@ -282,37 +285,86 @@ bool AsyncWrap::EmitAfter(Environment* env, double async_id) {
|
||||
class PromiseWrap : public AsyncWrap {
|
||||
public:
|
||||
PromiseWrap(Environment* env, Local<Object> object, bool silent)
|
||||
: AsyncWrap(env, object, PROVIDER_PROMISE, silent) {}
|
||||
: AsyncWrap(env, object, PROVIDER_PROMISE, silent) {
|
||||
MakeWeak(this);
|
||||
}
|
||||
size_t self_size() const override { return sizeof(*this); }
|
||||
|
||||
static constexpr int kPromiseField = 1;
|
||||
static constexpr int kParentIdField = 2;
|
||||
static constexpr int kInternalFieldCount = 3;
|
||||
|
||||
static PromiseWrap* New(Environment* env,
|
||||
Local<Promise> promise,
|
||||
PromiseWrap* parent_wrap,
|
||||
bool silent);
|
||||
static void GetPromise(Local<String> property,
|
||||
const PropertyCallbackInfo<Value>& info);
|
||||
static void GetParentId(Local<String> property,
|
||||
const PropertyCallbackInfo<Value>& info);
|
||||
};
|
||||
|
||||
PromiseWrap* PromiseWrap::New(Environment* env,
|
||||
Local<Promise> promise,
|
||||
PromiseWrap* parent_wrap,
|
||||
bool silent) {
|
||||
Local<Object> object = env->promise_wrap_template()
|
||||
->NewInstance(env->context()).ToLocalChecked();
|
||||
object->SetInternalField(PromiseWrap::kPromiseField, promise);
|
||||
if (parent_wrap != nullptr) {
|
||||
object->SetInternalField(PromiseWrap::kParentIdField,
|
||||
Number::New(env->isolate(),
|
||||
parent_wrap->get_id()));
|
||||
}
|
||||
CHECK_EQ(promise->GetAlignedPointerFromInternalField(0), nullptr);
|
||||
promise->SetInternalField(0, object);
|
||||
return new PromiseWrap(env, object, silent);
|
||||
}
|
||||
|
||||
void PromiseWrap::GetPromise(Local<String> property,
|
||||
const PropertyCallbackInfo<Value>& info) {
|
||||
info.GetReturnValue().Set(info.Holder()->GetInternalField(kPromiseField));
|
||||
}
|
||||
|
||||
void PromiseWrap::GetParentId(Local<String> property,
|
||||
const PropertyCallbackInfo<Value>& info) {
|
||||
info.GetReturnValue().Set(info.Holder()->GetInternalField(kParentIdField));
|
||||
}
|
||||
|
||||
static void PromiseHook(PromiseHookType type, Local<Promise> promise,
|
||||
Local<Value> parent, void* arg) {
|
||||
Local<Context> context = promise->CreationContext();
|
||||
Environment* env = Environment::GetCurrent(context);
|
||||
PromiseWrap* wrap = Unwrap<PromiseWrap>(promise);
|
||||
Local<Value> resource_object_value = promise->GetInternalField(0);
|
||||
PromiseWrap* wrap = nullptr;
|
||||
if (resource_object_value->IsObject()) {
|
||||
Local<Object> resource_object = resource_object_value.As<Object>();
|
||||
wrap = Unwrap<PromiseWrap>(resource_object);
|
||||
}
|
||||
if (type == PromiseHookType::kInit || wrap == nullptr) {
|
||||
bool silent = type != PromiseHookType::kInit;
|
||||
PromiseWrap* parent_wrap = nullptr;
|
||||
|
||||
// set parent promise's async Id as this promise's triggerId
|
||||
if (parent->IsPromise()) {
|
||||
// parent promise exists, current promise
|
||||
// is a chained promise, so we set parent promise's id as
|
||||
// current promise's triggerId
|
||||
Local<Promise> parent_promise = parent.As<Promise>();
|
||||
auto parent_wrap = Unwrap<PromiseWrap>(parent_promise);
|
||||
Local<Value> parent_resource = parent_promise->GetInternalField(0);
|
||||
if (parent_resource->IsObject()) {
|
||||
parent_wrap = Unwrap<PromiseWrap>(parent_resource.As<Object>());
|
||||
}
|
||||
|
||||
if (parent_wrap == nullptr) {
|
||||
// create a new PromiseWrap for parent promise with silent parameter
|
||||
parent_wrap = new PromiseWrap(env, parent_promise, true);
|
||||
parent_wrap->MakeWeak(parent_wrap);
|
||||
parent_wrap = PromiseWrap::New(env, parent_promise, nullptr, true);
|
||||
}
|
||||
// get id from parentWrap
|
||||
double trigger_id = parent_wrap->get_id();
|
||||
env->set_init_trigger_id(trigger_id);
|
||||
}
|
||||
wrap = new PromiseWrap(env, promise, silent);
|
||||
wrap->MakeWeak(wrap);
|
||||
|
||||
wrap = PromiseWrap::New(env, promise, parent_wrap, silent);
|
||||
} else if (type == PromiseHookType::kResolve) {
|
||||
// TODO(matthewloring): need to expose this through the async hooks api.
|
||||
}
|
||||
@ -351,6 +403,22 @@ static void SetupHooks(const FunctionCallbackInfo<Value>& args) {
|
||||
SET_HOOK_FN(destroy);
|
||||
env->AddPromiseHook(PromiseHook, nullptr);
|
||||
#undef SET_HOOK_FN
|
||||
|
||||
{
|
||||
Local<FunctionTemplate> ctor =
|
||||
FunctionTemplate::New(env->isolate());
|
||||
ctor->SetClassName(FIXED_ONE_BYTE_STRING(env->isolate(), "PromiseWrap"));
|
||||
Local<ObjectTemplate> promise_wrap_template = ctor->InstanceTemplate();
|
||||
promise_wrap_template->SetInternalFieldCount(
|
||||
PromiseWrap::kInternalFieldCount);
|
||||
promise_wrap_template->SetAccessor(
|
||||
FIXED_ONE_BYTE_STRING(env->isolate(), "promise"),
|
||||
PromiseWrap::GetPromise);
|
||||
promise_wrap_template->SetAccessor(
|
||||
FIXED_ONE_BYTE_STRING(env->isolate(), "parentId"),
|
||||
PromiseWrap::GetParentId);
|
||||
env->set_promise_wrap_template(promise_wrap_template);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -268,6 +268,7 @@ namespace node {
|
||||
V(pipe_constructor_template, v8::FunctionTemplate) \
|
||||
V(process_object, v8::Object) \
|
||||
V(promise_reject_function, v8::Function) \
|
||||
V(promise_wrap_template, v8::ObjectTemplate) \
|
||||
V(push_values_to_array_function, v8::Function) \
|
||||
V(randombytes_constructor_template, v8::ObjectTemplate) \
|
||||
V(script_context_constructor_template, v8::FunctionTemplate) \
|
||||
|
23
test/parallel/test-async-hooks-promise.js
Normal file
23
test/parallel/test-async-hooks-promise.js
Normal file
@ -0,0 +1,23 @@
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const async_hooks = require('async_hooks');
|
||||
|
||||
const initCalls = [];
|
||||
|
||||
async_hooks.createHook({
|
||||
init: common.mustCall((id, type, triggerId, resource) => {
|
||||
assert.strictEqual(type, 'PROMISE');
|
||||
initCalls.push({id, triggerId, resource});
|
||||
}, 2)
|
||||
}).enable();
|
||||
|
||||
const a = Promise.resolve(42);
|
||||
const b = a.then(common.mustCall());
|
||||
|
||||
assert.strictEqual(initCalls[0].triggerId, 1);
|
||||
assert.strictEqual(initCalls[0].resource.parentId, undefined);
|
||||
assert.strictEqual(initCalls[0].resource.promise, a);
|
||||
assert.strictEqual(initCalls[1].triggerId, initCalls[0].id);
|
||||
assert.strictEqual(initCalls[1].resource.parentId, initCalls[0].id);
|
||||
assert.strictEqual(initCalls[1].resource.promise, b);
|
Loading…
Reference in New Issue
Block a user