Skip to content

Commit 5cfcba2

Browse files
addaleaxjasnell
authored andcommitted
src: add environment cleanup hooks
Original PR: ayojs/ayo#82 > This adds pairs of methods to the `Environment` class and to public APIs > which can add and remove cleanup handlers. > Unlike `AtExit`, this API targets addon developers rather than > embedders, giving them (and Node’s internals) the ability to register > per-`Environment` cleanup work. > PR-URL: ayojs/ayo#82 > Reviewed-By: Stephen Belanger <admin@stephenbelanger.com>
1 parent 53d6465 commit 5cfcba2

File tree

7 files changed

+113
-0
lines changed

7 files changed

+113
-0
lines changed

src/env-inl.h

+20
Original file line numberDiff line numberDiff line change
@@ -599,6 +599,26 @@ inline void Environment::SetTemplateMethod(v8::Local<v8::FunctionTemplate> that,
599599
t->SetClassName(name_string); // NODE_SET_METHOD() compatibility.
600600
}
601601

602+
void Environment::AddCleanupHook(void (*fn)(void*), void* arg) {
603+
cleanup_hooks_[arg].push_back(
604+
CleanupHookCallback { fn, arg, cleanup_hook_counter_++ });
605+
}
606+
607+
void Environment::RemoveCleanupHook(void (*fn)(void*), void* arg) {
608+
auto map_it = cleanup_hooks_.find(arg);
609+
if (map_it == cleanup_hooks_.end())
610+
return;
611+
612+
for (auto it = map_it->second.begin(); it != map_it->second.end(); ++it) {
613+
if (it->fun_ == fn && it->arg_ == arg) {
614+
map_it->second.erase(it);
615+
if (map_it->second.empty())
616+
cleanup_hooks_.erase(arg);
617+
return;
618+
}
619+
}
620+
}
621+
602622
#define VP(PropertyName, StringValue) V(v8::Private, PropertyName)
603623
#define VS(PropertyName, StringValue) V(v8::String, PropertyName)
604624
#define V(TypeName, PropertyName) \

src/env.cc

+20
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,26 @@ void Environment::PrintSyncTrace() const {
166166
fflush(stderr);
167167
}
168168

169+
void Environment::RunCleanup() {
170+
while (!cleanup_hooks_.empty()) {
171+
std::vector<CleanupHookCallback> callbacks;
172+
// Concatenate all vectors in cleanup_hooks_
173+
for (const auto& pair : cleanup_hooks_)
174+
callbacks.insert(callbacks.end(), pair.second.begin(), pair.second.end());
175+
cleanup_hooks_.clear();
176+
std::sort(callbacks.begin(), callbacks.end(),
177+
[](const CleanupHookCallback& a, const CleanupHookCallback& b) {
178+
// Sort in descending order so that the last-inserted callbacks get run
179+
// first.
180+
return a.insertion_order_counter_ > b.insertion_order_counter_;
181+
});
182+
183+
for (const CleanupHookCallback& cb : callbacks) {
184+
cb.fun_(cb.arg_);
185+
}
186+
}
187+
}
188+
169189
void Environment::RunAtExitCallbacks() {
170190
for (AtExitCallback at_exit : at_exit_functions_) {
171191
at_exit.cb_(at_exit.arg_);

src/env.h

+13
Original file line numberDiff line numberDiff line change
@@ -680,6 +680,10 @@ class Environment {
680680
bool RemovePromiseHook(promise_hook_func fn, void* arg);
681681
bool EmitNapiWarning();
682682

683+
inline void AddCleanupHook(void (*fn)(void*), void* arg);
684+
inline void RemoveCleanupHook(void (*fn)(void*), void* arg);
685+
void RunCleanup();
686+
683687
private:
684688
inline void ThrowError(v8::Local<v8::Value> (*fun)(v8::Local<v8::String>),
685689
const char* errmsg);
@@ -738,6 +742,15 @@ class Environment {
738742
};
739743
std::vector<PromiseHookCallback> promise_hooks_;
740744

745+
struct CleanupHookCallback {
746+
void (*fun_)(void*);
747+
void* arg_;
748+
int64_t insertion_order_counter_;
749+
};
750+
751+
std::unordered_map<void*, std::vector<CleanupHookCallback>> cleanup_hooks_;
752+
int64_t cleanup_hook_counter_ = 0;
753+
741754
static void EnvPromiseHook(v8::PromiseHookType type,
742755
v8::Local<v8::Promise> promise,
743756
v8::Local<v8::Value> parent);

src/node.cc

+18
Original file line numberDiff line numberDiff line change
@@ -1339,6 +1339,22 @@ void AddPromiseHook(v8::Isolate* isolate, promise_hook_func fn, void* arg) {
13391339
env->AddPromiseHook(fn, arg);
13401340
}
13411341

1342+
void AddEnvironmentCleanupHook(v8::Isolate* isolate,
1343+
void (*fun)(void* arg),
1344+
void* arg) {
1345+
Environment* env = Environment::GetCurrent(isolate);
1346+
env->AddCleanupHook(fun, arg);
1347+
}
1348+
1349+
1350+
void RemoveEnvironmentCleanupHook(v8::Isolate* isolate,
1351+
void (*fun)(void* arg),
1352+
void* arg) {
1353+
Environment* env = Environment::GetCurrent(isolate);
1354+
env->RemoveCleanupHook(fun, arg);
1355+
}
1356+
1357+
13421358
CallbackScope::CallbackScope(Isolate* isolate,
13431359
Local<Object> object,
13441360
async_context asyncContext)
@@ -4747,6 +4763,8 @@ inline int Start(Isolate* isolate, IsolateData* isolate_data,
47474763
env.set_trace_sync_io(false);
47484764

47494765
const int exit_code = EmitExit(&env);
4766+
4767+
env.RunCleanup();
47504768
RunAtExit(&env);
47514769
uv_key_delete(&thread_local_env);
47524770

src/node.h

+13
Original file line numberDiff line numberDiff line change
@@ -546,6 +546,19 @@ NODE_EXTERN void AddPromiseHook(v8::Isolate* isolate,
546546
promise_hook_func fn,
547547
void* arg);
548548

549+
/* This is a lot like node::AtExit, except that the hooks added via this
550+
* function are run before the AtExit ones and will always be registered
551+
* for the current Environment instance.
552+
* These functions are safe to use in an addon supporting multiple
553+
* threads/isolates. */
554+
NODE_EXTERN void AddEnvironmentCleanupHook(v8::Isolate* isolate,
555+
void (*fun)(void* arg),
556+
void* arg);
557+
558+
NODE_EXTERN void RemoveEnvironmentCleanupHook(v8::Isolate* isolate,
559+
void (*fun)(void* arg),
560+
void* arg);
561+
549562
/* Returns the id of the current execution context. If the return value is
550563
* zero then no execution has been set. This will happen if the user handles
551564
* I/O from native code. */

src/node_api.cc

+22
Original file line numberDiff line numberDiff line change
@@ -868,6 +868,28 @@ void napi_module_register(napi_module* mod) {
868868
node::node_module_register(nm);
869869
}
870870

871+
napi_status napi_add_env_cleanup_hook(napi_env env,
872+
void (*fun)(void* arg),
873+
void* arg) {
874+
CHECK_ENV(env);
875+
CHECK_ARG(env, fun);
876+
877+
node::AddEnvironmentCleanupHook(env->isolate, fun, arg);
878+
879+
return napi_ok;
880+
}
881+
882+
napi_status napi_remove_env_cleanup_hook(napi_env env,
883+
void (*fun)(void* arg),
884+
void* arg) {
885+
CHECK_ENV(env);
886+
CHECK_ARG(env, fun);
887+
888+
node::RemoveEnvironmentCleanupHook(env->isolate, fun, arg);
889+
890+
return napi_ok;
891+
}
892+
871893
// Warning: Keep in-sync with napi_status enum
872894
const char* error_messages[] = {nullptr,
873895
"Invalid argument",

src/node_api.h

+7
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,13 @@ EXTERN_C_START
106106

107107
NAPI_EXTERN void napi_module_register(napi_module* mod);
108108

109+
NAPI_EXTERN napi_status napi_add_env_cleanup_hook(napi_env env,
110+
void (*fun)(void* arg),
111+
void* arg);
112+
NAPI_EXTERN napi_status napi_remove_env_cleanup_hook(napi_env env,
113+
void (*fun)(void* arg),
114+
void* arg);
115+
109116
NAPI_EXTERN napi_status
110117
napi_get_last_error_info(napi_env env,
111118
const napi_extended_error_info** result);

0 commit comments

Comments
 (0)