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 |
|
110862 |
static Maybe<double> GetAssignedPromiseAsyncId(Environment* env, |
35 |
|
|
Local<Promise> promise, |
36 |
|
|
Local<Value> id_symbol) { |
37 |
|
|
Local<Value> maybe_async_id; |
38 |
✗✓ |
221724 |
if (!promise->Get(env->context(), id_symbol).ToLocal(&maybe_async_id)) { |
39 |
|
|
return v8::Just(AsyncWrap::kInvalidAsyncId); |
40 |
|
|
} |
41 |
|
110862 |
return maybe_async_id->IsNumber() |
42 |
|
124 |
? maybe_async_id->NumberValue(env->context()) |
43 |
✓✓ |
110986 |
: v8::Just(AsyncWrap::kInvalidAsyncId); |
44 |
|
|
} |
45 |
|
|
|
46 |
|
110738 |
static Maybe<double> GetAssignedPromiseWrapAsyncId(Environment* env, |
47 |
|
|
Local<Promise> promise, |
48 |
|
|
Local<Value> id_symbol) { |
49 |
|
|
// This check is imperfect. If the internal field is set, it should |
50 |
|
|
// be an object. If it's not, we just ignore it. Ideally v8 would |
51 |
|
|
// have had GetInternalField returning a MaybeLocal but this works |
52 |
|
|
// for now. |
53 |
|
221476 |
Local<Value> promiseWrap = promise->GetInternalField(0); |
54 |
✗✓ |
110738 |
if (promiseWrap->IsObject()) { |
55 |
|
|
Local<Value> maybe_async_id; |
56 |
|
|
if (!promiseWrap.As<Object>()->Get(env->context(), id_symbol) |
57 |
|
|
.ToLocal(&maybe_async_id)) { |
58 |
|
|
return v8::Just(AsyncWrap::kInvalidAsyncId); |
59 |
|
|
} |
60 |
|
|
return maybe_async_id->IsNumber() |
61 |
|
|
? maybe_async_id->NumberValue(env->context()) |
62 |
|
|
: v8::Just(AsyncWrap::kInvalidAsyncId); |
63 |
|
|
} else { |
64 |
|
110738 |
return v8::Just(AsyncWrap::kInvalidAsyncId); |
65 |
|
|
} |
66 |
|
|
} |
67 |
|
|
|
68 |
|
55432 |
void PromiseRejectCallback(PromiseRejectMessage message) { |
69 |
|
|
static std::atomic<uint64_t> unhandledRejections{0}; |
70 |
|
|
static std::atomic<uint64_t> rejectionsHandledAfter{0}; |
71 |
|
|
|
72 |
|
55432 |
Local<Promise> promise = message.GetPromise(); |
73 |
|
55432 |
Isolate* isolate = promise->GetIsolate(); |
74 |
|
55432 |
PromiseRejectEvent event = message.GetEvent(); |
75 |
|
|
|
76 |
|
55432 |
Environment* env = Environment::GetCurrent(isolate); |
77 |
|
|
|
78 |
✓✗✓✓ ✓✓ |
55432 |
if (env == nullptr || !env->can_call_into_js()) return; |
79 |
|
|
|
80 |
|
55431 |
Local<Function> callback = env->promise_reject_callback(); |
81 |
|
|
// The promise is rejected before JS land calls SetPromiseRejectCallback |
82 |
|
|
// to initializes the promise reject callback during bootstrap. |
83 |
✗✓ |
55431 |
CHECK(!callback.IsEmpty()); |
84 |
|
|
|
85 |
|
|
Local<Value> value; |
86 |
|
55431 |
Local<Value> type = Number::New(env->isolate(), event); |
87 |
|
|
|
88 |
✓✓ |
55431 |
if (event == kPromiseRejectWithNoHandler) { |
89 |
|
24674 |
value = message.GetValue(); |
90 |
|
24674 |
unhandledRejections++; |
91 |
✓✓✓✓
|
24906 |
TRACE_COUNTER2(TRACING_CATEGORY_NODE2(promises, rejections), |
92 |
|
|
"rejections", |
93 |
|
|
"unhandled", unhandledRejections, |
94 |
|
|
"handledAfter", rejectionsHandledAfter); |
95 |
✓✓ |
30757 |
} else if (event == kPromiseHandlerAddedAfterReject) { |
96 |
|
23164 |
value = Undefined(isolate); |
97 |
|
23164 |
rejectionsHandledAfter++; |
98 |
✓✓✗✓
|
23371 |
TRACE_COUNTER2(TRACING_CATEGORY_NODE2(promises, rejections), |
99 |
|
|
"rejections", |
100 |
|
|
"unhandled", unhandledRejections, |
101 |
|
|
"handledAfter", rejectionsHandledAfter); |
102 |
✓✓ |
7593 |
} else if (event == kPromiseResolveAfterResolved) { |
103 |
|
86 |
value = message.GetValue(); |
104 |
✓✗ |
7507 |
} else if (event == kPromiseRejectAfterResolved) { |
105 |
|
7507 |
value = message.GetValue(); |
106 |
|
|
} else { |
107 |
|
|
return; |
108 |
|
|
} |
109 |
|
|
|
110 |
✗✓ |
55431 |
if (value.IsEmpty()) { |
111 |
|
|
value = Undefined(isolate); |
112 |
|
|
} |
113 |
|
|
|
114 |
|
55431 |
Local<Value> args[] = { type, promise, value }; |
115 |
|
|
|
116 |
|
55431 |
double async_id = AsyncWrap::kInvalidAsyncId; |
117 |
|
55431 |
double trigger_async_id = AsyncWrap::kInvalidAsyncId; |
118 |
|
55431 |
TryCatchScope try_catch(env); |
119 |
|
|
|
120 |
|
110862 |
if (!GetAssignedPromiseAsyncId(env, promise, env->async_id_symbol()) |
121 |
✗✓ |
55431 |
.To(&async_id)) return; |
122 |
|
110862 |
if (!GetAssignedPromiseAsyncId(env, promise, env->trigger_async_id_symbol()) |
123 |
✗✓ |
55431 |
.To(&trigger_async_id)) return; |
124 |
|
|
|
125 |
✓✓ |
55431 |
if (async_id == AsyncWrap::kInvalidAsyncId && |
126 |
✓✗ |
55369 |
trigger_async_id == AsyncWrap::kInvalidAsyncId) { |
127 |
|
|
// That means that promise might be a PromiseWrap, so we'll |
128 |
|
|
// check there as well. |
129 |
|
110738 |
if (!GetAssignedPromiseWrapAsyncId(env, promise, env->async_id_symbol()) |
130 |
✗✓ |
55369 |
.To(&async_id)) return; |
131 |
|
110738 |
if (!GetAssignedPromiseWrapAsyncId( |
132 |
|
110738 |
env, promise, env->trigger_async_id_symbol()) |
133 |
✗✓ |
55369 |
.To(&trigger_async_id)) return; |
134 |
|
|
} |
135 |
|
|
|
136 |
✓✓ |
55431 |
if (async_id != AsyncWrap::kInvalidAsyncId && |
137 |
✓✗ |
62 |
trigger_async_id != AsyncWrap::kInvalidAsyncId) { |
138 |
|
124 |
env->async_hooks()->push_async_context( |
139 |
|
|
async_id, trigger_async_id, promise); |
140 |
|
|
} |
141 |
|
|
|
142 |
|
55431 |
USE(callback->Call( |
143 |
|
110862 |
env->context(), Undefined(isolate), arraysize(args), args)); |
144 |
|
|
|
145 |
|
110924 |
if (async_id != AsyncWrap::kInvalidAsyncId && |
146 |
✓✓✓✗ ✓✓ |
55493 |
trigger_async_id != AsyncWrap::kInvalidAsyncId && |
147 |
✓✗ |
62 |
env->execution_async_id() == async_id) { |
148 |
|
|
// This condition might not be true if async_hooks was enabled during |
149 |
|
|
// the promise callback execution. |
150 |
|
62 |
env->async_hooks()->pop_async_context(async_id); |
151 |
|
|
} |
152 |
|
|
|
153 |
|
|
// V8 does not expect this callback to have a scheduled exceptions once it |
154 |
|
|
// returns, so we print them out in a best effort to do something about it |
155 |
|
|
// without failing silently and without crashing the process. |
156 |
✓✓✓✗ ✓✓ |
55431 |
if (try_catch.HasCaught() && !try_catch.HasTerminated()) { |
157 |
|
424 |
fprintf(stderr, "Exception in PromiseRejectCallback:\n"); |
158 |
|
424 |
PrintCaughtException(isolate, env->context(), try_catch); |
159 |
|
|
} |
160 |
|
|
} |
161 |
|
|
namespace task_queue { |
162 |
|
|
|
163 |
|
1405 |
static void EnqueueMicrotask(const FunctionCallbackInfo<Value>& args) { |
164 |
|
1405 |
Environment* env = Environment::GetCurrent(args); |
165 |
|
1405 |
Isolate* isolate = env->isolate(); |
166 |
|
|
|
167 |
✗✓ |
1405 |
CHECK(args[0]->IsFunction()); |
168 |
|
|
|
169 |
|
2810 |
isolate->GetCurrentContext()->GetMicrotaskQueue() |
170 |
✓✗ |
4215 |
->EnqueueMicrotask(isolate, args[0].As<Function>()); |
171 |
|
1405 |
} |
172 |
|
|
|
173 |
|
243249 |
static void RunMicrotasks(const FunctionCallbackInfo<Value>& args) { |
174 |
|
243249 |
Environment* env = Environment::GetCurrent(args); |
175 |
|
486498 |
env->context()->GetMicrotaskQueue()->PerformCheckpoint(env->isolate()); |
176 |
|
243238 |
} |
177 |
|
|
|
178 |
|
784 |
static void SetTickCallback(const FunctionCallbackInfo<Value>& args) { |
179 |
|
784 |
Environment* env = Environment::GetCurrent(args); |
180 |
✗✓ |
784 |
CHECK(args[0]->IsFunction()); |
181 |
|
1568 |
env->set_tick_callback_function(args[0].As<Function>()); |
182 |
|
784 |
} |
183 |
|
|
|
184 |
|
784 |
static void SetPromiseRejectCallback( |
185 |
|
|
const FunctionCallbackInfo<Value>& args) { |
186 |
|
784 |
Environment* env = Environment::GetCurrent(args); |
187 |
|
|
|
188 |
✗✓ |
784 |
CHECK(args[0]->IsFunction()); |
189 |
|
1568 |
env->set_promise_reject_callback(args[0].As<Function>()); |
190 |
|
784 |
} |
191 |
|
|
|
192 |
|
784 |
static void Initialize(Local<Object> target, |
193 |
|
|
Local<Value> unused, |
194 |
|
|
Local<Context> context, |
195 |
|
|
void* priv) { |
196 |
|
784 |
Environment* env = Environment::GetCurrent(context); |
197 |
|
784 |
Isolate* isolate = env->isolate(); |
198 |
|
|
|
199 |
|
784 |
SetMethod(context, target, "enqueueMicrotask", EnqueueMicrotask); |
200 |
|
784 |
SetMethod(context, target, "setTickCallback", SetTickCallback); |
201 |
|
784 |
SetMethod(context, target, "runMicrotasks", RunMicrotasks); |
202 |
|
784 |
target->Set(env->context(), |
203 |
|
|
FIXED_ONE_BYTE_STRING(isolate, "tickInfo"), |
204 |
|
2352 |
env->tick_info()->fields().GetJSArray()).Check(); |
205 |
|
|
|
206 |
|
784 |
Local<Object> events = Object::New(isolate); |
207 |
|
2352 |
NODE_DEFINE_CONSTANT(events, kPromiseRejectWithNoHandler); |
208 |
|
2352 |
NODE_DEFINE_CONSTANT(events, kPromiseHandlerAddedAfterReject); |
209 |
|
2352 |
NODE_DEFINE_CONSTANT(events, kPromiseResolveAfterResolved); |
210 |
|
2352 |
NODE_DEFINE_CONSTANT(events, kPromiseRejectAfterResolved); |
211 |
|
|
|
212 |
|
784 |
target->Set(env->context(), |
213 |
|
|
FIXED_ONE_BYTE_STRING(isolate, "promiseRejectEvents"), |
214 |
|
1568 |
events).Check(); |
215 |
|
784 |
SetMethod( |
216 |
|
|
context, target, "setPromiseRejectCallback", SetPromiseRejectCallback); |
217 |
|
784 |
} |
218 |
|
|
|
219 |
|
5527 |
void RegisterExternalReferences(ExternalReferenceRegistry* registry) { |
220 |
|
5527 |
registry->Register(EnqueueMicrotask); |
221 |
|
5527 |
registry->Register(SetTickCallback); |
222 |
|
5527 |
registry->Register(RunMicrotasks); |
223 |
|
5527 |
registry->Register(SetPromiseRejectCallback); |
224 |
|
5527 |
} |
225 |
|
|
|
226 |
|
|
} // namespace task_queue |
227 |
|
|
} // namespace node |
228 |
|
|
|
229 |
|
5597 |
NODE_MODULE_CONTEXT_AWARE_INTERNAL(task_queue, node::task_queue::Initialize) |
230 |
|
5527 |
NODE_MODULE_EXTERNAL_REFERENCE(task_queue, |
231 |
|
|
node::task_queue::RegisterExternalReferences) |