• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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