Skip to content

Commit 745c213

Browse files
committed
src: add environment cleanup hooks
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#82
1 parent 71f514d commit 745c213

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
@@ -597,6 +597,26 @@ inline void Environment::SetTemplateMethod(v8::Local<v8::FunctionTemplate> that,
597597
t->SetClassName(name_string); // NODE_SET_METHOD() compatibility.
598598
}
599599

600+
void Environment::AddCleanupHook(void (*fn)(void*), void* arg) {
601+
cleanup_hooks_[arg].push_back(
602+
CleanupHookCallback { fn, arg, cleanup_hook_counter_++ });
603+
}
604+
605+
void Environment::RemoveCleanupHook(void (*fn)(void*), void* arg) {
606+
auto map_it = cleanup_hooks_.find(arg);
607+
if (map_it == cleanup_hooks_.end())
608+
return;
609+
610+
for (auto it = map_it->second.begin(); it != map_it->second.end(); ++it) {
611+
if (it->fun_ == fn && it->arg_ == arg) {
612+
map_it->second.erase(it);
613+
if (map_it->second.empty())
614+
cleanup_hooks_.erase(arg);
615+
return;
616+
}
617+
}
618+
}
619+
600620
#define VP(PropertyName, StringValue) V(v8::Private, PropertyName)
601621
#define VS(PropertyName, StringValue) V(v8::String, PropertyName)
602622
#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
@@ -678,6 +678,10 @@ class Environment {
678678
bool RemovePromiseHook(promise_hook_func fn, void* arg);
679679
bool EmitNapiWarning();
680680

681+
inline void AddCleanupHook(void (*fn)(void*), void* arg);
682+
inline void RemoveCleanupHook(void (*fn)(void*), void* arg);
683+
void RunCleanup();
684+
681685
private:
682686
inline void ThrowError(v8::Local<v8::Value> (*fun)(v8::Local<v8::String>),
683687
const char* errmsg);
@@ -736,6 +740,15 @@ class Environment {
736740
};
737741
std::vector<PromiseHookCallback> promise_hooks_;
738742

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

src/node.cc

+18
Original file line numberDiff line numberDiff line change
@@ -1342,6 +1342,22 @@ void AddPromiseHook(v8::Isolate* isolate, promise_hook_func fn, void* arg) {
13421342
env->AddPromiseHook(fn, arg);
13431343
}
13441344

1345+
void AddEnvironmentCleanupHook(v8::Isolate* isolate,
1346+
void (*fun)(void* arg),
1347+
void* arg) {
1348+
Environment* env = Environment::GetCurrent(isolate);
1349+
env->AddCleanupHook(fun, arg);
1350+
}
1351+
1352+
1353+
void RemoveEnvironmentCleanupHook(v8::Isolate* isolate,
1354+
void (*fun)(void* arg),
1355+
void* arg) {
1356+
Environment* env = Environment::GetCurrent(isolate);
1357+
env->RemoveCleanupHook(fun, arg);
1358+
}
1359+
1360+
13451361
CallbackScope::CallbackScope(Isolate* isolate,
13461362
Local<Object> object,
13471363
async_context asyncContext)
@@ -4739,6 +4755,8 @@ inline int Start(Isolate* isolate, IsolateData* isolate_data,
47394755
env.set_trace_sync_io(false);
47404756

47414757
const int exit_code = EmitExit(&env);
4758+
4759+
env.RunCleanup();
47424760
RunAtExit(&env);
47434761
uv_key_delete(&thread_local_env);
47444762

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
@@ -857,6 +857,28 @@ void napi_module_register(napi_module* mod) {
857857
node::node_module_register(nm);
858858
}
859859

860+
napi_status napi_add_env_cleanup_hook(napi_env env,
861+
void (*fun)(void* arg),
862+
void* arg) {
863+
CHECK_ENV(env);
864+
CHECK_ARG(env, fun);
865+
866+
node::AddEnvironmentCleanupHook(env->isolate, fun, arg);
867+
868+
return napi_ok;
869+
}
870+
871+
napi_status napi_remove_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::RemoveEnvironmentCleanupHook(env->isolate, fun, arg);
878+
879+
return napi_ok;
880+
}
881+
860882
// Warning: Keep in-sync with napi_status enum
861883
const char* error_messages[] = {nullptr,
862884
"Invalid pointer passed as argument",

src/node_api.h

+7
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,13 @@ EXTERN_C_START
104104

105105
NAPI_EXTERN void napi_module_register(napi_module* mod);
106106

107+
NAPI_EXTERN napi_status napi_add_env_cleanup_hook(napi_env env,
108+
void (*fun)(void* arg),
109+
void* arg);
110+
NAPI_EXTERN napi_status napi_remove_env_cleanup_hook(napi_env env,
111+
void (*fun)(void* arg),
112+
void* arg);
113+
107114
NAPI_EXTERN napi_status
108115
napi_get_last_error_info(napi_env env,
109116
const napi_extended_error_info** result);

0 commit comments

Comments
 (0)