Skip to content

Commit

Permalink
async_hooks: move PromiseHook handler to JS
Browse files Browse the repository at this point in the history
This avoids the need to wrap every promise in an AsyncWrap and also
makes it easier to skip the machinery to track destroy events when
there's no destroy listener.
  • Loading branch information
Qard committed Apr 17, 2020
1 parent 5a4f24e commit 9b75102
Show file tree
Hide file tree
Showing 6 changed files with 148 additions and 113 deletions.
11 changes: 11 additions & 0 deletions TODO.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# C++

* [ ] Move async_hooks-related methods from Environment to AsyncHooks.

```cpp
// The necessary API for async_hooks.
inline double new_async_id();
inline double execution_async_id();
inline double trigger_async_id();
inline double get_default_trigger_async_id();
```
80 changes: 79 additions & 1 deletion lib/internal/async_hooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const {
Error,
FunctionPrototypeBind,
ObjectDefineProperty,
Promise,
Symbol,
} = primordials;

Expand Down Expand Up @@ -243,13 +244,90 @@ function restoreActiveHooks() {
active_hooks.tmp_fields = null;
}

const promiseWrap = Symbol('promiseWrap');
const destroyedSymbol = Symbol('destroyed');

class PromiseWrap {
constructor(promise, parent_wrap, silent) {
promise[promiseWrap] = this;
this.isChainedPromise = !!parent_wrap;
this.promise = promise;
this[destroyedSymbol] = undefined;

const asyncId = getOrSetAsyncId(this);

if (typeof parent_wrap !== 'undefined') {
this.triggerAsyncId = getOrSetAsyncId(parent_wrap);
} else {
this.triggerAsyncId = getDefaultTriggerAsyncId();
}

if (!silent && initHooksExist()) {
emitInitScript(asyncId, 'PROMISE', this.triggerAsyncId, this);

if (destroyHooksExist()) {
const destroyed = { destroyed: false };
this[destroyedSymbol] = destroyed;
registerDestroyHook(this, asyncId, destroyed);
}
}
}

before() {
emitBeforeScript(getOrSetAsyncId(this), this.triggerAsyncId, this);
}

after() {
emitAfterScript(getOrSetAsyncId(this));
}

promiseResolve() {
emitPromiseResolveNative(getOrSetAsyncId(this));
}

static from(promise) {
return promise && promise[promiseWrap];
}
}

function promiseHook(type, promise, parent) {
let wrap = PromiseWrap.from(promise);
if (type === 'init' || !wrap) {
const silent = type !== 'init';
if (parent instanceof Promise) {
let parentWrap = PromiseWrap.from(parent);
if (!parentWrap) {
parentWrap = new PromiseWrap(parent, null, true);
if (!parentWrap) return;
}

wrap = new PromiseWrap(promise, parentWrap, silent);
} else {
wrap = new PromiseWrap(promise, undefined, silent);
}
}

if (!wrap) return;

switch (type) {
case 'before':
wrap.before();
break;
case 'after':
wrap.after();
break;
case 'promiseResolve':
wrap.promiseResolve();
break;
}
}

let wantPromiseHook = false;
function enableHooks() {
async_hook_fields[kCheck] += 1;

wantPromiseHook = true;
enablePromiseHook();
enablePromiseHook(promiseHook);
}

function disableHooks() {
Expand Down
126 changes: 15 additions & 111 deletions src/async_wrap.cc
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,9 @@ using v8::Local;
using v8::MaybeLocal;
using v8::Number;
using v8::Object;
using v8::ObjectTemplate;
using v8::Promise;
using v8::PromiseHookType;
using v8::PropertyAttribute;
using v8::PropertyCallbackInfo;
using v8::ReadOnly;
using v8::String;
using v8::Uint32;
Expand Down Expand Up @@ -173,59 +171,6 @@ void AsyncWrap::EmitAfter(Environment* env, double async_id) {
env->async_hooks_after_function());
}

class PromiseWrap : public AsyncWrap {
public:
enum InternalFields {
kIsChainedPromiseField = AsyncWrap::kInternalFieldCount,
kInternalFieldCount
};
PromiseWrap(Environment* env, Local<Object> object, bool silent)
: AsyncWrap(env, object, PROVIDER_PROMISE, kInvalidAsyncId, silent) {
MakeWeak();
}

SET_NO_MEMORY_INFO()
SET_MEMORY_INFO_NAME(PromiseWrap)
SET_SELF_SIZE(PromiseWrap)

static PromiseWrap* New(Environment* env,
Local<Promise> promise,
PromiseWrap* parent_wrap,
bool silent);
static void getIsChainedPromise(Local<String> property,
const PropertyCallbackInfo<Value>& info);
};

PromiseWrap* PromiseWrap::New(Environment* env,
Local<Promise> promise,
PromiseWrap* parent_wrap,
bool silent) {
Local<Object> obj;
if (!env->promise_wrap_template()->NewInstance(env->context()).ToLocal(&obj))
return nullptr;
obj->SetInternalField(PromiseWrap::kIsChainedPromiseField,
parent_wrap != nullptr ? v8::True(env->isolate())
: v8::False(env->isolate()));
CHECK_NULL(promise->GetAlignedPointerFromInternalField(0));
promise->SetInternalField(0, obj);
return new PromiseWrap(env, obj, silent);
}

void PromiseWrap::getIsChainedPromise(Local<String> property,
const PropertyCallbackInfo<Value>& info) {
info.GetReturnValue().Set(
info.Holder()->GetInternalField(PromiseWrap::kIsChainedPromiseField));
}

static PromiseWrap* extractPromiseWrap(Local<Promise> promise) {
// This check is imperfect. If the internal field is set, it should
// be an object. If it's not, we just ignore it. Ideally v8 would
// have had GetInternalField returning a MaybeLocal but this works
// for now.
Local<Value> obj = promise->GetInternalField(0);
return obj->IsObject() ? Unwrap<PromiseWrap>(obj.As<Object>()) : nullptr;
}

static void PromiseHook(PromiseHookType type, Local<Promise> promise,
Local<Value> parent) {
Local<Context> context = promise->CreationContext();
Expand All @@ -235,50 +180,14 @@ static void PromiseHook(PromiseHookType type, Local<Promise> promise,
TraceEventScope trace_scope(TRACING_CATEGORY_NODE1(environment),
"EnvPromiseHook", env);

PromiseWrap* wrap = extractPromiseWrap(promise);
if (type == PromiseHookType::kInit || wrap == nullptr) {
bool silent = type != PromiseHookType::kInit;

// set parent promise's async Id as this promise's triggerAsyncId
if (parent->IsPromise()) {
// parent promise exists, current promise
// is a chained promise, so we set parent promise's id as
// current promise's triggerAsyncId
Local<Promise> parent_promise = parent.As<Promise>();
PromiseWrap* parent_wrap = extractPromiseWrap(parent_promise);
if (parent_wrap == nullptr) {
parent_wrap = PromiseWrap::New(env, parent_promise, nullptr, true);
if (parent_wrap == nullptr) return;
}

AsyncHooks::DefaultTriggerAsyncIdScope trigger_scope(parent_wrap);
wrap = PromiseWrap::New(env, promise, parent_wrap, silent);
} else {
wrap = PromiseWrap::New(env, promise, nullptr, silent);
}
}
Local<Value> argv[] = {
env->isolate_data()->promise_hook_type(static_cast<int>(type)),
promise,
parent
};

if (wrap == nullptr) return;

if (type == PromiseHookType::kBefore) {
env->async_hooks()->push_async_context(wrap->get_async_id(),
wrap->get_trigger_async_id(), wrap->object());
wrap->EmitTraceEventBefore();
AsyncWrap::EmitBefore(wrap->env(), wrap->get_async_id());
} else if (type == PromiseHookType::kAfter) {
wrap->EmitTraceEventAfter(wrap->provider_type(), wrap->get_async_id());
AsyncWrap::EmitAfter(wrap->env(), wrap->get_async_id());
if (env->execution_async_id() == wrap->get_async_id()) {
// This condition might not be true if async_hooks was enabled during
// the promise callback execution.
// Popping it off the stack can be skipped in that case, because it is
// known that it would correspond to exactly one call with
// PromiseHookType::kBefore that was not witnessed by the PromiseHook.
env->async_hooks()->pop_async_context(wrap->get_async_id());
}
} else if (type == PromiseHookType::kResolve) {
AsyncWrap::EmitPromiseResolve(wrap->env(), wrap->get_async_id());
}
Local<Function> promise_hook = env->promise_hook_handler();
USE(promise_hook->Call(context, Undefined(env->isolate()), 3, argv));
}


Expand Down Expand Up @@ -310,23 +219,18 @@ static void SetupHooks(const FunctionCallbackInfo<Value>& args) {
SET_HOOK_FN(destroy);
SET_HOOK_FN(promise_resolve);
#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(), "isChainedPromise"),
PromiseWrap::getIsChainedPromise);
env->set_promise_wrap_template(promise_wrap_template);
static void EnablePromiseHook(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);

if (!args[0]->IsFunction()) {
return node::THROW_ERR_INVALID_ARG_TYPE(
env, "handler must be a function");
}
}

env->set_promise_hook_handler(args[0].As<Function>());

static void EnablePromiseHook(const FunctionCallbackInfo<Value>& args) {
args.GetIsolate()->SetPromiseHook(PromiseHook);
}

Expand Down
4 changes: 4 additions & 0 deletions src/env-inl.h
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,10 @@ inline v8::Local<v8::String> IsolateData::async_wrap_provider(int index) const {
return async_wrap_providers_[index].Get(isolate_);
}

inline v8::Local<v8::String> IsolateData::promise_hook_type(int index) const {
return promise_hook_types_[index].Get(isolate_);
}

inline AsyncHooks::AsyncHooks()
: async_ids_stack_(env()->isolate(), 16 * 2),
fields_(env()->isolate(), kFieldsCount),
Expand Down
33 changes: 33 additions & 0 deletions src/env.cc
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@ std::vector<size_t> IsolateData::Serialize(SnapshotCreator* creator) {
for (size_t i = 0; i < AsyncWrap::PROVIDERS_LENGTH; i++)
indexes.push_back(creator->AddData(async_wrap_provider(i)));

for (size_t i = 0; i < 4; i++)
indexes.push_back(creator->AddData(promise_hook_type(i)));

return indexes;
}

Expand Down Expand Up @@ -117,6 +120,15 @@ void IsolateData::DeserializeProperties(const std::vector<size_t>* indexes) {
}
async_wrap_providers_[j].Set(isolate_, field.ToLocalChecked());
}

for (size_t j = 0; j < 4; j++) {
MaybeLocal<String> field =
isolate_->GetDataFromSnapshotOnce<String>((*indexes)[i++]);
if (field.IsEmpty()) {
fprintf(stderr, "Failed to deserialize PromiseHook type %zu\n", j);
}
promise_hook_types_[j].Set(isolate_, field.ToLocalChecked());
}
}

void IsolateData::CreateProperties() {
Expand Down Expand Up @@ -181,6 +193,26 @@ void IsolateData::CreateProperties() {
sizeof(#Provider) - 1).ToLocalChecked());
NODE_ASYNC_PROVIDER_TYPES(V)
#undef V

// Create all the hook type strings that will be passed to JS. Place them in
// an array so the array index matches the type id offset. This way the
// strings can be retrieved quickly.
#define PROMISE_HOOK_TYPES(V) \
V(0, init) \
V(1, promiseResolve) \
V(2, before) \
V(3, after)
#define V(index, name) \
promise_hook_types_[index].Set( \
isolate_, \
v8::String::NewFromOneByte( \
isolate_, \
reinterpret_cast<const uint8_t*>(#name), \
v8::NewStringType::kInternalized, \
sizeof(#name) - 1).ToLocalChecked());
PROMISE_HOOK_TYPES(V)
#undef V
#undef PROMISE_HOOK_TYPES
}

IsolateData::IsolateData(Isolate* isolate,
Expand Down Expand Up @@ -219,6 +251,7 @@ void IsolateData::MemoryInfo(MemoryTracker* tracker) const {
#undef V

tracker->TrackField("async_wrap_providers", async_wrap_providers_);
tracker->TrackField("promise_hook_types", promise_hook_types_);

if (node_allocator_ != nullptr) {
tracker->TrackFieldWithSize(
Expand Down
7 changes: 6 additions & 1 deletion src/env.h
Original file line number Diff line number Diff line change
Expand Up @@ -408,7 +408,6 @@ constexpr size_t kFsStatsBufferLength =
V(libuv_stream_wrap_ctor_template, v8::FunctionTemplate) \
V(message_port_constructor_template, v8::FunctionTemplate) \
V(pipe_constructor_template, v8::FunctionTemplate) \
V(promise_wrap_template, v8::ObjectTemplate) \
V(sab_lifetimepartner_constructor_template, v8::FunctionTemplate) \
V(script_context_constructor_template, v8::FunctionTemplate) \
V(secure_context_constructor_template, v8::FunctionTemplate) \
Expand Down Expand Up @@ -458,6 +457,7 @@ constexpr size_t kFsStatsBufferLength =
V(prepare_stack_trace_callback, v8::Function) \
V(process_object, v8::Object) \
V(primordials, v8::Object) \
V(promise_hook_handler, v8::Function) \
V(promise_reject_callback, v8::Function) \
V(script_data_constructor_function, v8::Function) \
V(source_map_cache_getter, v8::Function) \
Expand Down Expand Up @@ -507,6 +507,7 @@ class IsolateData : public MemoryRetainer {
#undef VS
#undef VP
inline v8::Local<v8::String> async_wrap_provider(int index) const;
inline v8::Local<v8::String> promise_hook_type(int index) const;

std::unordered_map<const char*, v8::Eternal<v8::String>> static_str_map;

Expand Down Expand Up @@ -536,6 +537,10 @@ class IsolateData : public MemoryRetainer {
std::array<v8::Eternal<v8::String>, AsyncWrap::PROVIDERS_LENGTH>
async_wrap_providers_;

// Keep a list of all Persistent strings used for PromiseHook types.
std::array<v8::Eternal<v8::String>, 4>
promise_hook_types_;

v8::Isolate* const isolate_;
uv_loop_t* const event_loop_;
v8::ArrayBuffer::Allocator* const allocator_;
Expand Down

0 comments on commit 9b75102

Please sign in to comment.