diff --git a/lib/internal/async_hooks.js b/lib/internal/async_hooks.js index da7b2745e53bbc..e400f24689fbcc 100644 --- a/lib/internal/async_hooks.js +++ b/lib/internal/async_hooks.js @@ -1,15 +1,18 @@ 'use strict'; const { + ArrayPrototypeUnshift, Error, FunctionPrototypeBind, ObjectPrototypeHasOwnProperty, ObjectDefineProperty, Promise, + ReflectApply, Symbol, } = primordials; const async_wrap = internalBinding('async_wrap'); +const { setCallbackTrampoline } = async_wrap; /* async_hook_fields is a Uint32Array wrapping the uint32_t array of * Environment::AsyncHooks::fields_[]. Each index tracks the number of active * hooks for each type. @@ -103,6 +106,26 @@ const emitDestroyNative = emitHookFactory(destroy_symbol, 'emitDestroyNative'); const emitPromiseResolveNative = emitHookFactory(promise_resolve_symbol, 'emitPromiseResolveNative'); +function callbackTrampoline(asyncId, cb, domain_cb, ...args) { + if (hasHooks(kBefore)) + emitBeforeNative(asyncId); + + let result; + if (typeof domain_cb === 'function') { + ArrayPrototypeUnshift(args, cb); + result = ReflectApply(domain_cb, this, args); + } else { + result = ReflectApply(cb, this, args); + } + + if (hasHooks(kAfter)) + emitAfterNative(asyncId); + + return result; +} + +setCallbackTrampoline(callbackTrampoline); + const topLevelResource = {}; function executionAsyncResource() { diff --git a/src/api/callback.cc b/src/api/callback.cc index a03a2587b4c796..7daddcd977e33f 100644 --- a/src/api/callback.cc +++ b/src/api/callback.cc @@ -158,20 +158,46 @@ MaybeLocal InternalMakeCallback(Environment* env, CHECK(!argv[i].IsEmpty()); #endif - InternalCallbackScope scope(env, resource, asyncContext); + Local hook_cb = env->async_hooks_callback_trampoline(); + int flags = InternalCallbackScope::kNoFlags; + int hook_count = 0; + 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]; + } + + InternalCallbackScope scope(env, resource, asyncContext, flags); if (scope.Failed()) { return MaybeLocal(); } Local domain_cb = env->domain_callback(); MaybeLocal ret; - if (asyncContext.async_id != 0 || domain_cb.IsEmpty()) { - ret = callback->Call(env->context(), recv, argc, argv); - } else { - std::vector> args(1 + argc); + + if (asyncContext.async_id != 0 && hook_count != 0) { + MaybeStackBuffer, 16> args(3 + argc); + args[0] = v8::Number::New(env->isolate(), asyncContext.async_id); + args[1] = callback; + if (domain_cb.IsEmpty()) { + args[2] = Undefined(env->isolate()); + } else { + args[2] = domain_cb; + } + for (int i = 0; i < argc; i++) { + args[i + 3] = argv[i]; + } + ret = hook_cb->Call(env->context(), recv, args.length(), &args[0]); + } else if (asyncContext.async_id == 0 && !domain_cb.IsEmpty()) { + MaybeStackBuffer, 16> args(1 + argc); args[0] = callback; - std::copy(&argv[0], &argv[argc], args.begin() + 1); - ret = domain_cb->Call(env->context(), recv, args.size(), &args[0]); + for (int i = 0; i < argc; i++) { + args[i + 1] = argv[i]; + } + ret = domain_cb->Call(env->context(), recv, args.length(), &args[0]); + } else { + ret = callback->Call(env->context(), recv, argc, argv); } if (ret.IsEmpty()) { diff --git a/src/async_wrap.cc b/src/async_wrap.cc index 80342b78284892..123f85f38a715f 100644 --- a/src/async_wrap.cc +++ b/src/async_wrap.cc @@ -552,6 +552,14 @@ void AsyncWrap::QueueDestroyAsyncId(const FunctionCallbackInfo& args) { args[0].As()->Value()); } +void AsyncWrap::SetCallbackTrampoline(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + + CHECK(args[0]->IsFunction()); + + env->set_async_hooks_callback_trampoline(args[0].As()); +} + Local AsyncWrap::GetConstructorTemplate(Environment* env) { Local tmpl = env->async_wrap_ctor_template(); if (tmpl.IsEmpty()) { @@ -575,6 +583,7 @@ void AsyncWrap::Initialize(Local target, HandleScope scope(isolate); env->SetMethod(target, "setupHooks", SetupHooks); + env->SetMethod(target, "setCallbackTrampoline", SetCallbackTrampoline); env->SetMethod(target, "pushAsyncContext", PushAsyncContext); env->SetMethod(target, "popAsyncContext", PopAsyncContext); env->SetMethod(target, "queueDestroyAsyncId", QueueDestroyAsyncId); diff --git a/src/async_wrap.h b/src/async_wrap.h index fa6634b6d3ab4c..2a8c80c2f0028a 100644 --- a/src/async_wrap.h +++ b/src/async_wrap.h @@ -146,6 +146,8 @@ class AsyncWrap : public BaseObject { static void GetProviderType(const v8::FunctionCallbackInfo& args); static void QueueDestroyAsyncId( const v8::FunctionCallbackInfo& args); + static void SetCallbackTrampoline( + const v8::FunctionCallbackInfo& args); static void EmitAsyncInit(Environment* env, v8::Local object, diff --git a/src/env.h b/src/env.h index 706a2de16a0073..09c8ff9db45ce4 100644 --- a/src/env.h +++ b/src/env.h @@ -479,6 +479,7 @@ constexpr size_t kFsStatsBufferLength = #define ENVIRONMENT_STRONG_PERSISTENT_VALUES(V) \ V(async_hooks_after_function, v8::Function) \ V(async_hooks_before_function, v8::Function) \ + V(async_hooks_callback_trampoline, v8::Function) \ V(async_hooks_binding, v8::Object) \ V(async_hooks_destroy_function, v8::Function) \ V(async_hooks_init_function, v8::Function) \