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
GetAssignedPromiseAsyncId(Environment * env,Local<Promise> promise,Local<Value> id_symbol)34 static Maybe<double> GetAssignedPromiseAsyncId(Environment* env,
35 Local<Promise> promise,
36 Local<Value> id_symbol) {
37 Local<Value> maybe_async_id;
38 if (!promise->Get(env->context(), id_symbol).ToLocal(&maybe_async_id)) {
39 return v8::Just(AsyncWrap::kInvalidAsyncId);
40 }
41 return maybe_async_id->IsNumber()
42 ? maybe_async_id->NumberValue(env->context())
43 : v8::Just(AsyncWrap::kInvalidAsyncId);
44 }
45
GetAssignedPromiseWrapAsyncId(Environment * env,Local<Promise> promise,Local<Value> id_symbol)46 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 Local<Value> promiseWrap = promise->GetInternalField(0).As<Value>();
54 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 return v8::Just(AsyncWrap::kInvalidAsyncId);
65 }
66 }
67
PromiseRejectCallback(PromiseRejectMessage message)68 void PromiseRejectCallback(PromiseRejectMessage message) {
69 static std::atomic<uint64_t> unhandledRejections{0};
70 static std::atomic<uint64_t> rejectionsHandledAfter{0};
71
72 Local<Promise> promise = message.GetPromise();
73 Isolate* isolate = promise->GetIsolate();
74 PromiseRejectEvent event = message.GetEvent();
75
76 Environment* env = Environment::GetCurrent(isolate);
77
78 if (env == nullptr || !env->can_call_into_js()) return;
79
80 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 CHECK(!callback.IsEmpty());
84
85 Local<Value> value;
86 Local<Value> type = Number::New(env->isolate(), event);
87
88 if (event == kPromiseRejectWithNoHandler) {
89 value = message.GetValue();
90 unhandledRejections++;
91 TRACE_COUNTER2(TRACING_CATEGORY_NODE2(promises, rejections),
92 "rejections",
93 "unhandled", unhandledRejections,
94 "handledAfter", rejectionsHandledAfter);
95 } else if (event == kPromiseHandlerAddedAfterReject) {
96 value = Undefined(isolate);
97 rejectionsHandledAfter++;
98 TRACE_COUNTER2(TRACING_CATEGORY_NODE2(promises, rejections),
99 "rejections",
100 "unhandled", unhandledRejections,
101 "handledAfter", rejectionsHandledAfter);
102 } else if (event == kPromiseResolveAfterResolved) {
103 value = message.GetValue();
104 } else if (event == kPromiseRejectAfterResolved) {
105 value = message.GetValue();
106 } else {
107 return;
108 }
109
110 if (value.IsEmpty()) {
111 value = Undefined(isolate);
112 }
113
114 Local<Value> args[] = { type, promise, value };
115
116 double async_id = AsyncWrap::kInvalidAsyncId;
117 double trigger_async_id = AsyncWrap::kInvalidAsyncId;
118 TryCatchScope try_catch(env);
119
120 if (!GetAssignedPromiseAsyncId(env, promise, env->async_id_symbol())
121 .To(&async_id)) return;
122 if (!GetAssignedPromiseAsyncId(env, promise, env->trigger_async_id_symbol())
123 .To(&trigger_async_id)) return;
124
125 if (async_id == AsyncWrap::kInvalidAsyncId &&
126 trigger_async_id == AsyncWrap::kInvalidAsyncId) {
127 // That means that promise might be a PromiseWrap, so we'll
128 // check there as well.
129 if (!GetAssignedPromiseWrapAsyncId(env, promise, env->async_id_symbol())
130 .To(&async_id)) return;
131 if (!GetAssignedPromiseWrapAsyncId(
132 env, promise, env->trigger_async_id_symbol())
133 .To(&trigger_async_id)) return;
134 }
135
136 if (async_id != AsyncWrap::kInvalidAsyncId &&
137 trigger_async_id != AsyncWrap::kInvalidAsyncId) {
138 env->async_hooks()->push_async_context(
139 async_id, trigger_async_id, promise);
140 }
141
142 USE(callback->Call(
143 env->context(), Undefined(isolate), arraysize(args), args));
144
145 if (async_id != AsyncWrap::kInvalidAsyncId &&
146 trigger_async_id != AsyncWrap::kInvalidAsyncId &&
147 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 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 if (try_catch.HasCaught() && !try_catch.HasTerminated()) {
157 fprintf(stderr, "Exception in PromiseRejectCallback:\n");
158 PrintCaughtException(isolate, env->context(), try_catch);
159 }
160 }
161 namespace task_queue {
162
EnqueueMicrotask(const FunctionCallbackInfo<Value> & args)163 static void EnqueueMicrotask(const FunctionCallbackInfo<Value>& args) {
164 Environment* env = Environment::GetCurrent(args);
165 Isolate* isolate = env->isolate();
166
167 CHECK(args[0]->IsFunction());
168
169 isolate->GetCurrentContext()->GetMicrotaskQueue()
170 ->EnqueueMicrotask(isolate, args[0].As<Function>());
171 }
172
RunMicrotasks(const FunctionCallbackInfo<Value> & args)173 static void RunMicrotasks(const FunctionCallbackInfo<Value>& args) {
174 Environment* env = Environment::GetCurrent(args);
175 env->context()->GetMicrotaskQueue()->PerformCheckpoint(env->isolate());
176 }
177
SetTickCallback(const FunctionCallbackInfo<Value> & args)178 static void SetTickCallback(const FunctionCallbackInfo<Value>& args) {
179 Environment* env = Environment::GetCurrent(args);
180 CHECK(args[0]->IsFunction());
181 env->set_tick_callback_function(args[0].As<Function>());
182 }
183
SetPromiseRejectCallback(const FunctionCallbackInfo<Value> & args)184 static void SetPromiseRejectCallback(
185 const FunctionCallbackInfo<Value>& args) {
186 Environment* env = Environment::GetCurrent(args);
187
188 CHECK(args[0]->IsFunction());
189 env->set_promise_reject_callback(args[0].As<Function>());
190 }
191
Initialize(Local<Object> target,Local<Value> unused,Local<Context> context,void * priv)192 static void Initialize(Local<Object> target,
193 Local<Value> unused,
194 Local<Context> context,
195 void* priv) {
196 Environment* env = Environment::GetCurrent(context);
197 Isolate* isolate = env->isolate();
198
199 SetMethod(context, target, "enqueueMicrotask", EnqueueMicrotask);
200 SetMethod(context, target, "setTickCallback", SetTickCallback);
201 SetMethod(context, target, "runMicrotasks", RunMicrotasks);
202 target->Set(env->context(),
203 FIXED_ONE_BYTE_STRING(isolate, "tickInfo"),
204 env->tick_info()->fields().GetJSArray()).Check();
205
206 Local<Object> events = Object::New(isolate);
207 NODE_DEFINE_CONSTANT(events, kPromiseRejectWithNoHandler);
208 NODE_DEFINE_CONSTANT(events, kPromiseHandlerAddedAfterReject);
209 NODE_DEFINE_CONSTANT(events, kPromiseResolveAfterResolved);
210 NODE_DEFINE_CONSTANT(events, kPromiseRejectAfterResolved);
211
212 target->Set(env->context(),
213 FIXED_ONE_BYTE_STRING(isolate, "promiseRejectEvents"),
214 events).Check();
215 SetMethod(
216 context, target, "setPromiseRejectCallback", SetPromiseRejectCallback);
217 }
218
RegisterExternalReferences(ExternalReferenceRegistry * registry)219 void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
220 registry->Register(EnqueueMicrotask);
221 registry->Register(SetTickCallback);
222 registry->Register(RunMicrotasks);
223 registry->Register(SetPromiseRejectCallback);
224 }
225
226 } // namespace task_queue
227 } // namespace node
228
229 NODE_BINDING_CONTEXT_AWARE_INTERNAL(task_queue, node::task_queue::Initialize)
230 NODE_BINDING_EXTERNAL_REFERENCE(task_queue,
231 node::task_queue::RegisterExternalReferences)
232