Skip to content

Commit 28c8a96

Browse files
author
Gabriel Schulhof
committed
n-api: re-implement async env cleanup hooks
* Avoid passing core `void*` and function pointers into add-on. * Document `napi_async_cleanup_hook_handle` type. * Render receipt of the handle mandatory. Removal of the handle remains mandatory. Fixes: nodejs#34715 Signed-off-by: Gabriel Schulhof <[email protected]>
1 parent 42b5f5f commit 28c8a96

File tree

5 files changed

+108
-60
lines changed

5 files changed

+108
-60
lines changed

doc/api/n-api.md

+52-12
Original file line numberDiff line numberDiff line change
@@ -619,6 +619,15 @@ typedef struct {
619619
} napi_type_tag;
620620
```
621621

622+
#### napi_async_cleanup_hook_handle
623+
<!-- YAML
624+
added: REPLACEME
625+
-->
626+
627+
An opaque value returned by [`napi_add_async_cleanup_hook`][]. It must be passed
628+
to [`napi_remove_async_cleanup_hook`][] when the chain of asynchronous cleanup
629+
events completes.
630+
622631
### N-API callback types
623632

624633
#### napi_callback_info
@@ -747,6 +756,28 @@ typedef void (*napi_threadsafe_function_call_js)(napi_env env,
747756
Unless for reasons discussed in [Object Lifetime Management][], creating a
748757
handle and/or callback scope inside the function body is not necessary.
749758

759+
#### napi_async_cleanup_hook
760+
<!-- YAML
761+
added: REPLACEME
762+
-->
763+
764+
Function pointer used with [`napi_add_async_cleanup_hook`][]. It will be called
765+
when the environment is being torn down.
766+
767+
Callback functions must satisfy the following signature:
768+
769+
```c
770+
typedef void (*napi_async_cleanup_hook)(napi_async_cleanup_hook_handle handle,
771+
void* data);
772+
```
773+
774+
* `[in] handle`: The handle obtained from [`napi_add_async_cleanup_hook`][].
775+
* `[in] data`: The data that was passed to [`napi_add_async_cleanup_hook`][].
776+
777+
The body of the function should initiate the asynchronous cleanup actions at the
778+
end of which `handle` must be passed in a call to
779+
[`napi_remove_async_cleanup_hook`][].
780+
750781
## Error handling
751782

752783
N-API uses both return values and JavaScript exceptions for error handling.
@@ -1583,23 +1614,30 @@ added: v14.8.0
15831614
```c
15841615
NAPI_EXTERN napi_status napi_add_async_cleanup_hook(
15851616
napi_env env,
1586-
void (*fun)(void* arg, void(* cb)(void*), void* cbarg),
1617+
napi_async_cleanup_hook hook,
15871618
void* arg,
15881619
napi_async_cleanup_hook_handle* remove_handle);
15891620
```
15901621

1591-
Registers `fun` as a function to be run with the `arg` parameter once the
1592-
current Node.js environment exits. Unlike [`napi_add_env_cleanup_hook`][],
1593-
the hook is allowed to be asynchronous in this case, and must invoke the passed
1594-
`cb()` function with `cbarg` once all asynchronous activity is finished.
1622+
* `[in] env`: The environment that the API is invoked under.
1623+
* `[in] hook`: The function pointer to call at environment teardown.
1624+
* `[in] arg`: The pointer to pass to `hook` when it gets called.
1625+
* `[out] remove_handle`: The handle that refers to the asynchronous cleanup
1626+
hook. This handle must be passed to [`napi_remove_async_cleanup_hook`][] even if
1627+
`hook` gets called.
1628+
1629+
Registers `hook` as a function to be run with the `remove_handle` and `arg`
1630+
parameters once the current Node.js environment exits. Unlike
1631+
[`napi_add_env_cleanup_hook`][], the hook is allowed to be asynchronous, and
1632+
must pass `remove_handle` in a call to [`napi_remove_env_cleanup_hook`][] once
1633+
all asynchronous activity is finished.
15951634

15961635
Otherwise, behavior generally matches that of [`napi_add_env_cleanup_hook`][].
15971636

1598-
If `remove_handle` is not `NULL`, an opaque value will be stored in it
1599-
that must later be passed to [`napi_remove_async_cleanup_hook`][],
1600-
regardless of whether the hook has already been invoked.
1601-
Typically, that happens when the resource for which this hook was added
1602-
is being torn down anyway.
1637+
An opaque value will be stored in `remove_handle` that must later be passed to
1638+
[`napi_remove_async_cleanup_hook`][], regardless of whether the hook has already
1639+
been invoked. Typically, that happens when the resource for which this hook was
1640+
added is being torn down anyway.
16031641

16041642
#### napi_remove_async_cleanup_hook
16051643
<!-- YAML
@@ -1610,13 +1648,15 @@ added: v14.8.0
16101648

16111649
```c
16121650
NAPI_EXTERN napi_status napi_remove_async_cleanup_hook(
1613-
napi_env env,
16141651
napi_async_cleanup_hook_handle remove_handle);
16151652
```
16161653

1654+
* `[in] remove_handle`: The handle to an asynchronous cleanup hook that was
1655+
created with [`napi_add_async_cleanup_hook`][].
1656+
16171657
Unregisters the cleanup hook corresponding to `remove_handle`. This will prevent
16181658
the hook from being executed, unless it has already started executing.
1619-
This must be called on any `napi_async_cleanup_hook_handle` value retrieved
1659+
This must be called on any `napi_async_cleanup_hook_handle` value obtained
16201660
from [`napi_add_async_cleanup_hook`][].
16211661

16221662
## Module registration

src/node_api.cc

+44-19
Original file line numberDiff line numberDiff line change
@@ -519,41 +519,66 @@ napi_status napi_remove_env_cleanup_hook(napi_env env,
519519
}
520520

521521
struct napi_async_cleanup_hook_handle__ {
522-
node::AsyncCleanupHookHandle handle;
522+
napi_async_cleanup_hook_handle__(napi_env env,
523+
napi_async_cleanup_hook user_hook,
524+
void* user_data):
525+
handle_(std::move(node::AddEnvironmentCleanupHook(env->isolate,
526+
Hook,
527+
this))),
528+
env_(env),
529+
user_hook_(user_hook),
530+
user_data_(user_data) {
531+
env->Ref();
532+
}
533+
534+
~napi_async_cleanup_hook_handle__() {
535+
node::RemoveEnvironmentCleanupHook(std::move(handle_));
536+
if (done_cb_)
537+
done_cb_(done_data_);
538+
539+
// Release the `env` handle asynchronously since it would be surprising if
540+
// a call to a N-API function would destroy `env` synchronously.
541+
static_cast<node_napi_env>(env_)->node_env()
542+
->SetImmediate([env = env_](node::Environment*) { env->Unref(); });
543+
}
544+
545+
static void Hook(void* data, void (*done_cb)(void*), void* done_data) {
546+
auto handle = static_cast<napi_async_cleanup_hook_handle__*>(data);
547+
handle->done_cb_ = done_cb;
548+
handle->done_data_ = done_data;
549+
handle->user_hook_(handle, handle->user_data_);
550+
}
551+
552+
node::AsyncCleanupHookHandle handle_;
553+
napi_env env_ = nullptr;
554+
napi_async_cleanup_hook user_hook_ = nullptr;
555+
void* user_data_ = nullptr;
556+
void (*done_cb_)(void*) = nullptr;
557+
void* done_data_ = nullptr;
523558
};
524559

525560
napi_status napi_add_async_cleanup_hook(
526561
napi_env env,
527-
void (*fun)(void* arg, void(* cb)(void*), void* cbarg),
562+
napi_async_cleanup_hook hook,
528563
void* arg,
529564
napi_async_cleanup_hook_handle* remove_handle) {
530565
CHECK_ENV(env);
531-
CHECK_ARG(env, fun);
532-
533-
auto handle = node::AddEnvironmentCleanupHook(env->isolate, fun, arg);
534-
if (remove_handle != nullptr) {
535-
*remove_handle = new napi_async_cleanup_hook_handle__ { std::move(handle) };
536-
env->Ref();
537-
}
566+
CHECK_ARG(env, hook);
567+
CHECK_ARG(env, remove_handle);
538568

569+
*remove_handle = new napi_async_cleanup_hook_handle__(env, hook, arg);
539570
return napi_clear_last_error(env);
540571
}
541572

542573
napi_status napi_remove_async_cleanup_hook(
543-
napi_env env,
544574
napi_async_cleanup_hook_handle remove_handle) {
545-
CHECK_ENV(env);
546-
CHECK_ARG(env, remove_handle);
547575

548-
node::RemoveEnvironmentCleanupHook(std::move(remove_handle->handle));
549-
delete remove_handle;
576+
if (remove_handle == nullptr)
577+
return napi_invalid_arg;
550578

551-
// Release the `env` handle asynchronously since it would be surprising if
552-
// a call to a N-API function would destroy `env` synchronously.
553-
static_cast<node_napi_env>(env)->node_env()
554-
->SetImmediate([env](node::Environment*) { env->Unref(); });
579+
delete remove_handle;
555580

556-
return napi_clear_last_error(env);
581+
return napi_ok;
557582
}
558583

559584
napi_status napi_fatal_exception(napi_env env, napi_value err) {

src/node_api.h

+1-2
Original file line numberDiff line numberDiff line change
@@ -254,12 +254,11 @@ napi_ref_threadsafe_function(napi_env env, napi_threadsafe_function func);
254254

255255
NAPI_EXTERN napi_status napi_add_async_cleanup_hook(
256256
napi_env env,
257-
void (*fun)(void* arg, void(* cb)(void*), void* cbarg),
257+
napi_async_cleanup_hook hook,
258258
void* arg,
259259
napi_async_cleanup_hook_handle* remove_handle);
260260

261261
NAPI_EXTERN napi_status napi_remove_async_cleanup_hook(
262-
napi_env env,
263262
napi_async_cleanup_hook_handle remove_handle);
264263

265264
#endif // NAPI_EXPERIMENTAL

src/node_api_types.h

+2
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ typedef struct {
4343

4444
#ifdef NAPI_EXPERIMENTAL
4545
typedef struct napi_async_cleanup_hook_handle__* napi_async_cleanup_hook_handle;
46+
typedef void (*napi_async_cleanup_hook)(napi_async_cleanup_hook_handle handle,
47+
void* data);
4648
#endif // NAPI_EXPERIMENTAL
4749

4850
#endif // SRC_NODE_API_TYPES_H_

test/node-api/test_async_cleanup_hook/binding.c

+9-27
Original file line numberDiff line numberDiff line change
@@ -5,44 +5,34 @@
55
#include <stdlib.h>
66
#include "../../js-native-api/common.h"
77

8-
void MustNotCall(void* arg, void(*cb)(void*), void* cbarg) {
8+
static void MustNotCall(napi_async_cleanup_hook_handle hook, void* arg) {
99
assert(0);
1010
}
1111

1212
struct AsyncData {
1313
uv_async_t async;
1414
napi_env env;
1515
napi_async_cleanup_hook_handle handle;
16-
void (*done_cb)(void*);
17-
void* done_arg;
1816
};
1917

20-
struct AsyncData* CreateAsyncData() {
18+
static struct AsyncData* CreateAsyncData() {
2119
struct AsyncData* data = (struct AsyncData*) malloc(sizeof(struct AsyncData));
2220
data->handle = NULL;
2321
return data;
2422
}
2523

26-
void AfterCleanupHookTwo(uv_handle_t* handle) {
24+
static void AfterCleanupHookTwo(uv_handle_t* handle) {
2725
struct AsyncData* data = (struct AsyncData*) handle->data;
28-
data->done_cb(data->done_arg);
26+
napi_status status = napi_remove_async_cleanup_hook(data->handle);
27+
assert(status == napi_ok);
2928
free(data);
3029
}
3130

32-
void AfterCleanupHookOne(uv_async_t* async) {
33-
struct AsyncData* data = (struct AsyncData*) async->data;
34-
if (data->handle != NULL) {
35-
// Verify that removing the hook is okay between starting and finishing
36-
// of its execution.
37-
napi_status status =
38-
napi_remove_async_cleanup_hook(data->env, data->handle);
39-
assert(status == napi_ok);
40-
}
41-
31+
static void AfterCleanupHookOne(uv_async_t* async) {
4232
uv_close((uv_handle_t*) async, AfterCleanupHookTwo);
4333
}
4434

45-
void AsyncCleanupHook(void* arg, void(*cb)(void*), void* cbarg) {
35+
static void AsyncCleanupHook(napi_async_cleanup_hook_handle handle, void* arg) {
4636
struct AsyncData* data = (struct AsyncData*) arg;
4737
uv_loop_t* loop;
4838
napi_status status = napi_get_uv_event_loop(data->env, &loop);
@@ -51,29 +41,21 @@ void AsyncCleanupHook(void* arg, void(*cb)(void*), void* cbarg) {
5141
assert(err == 0);
5242

5343
data->async.data = data;
54-
data->done_cb = cb;
55-
data->done_arg = cbarg;
5644
uv_async_send(&data->async);
5745
}
5846

59-
napi_value Init(napi_env env, napi_value exports) {
47+
static napi_value Init(napi_env env, napi_value exports) {
6048
{
6149
struct AsyncData* data = CreateAsyncData();
6250
data->env = env;
6351
napi_add_async_cleanup_hook(env, AsyncCleanupHook, data, &data->handle);
6452
}
6553

66-
{
67-
struct AsyncData* data = CreateAsyncData();
68-
data->env = env;
69-
napi_add_async_cleanup_hook(env, AsyncCleanupHook, data, NULL);
70-
}
71-
7254
{
7355
napi_async_cleanup_hook_handle must_not_call_handle;
7456
napi_add_async_cleanup_hook(
7557
env, MustNotCall, NULL, &must_not_call_handle);
76-
napi_remove_async_cleanup_hook(env, must_not_call_handle);
58+
napi_remove_async_cleanup_hook(must_not_call_handle);
7759
}
7860

7961
return NULL;

0 commit comments

Comments
 (0)