Coverage Report

Created: 2025-07-04 09:33

/src/node/src/node_task_queue.cc
Line
Count
Source (jump to first uncovered line)
1
#include "async_wrap.h"
2
#include "env-inl.h"
3
#include "node.h"
4
#include "node_errors.h"
5
#include "node_external_reference.h"
6
#include "node_internals.h"
7
#include "node_process-inl.h"
8
#include "util-inl.h"
9
#include "v8.h"
10
11
#include <atomic>
12
13
namespace node {
14
15
using errors::TryCatchScope;
16
using v8::Context;
17
using v8::Function;
18
using v8::FunctionCallbackInfo;
19
using v8::Isolate;
20
using v8::Just;
21
using v8::kPromiseHandlerAddedAfterReject;
22
using v8::kPromiseRejectAfterResolved;
23
using v8::kPromiseRejectWithNoHandler;
24
using v8::kPromiseResolveAfterResolved;
25
using v8::Local;
26
using v8::Maybe;
27
using v8::Number;
28
using v8::Object;
29
using v8::Promise;
30
using v8::PromiseRejectEvent;
31
using v8::PromiseRejectMessage;
32
using v8::Value;
33
34
static Maybe<double> GetAssignedPromiseAsyncId(Environment* env,
35
                                               Local<Promise> promise,
36
0
                                               Local<Value> id_symbol) {
37
0
  Local<Value> maybe_async_id;
38
0
  if (!promise->Get(env->context(), id_symbol).ToLocal(&maybe_async_id)) {
39
0
    return v8::Just(AsyncWrap::kInvalidAsyncId);
40
0
  }
41
0
  return maybe_async_id->IsNumber()
42
0
      ? maybe_async_id->NumberValue(env->context())
43
0
      : v8::Just(AsyncWrap::kInvalidAsyncId);
44
0
}
45
46
0
void PromiseRejectCallback(PromiseRejectMessage message) {
47
0
  static std::atomic<uint64_t> unhandledRejections{0};
48
0
  static std::atomic<uint64_t> rejectionsHandledAfter{0};
49
50
0
  Local<Promise> promise = message.GetPromise();
51
0
  Isolate* isolate = promise->GetIsolate();
52
0
  PromiseRejectEvent event = message.GetEvent();
53
54
0
  Environment* env = Environment::GetCurrent(isolate);
55
56
0
  if (env == nullptr || !env->can_call_into_js()) return;
57
58
0
  Local<Function> callback = env->promise_reject_callback();
59
  // The promise is rejected before JS land calls SetPromiseRejectCallback
60
  // to initializes the promise reject callback during bootstrap.
61
0
  CHECK(!callback.IsEmpty());
62
63
0
  Local<Value> value;
64
0
  Local<Value> type = Number::New(env->isolate(), event);
65
66
0
  if (event == kPromiseRejectWithNoHandler) {
67
0
    value = message.GetValue();
68
0
    unhandledRejections++;
69
0
    TRACE_COUNTER2(TRACING_CATEGORY_NODE2(promises, rejections),
70
0
                  "rejections",
71
0
                  "unhandled", unhandledRejections,
72
0
                  "handledAfter", rejectionsHandledAfter);
73
0
  } else if (event == kPromiseHandlerAddedAfterReject) {
74
0
    value = Undefined(isolate);
75
0
    rejectionsHandledAfter++;
76
0
    TRACE_COUNTER2(TRACING_CATEGORY_NODE2(promises, rejections),
77
0
                  "rejections",
78
0
                  "unhandled", unhandledRejections,
79
0
                  "handledAfter", rejectionsHandledAfter);
80
0
  } else if (event == kPromiseResolveAfterResolved) {
81
0
    value = message.GetValue();
82
0
  } else if (event == kPromiseRejectAfterResolved) {
83
0
    value = message.GetValue();
84
0
  } else {
85
0
    return;
86
0
  }
87
88
0
  if (value.IsEmpty()) {
89
0
    value = Undefined(isolate);
90
0
  }
91
92
0
  Local<Value> args[] = { type, promise, value };
93
94
0
  double async_id = AsyncWrap::kInvalidAsyncId;
95
0
  double trigger_async_id = AsyncWrap::kInvalidAsyncId;
96
0
  TryCatchScope try_catch(env);
97
98
0
  if (!GetAssignedPromiseAsyncId(env, promise, env->async_id_symbol())
99
0
          .To(&async_id)) return;
100
0
  if (!GetAssignedPromiseAsyncId(env, promise, env->trigger_async_id_symbol())
101
0
          .To(&trigger_async_id)) return;
102
103
0
  if (async_id != AsyncWrap::kInvalidAsyncId &&
104
0
      trigger_async_id != AsyncWrap::kInvalidAsyncId) {
105
0
    env->async_hooks()->push_async_context(
106
0
        async_id, trigger_async_id, promise);
107
0
  }
108
109
0
  USE(callback->Call(
110
0
      env->context(), Undefined(isolate), arraysize(args), args));
111
112
0
  if (async_id != AsyncWrap::kInvalidAsyncId &&
113
0
      trigger_async_id != AsyncWrap::kInvalidAsyncId &&
114
0
      env->execution_async_id() == async_id) {
115
    // This condition might not be true if async_hooks was enabled during
116
    // the promise callback execution.
117
0
    env->async_hooks()->pop_async_context(async_id);
118
0
  }
119
120
  // V8 does not expect this callback to have a scheduled exceptions once it
121
  // returns, so we print them out in a best effort to do something about it
122
  // without failing silently and without crashing the process.
123
0
  if (try_catch.HasCaught() && !try_catch.HasTerminated()) {
124
0
    fprintf(stderr, "Exception in PromiseRejectCallback:\n");
125
0
    PrintCaughtException(isolate, env->context(), try_catch);
126
0
  }
127
0
}
128
namespace task_queue {
129
130
1
static void EnqueueMicrotask(const FunctionCallbackInfo<Value>& args) {
131
1
  Environment* env = Environment::GetCurrent(args);
132
1
  Isolate* isolate = env->isolate();
133
134
1
  CHECK(args[0]->IsFunction());
135
136
1
  isolate->GetCurrentContext()->GetMicrotaskQueue()
137
1
      ->EnqueueMicrotask(isolate, args[0].As<Function>());
138
1
}
139
140
8.76k
static void RunMicrotasks(const FunctionCallbackInfo<Value>& args) {
141
8.76k
  Environment* env = Environment::GetCurrent(args);
142
8.76k
  env->context()->GetMicrotaskQueue()->PerformCheckpoint(env->isolate());
143
8.76k
}
144
145
134k
static void SetTickCallback(const FunctionCallbackInfo<Value>& args) {
146
134k
  Environment* env = Environment::GetCurrent(args);
147
134k
  CHECK(args[0]->IsFunction());
148
134k
  env->set_tick_callback_function(args[0].As<Function>());
149
134k
}
150
151
static void SetPromiseRejectCallback(
152
134k
    const FunctionCallbackInfo<Value>& args) {
153
134k
  Environment* env = Environment::GetCurrent(args);
154
155
134k
  CHECK(args[0]->IsFunction());
156
134k
  env->set_promise_reject_callback(args[0].As<Function>());
157
134k
}
158
159
static void Initialize(Local<Object> target,
160
                       Local<Value> unused,
161
                       Local<Context> context,
162
134k
                       void* priv) {
163
134k
  Environment* env = Environment::GetCurrent(context);
164
134k
  Isolate* isolate = env->isolate();
165
166
134k
  SetMethod(context, target, "enqueueMicrotask", EnqueueMicrotask);
167
134k
  SetMethod(context, target, "setTickCallback", SetTickCallback);
168
134k
  SetMethod(context, target, "runMicrotasks", RunMicrotasks);
169
134k
  target->Set(env->context(),
170
134k
              FIXED_ONE_BYTE_STRING(isolate, "tickInfo"),
171
134k
              env->tick_info()->fields().GetJSArray()).Check();
172
173
134k
  Local<Object> events = Object::New(isolate);
174
134k
  NODE_DEFINE_CONSTANT(events, kPromiseRejectWithNoHandler);
175
134k
  NODE_DEFINE_CONSTANT(events, kPromiseHandlerAddedAfterReject);
176
134k
  NODE_DEFINE_CONSTANT(events, kPromiseResolveAfterResolved);
177
134k
  NODE_DEFINE_CONSTANT(events, kPromiseRejectAfterResolved);
178
179
134k
  target->Set(env->context(),
180
134k
              FIXED_ONE_BYTE_STRING(isolate, "promiseRejectEvents"),
181
134k
              events).Check();
182
134k
  SetMethod(
183
134k
      context, target, "setPromiseRejectCallback", SetPromiseRejectCallback);
184
134k
}
185
186
0
void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
187
0
  registry->Register(EnqueueMicrotask);
188
0
  registry->Register(SetTickCallback);
189
0
  registry->Register(RunMicrotasks);
190
0
  registry->Register(SetPromiseRejectCallback);
191
0
}
192
193
}  // namespace task_queue
194
}  // namespace node
195
196
NODE_BINDING_CONTEXT_AWARE_INTERNAL(task_queue, node::task_queue::Initialize)
197
NODE_BINDING_EXTERNAL_REFERENCE(task_queue,
198
                                node::task_queue::RegisterExternalReferences)