mirror of
https://github.com/nodejs/node.git
synced 2025-08-15 13:48:44 +02:00

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>
387 lines
13 KiB
C++
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
|