xref: /third_party/node/src/api/callback.cc (revision 1cb0ef41)
1#include "node.h"
2#include "async_wrap-inl.h"
3#include "env-inl.h"
4#include "v8.h"
5
6namespace node {
7
8using v8::Context;
9using v8::EscapableHandleScope;
10using v8::Function;
11using v8::HandleScope;
12using v8::Isolate;
13using v8::Local;
14using v8::MaybeLocal;
15using v8::Object;
16using v8::String;
17using v8::Value;
18
19CallbackScope::CallbackScope(Isolate* isolate,
20                             Local<Object> object,
21                             async_context async_context)
22  : CallbackScope(Environment::GetCurrent(isolate), object, async_context) {}
23
24CallbackScope::CallbackScope(Environment* env,
25                             Local<Object> object,
26                             async_context asyncContext)
27  : private_(new InternalCallbackScope(env,
28                                       object,
29                                       asyncContext)),
30    try_catch_(env->isolate()) {
31  try_catch_.SetVerbose(true);
32}
33
34CallbackScope::~CallbackScope() {
35  if (try_catch_.HasCaught())
36    private_->MarkAsFailed();
37  delete private_;
38}
39
40InternalCallbackScope::InternalCallbackScope(AsyncWrap* async_wrap, int flags)
41    : InternalCallbackScope(async_wrap->env(),
42                            async_wrap->object(),
43                            { async_wrap->get_async_id(),
44                              async_wrap->get_trigger_async_id() },
45                            flags) {}
46
47InternalCallbackScope::InternalCallbackScope(Environment* env,
48                                             Local<Object> object,
49                                             const async_context& asyncContext,
50                                             int flags)
51  : env_(env),
52    async_context_(asyncContext),
53    object_(object),
54    skip_hooks_(flags & kSkipAsyncHooks),
55    skip_task_queues_(flags & kSkipTaskQueues) {
56  CHECK_NOT_NULL(env);
57  env->PushAsyncCallbackScope();
58
59  if (!env->can_call_into_js()) {
60    failed_ = true;
61    return;
62  }
63
64  Isolate* isolate = env->isolate();
65
66  HandleScope handle_scope(isolate);
67  Local<Context> current_context = isolate->GetCurrentContext();
68  // If you hit this assertion, the caller forgot to enter the right Node.js
69  // Environment's v8::Context first.
70  // We first check `env->context() != current_context` because the contexts
71  // likely *are* the same, in which case we can skip the slightly more
72  // expensive Environment::GetCurrent() call.
73  if (UNLIKELY(env->context() != current_context)) {
74    CHECK_EQ(Environment::GetCurrent(isolate), env);
75  }
76
77  isolate->SetIdle(false);
78
79  env->async_hooks()->push_async_context(
80    async_context_.async_id, async_context_.trigger_async_id, object);
81
82  pushed_ids_ = true;
83
84  if (asyncContext.async_id != 0 && !skip_hooks_) {
85    // No need to check a return value because the application will exit if
86    // an exception occurs.
87    AsyncWrap::EmitBefore(env, asyncContext.async_id);
88  }
89}
90
91InternalCallbackScope::~InternalCallbackScope() {
92  Close();
93  env_->PopAsyncCallbackScope();
94}
95
96void InternalCallbackScope::Close() {
97  if (closed_) return;
98  closed_ = true;
99
100  // This function must ends up with either cleanup the
101  // async id stack or pop the topmost one from it
102
103  auto perform_stopping_check = [&]() {
104    if (env_->is_stopping()) {
105      MarkAsFailed();
106      env_->async_hooks()->clear_async_id_stack();
107    }
108  };
109  perform_stopping_check();
110
111  if (env_->is_stopping()) return;
112
113  Isolate* isolate = env_->isolate();
114  auto idle = OnScopeLeave([&]() { isolate->SetIdle(true); });
115
116  if (!failed_ && async_context_.async_id != 0 && !skip_hooks_) {
117    AsyncWrap::EmitAfter(env_, async_context_.async_id);
118  }
119
120  if (pushed_ids_)
121    env_->async_hooks()->pop_async_context(async_context_.async_id);
122
123  if (failed_) return;
124
125  if (env_->async_callback_scope_depth() > 1 || skip_task_queues_) {
126    return;
127  }
128
129  TickInfo* tick_info = env_->tick_info();
130
131  if (!env_->can_call_into_js()) return;
132
133  auto weakref_cleanup = OnScopeLeave([&]() { env_->RunWeakRefCleanup(); });
134
135  Local<Context> context = env_->context();
136  if (!tick_info->has_tick_scheduled()) {
137    context->GetMicrotaskQueue()->PerformCheckpoint(isolate);
138
139    perform_stopping_check();
140  }
141
142  // Make sure the stack unwound properly. If there are nested MakeCallback's
143  // then it should return early and not reach this code.
144  if (env_->async_hooks()->fields()[AsyncHooks::kTotals]) {
145    CHECK_EQ(env_->execution_async_id(), 0);
146    CHECK_EQ(env_->trigger_async_id(), 0);
147  }
148
149  if (!tick_info->has_tick_scheduled() && !tick_info->has_rejection_to_warn()) {
150    return;
151  }
152
153  HandleScope handle_scope(isolate);
154  Local<Object> process = env_->process_object();
155
156  if (!env_->can_call_into_js()) return;
157
158  Local<Function> tick_callback = env_->tick_callback_function();
159
160  // The tick is triggered before JS land calls SetTickCallback
161  // to initializes the tick callback during bootstrap.
162  CHECK(!tick_callback.IsEmpty());
163
164  if (tick_callback->Call(context, process, 0, nullptr).IsEmpty()) {
165    failed_ = true;
166  }
167  perform_stopping_check();
168}
169
170MaybeLocal<Value> InternalMakeCallback(Environment* env,
171                                       Local<Object> resource,
172                                       Local<Object> recv,
173                                       const Local<Function> callback,
174                                       int argc,
175                                       Local<Value> argv[],
176                                       async_context asyncContext) {
177  CHECK(!recv.IsEmpty());
178#ifdef DEBUG
179  for (int i = 0; i < argc; i++)
180    CHECK(!argv[i].IsEmpty());
181#endif
182
183  Local<Function> hook_cb = env->async_hooks_callback_trampoline();
184  int flags = InternalCallbackScope::kNoFlags;
185  bool use_async_hooks_trampoline = false;
186  AsyncHooks* async_hooks = env->async_hooks();
187  if (!hook_cb.IsEmpty()) {
188    // Use the callback trampoline if there are any before or after hooks, or
189    // we can expect some kind of usage of async_hooks.executionAsyncResource().
190    flags = InternalCallbackScope::kSkipAsyncHooks;
191    use_async_hooks_trampoline =
192        async_hooks->fields()[AsyncHooks::kBefore] +
193        async_hooks->fields()[AsyncHooks::kAfter] +
194        async_hooks->fields()[AsyncHooks::kUsesExecutionAsyncResource] > 0;
195  }
196
197  InternalCallbackScope scope(env, resource, asyncContext, flags);
198  if (scope.Failed()) {
199    return MaybeLocal<Value>();
200  }
201
202  MaybeLocal<Value> ret;
203
204  Local<Context> context = env->context();
205  if (use_async_hooks_trampoline) {
206    MaybeStackBuffer<Local<Value>, 16> args(3 + argc);
207    args[0] = v8::Number::New(env->isolate(), asyncContext.async_id);
208    args[1] = resource;
209    args[2] = callback;
210    for (int i = 0; i < argc; i++) {
211      args[i + 3] = argv[i];
212    }
213    ret = hook_cb->Call(context, recv, args.length(), &args[0]);
214  } else {
215    ret = callback->Call(context, recv, argc, argv);
216  }
217
218  if (ret.IsEmpty()) {
219    scope.MarkAsFailed();
220    return MaybeLocal<Value>();
221  }
222
223  scope.Close();
224  if (scope.Failed()) {
225    return MaybeLocal<Value>();
226  }
227
228  return ret;
229}
230
231// Public MakeCallback()s
232
233MaybeLocal<Value> MakeCallback(Isolate* isolate,
234                               Local<Object> recv,
235                               const char* method,
236                               int argc,
237                               Local<Value> argv[],
238                               async_context asyncContext) {
239  Local<String> method_string =
240      String::NewFromUtf8(isolate, method).ToLocalChecked();
241  return MakeCallback(isolate, recv, method_string, argc, argv, asyncContext);
242}
243
244MaybeLocal<Value> MakeCallback(Isolate* isolate,
245                               Local<Object> recv,
246                               Local<String> symbol,
247                               int argc,
248                               Local<Value> argv[],
249                               async_context asyncContext) {
250  // Check can_call_into_js() first because calling Get() might do so.
251  Environment* env =
252      Environment::GetCurrent(recv->GetCreationContext().ToLocalChecked());
253  CHECK_NOT_NULL(env);
254  if (!env->can_call_into_js()) return Local<Value>();
255
256  Local<Value> callback_v;
257  if (!recv->Get(isolate->GetCurrentContext(), symbol).ToLocal(&callback_v))
258    return Local<Value>();
259  if (!callback_v->IsFunction()) {
260    // This used to return an empty value, but Undefined() makes more sense
261    // since no exception is pending here.
262    return Undefined(isolate);
263  }
264  Local<Function> callback = callback_v.As<Function>();
265  return MakeCallback(isolate, recv, callback, argc, argv, asyncContext);
266}
267
268MaybeLocal<Value> MakeCallback(Isolate* isolate,
269                               Local<Object> recv,
270                               Local<Function> callback,
271                               int argc,
272                               Local<Value> argv[],
273                               async_context asyncContext) {
274  // Observe the following two subtleties:
275  //
276  // 1. The environment is retrieved from the callback function's context.
277  // 2. The context to enter is retrieved from the environment.
278  //
279  // Because of the AssignToContext() call in src/node_contextify.cc,
280  // the two contexts need not be the same.
281  Environment* env =
282      Environment::GetCurrent(callback->GetCreationContext().ToLocalChecked());
283  CHECK_NOT_NULL(env);
284  Context::Scope context_scope(env->context());
285  MaybeLocal<Value> ret =
286      InternalMakeCallback(env, recv, recv, callback, argc, argv, asyncContext);
287  if (ret.IsEmpty() && env->async_callback_scope_depth() == 0) {
288    // This is only for legacy compatibility and we may want to look into
289    // removing/adjusting it.
290    return Undefined(isolate);
291  }
292  return ret;
293}
294
295// Use this if you just want to safely invoke some JS callback and
296// would like to retain the currently active async_context, if any.
297// In case none is available, a fixed default context will be
298// installed otherwise.
299MaybeLocal<Value> MakeSyncCallback(Isolate* isolate,
300                                   Local<Object> recv,
301                                   Local<Function> callback,
302                                   int argc,
303                                   Local<Value> argv[]) {
304  Environment* env =
305      Environment::GetCurrent(callback->GetCreationContext().ToLocalChecked());
306  CHECK_NOT_NULL(env);
307  if (!env->can_call_into_js()) return Local<Value>();
308
309  Local<Context> context = env->context();
310  Context::Scope context_scope(context);
311  if (env->async_callback_scope_depth()) {
312    // There's another MakeCallback() on the stack, piggy back on it.
313    // In particular, retain the current async_context.
314    return callback->Call(context, recv, argc, argv);
315  }
316
317  // This is a toplevel invocation and the caller (intentionally)
318  // didn't provide any async_context to run in. Install a default context.
319  MaybeLocal<Value> ret =
320    InternalMakeCallback(env, env->process_object(), recv, callback, argc, argv,
321                         async_context{0, 0});
322  return ret;
323}
324
325// Legacy MakeCallback()s
326
327Local<Value> MakeCallback(Isolate* isolate,
328                          Local<Object> recv,
329                          const char* method,
330                          int argc,
331                          Local<Value>* argv) {
332  EscapableHandleScope handle_scope(isolate);
333  return handle_scope.Escape(
334      MakeCallback(isolate, recv, method, argc, argv, {0, 0})
335          .FromMaybe(Local<Value>()));
336}
337
338Local<Value> MakeCallback(Isolate* isolate,
339                          Local<Object> recv,
340                          Local<String> symbol,
341                          int argc,
342                          Local<Value>* argv) {
343  EscapableHandleScope handle_scope(isolate);
344  return handle_scope.Escape(
345      MakeCallback(isolate, recv, symbol, argc, argv, {0, 0})
346          .FromMaybe(Local<Value>()));
347}
348
349Local<Value> MakeCallback(Isolate* isolate,
350                          Local<Object> recv,
351                          Local<Function> callback,
352                          int argc,
353                          Local<Value>* argv) {
354  EscapableHandleScope handle_scope(isolate);
355  return handle_scope.Escape(
356      MakeCallback(isolate, recv, callback, argc, argv, {0, 0})
357          .FromMaybe(Local<Value>()));
358}
359
360}  // namespace node
361