| #include "node.h" |
| #include "async_wrap-inl.h" |
| #include "env-inl.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::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) {} |
| |
| InternalCallbackScope::InternalCallbackScope(Environment* env, |
| Local<Object> object, |
| const async_context& asyncContext, |
| int flags) |
| : 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); |
| |
| 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; |
| |
| Isolate* isolate = env_->isolate(); |
| auto idle = OnScopeLeave([&]() { isolate->SetIdle(true); }); |
| |
| if (!env_->can_call_into_js()) return; |
| auto perform_stopping_check = [&]() { |
| if (env_->is_stopping()) { |
| MarkAsFailed(); |
| env_->async_hooks()->clear_async_id_stack(); |
| } |
| }; |
| perform_stopping_check(); |
| |
| 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); |
| |
| 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) { |
| 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); |
| 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->GetCreationContext().ToLocalChecked()); |
| 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) { |
| // 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->GetCreationContext().ToLocalChecked()); |
| CHECK_NOT_NULL(env); |
| Context::Scope context_scope(env->context()); |
| MaybeLocal<Value> ret = |
| InternalMakeCallback(env, recv, recv, callback, argc, argv, asyncContext); |
| 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->GetCreationContext().ToLocalChecked()); |
| 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}); |
| 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 |