Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

async_hooks: improve resource stack performance #34319

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
31 changes: 17 additions & 14 deletions lib/internal/async_hooks.js
Expand Up @@ -50,7 +50,9 @@ const {
// each hook's after() callback.
const {
pushAsyncContext: pushAsyncContext_,
popAsyncContext: popAsyncContext_
popAsyncContext: popAsyncContext_,
executionAsyncResource: executionAsyncResource_,
clearAsyncIdStack,
} = async_wrap;
// For performance reasons, only track Promises when a hook is enabled.
const { enablePromiseHook, disablePromiseHook } = async_wrap;
Expand Down Expand Up @@ -89,7 +91,8 @@ const { resource_symbol, owner_symbol } = internalBinding('symbols');
// for a given step, that step can bail out early.
const { kInit, kBefore, kAfter, kDestroy, kTotals, kPromiseResolve,
kCheck, kExecutionAsyncId, kAsyncIdCounter, kTriggerAsyncId,
kDefaultTriggerAsyncId, kStackLength } = async_wrap.constants;
kDefaultTriggerAsyncId, kStackLength, kUsesExecutionAsyncResource
} = async_wrap.constants;

const { async_id_symbol,
trigger_async_id_symbol } = internalBinding('symbols');
Expand All @@ -111,7 +114,10 @@ function useDomainTrampoline(fn) {
domain_cb = fn;
}

function callbackTrampoline(asyncId, cb, ...args) {
function callbackTrampoline(asyncId, resource, cb, ...args) {
const index = async_hook_fields[kStackLength] - 1;
execution_async_resources[index] = resource;

if (asyncId !== 0 && hasHooks(kBefore))
emitBeforeNative(asyncId);

Expand All @@ -126,6 +132,7 @@ function callbackTrampoline(asyncId, cb, ...args) {
if (asyncId !== 0 && hasHooks(kAfter))
emitAfterNative(asyncId);

execution_async_resources.pop();
return result;
}

Expand All @@ -134,9 +141,15 @@ setCallbackTrampoline(callbackTrampoline);
const topLevelResource = {};

function executionAsyncResource() {
// Indicate to the native layer that this function is likely to be used,
// in which case it will inform JS about the current async resource via
// the trampoline above.
async_hook_fields[kUsesExecutionAsyncResource] = 1;

const index = async_hook_fields[kStackLength] - 1;
if (index === -1) return topLevelResource;
const resource = execution_async_resources[index];
const resource = execution_async_resources[index] ||
executionAsyncResource_(index);
return lookupPublicResource(resource);
}

Expand Down Expand Up @@ -478,16 +491,6 @@ function emitDestroyScript(asyncId) {
}


// Keep in sync with Environment::AsyncHooks::clear_async_id_stack
// in src/env-inl.h.
function clearAsyncIdStack() {
async_id_fields[kExecutionAsyncId] = 0;
async_id_fields[kTriggerAsyncId] = 0;
async_hook_fields[kStackLength] = 0;
execution_async_resources.splice(0, execution_async_resources.length);
}


function hasAsyncIdStack() {
return hasHooks(kStackLength);
}
Expand Down
12 changes: 7 additions & 5 deletions src/api/callback.cc
Expand Up @@ -161,11 +161,12 @@ MaybeLocal<Value> InternalMakeCallback(Environment* env,
Local<Function> hook_cb = env->async_hooks_callback_trampoline();
int flags = InternalCallbackScope::kNoFlags;
int hook_count = 0;
AsyncHooks* async_hooks = env->async_hooks();
if (!hook_cb.IsEmpty()) {
flags = InternalCallbackScope::kSkipAsyncHooks;
AsyncHooks* async_hooks = env->async_hooks();
hook_count = async_hooks->fields()[AsyncHooks::kBefore] +
async_hooks->fields()[AsyncHooks::kAfter];
async_hooks->fields()[AsyncHooks::kAfter] +
async_hooks->fields()[AsyncHooks::kUsesExecutionAsyncResource];
addaleax marked this conversation as resolved.
Show resolved Hide resolved
}

InternalCallbackScope scope(env, resource, asyncContext, flags);
Expand All @@ -176,11 +177,12 @@ MaybeLocal<Value> InternalMakeCallback(Environment* env,
MaybeLocal<Value> ret;

if (hook_count != 0) {
MaybeStackBuffer<Local<Value>, 16> args(2 + argc);
MaybeStackBuffer<Local<Value>, 16> args(3 + argc);
args[0] = v8::Number::New(env->isolate(), asyncContext.async_id);
args[1] = callback;
args[1] = resource;
args[2] = callback;
for (int i = 0; i < argc; i++) {
args[i + 2] = argv[i];
args[i + 3] = argv[i];
}
ret = hook_cb->Call(env->context(), recv, args.length(), &args[0]);
} else {
Expand Down
24 changes: 22 additions & 2 deletions src/async_wrap.cc
Expand Up @@ -502,7 +502,8 @@ void AsyncWrap::PushAsyncContext(const FunctionCallbackInfo<Value>& args) {
// then the checks in push_async_ids() and pop_async_id() will.
double async_id = args[0]->NumberValue(env->context()).FromJust();
double trigger_async_id = args[1]->NumberValue(env->context()).FromJust();
env->async_hooks()->push_async_context(async_id, trigger_async_id, args[2]);
env->async_hooks()->push_async_context(
async_id, trigger_async_id, args[2].As<Object>());
}


Expand All @@ -513,6 +514,22 @@ void AsyncWrap::PopAsyncContext(const FunctionCallbackInfo<Value>& args) {
}


void AsyncWrap::ExecutionAsyncResource(
const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
uint32_t index;
if (!args[0]->Uint32Value(env->context()).To(&index)) return;
args.GetReturnValue().Set(
env->async_hooks()->native_execution_async_resource(index));
}


void AsyncWrap::ClearAsyncIdStack(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
env->async_hooks()->clear_async_id_stack();
}


void AsyncWrap::AsyncReset(const FunctionCallbackInfo<Value>& args) {
CHECK(args[0]->IsObject());

Expand Down Expand Up @@ -586,6 +603,8 @@ void AsyncWrap::Initialize(Local<Object> target,
env->SetMethod(target, "setCallbackTrampoline", SetCallbackTrampoline);
env->SetMethod(target, "pushAsyncContext", PushAsyncContext);
env->SetMethod(target, "popAsyncContext", PopAsyncContext);
env->SetMethod(target, "executionAsyncResource", ExecutionAsyncResource);
env->SetMethod(target, "clearAsyncIdStack", ClearAsyncIdStack);
env->SetMethod(target, "queueDestroyAsyncId", QueueDestroyAsyncId);
env->SetMethod(target, "enablePromiseHook", EnablePromiseHook);
env->SetMethod(target, "disablePromiseHook", DisablePromiseHook);
Expand Down Expand Up @@ -624,7 +643,7 @@ void AsyncWrap::Initialize(Local<Object> target,

FORCE_SET_TARGET_FIELD(target,
"execution_async_resources",
env->async_hooks()->execution_async_resources());
env->async_hooks()->js_execution_async_resources());

target->Set(context,
env->async_ids_stack_string(),
Expand All @@ -646,6 +665,7 @@ void AsyncWrap::Initialize(Local<Object> target,
SET_HOOKS_CONSTANT(kTriggerAsyncId);
SET_HOOKS_CONSTANT(kAsyncIdCounter);
SET_HOOKS_CONSTANT(kDefaultTriggerAsyncId);
SET_HOOKS_CONSTANT(kUsesExecutionAsyncResource);
SET_HOOKS_CONSTANT(kStackLength);
#undef SET_HOOKS_CONSTANT
FORCE_SET_TARGET_FIELD(target, "constants", constants);
Expand Down
4 changes: 4 additions & 0 deletions src/async_wrap.h
Expand Up @@ -143,6 +143,10 @@ class AsyncWrap : public BaseObject {
static void GetAsyncId(const v8::FunctionCallbackInfo<v8::Value>& args);
static void PushAsyncContext(const v8::FunctionCallbackInfo<v8::Value>& args);
static void PopAsyncContext(const v8::FunctionCallbackInfo<v8::Value>& args);
static void ExecutionAsyncResource(
const v8::FunctionCallbackInfo<v8::Value>& args);
static void ClearAsyncIdStack(
const v8::FunctionCallbackInfo<v8::Value>& args);
static void AsyncReset(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetProviderType(const v8::FunctionCallbackInfo<v8::Value>& args);
static void QueueDestroyAsyncId(
Expand Down
54 changes: 41 additions & 13 deletions src/env-inl.h
Expand Up @@ -105,8 +105,17 @@ inline AliasedFloat64Array& AsyncHooks::async_ids_stack() {
return async_ids_stack_;
}

inline v8::Local<v8::Array> AsyncHooks::execution_async_resources() {
return PersistentToLocal::Strong(execution_async_resources_);
v8::Local<v8::Array> AsyncHooks::js_execution_async_resources() {
if (UNLIKELY(js_execution_async_resources_.IsEmpty())) {
js_execution_async_resources_.Reset(
env()->isolate(), v8::Array::New(env()->isolate()));
}
return PersistentToLocal::Strong(js_execution_async_resources_);
}

v8::Local<v8::Object> AsyncHooks::native_execution_async_resource(size_t i) {
if (i >= native_execution_async_resources_.size()) return {};
return PersistentToLocal::Strong(native_execution_async_resources_[i]);
}

inline v8::Local<v8::String> AsyncHooks::provider_string(int idx) {
Expand All @@ -124,9 +133,7 @@ inline Environment* AsyncHooks::env() {
// Remember to keep this code aligned with pushAsyncContext() in JS.
inline void AsyncHooks::push_async_context(double async_id,
double trigger_async_id,
v8::Local<v8::Value> resource) {
v8::HandleScope handle_scope(env()->isolate());

v8::Local<v8::Object> resource) {
// Since async_hooks is experimental, do only perform the check
// when async_hooks is enabled.
if (fields_[kCheck] > 0) {
Expand All @@ -143,8 +150,8 @@ inline void AsyncHooks::push_async_context(double async_id,
async_id_fields_[kExecutionAsyncId] = async_id;
async_id_fields_[kTriggerAsyncId] = trigger_async_id;

auto resources = execution_async_resources();
USE(resources->Set(env()->context(), offset, resource));
native_execution_async_resources_.resize(offset + 1);
native_execution_async_resources_[offset].Reset(env()->isolate(), resource);
}

// Remember to keep this code aligned with popAsyncContext() in JS.
Expand Down Expand Up @@ -177,17 +184,38 @@ inline bool AsyncHooks::pop_async_context(double async_id) {
async_id_fields_[kTriggerAsyncId] = async_ids_stack_[2 * offset + 1];
fields_[kStackLength] = offset;

auto resources = execution_async_resources();
USE(resources->Delete(env()->context(), offset));
if (LIKELY(offset < native_execution_async_resources_.size() &&
!native_execution_async_resources_[offset].IsEmpty())) {
native_execution_async_resources_.resize(offset);
if (native_execution_async_resources_.size() <
native_execution_async_resources_.capacity() / 2 &&
native_execution_async_resources_.size() > 16) {
native_execution_async_resources_.shrink_to_fit();
}
}

if (UNLIKELY(js_execution_async_resources()->Length() > offset)) {
v8::HandleScope handle_scope(env()->isolate());
USE(js_execution_async_resources()->Set(
env()->context(),
env()->length_string(),
v8::Integer::NewFromUnsigned(env()->isolate(), offset)));
}

return fields_[kStackLength] > 0;
}

// Keep in sync with clearAsyncIdStack in lib/internal/async_hooks.js.
inline void AsyncHooks::clear_async_id_stack() {
auto isolate = env()->isolate();
void AsyncHooks::clear_async_id_stack() {
v8::Isolate* isolate = env()->isolate();
v8::HandleScope handle_scope(isolate);
execution_async_resources_.Reset(isolate, v8::Array::New(isolate));
if (!js_execution_async_resources_.IsEmpty()) {
USE(PersistentToLocal::Strong(js_execution_async_resources_)->Set(
env()->context(),
env()->length_string(),
v8::Integer::NewFromUnsigned(isolate, 0)));
}
native_execution_async_resources_.clear();
native_execution_async_resources_.shrink_to_fit();

async_id_fields_[kExecutionAsyncId] = 0;
async_id_fields_[kTriggerAsyncId] = 0;
Expand Down
10 changes: 7 additions & 3 deletions src/env.h
Expand Up @@ -275,6 +275,7 @@ constexpr size_t kFsStatsBufferLength =
V(issuercert_string, "issuerCertificate") \
V(kill_signal_string, "killSignal") \
V(kind_string, "kind") \
V(length_string, "length") \
V(library_string, "library") \
V(mac_string, "mac") \
V(max_buffer_string, "maxBuffer") \
Expand Down Expand Up @@ -661,6 +662,7 @@ class AsyncHooks : public MemoryRetainer {
kTotals,
kCheck,
kStackLength,
kUsesExecutionAsyncResource,
kFieldsCount,
};

Expand All @@ -675,15 +677,16 @@ class AsyncHooks : public MemoryRetainer {
inline AliasedUint32Array& fields();
inline AliasedFloat64Array& async_id_fields();
inline AliasedFloat64Array& async_ids_stack();
inline v8::Local<v8::Array> execution_async_resources();
inline v8::Local<v8::Array> js_execution_async_resources();
inline v8::Local<v8::Object> native_execution_async_resource(size_t index);

inline v8::Local<v8::String> provider_string(int idx);

inline void no_force_checks();
inline Environment* env();

inline void push_async_context(double async_id, double trigger_async_id,
v8::Local<v8::Value> execution_async_resource_);
v8::Local<v8::Object> execution_async_resource_);
inline bool pop_async_context(double async_id);
inline void clear_async_id_stack(); // Used in fatal exceptions.

Expand Down Expand Up @@ -728,7 +731,8 @@ class AsyncHooks : public MemoryRetainer {

void grow_async_ids_stack();

v8::Global<v8::Array> execution_async_resources_;
v8::Global<v8::Array> js_execution_async_resources_;
std::vector<v8::Global<v8::Object>> native_execution_async_resources_;
};

class ImmediateInfo : public MemoryRetainer {
Expand Down