-
Notifications
You must be signed in to change notification settings - Fork 29.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
n-api: re-implement async env cleanup hooks #34819
Changes from all commits
28c8a96
5986bab
fc6e0b7
2d7c4f2
6037705
5b694e4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -619,6 +619,15 @@ typedef struct { | |
} napi_type_tag; | ||
``` | ||
|
||
#### napi_async_cleanup_hook_handle | ||
<!-- YAML | ||
added: REPLACEME | ||
--> | ||
|
||
An opaque value returned by [`napi_add_async_cleanup_hook`][]. It must be passed | ||
to [`napi_remove_async_cleanup_hook`][] when the chain of asynchronous cleanup | ||
events completes. | ||
|
||
### N-API callback types | ||
|
||
#### napi_callback_info | ||
|
@@ -747,6 +756,30 @@ typedef void (*napi_threadsafe_function_call_js)(napi_env env, | |
Unless for reasons discussed in [Object Lifetime Management][], creating a | ||
handle and/or callback scope inside the function body is not necessary. | ||
|
||
#### napi_async_cleanup_hook | ||
<!-- YAML | ||
added: REPLACEME | ||
--> | ||
|
||
Function pointer used with [`napi_add_async_cleanup_hook`][]. It will be called | ||
when the environment is being torn down. | ||
|
||
Callback functions must satisfy the following signature: | ||
|
||
```c | ||
typedef void (*napi_async_cleanup_hook)(napi_async_cleanup_hook_handle handle, | ||
void* data); | ||
``` | ||
|
||
* `[in] handle`: The handle that must be passed to | ||
[`napi_remove_async_cleanup_hook`][] after completion of the asynchronous | ||
cleanup. | ||
* `[in] data`: The data that was passed to [`napi_add_async_cleanup_hook`][]. | ||
|
||
The body of the function should initiate the asynchronous cleanup actions at the | ||
end of which `handle` must be passed in a call to | ||
[`napi_remove_async_cleanup_hook`][]. | ||
|
||
## Error handling | ||
|
||
N-API uses both return values and JavaScript exceptions for error handling. | ||
|
@@ -1576,22 +1609,33 @@ with `napi_add_env_cleanup_hook`, otherwise the process will abort. | |
#### napi_add_async_cleanup_hook | ||
<!-- YAML | ||
added: v14.8.0 | ||
changes: | ||
- version: REPLACEME | ||
pr-url: https://github.com/nodejs/node/pull/34819 | ||
description: Changed signature of the `hook` callback. | ||
--> | ||
|
||
> Stability: 1 - Experimental | ||
|
||
```c | ||
NAPI_EXTERN napi_status napi_add_async_cleanup_hook( | ||
napi_env env, | ||
void (*fun)(void* arg, void(* cb)(void*), void* cbarg), | ||
napi_async_cleanup_hook hook, | ||
void* arg, | ||
napi_async_cleanup_hook_handle* remove_handle); | ||
``` | ||
|
||
Registers `fun` as a function to be run with the `arg` parameter once the | ||
current Node.js environment exits. Unlike [`napi_add_env_cleanup_hook`][], | ||
the hook is allowed to be asynchronous in this case, and must invoke the passed | ||
`cb()` function with `cbarg` once all asynchronous activity is finished. | ||
* `[in] env`: The environment that the API is invoked under. | ||
* `[in] hook`: The function pointer to call at environment teardown. | ||
* `[in] arg`: The pointer to pass to `hook` when it gets called. | ||
* `[out] remove_handle`: Optional handle that refers to the asynchronous cleanup | ||
hook. | ||
|
||
Registers `hook`, which is a function of type [`napi_async_cleanup_hook`][], as | ||
a function to be run with the `remove_handle` and `arg` parameters once the | ||
current Node.js environment exits. | ||
|
||
Unlike [`napi_add_env_cleanup_hook`][], the hook is allowed to be asynchronous. | ||
|
||
Otherwise, behavior generally matches that of [`napi_add_env_cleanup_hook`][]. | ||
|
||
|
@@ -1604,19 +1648,25 @@ is being torn down anyway. | |
#### napi_remove_async_cleanup_hook | ||
<!-- YAML | ||
added: v14.8.0 | ||
changes: | ||
- version: REPLACEME | ||
pr-url: https://github.com/nodejs/node/pull/34819 | ||
description: Removed `env` parameter. | ||
--> | ||
|
||
> Stability: 1 - Experimental | ||
|
||
```c | ||
NAPI_EXTERN napi_status napi_remove_async_cleanup_hook( | ||
napi_env env, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I removed the need to pass We store |
||
napi_async_cleanup_hook_handle remove_handle); | ||
``` | ||
|
||
* `[in] remove_handle`: The handle to an asynchronous cleanup hook that was | ||
created with [`napi_add_async_cleanup_hook`][]. | ||
|
||
Unregisters the cleanup hook corresponding to `remove_handle`. This will prevent | ||
the hook from being executed, unless it has already started executing. | ||
This must be called on any `napi_async_cleanup_hook_handle` value retrieved | ||
This must be called on any `napi_async_cleanup_hook_handle` value obtained | ||
from [`napi_add_async_cleanup_hook`][]. | ||
|
||
## Module registration | ||
|
@@ -5759,6 +5809,7 @@ This API may only be called from the main thread. | |
[`napi_add_async_cleanup_hook`]: #n_api_napi_add_async_cleanup_hook | ||
[`napi_add_env_cleanup_hook`]: #n_api_napi_add_env_cleanup_hook | ||
[`napi_add_finalizer`]: #n_api_napi_add_finalizer | ||
[`napi_async_cleanup_hook`]: #n_api_napi_async_cleanup_hook | ||
[`napi_async_complete_callback`]: #n_api_napi_async_complete_callback | ||
[`napi_async_init`]: #n_api_napi_async_init | ||
[`napi_callback`]: #n_api_napi_callback | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -519,41 +519,68 @@ napi_status napi_remove_env_cleanup_hook(napi_env env, | |
} | ||
|
||
struct napi_async_cleanup_hook_handle__ { | ||
node::AsyncCleanupHookHandle 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, | ||
void (*fun)(void* arg, void(* cb)(void*), void* cbarg), | ||
napi_async_cleanup_hook hook, | ||
void* arg, | ||
napi_async_cleanup_hook_handle* remove_handle) { | ||
CHECK_ENV(env); | ||
CHECK_ARG(env, fun); | ||
CHECK_ARG(env, hook); | ||
|
||
auto handle = node::AddEnvironmentCleanupHook(env->isolate, fun, arg); | ||
if (remove_handle != nullptr) { | ||
*remove_handle = new napi_async_cleanup_hook_handle__ { std::move(handle) }; | ||
env->Ref(); | ||
} | ||
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_env env, | ||
napi_async_cleanup_hook_handle remove_handle) { | ||
CHECK_ENV(env); | ||
CHECK_ARG(env, remove_handle); | ||
|
||
node::RemoveEnvironmentCleanupHook(std::move(remove_handle->handle)); | ||
delete remove_handle; | ||
if (remove_handle == nullptr) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it can still be optional to get the handle when the hook is added. When the hook is called it is passed the handle so unless it wants to call remove before the hook runs, it does not need to get/store the handle. I'd suggest we change back to this being optional. |
||
return napi_invalid_arg; | ||
|
||
// 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](node::Environment*) { env->Unref(); }); | ||
delete remove_handle; | ||
|
||
return napi_clear_last_error(env); | ||
return napi_ok; | ||
} | ||
|
||
napi_status napi_fatal_exception(napi_env env, napi_value err) { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you're removing the signature here, please add a link to the definition of
napi_async_cleanup_hook
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added the link below.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do like how this better hides the internal implementation. It seemed like we were leaking a bit more than needed in the N-API APIs wrapping the internal node api.