/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) |