-
Notifications
You must be signed in to change notification settings - Fork 15k
/
n-api_src_provide_asynchronous_cleanup_hooks.patch
250 lines (236 loc) · 8.79 KB
/
n-api_src_provide_asynchronous_cleanup_hooks.patch
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Shelley Vohr <shelley.vohr@gmail.com>
Date: Tue, 25 Aug 2020 19:34:12 -0700
Subject: n-api,src: provide asynchronous cleanup hooks
Backports https://github.com/nodejs/node/pull/34572 and
https://github.com/nodejs/node/pull/34819.
Sometimes addons need to perform cleanup actions, for example
closing libuv handles or waiting for requests to finish, that
cannot be performed synchronously.
Add C++ API and N-API functions that allow providing such
asynchronous cleanup hooks.
This patch can be removed when Electron upgrades to a version of Node.js
which contains the above referenced commit(s).
diff --git a/src/api/hooks.cc b/src/api/hooks.cc
index 037bdda6f41c825dd112b0cc9fca0ebde47c6163..3b16c0350d8a8494202144407664af41d338fe04 100644
--- a/src/api/hooks.cc
+++ b/src/api/hooks.cc
@@ -73,8 +73,35 @@ int EmitExit(Environment* env) {
.ToChecked();
}
+typedef void (*CleanupHook)(void* arg);
+typedef void (*AsyncCleanupHook)(void* arg, void(*)(void*), void*);
+
+struct AsyncCleanupHookInfo final {
+ Environment* env;
+ AsyncCleanupHook fun;
+ void* arg;
+ bool started = false;
+ // Use a self-reference to make sure the storage is kept alive while the
+ // cleanup hook is registered but not yet finished.
+ std::shared_ptr<AsyncCleanupHookInfo> self;
+};
+
+// Opaque type that is basically an alias for `shared_ptr<AsyncCleanupHookInfo>`
+// (but not publicly so for easier ABI/API changes). In particular,
+// std::shared_ptr does not generally maintain a consistent ABI even on a
+// specific platform.
+struct ACHHandle final {
+ std::shared_ptr<AsyncCleanupHookInfo> info;
+};
+// This is implemented as an operator on a struct because otherwise you can't
+// default-initialize AsyncCleanupHookHandle, because in C++ for a
+// std::unique_ptr to be default-initializable the deleter type also needs
+// to be default-initializable; in particular, function types don't satisfy
+// this.
+void DeleteACHHandle::operator ()(ACHHandle* handle) const { delete handle; }
+
void AddEnvironmentCleanupHook(Isolate* isolate,
- void (*fun)(void* arg),
+ CleanupHook fun,
void* arg) {
Environment* env = Environment::GetCurrent(isolate);
CHECK_NOT_NULL(env);
@@ -82,13 +109,50 @@ void AddEnvironmentCleanupHook(Isolate* isolate,
}
void RemoveEnvironmentCleanupHook(Isolate* isolate,
- void (*fun)(void* arg),
+ CleanupHook fun,
void* arg) {
Environment* env = Environment::GetCurrent(isolate);
CHECK_NOT_NULL(env);
env->RemoveCleanupHook(fun, arg);
}
+static void FinishAsyncCleanupHook(void* arg) {
+ AsyncCleanupHookInfo* info = static_cast<AsyncCleanupHookInfo*>(arg);
+ std::shared_ptr<AsyncCleanupHookInfo> keep_alive = info->self;
+
+ info->env->DecreaseWaitingRequestCounter();
+ info->self.reset();
+}
+
+static void RunAsyncCleanupHook(void* arg) {
+ AsyncCleanupHookInfo* info = static_cast<AsyncCleanupHookInfo*>(arg);
+ info->env->IncreaseWaitingRequestCounter();
+ info->started = true;
+ info->fun(info->arg, FinishAsyncCleanupHook, info);
+}
+
+AsyncCleanupHookHandle AddEnvironmentCleanupHook(
+ Isolate* isolate,
+ AsyncCleanupHook fun,
+ void* arg) {
+ Environment* env = Environment::GetCurrent(isolate);
+ CHECK_NOT_NULL(env);
+ auto info = std::make_shared<AsyncCleanupHookInfo>();
+ info->env = env;
+ info->fun = fun;
+ info->arg = arg;
+ info->self = info;
+ env->AddCleanupHook(RunAsyncCleanupHook, info.get());
+ return AsyncCleanupHookHandle(new ACHHandle { info });
+}
+
+void RemoveEnvironmentCleanupHook(
+ AsyncCleanupHookHandle handle) {
+ if (handle->info->started) return;
+ handle->info->self.reset();
+ handle->info->env->RemoveCleanupHook(RunAsyncCleanupHook, handle->info.get());
+}
+
async_id AsyncHooksGetExecutionAsyncId(Isolate* isolate) {
Environment* env = Environment::GetCurrent(isolate);
if (env == nullptr) return -1;
diff --git a/src/node.h b/src/node.h
index 60ecc3bd3499c23b297bc11e7f052aede20520c2..4c4e55e338d7b42c36818a45f6b41170c495adde 100644
--- a/src/node.h
+++ b/src/node.h
@@ -739,6 +739,20 @@ NODE_EXTERN void RemoveEnvironmentCleanupHook(v8::Isolate* isolate,
void (*fun)(void* arg),
void* arg);
+/* These are async equivalents of the above. After the cleanup hook is invoked,
+ * `cb(cbarg)` *must* be called, and attempting to remove the cleanup hook will
+ * have no effect. */
+struct ACHHandle;
+struct NODE_EXTERN DeleteACHHandle { void operator()(ACHHandle*) const; };
+typedef std::unique_ptr<ACHHandle, DeleteACHHandle> AsyncCleanupHookHandle;
+
+NODE_EXTERN AsyncCleanupHookHandle AddEnvironmentCleanupHook(
+ v8::Isolate* isolate,
+ void (*fun)(void* arg, void (*cb)(void*), void* cbarg),
+ void* arg);
+
+NODE_EXTERN void RemoveEnvironmentCleanupHook(AsyncCleanupHookHandle holder);
+
/* Returns the id of the current execution context. If the return value is
* zero then no execution has been set. This will happen if the user handles
* I/O from native code. */
diff --git a/src/node_api.cc b/src/node_api.cc
index fe24eca1b8e2d81fbafd0a1e2da38d957fbaa1c1..66168bd2c28ce6481516e63734616f487e3ec3e1 100644
--- a/src/node_api.cc
+++ b/src/node_api.cc
@@ -507,6 +507,70 @@ napi_status napi_remove_env_cleanup_hook(napi_env env,
return napi_ok;
}
+struct napi_async_cleanup_hook_handle__ {
+ napi_async_cleanup_hook_handle__(napi_env env,
+ napi_async_cleanup_hook user_hook,
+ void* user_data):
+ env_(env),
+ user_hook_(user_hook),
+ user_data_(user_data) {
+ handle_ = node::AddEnvironmentCleanupHook(env->isolate, Hook, this);
+ env->Ref();
+ }
+
+ ~napi_async_cleanup_hook_handle__() {
+ node::RemoveEnvironmentCleanupHook(std::move(handle_));
+ if (done_cb_ != nullptr)
+ done_cb_(done_data_);
+
+ // Release the `env` handle asynchronously since it would be surprising if
+ // a call to a N-API function would destroy `env` synchronously.
+ static_cast<node_napi_env>(env_)->node_env()
+ ->SetImmediate([env = env_](node::Environment*) { env->Unref(); });
+ }
+
+ static void Hook(void* data, void (*done_cb)(void*), void* done_data) {
+ auto handle = static_cast<napi_async_cleanup_hook_handle__*>(data);
+ handle->done_cb_ = done_cb;
+ handle->done_data_ = done_data;
+ handle->user_hook_(handle, handle->user_data_);
+ }
+
+ node::AsyncCleanupHookHandle handle_;
+ napi_env env_ = nullptr;
+ napi_async_cleanup_hook user_hook_ = nullptr;
+ void* user_data_ = nullptr;
+ void (*done_cb_)(void*) = nullptr;
+ void* done_data_ = nullptr;
+};
+
+napi_status napi_add_async_cleanup_hook(
+ napi_env env,
+ napi_async_cleanup_hook hook,
+ void* arg,
+ napi_async_cleanup_hook_handle* remove_handle) {
+ CHECK_ENV(env);
+ CHECK_ARG(env, hook);
+
+ napi_async_cleanup_hook_handle__* handle =
+ new napi_async_cleanup_hook_handle__(env, hook, arg);
+
+ if (remove_handle != nullptr)
+ *remove_handle = handle;
+
+ return napi_clear_last_error(env);
+}
+
+napi_status napi_remove_async_cleanup_hook(
+ napi_async_cleanup_hook_handle remove_handle) {
+
+ if (remove_handle == nullptr)
+ return napi_invalid_arg;
+
+ delete remove_handle;
+ return napi_ok;
+}
+
napi_status napi_fatal_exception(napi_env env, napi_value err) {
NAPI_PREAMBLE(env);
CHECK_ARG(env, err);
diff --git a/src/node_api.h b/src/node_api.h
index 2f1b45572d8130f27492eb6188c1aa611f2e01a3..577a1dcd94987202819e7a36a2d9674f13d13614 100644
--- a/src/node_api.h
+++ b/src/node_api.h
@@ -250,6 +250,19 @@ napi_ref_threadsafe_function(napi_env env, napi_threadsafe_function func);
#endif // NAPI_VERSION >= 4
+#ifdef NAPI_EXPERIMENTAL
+
+NAPI_EXTERN napi_status napi_add_async_cleanup_hook(
+ napi_env env,
+ napi_async_cleanup_hook hook,
+ void* arg,
+ napi_async_cleanup_hook_handle* remove_handle);
+
+NAPI_EXTERN napi_status napi_remove_async_cleanup_hook(
+ napi_async_cleanup_hook_handle remove_handle);
+
+#endif // NAPI_EXPERIMENTAL
+
EXTERN_C_END
#endif // SRC_NODE_API_H_
diff --git a/src/node_api_types.h b/src/node_api_types.h
index 1c9a2b8aa21889c0d29fb02b234ae9698d122c2c..0e400e9676df5ba09d350fe7a2a70a1dc9e4d3d6 100644
--- a/src/node_api_types.h
+++ b/src/node_api_types.h
@@ -41,4 +41,10 @@ typedef struct {
const char* release;
} napi_node_version;
+#ifdef NAPI_EXPERIMENTAL
+typedef struct napi_async_cleanup_hook_handle__* napi_async_cleanup_hook_handle;
+typedef void (*napi_async_cleanup_hook)(napi_async_cleanup_hook_handle handle,
+ void* data);
+#endif // NAPI_EXPERIMENTAL
+
#endif // SRC_NODE_API_TYPES_H_