xref: /third_party/node/src/api/hooks.cc (revision 1cb0ef41)
1#include "env-inl.h"
2#include "node_internals.h"
3#include "node_process-inl.h"
4#include "async_wrap.h"
5
6namespace node {
7
8using v8::Context;
9using v8::HandleScope;
10using v8::Integer;
11using v8::Isolate;
12using v8::Just;
13using v8::Local;
14using v8::Maybe;
15using v8::NewStringType;
16using v8::Nothing;
17using v8::Object;
18using v8::String;
19using v8::Value;
20
21void RunAtExit(Environment* env) {
22  env->RunAtExitCallbacks();
23}
24
25void AtExit(Environment* env, void (*cb)(void* arg), void* arg) {
26  CHECK_NOT_NULL(env);
27  env->AtExit(cb, arg);
28}
29
30void EmitBeforeExit(Environment* env) {
31  USE(EmitProcessBeforeExit(env));
32}
33
34Maybe<bool> EmitProcessBeforeExit(Environment* env) {
35  TRACE_EVENT0(TRACING_CATEGORY_NODE1(environment), "BeforeExit");
36  if (!env->destroy_async_id_list()->empty())
37    AsyncWrap::DestroyAsyncIdsCallback(env);
38
39  HandleScope handle_scope(env->isolate());
40  Local<Context> context = env->context();
41  Context::Scope context_scope(context);
42
43  Local<Value> exit_code_v;
44  if (!env->process_object()->Get(context, env->exit_code_string())
45      .ToLocal(&exit_code_v)) return Nothing<bool>();
46
47  Local<Integer> exit_code;
48  if (!exit_code_v->ToInteger(context).ToLocal(&exit_code)) {
49    return Nothing<bool>();
50  }
51
52  return ProcessEmit(env, "beforeExit", exit_code).IsEmpty() ?
53      Nothing<bool>() : Just(true);
54}
55
56int EmitExit(Environment* env) {
57  return EmitProcessExit(env).FromMaybe(1);
58}
59
60Maybe<int> EmitProcessExit(Environment* env) {
61  // process.emit('exit')
62  Isolate* isolate = env->isolate();
63  HandleScope handle_scope(isolate);
64  Local<Context> context = env->context();
65  Context::Scope context_scope(context);
66  Local<Object> process_object = env->process_object();
67
68  // TODO(addaleax): It might be nice to share process.exitCode via
69  // getter/setter pairs that pass data directly to the native side, so that we
70  // don't manually have to read and write JS properties here. These getters
71  // could use e.g. a typed array for performance.
72  env->set_exiting(true);
73
74  Local<String> exit_code = env->exit_code_string();
75  Local<Value> code_v;
76  int code;
77  if (!process_object->Get(context, exit_code).ToLocal(&code_v) ||
78      !code_v->Int32Value(context).To(&code) ||
79      ProcessEmit(env, "exit", Integer::New(isolate, code)).IsEmpty() ||
80      // Reload exit code, it may be changed by `emit('exit')`
81      !process_object->Get(context, exit_code).ToLocal(&code_v) ||
82      !code_v->Int32Value(context).To(&code)) {
83    return Nothing<int>();
84  }
85
86  return Just(code);
87}
88
89typedef void (*CleanupHook)(void* arg);
90typedef void (*AsyncCleanupHook)(void* arg, void(*)(void*), void*);
91
92struct AsyncCleanupHookInfo final {
93  Environment* env;
94  AsyncCleanupHook fun;
95  void* arg;
96  bool started = false;
97  // Use a self-reference to make sure the storage is kept alive while the
98  // cleanup hook is registered but not yet finished.
99  std::shared_ptr<AsyncCleanupHookInfo> self;
100};
101
102// Opaque type that is basically an alias for `shared_ptr<AsyncCleanupHookInfo>`
103// (but not publicly so for easier ABI/API changes). In particular,
104// std::shared_ptr does not generally maintain a consistent ABI even on a
105// specific platform.
106struct ACHHandle final {
107  std::shared_ptr<AsyncCleanupHookInfo> info;
108};
109// This is implemented as an operator on a struct because otherwise you can't
110// default-initialize AsyncCleanupHookHandle, because in C++ for a
111// std::unique_ptr to be default-initializable the deleter type also needs
112// to be default-initializable; in particular, function types don't satisfy
113// this.
114void DeleteACHHandle::operator ()(ACHHandle* handle) const { delete handle; }
115
116void AddEnvironmentCleanupHook(Isolate* isolate,
117                               CleanupHook fun,
118                               void* arg) {
119  Environment* env = Environment::GetCurrent(isolate);
120  CHECK_NOT_NULL(env);
121  env->AddCleanupHook(fun, arg);
122}
123
124void RemoveEnvironmentCleanupHook(Isolate* isolate,
125                                  CleanupHook fun,
126                                  void* arg) {
127  Environment* env = Environment::GetCurrent(isolate);
128  CHECK_NOT_NULL(env);
129  env->RemoveCleanupHook(fun, arg);
130}
131
132static void FinishAsyncCleanupHook(void* arg) {
133  AsyncCleanupHookInfo* info = static_cast<AsyncCleanupHookInfo*>(arg);
134  std::shared_ptr<AsyncCleanupHookInfo> keep_alive = info->self;
135
136  info->env->DecreaseWaitingRequestCounter();
137  info->self.reset();
138}
139
140static void RunAsyncCleanupHook(void* arg) {
141  AsyncCleanupHookInfo* info = static_cast<AsyncCleanupHookInfo*>(arg);
142  info->env->IncreaseWaitingRequestCounter();
143  info->started = true;
144  info->fun(info->arg, FinishAsyncCleanupHook, info);
145}
146
147ACHHandle* AddEnvironmentCleanupHookInternal(
148    Isolate* isolate,
149    AsyncCleanupHook fun,
150    void* arg) {
151  Environment* env = Environment::GetCurrent(isolate);
152  CHECK_NOT_NULL(env);
153  auto info = std::make_shared<AsyncCleanupHookInfo>();
154  info->env = env;
155  info->fun = fun;
156  info->arg = arg;
157  info->self = info;
158  env->AddCleanupHook(RunAsyncCleanupHook, info.get());
159  return new ACHHandle { info };
160}
161
162void RemoveEnvironmentCleanupHookInternal(
163    ACHHandle* handle) {
164  if (handle->info->started) return;
165  handle->info->self.reset();
166  handle->info->env->RemoveCleanupHook(RunAsyncCleanupHook, handle->info.get());
167}
168
169void RequestInterrupt(Environment* env, void (*fun)(void* arg), void* arg) {
170  env->RequestInterrupt([fun, arg](Environment* env) {
171    // Disallow JavaScript execution during interrupt.
172    Isolate::DisallowJavascriptExecutionScope scope(
173        env->isolate(),
174        Isolate::DisallowJavascriptExecutionScope::CRASH_ON_FAILURE);
175    fun(arg);
176  });
177}
178
179async_id AsyncHooksGetExecutionAsyncId(Isolate* isolate) {
180  Environment* env = Environment::GetCurrent(isolate);
181  if (env == nullptr) return -1;
182  return env->execution_async_id();
183}
184
185async_id AsyncHooksGetTriggerAsyncId(Isolate* isolate) {
186  Environment* env = Environment::GetCurrent(isolate);
187  if (env == nullptr) return -1;
188  return env->trigger_async_id();
189}
190
191
192async_context EmitAsyncInit(Isolate* isolate,
193                            Local<Object> resource,
194                            const char* name,
195                            async_id trigger_async_id) {
196  HandleScope handle_scope(isolate);
197  Local<String> type =
198      String::NewFromUtf8(isolate, name, NewStringType::kInternalized)
199          .ToLocalChecked();
200  return EmitAsyncInit(isolate, resource, type, trigger_async_id);
201}
202
203async_context EmitAsyncInit(Isolate* isolate,
204                            Local<Object> resource,
205                            Local<String> name,
206                            async_id trigger_async_id) {
207  DebugSealHandleScope handle_scope(isolate);
208  Environment* env = Environment::GetCurrent(isolate);
209  CHECK_NOT_NULL(env);
210
211  // Initialize async context struct
212  if (trigger_async_id == -1)
213    trigger_async_id = env->get_default_trigger_async_id();
214
215  async_context context = {
216    env->new_async_id(),  // async_id_
217    trigger_async_id  // trigger_async_id_
218  };
219
220  // Run init hooks
221  AsyncWrap::EmitAsyncInit(env, resource, name, context.async_id,
222                           context.trigger_async_id);
223
224  return context;
225}
226
227void EmitAsyncDestroy(Isolate* isolate, async_context asyncContext) {
228  EmitAsyncDestroy(Environment::GetCurrent(isolate), asyncContext);
229}
230
231void EmitAsyncDestroy(Environment* env, async_context asyncContext) {
232  AsyncWrap::EmitDestroy(env, asyncContext.async_id);
233}
234
235}  // namespace node
236