Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit ba26958

Browse filesBrowse 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. We may want to replace `AtExit` with this API at some point. Many thanks for Stephen Belanger for reviewing the original version of this commit in the Ayo.js project. Refs: ayojs/ayo#82 PR-URL: #19377 Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl> Reviewed-By: James M Snell <jasnell@gmail.com>
1 parent cecec46 commit ba26958
Copy full SHA for ba26958

File tree

Expand file treeCollapse file tree

11 files changed

+241
-1
lines changed
Open diff view settings
Filter options
Expand file treeCollapse file tree

11 files changed

+241
-1
lines changed
Open diff view settings
Collapse file

‎doc/api/n-api.md‎

Copy file name to clipboardExpand all lines: doc/api/n-api.md
+52Lines changed: 52 additions & 0 deletions
  • Display the source diff
  • Display the rich diff
Original file line numberDiff line numberDiff line change
@@ -886,6 +886,58 @@ If still valid, this API returns the `napi_value` representing the
886886
JavaScript `Object` associated with the `napi_ref`. Otherwise, result
887887
will be NULL.
888888

889+
### Cleanup on exit of the current Node.js instance
890+
891+
While a Node.js process typically releases all its resources when exiting,
892+
embedders of Node.js, or future Worker support, may require addons to register
893+
clean-up hooks that will be run once the current Node.js instance exits.
894+
895+
N-API provides functions for registering and un-registering such callbacks.
896+
When those callbacks are run, all resources that are being held by the addon
897+
should be freed up.
898+
899+
#### napi_add_env_cleanup_hook
900+
<!-- YAML
901+
added: REPLACEME
902+
-->
903+
```C
904+
NODE_EXTERN napi_status napi_add_env_cleanup_hook(napi_env env,
905+
void (*fun)(void* arg),
906+
void* arg);
907+
```
908+
909+
Registers `fun` as a function to be run with the `arg` parameter once the
910+
current Node.js environment exits.
911+
912+
A function can safely be specified multiple times with different
913+
`arg` values. In that case, it will be called multiple times as well.
914+
Providing the same `fun` and `arg` values multiple times is not allowed
915+
and will lead the process to abort.
916+
917+
The hooks will be called in reverse order, i.e. the most recently added one
918+
will be called first.
919+
920+
Removing this hook can be done by using `napi_remove_env_cleanup_hook`.
921+
Typically, that happens when the resource for which this hook was added
922+
is being torn down anyway.
923+
924+
#### napi_remove_env_cleanup_hook
925+
<!-- YAML
926+
added: REPLACEME
927+
-->
928+
```C
929+
NAPI_EXTERN napi_status napi_remove_env_cleanup_hook(napi_env env,
930+
void (*fun)(void* arg),
931+
void* arg);
932+
```
933+
934+
Unregisters `fun` as a function to be run with the `arg` parameter once the
935+
current Node.js environment exits. Both the argument and the function value
936+
need to be exact matches.
937+
938+
The function must have originally been registered
939+
with `napi_add_env_cleanup_hook`, otherwise the process will abort.
940+
889941
## Module registration
890942
N-API modules are registered in a manner similar to other modules
891943
except that instead of using the `NODE_MODULE` macro the following
Collapse file

‎src/env-inl.h‎

Copy file name to clipboardExpand all lines: src/env-inl.h
+23Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -629,6 +629,29 @@ inline void Environment::SetTemplateMethod(v8::Local<v8::FunctionTemplate> that,
629629
t->SetClassName(name_string); // NODE_SET_METHOD() compatibility.
630630
}
631631

632+
void Environment::AddCleanupHook(void (*fn)(void*), void* arg) {
633+
auto insertion_info = cleanup_hooks_.emplace(CleanupHookCallback {
634+
fn, arg, cleanup_hook_counter_++
635+
});
636+
// Make sure there was no existing element with these values.
637+
CHECK_EQ(insertion_info.second, true);
638+
}
639+
640+
void Environment::RemoveCleanupHook(void (*fn)(void*), void* arg) {
641+
CleanupHookCallback search { fn, arg, 0 };
642+
cleanup_hooks_.erase(search);
643+
}
644+
645+
size_t Environment::CleanupHookCallback::Hash::operator()(
646+
const CleanupHookCallback& cb) const {
647+
return std::hash<void*>()(cb.arg_);
648+
}
649+
650+
bool Environment::CleanupHookCallback::Equal::operator()(
651+
const CleanupHookCallback& a, const CleanupHookCallback& b) const {
652+
return a.fn_ == b.fn_ && a.arg_ == b.arg_;
653+
}
654+
632655
#define VP(PropertyName, StringValue) V(v8::Private, PropertyName)
633656
#define VS(PropertyName, StringValue) V(v8::String, PropertyName)
634657
#define V(TypeName, PropertyName) \
Collapse file

‎src/env.cc‎

Copy file name to clipboardExpand all lines: src/env.cc
+29Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,35 @@ void Environment::PrintSyncTrace() const {
305305
fflush(stderr);
306306
}
307307

308+
void Environment::RunCleanup() {
309+
while (!cleanup_hooks_.empty()) {
310+
// Copy into a vector, since we can't sort an unordered_set in-place.
311+
std::vector<CleanupHookCallback> callbacks(
312+
cleanup_hooks_.begin(), cleanup_hooks_.end());
313+
// We can't erase the copied elements from `cleanup_hooks_` yet, because we
314+
// need to be able to check whether they were un-scheduled by another hook.
315+
316+
std::sort(callbacks.begin(), callbacks.end(),
317+
[](const CleanupHookCallback& a, const CleanupHookCallback& b) {
318+
// Sort in descending order so that the most recently inserted callbacks
319+
// are run first.
320+
return a.insertion_order_counter_ > b.insertion_order_counter_;
321+
});
322+
323+
for (const CleanupHookCallback& cb : callbacks) {
324+
if (cleanup_hooks_.count(cb) == 0) {
325+
// This hook was removed from the `cleanup_hooks_` set during another
326+
// hook that was run earlier. Nothing to do here.
327+
continue;
328+
}
329+
330+
cb.fn_(cb.arg_);
331+
cleanup_hooks_.erase(cb);
332+
CleanupHandles();
333+
}
334+
}
335+
}
336+
308337
void Environment::RunBeforeExitCallbacks() {
309338
for (ExitCallback before_exit : before_exit_functions_) {
310339
before_exit.cb_(before_exit.arg_);
Collapse file

‎src/env.h‎

Copy file name to clipboardExpand all lines: src/env.h
+31Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
#include <stdint.h>
4343
#include <vector>
4444
#include <unordered_map>
45+
#include <unordered_set>
4546

4647
struct nghttp2_rcbuf;
4748

@@ -775,6 +776,10 @@ class Environment {
775776

776777
v8::Local<v8::Value> GetNow();
777778

779+
inline void AddCleanupHook(void (*fn)(void*), void* arg);
780+
inline void RemoveCleanupHook(void (*fn)(void*), void* arg);
781+
void RunCleanup();
782+
778783
private:
779784
inline void CreateImmediate(native_immediate_callback cb,
780785
void* data,
@@ -863,6 +868,32 @@ class Environment {
863868
void RunAndClearNativeImmediates();
864869
static void CheckImmediate(uv_check_t* handle);
865870

871+
struct CleanupHookCallback {
872+
void (*fn_)(void*);
873+
void* arg_;
874+
875+
// We keep track of the insertion order for these objects, so that we can
876+
// call the callbacks in reverse order when we are cleaning up.
877+
uint64_t insertion_order_counter_;
878+
879+
// Only hashes `arg_`, since that is usually enough to identify the hook.
880+
struct Hash {
881+
inline size_t operator()(const CleanupHookCallback& cb) const;
882+
};
883+
884+
// Compares by `fn_` and `arg_` being equal.
885+
struct Equal {
886+
inline bool operator()(const CleanupHookCallback& a,
887+
const CleanupHookCallback& b) const;
888+
};
889+
};
890+
891+
// Use an unordered_set, so that we have efficient insertion and removal.
892+
std::unordered_set<CleanupHookCallback,
893+
CleanupHookCallback::Hash,
894+
CleanupHookCallback::Equal> cleanup_hooks_;
895+
uint64_t cleanup_hook_counter_ = 0;
896+
866897
static void EnvPromiseHook(v8::PromiseHookType type,
867898
v8::Local<v8::Promise> promise,
868899
v8::Local<v8::Value> parent);
Collapse file

‎src/node.cc‎

Copy file name to clipboardExpand all lines: src/node.cc
+19-1Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -908,6 +908,22 @@ void AddPromiseHook(v8::Isolate* isolate, promise_hook_func fn, void* arg) {
908908
env->AddPromiseHook(fn, arg);
909909
}
910910

911+
void AddEnvironmentCleanupHook(v8::Isolate* isolate,
912+
void (*fun)(void* arg),
913+
void* arg) {
914+
Environment* env = Environment::GetCurrent(isolate);
915+
env->AddCleanupHook(fun, arg);
916+
}
917+
918+
919+
void RemoveEnvironmentCleanupHook(v8::Isolate* isolate,
920+
void (*fun)(void* arg),
921+
void* arg) {
922+
Environment* env = Environment::GetCurrent(isolate);
923+
env->RemoveCleanupHook(fun, arg);
924+
}
925+
926+
911927
CallbackScope::CallbackScope(Isolate* isolate,
912928
Local<Object> object,
913929
async_context asyncContext)
@@ -4463,7 +4479,7 @@ Environment* CreateEnvironment(IsolateData* isolate_data,
44634479

44644480

44654481
void FreeEnvironment(Environment* env) {
4466-
env->CleanupHandles();
4482+
env->RunCleanup();
44674483
delete env;
44684484
}
44694485

@@ -4561,6 +4577,8 @@ inline int Start(Isolate* isolate, IsolateData* isolate_data,
45614577
env.set_trace_sync_io(false);
45624578

45634579
const int exit_code = EmitExit(&env);
4580+
4581+
env.RunCleanup();
45644582
RunAtExit(&env);
45654583

45664584
v8_platform.DrainVMTasks(isolate);
Collapse file

‎src/node.h‎

Copy file name to clipboardExpand all lines: src/node.h
+13Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -583,6 +583,19 @@ NODE_EXTERN void AddPromiseHook(v8::Isolate* isolate,
583583
promise_hook_func fn,
584584
void* arg);
585585

586+
/* This is a lot like node::AtExit, except that the hooks added via this
587+
* function are run before the AtExit ones and will always be registered
588+
* for the current Environment instance.
589+
* These functions are safe to use in an addon supporting multiple
590+
* threads/isolates. */
591+
NODE_EXTERN void AddEnvironmentCleanupHook(v8::Isolate* isolate,
592+
void (*fun)(void* arg),
593+
void* arg);
594+
595+
NODE_EXTERN void RemoveEnvironmentCleanupHook(v8::Isolate* isolate,
596+
void (*fun)(void* arg),
597+
void* arg);
598+
586599
/* Returns the id of the current execution context. If the return value is
587600
* zero then no execution has been set. This will happen if the user handles
588601
* I/O from native code. */
Collapse file

‎src/node_api.cc‎

Copy file name to clipboardExpand all lines: src/node_api.cc
+22Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -902,6 +902,28 @@ void napi_module_register(napi_module* mod) {
902902
node::node_module_register(nm);
903903
}
904904

905+
napi_status napi_add_env_cleanup_hook(napi_env env,
906+
void (*fun)(void* arg),
907+
void* arg) {
908+
CHECK_ENV(env);
909+
CHECK_ARG(env, fun);
910+
911+
node::AddEnvironmentCleanupHook(env->isolate, fun, arg);
912+
913+
return napi_ok;
914+
}
915+
916+
napi_status napi_remove_env_cleanup_hook(napi_env env,
917+
void (*fun)(void* arg),
918+
void* arg) {
919+
CHECK_ENV(env);
920+
CHECK_ARG(env, fun);
921+
922+
node::RemoveEnvironmentCleanupHook(env->isolate, fun, arg);
923+
924+
return napi_ok;
925+
}
926+
905927
// Warning: Keep in-sync with napi_status enum
906928
static
907929
const char* error_messages[] = {nullptr,
Collapse file

‎src/node_api.h‎

Copy file name to clipboardExpand all lines: src/node_api.h
+7Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,13 @@ EXTERN_C_START
118118

119119
NAPI_EXTERN void napi_module_register(napi_module* mod);
120120

121+
NAPI_EXTERN napi_status napi_add_env_cleanup_hook(napi_env env,
122+
void (*fun)(void* arg),
123+
void* arg);
124+
NAPI_EXTERN napi_status napi_remove_env_cleanup_hook(napi_env env,
125+
void (*fun)(void* arg),
126+
void* arg);
127+
121128
NAPI_EXTERN napi_status
122129
napi_get_last_error_info(napi_env env,
123130
const napi_extended_error_info** result);
Collapse file
+24Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#include "node_api.h"
2+
#include "uv.h"
3+
#include "../common.h"
4+
5+
namespace {
6+
7+
void cleanup(void* arg) {
8+
printf("cleanup(%d)\n", *static_cast<int*>(arg));
9+
}
10+
11+
int secret = 42;
12+
int wrong_secret = 17;
13+
14+
napi_value Init(napi_env env, napi_value exports) {
15+
napi_add_env_cleanup_hook(env, cleanup, &wrong_secret);
16+
napi_add_env_cleanup_hook(env, cleanup, &secret);
17+
napi_remove_env_cleanup_hook(env, cleanup, &wrong_secret);
18+
19+
return nullptr;
20+
}
21+
22+
} // anonymous namespace
23+
24+
NAPI_MODULE(NODE_GYP_MODULE_NAME, Init)
Collapse file
+9Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
'targets': [
3+
{
4+
'target_name': 'binding',
5+
'defines': [ 'V8_DEPRECATION_WARNINGS=1' ],
6+
'sources': [ 'binding.cc' ]
7+
}
8+
]
9+
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.