node/src/api/callback.cc
Stephen Belanger d1229eeca4
lib: rewrite AsyncLocalStorage without async_hooks
PR-URL: https://github.com/nodejs/node/pull/48528
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
Reviewed-By: Rafael Gonzaga <rafael.nunu@hotmail.com>
Reviewed-By: Chengzhong Wu <legendecas@gmail.com>
Reviewed-By: Santiago Gimeno <santiago.gimeno@gmail.com>
Reviewed-By: Gerhard Stöbich <deb2001-github@yahoo.de>
2024-08-02 19:44:20 +00:00

387 lines
13 KiB
C++

#include "async_context_frame.h"
#include "async_wrap-inl.h"
#include "env-inl.h"
#include "node.h"
#include "v8.h"
namespace node {
using v8::Context;
using v8::EscapableHandleScope;
using v8::Function;
using v8::HandleScope;
using v8::Isolate;
using v8::Local;
using v8::MaybeLocal;
using v8::Object;
using v8::String;
using v8::Undefined;
using v8::Value;
CallbackScope::CallbackScope(Isolate* isolate,
Local<Object> object,
async_context async_context)
: CallbackScope(Environment::GetCurrent(isolate), object, async_context) {}
CallbackScope::CallbackScope(Environment* env,
Local<Object> object,
async_context asyncContext)
: private_(new InternalCallbackScope(env,
object,
asyncContext)),
try_catch_(env->isolate()) {
try_catch_.SetVerbose(true);
}
CallbackScope::~CallbackScope() {
if (try_catch_.HasCaught())
private_->MarkAsFailed();
delete private_;
}
InternalCallbackScope::InternalCallbackScope(AsyncWrap* async_wrap, int flags)
: InternalCallbackScope(
async_wrap->env(),
async_wrap->object(),
{async_wrap->get_async_id(), async_wrap->get_trigger_async_id()},
flags,
async_wrap->context_frame()) {}
InternalCallbackScope::InternalCallbackScope(Environment* env,
Local<Object> object,
const async_context& asyncContext,
int flags,
v8::Local<v8::Value> context_frame)
: env_(env),
async_context_(asyncContext),
object_(object),
skip_hooks_(flags & kSkipAsyncHooks),
skip_task_queues_(flags & kSkipTaskQueues) {
CHECK_NOT_NULL(env);
env->PushAsyncCallbackScope();
if (!env->can_call_into_js()) {
failed_ = true;
return;
}
Isolate* isolate = env->isolate();
HandleScope handle_scope(isolate);
Local<Context> current_context = isolate->GetCurrentContext();
// If you hit this assertion, the caller forgot to enter the right Node.js
// Environment's v8::Context first.
// We first check `env->context() != current_context` because the contexts
// likely *are* the same, in which case we can skip the slightly more
// expensive Environment::GetCurrent() call.
if (UNLIKELY(env->context() != current_context)) {
CHECK_EQ(Environment::GetCurrent(isolate), env);
}
isolate->SetIdle(false);
prior_context_frame_.Reset(
isolate, async_context_frame::exchange(isolate, context_frame));
env->async_hooks()->push_async_context(
async_context_.async_id, async_context_.trigger_async_id, object);
pushed_ids_ = true;
if (asyncContext.async_id != 0 && !skip_hooks_) {
// No need to check a return value because the application will exit if
// an exception occurs.
AsyncWrap::EmitBefore(env, asyncContext.async_id);
}
}
InternalCallbackScope::~InternalCallbackScope() {
Close();
env_->PopAsyncCallbackScope();
}
void InternalCallbackScope::Close() {
if (closed_) return;
closed_ = true;
// This function must ends up with either cleanup the
// async id stack or pop the topmost one from it
auto perform_stopping_check = [&]() {
if (env_->is_stopping()) {
MarkAsFailed();
env_->async_hooks()->clear_async_id_stack();
}
};
perform_stopping_check();
if (env_->is_stopping()) return;
Isolate* isolate = env_->isolate();
auto idle = OnScopeLeave([&]() { isolate->SetIdle(true); });
if (!failed_ && async_context_.async_id != 0 && !skip_hooks_) {
AsyncWrap::EmitAfter(env_, async_context_.async_id);
}
if (pushed_ids_) {
env_->async_hooks()->pop_async_context(async_context_.async_id);
async_context_frame::exchange(isolate, prior_context_frame_.Get(isolate));
}
if (failed_) return;
if (env_->async_callback_scope_depth() > 1 || skip_task_queues_) {
return;
}
TickInfo* tick_info = env_->tick_info();
if (!env_->can_call_into_js()) return;
auto weakref_cleanup = OnScopeLeave([&]() { env_->RunWeakRefCleanup(); });
Local<Context> context = env_->context();
if (!tick_info->has_tick_scheduled()) {
context->GetMicrotaskQueue()->PerformCheckpoint(isolate);
perform_stopping_check();
}
// Make sure the stack unwound properly. If there are nested MakeCallback's
// then it should return early and not reach this code.
if (env_->async_hooks()->fields()[AsyncHooks::kTotals]) {
CHECK_EQ(env_->execution_async_id(), 0);
CHECK_EQ(env_->trigger_async_id(), 0);
}
if (!tick_info->has_tick_scheduled() && !tick_info->has_rejection_to_warn()) {
return;
}
HandleScope handle_scope(isolate);
Local<Object> process = env_->process_object();
if (!env_->can_call_into_js()) return;
Local<Function> tick_callback = env_->tick_callback_function();
// The tick is triggered before JS land calls SetTickCallback
// to initializes the tick callback during bootstrap.
CHECK(!tick_callback.IsEmpty());
if (tick_callback->Call(context, process, 0, nullptr).IsEmpty()) {
failed_ = true;
}
perform_stopping_check();
}
MaybeLocal<Value> InternalMakeCallback(Environment* env,
Local<Object> resource,
Local<Object> recv,
const Local<Function> callback,
int argc,
Local<Value> argv[],
async_context asyncContext,
Local<Value> context_frame) {
CHECK(!recv.IsEmpty());
#ifdef DEBUG
for (int i = 0; i < argc; i++)
CHECK(!argv[i].IsEmpty());
#endif
Local<Function> hook_cb = env->async_hooks_callback_trampoline();
int flags = InternalCallbackScope::kNoFlags;
bool use_async_hooks_trampoline = false;
AsyncHooks* async_hooks = env->async_hooks();
if (!hook_cb.IsEmpty()) {
// Use the callback trampoline if there are any before or after hooks, or
// we can expect some kind of usage of async_hooks.executionAsyncResource().
flags = InternalCallbackScope::kSkipAsyncHooks;
use_async_hooks_trampoline =
async_hooks->fields()[AsyncHooks::kBefore] +
async_hooks->fields()[AsyncHooks::kAfter] +
async_hooks->fields()[AsyncHooks::kUsesExecutionAsyncResource] > 0;
}
InternalCallbackScope scope(
env, resource, asyncContext, flags, context_frame);
if (scope.Failed()) {
return MaybeLocal<Value>();
}
MaybeLocal<Value> ret;
Local<Context> context = env->context();
if (use_async_hooks_trampoline) {
MaybeStackBuffer<Local<Value>, 16> args(3 + argc);
args[0] = v8::Number::New(env->isolate(), asyncContext.async_id);
args[1] = resource;
args[2] = callback;
for (int i = 0; i < argc; i++) {
args[i + 3] = argv[i];
}
ret = hook_cb->Call(context, recv, args.length(), &args[0]);
} else {
ret = callback->Call(context, recv, argc, argv);
}
if (ret.IsEmpty()) {
scope.MarkAsFailed();
return MaybeLocal<Value>();
}
scope.Close();
if (scope.Failed()) {
return MaybeLocal<Value>();
}
return ret;
}
// Public MakeCallback()s
MaybeLocal<Value> MakeCallback(Isolate* isolate,
Local<Object> recv,
const char* method,
int argc,
Local<Value> argv[],
async_context asyncContext) {
Local<String> method_string =
String::NewFromUtf8(isolate, method).ToLocalChecked();
return MakeCallback(isolate, recv, method_string, argc, argv, asyncContext);
}
MaybeLocal<Value> MakeCallback(Isolate* isolate,
Local<Object> recv,
Local<String> symbol,
int argc,
Local<Value> argv[],
async_context asyncContext) {
// Check can_call_into_js() first because calling Get() might do so.
Environment* env = Environment::GetCurrent(recv->GetCreationContextChecked());
CHECK_NOT_NULL(env);
if (!env->can_call_into_js()) return Local<Value>();
Local<Value> callback_v;
if (!recv->Get(isolate->GetCurrentContext(), symbol).ToLocal(&callback_v))
return Local<Value>();
if (!callback_v->IsFunction()) {
// This used to return an empty value, but Undefined() makes more sense
// since no exception is pending here.
return Undefined(isolate);
}
Local<Function> callback = callback_v.As<Function>();
return MakeCallback(isolate, recv, callback, argc, argv, asyncContext);
}
MaybeLocal<Value> MakeCallback(Isolate* isolate,
Local<Object> recv,
Local<Function> callback,
int argc,
Local<Value> argv[],
async_context asyncContext) {
return InternalMakeCallback(
isolate, recv, callback, argc, argv, asyncContext, Undefined(isolate));
}
MaybeLocal<Value> InternalMakeCallback(Isolate* isolate,
Local<Object> recv,
Local<Function> callback,
int argc,
Local<Value> argv[],
async_context asyncContext,
Local<Value> context_frame) {
// Observe the following two subtleties:
//
// 1. The environment is retrieved from the callback function's context.
// 2. The context to enter is retrieved from the environment.
//
// Because of the AssignToContext() call in src/node_contextify.cc,
// the two contexts need not be the same.
Environment* env =
Environment::GetCurrent(callback->GetCreationContextChecked());
CHECK_NOT_NULL(env);
Context::Scope context_scope(env->context());
MaybeLocal<Value> ret = InternalMakeCallback(
env, recv, recv, callback, argc, argv, asyncContext, context_frame);
if (ret.IsEmpty() && env->async_callback_scope_depth() == 0) {
// This is only for legacy compatibility and we may want to look into
// removing/adjusting it.
return Undefined(isolate);
}
return ret;
}
// Use this if you just want to safely invoke some JS callback and
// would like to retain the currently active async_context, if any.
// In case none is available, a fixed default context will be
// installed otherwise.
MaybeLocal<Value> MakeSyncCallback(Isolate* isolate,
Local<Object> recv,
Local<Function> callback,
int argc,
Local<Value> argv[]) {
Environment* env =
Environment::GetCurrent(callback->GetCreationContextChecked());
CHECK_NOT_NULL(env);
if (!env->can_call_into_js()) return Local<Value>();
Local<Context> context = env->context();
Context::Scope context_scope(context);
if (env->async_callback_scope_depth()) {
// There's another MakeCallback() on the stack, piggy back on it.
// In particular, retain the current async_context.
return callback->Call(context, recv, argc, argv);
}
// This is a toplevel invocation and the caller (intentionally)
// didn't provide any async_context to run in. Install a default context.
MaybeLocal<Value> ret = InternalMakeCallback(env,
env->process_object(),
recv,
callback,
argc,
argv,
async_context{0, 0},
v8::Undefined(isolate));
return ret;
}
// Legacy MakeCallback()s
Local<Value> MakeCallback(Isolate* isolate,
Local<Object> recv,
const char* method,
int argc,
Local<Value>* argv) {
EscapableHandleScope handle_scope(isolate);
return handle_scope.Escape(
MakeCallback(isolate, recv, method, argc, argv, {0, 0})
.FromMaybe(Local<Value>()));
}
Local<Value> MakeCallback(Isolate* isolate,
Local<Object> recv,
Local<String> symbol,
int argc,
Local<Value>* argv) {
EscapableHandleScope handle_scope(isolate);
return handle_scope.Escape(
MakeCallback(isolate, recv, symbol, argc, argv, {0, 0})
.FromMaybe(Local<Value>()));
}
Local<Value> MakeCallback(Isolate* isolate,
Local<Object> recv,
Local<Function> callback,
int argc,
Local<Value>* argv) {
EscapableHandleScope handle_scope(isolate);
return handle_scope.Escape(
MakeCallback(isolate, recv, callback, argc, argv, {0, 0})
.FromMaybe(Local<Value>()));
}
} // namespace node