Skip to content

Commit 32906dd

Browse files
gabrielschulhofrichardlau
authored andcommitted
node-api: segregate nogc APIs from rest via type system
We define a new type called `node_api_nogc_env` as the `const` version of `napi_env` and `node_api_nogc_finalize` as a variant of `napi_finalize` that accepts a `node_api_nogc_env` as its first argument. We then modify those APIs which do not affect GC state as accepting a `node_api_nogc_env`. APIs accepting finalizer callbacks are modified to accept `node_api_nogc_finalize` callbacks. Thus, the only way to attach a `napi_finalize` callback, wherein Node-APIs affecting GC state may be called is to call `node_api_post_finalizer` from a `node_api_nogc_finalize` callback. In keeping with the process of introducing new Node-APIs, this feature is guarded by `NAPI_EXPERIMENTAL`. Since this feature modifies APIs already marked as stable, it is additionally guared by `NODE_API_EXPERIMENTAL_NOGC_ENV`, so as to provide a further buffer to adoption. Nevertheless, both guards must be removed upon releasing a new version of Node-API. PR-URL: #50060 Reviewed-By: Chengzhong Wu <[email protected]> Reviewed-By: Vladimir Morozov <[email protected]> Reviewed-By: Michael Dawson <[email protected]> Backport-PR-URL: #51804 (cherry picked from commit 7a216d5)
1 parent 1aa71c2 commit 32906dd

File tree

16 files changed

+368
-146
lines changed

16 files changed

+368
-146
lines changed

doc/api/n-api.md

+103-36
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ it still gets the benefits of the ABI stability provided by the C API.
7878
When using `node-addon-api` instead of the C APIs, start with the API [docs][]
7979
for `node-addon-api`.
8080

81-
The [Node-API Resource](https://nodejs.github.io/node-addon-examples/) offers
81+
The [Node-API Resource](https://nodejs.github.io/node-addon-examples/) offers
8282
an excellent orientation and tips for developers just getting started with
8383
Node-API and `node-addon-api`. Additional media resources can be found on the
8484
[Node-API Media][] page.
@@ -175,7 +175,8 @@ developers have run into limitations in node-gyp.
175175
[CMake.js][] is an alternative build system based on [CMake][].
176176

177177
CMake.js is a good choice for projects that already use CMake or for
178-
developers affected by limitations in node-gyp.
178+
developers affected by limitations in node-gyp. [`build_with_cmake`][] is an
179+
example of a CMake-based native addon project.
179180

180181
### Uploading precompiled binaries
181182

@@ -237,6 +238,18 @@ Some of the Node-API surface is experimental and requires explicit opt-in:
237238
In this case the entire API surface, including any experimental APIs, will be
238239
available to the module code.
239240

241+
Occasionally, experimental features are introduced that affect already-released
242+
and stable APIs. These features can be disabled by an opt-out:
243+
244+
```c
245+
#define NAPI_EXPERIMENTAL
246+
#define NODE_API_EXPERIMENTAL_<FEATURE_NAME>_OPT_OUT
247+
#include <node_api.h>
248+
```
249+
250+
where `<FEATURE_NAME>` is the name of an experimental feature that affects both
251+
experimental and stable APIs.
252+
240253
## Node-API version matrix
241254

242255
Node-API versions are additive and versioned independently from Node.js.
@@ -398,7 +411,7 @@ napi_value create_addon(napi_env env) {
398411
#include <node_api.h>
399412
#include "addon.h"
400413

401-
NAPI_MODULE_INIT() {
414+
NAPI_MODULE_INIT(/* napi_env env, napi_value exports */) {
402415
// This function body is expected to return a `napi_value`.
403416
// The variables `napi_env env` and `napi_value exports` may be used within
404417
// the body, as they are provided by the definition of `NAPI_MODULE_INIT()`.
@@ -443,7 +456,7 @@ napiVersion: 6
443456
-->
444457

445458
```c
446-
napi_status napi_set_instance_data(napi_env env,
459+
napi_status napi_set_instance_data(node_api_nogc_env env,
447460
void* data,
448461
napi_finalize finalize_cb,
449462
void* finalize_hint);
@@ -475,7 +488,7 @@ napiVersion: 6
475488
-->
476489

477490
```c
478-
napi_status napi_get_instance_data(napi_env env,
491+
napi_status napi_get_instance_data(node_api_nogc_env env,
479492
void** data);
480493
```
481494

@@ -577,6 +590,22 @@ when an instance of a native addon is unloaded. Notification of this event is
577590
delivered through the callbacks given to [`napi_add_env_cleanup_hook`][] and
578591
[`napi_set_instance_data`][].
579592

593+
### `node_api_nogc_env`
594+
595+
> Stability: 1 - Experimental
596+
597+
This variant of `napi_env` is passed to synchronous finalizers
598+
([`node_api_nogc_finalize`][]). There is a subset of Node-APIs which accept
599+
a parameter of type `node_api_nogc_env` as their first argument. These APIs do
600+
not access the state of the JavaScript engine and are thus safe to call from
601+
synchronous finalizers. Passing a parameter of type `napi_env` to these APIs is
602+
allowed, however, passing a parameter of type `node_api_nogc_env` to APIs that
603+
access the JavaScript engine state is not allowed. Attempting to do so without
604+
a cast will produce a compiler warning or an error when add-ons are compiled
605+
with flags which cause them to emit warnings and/or errors when incorrect
606+
pointer types are passed into a function. Calling such APIs from a synchronous
607+
finalizer will ultimately result in the termination of the application.
608+
580609
### `napi_value`
581610

582611
This is an opaque pointer that is used to represent a JavaScript value.
@@ -741,32 +770,36 @@ typedef napi_value (*napi_callback)(napi_env, napi_callback_info);
741770
Unless for reasons discussed in [Object Lifetime Management][], creating a
742771
handle and/or callback scope inside a `napi_callback` is not necessary.
743772

744-
#### `napi_finalize`
773+
#### `node_api_nogc_finalize`
745774

746775
<!-- YAML
747-
added: v8.0.0
748-
napiVersion: 1
776+
added: REPLACEME
749777
-->
750778

779+
> Stability: 1 - Experimental
780+
751781
Function pointer type for add-on provided functions that allow the user to be
752782
notified when externally-owned data is ready to be cleaned up because the
753-
object with which it was associated with has been garbage-collected. The user
754-
must provide a function satisfying the following signature which would get
755-
called upon the object's collection. Currently, `napi_finalize` can be used for
783+
object it was associated with has been garbage-collected. The user must provide
784+
a function satisfying the following signature which would get called upon the
785+
object's collection. Currently, `node_api_nogc_finalize` can be used for
756786
finding out when objects that have external data are collected.
757787

758788
```c
759-
typedef void (*napi_finalize)(napi_env env,
760-
void* finalize_data,
761-
void* finalize_hint);
789+
typedef void (*node_api_nogc_finalize)(node_api_nogc_env env,
790+
void* finalize_data,
791+
void* finalize_hint);
762792
```
763793

764794
Unless for reasons discussed in [Object Lifetime Management][], creating a
765795
handle and/or callback scope inside the function body is not necessary.
766796

767797
Since these functions may be called while the JavaScript engine is in a state
768-
where it cannot execute JavaScript code, some Node-API calls may return
769-
`napi_pending_exception` even when there is no exception pending.
798+
where it cannot execute JavaScript code, only Node-APIs which accept a
799+
`node_api_nogc_env` as their first parameter may be called.
800+
[`node_api_post_finalizer`][] can be used to schedule Node-API calls that
801+
require access to the JavaScript engine's state to run after the current
802+
garbage collection cycle has completed.
770803

771804
In the case of [`node_api_create_external_string_latin1`][] and
772805
[`node_api_create_external_string_utf16`][] the `env` parameter may be null,
@@ -775,11 +808,39 @@ shutdown.
775808

776809
Change History:
777810

811+
* experimental (`NAPI_EXPERIMENTAL`):
812+
813+
Only Node-API calls that accept a `node_api_nogc_env` as their first
814+
parameter may be called, otherwise the application will be terminated with an
815+
appropriate error message. This feature can be turned off by defining
816+
`NODE_API_EXPERIMENTAL_NOGC_ENV_OPT_OUT`.
817+
818+
#### `napi_finalize`
819+
820+
<!-- YAML
821+
added: v8.0.0
822+
napiVersion: 1
823+
-->
824+
825+
Function pointer type for add-on provided function that allow the user to
826+
schedule a group of calls to Node-APIs in response to a garbage collection
827+
event, after the garbage collection cycle has completed. These function
828+
pointers can be used with [`node_api_post_finalizer`][].
829+
830+
```c
831+
typedef void (*napi_finalize)(napi_env env,
832+
void* finalize_data,
833+
void* finalize_hint);
834+
```
835+
836+
Change History:
837+
778838
* experimental (`NAPI_EXPERIMENTAL` is defined):
779839

780-
Node-API calls made from a finalizer will return `napi_cannot_run_js` when
781-
the JavaScript engine is unable to execute JavaScript, and will return
782-
`napi_exception_pending` if there is a pending exception.
840+
A function of this type may no longer be used as a finalizer, except with
841+
[`node_api_post_finalizer`][]. [`node_api_nogc_finalize`][] must be used
842+
instead. This feature can be turned off by defining
843+
`NODE_API_EXPERIMENTAL_NOGC_ENV_OPT_OUT`.
783844

784845
#### `napi_async_execute_callback`
785846

@@ -979,7 +1040,7 @@ napiVersion: 1
9791040

9801041
```c
9811042
napi_status
982-
napi_get_last_error_info(napi_env env,
1043+
napi_get_last_error_info(node_api_nogc_env env,
9831044
const napi_extended_error_info** result);
9841045
```
9851046

@@ -1798,7 +1859,7 @@ napiVersion: 3
17981859
-->
17991860

18001861
```c
1801-
NODE_EXTERN napi_status napi_add_env_cleanup_hook(napi_env env,
1862+
NODE_EXTERN napi_status napi_add_env_cleanup_hook(node_api_nogc_env env,
18021863
napi_cleanup_hook fun,
18031864
void* arg);
18041865
```
@@ -1828,7 +1889,7 @@ napiVersion: 3
18281889
-->
18291890

18301891
```c
1831-
NAPI_EXTERN napi_status napi_remove_env_cleanup_hook(napi_env env,
1892+
NAPI_EXTERN napi_status napi_remove_env_cleanup_hook(node_api_nogc_env env,
18321893
void (*fun)(void* arg),
18331894
void* arg);
18341895
```
@@ -1857,7 +1918,7 @@ changes:
18571918

18581919
```c
18591920
NAPI_EXTERN napi_status napi_add_async_cleanup_hook(
1860-
napi_env env,
1921+
node_api_nogc_env env,
18611922
napi_async_cleanup_hook hook,
18621923
void* arg,
18631924
napi_async_cleanup_hook_handle* remove_handle);
@@ -2015,7 +2076,7 @@ You can also use the `NAPI_MODULE_INIT` macro, which acts as a shorthand
20152076
for `NAPI_MODULE` and defining an `Init` function:
20162077

20172078
```c
2018-
NAPI_MODULE_INIT() {
2079+
NAPI_MODULE_INIT(/* napi_env env, napi_value exports */) {
20192080
napi_value answer;
20202081
napi_status result;
20212082

@@ -2029,6 +2090,9 @@ NAPI_MODULE_INIT() {
20292090
}
20302091
```
20312092

2093+
The parameters `env` and `exports` are provided to the body of the
2094+
`NAPI_MODULE_INIT` macro.
2095+
20322096
All Node-API addons are context-aware, meaning they may be loaded multiple
20332097
times. There are a few design considerations when declaring such a module.
20342098
The documentation on [context-aware addons][] provides more details.
@@ -5391,7 +5455,7 @@ napiVersion: 5
53915455
napi_status napi_add_finalizer(napi_env env,
53925456
napi_value js_object,
53935457
void* finalize_data,
5394-
napi_finalize finalize_cb,
5458+
node_api_nogc_finalize finalize_cb,
53955459
void* finalize_hint,
53965460
napi_ref* result);
53975461
```
@@ -5429,7 +5493,7 @@ added: v18.19.0
54295493
> Stability: 1 - Experimental
54305494

54315495
```c
5432-
napi_status node_api_post_finalizer(napi_env env,
5496+
napi_status node_api_post_finalizer(node_api_nogc_env env,
54335497
napi_finalize finalize_cb,
54345498
void* finalize_data,
54355499
void* finalize_hint);
@@ -5499,7 +5563,7 @@ Once created the async worker can be queued
54995563
for execution using the [`napi_queue_async_work`][] function:
55005564

55015565
```c
5502-
napi_status napi_queue_async_work(napi_env env,
5566+
napi_status napi_queue_async_work(node_api_nogc_env env,
55035567
napi_async_work work);
55045568
```
55055569

@@ -5591,7 +5655,7 @@ napiVersion: 1
55915655
-->
55925656

55935657
```c
5594-
napi_status napi_queue_async_work(napi_env env,
5658+
napi_status napi_queue_async_work(node_api_nogc_env env,
55955659
napi_async_work work);
55965660
```
55975661

@@ -5612,7 +5676,7 @@ napiVersion: 1
56125676
-->
56135677

56145678
```c
5615-
napi_status napi_cancel_async_work(napi_env env,
5679+
napi_status napi_cancel_async_work(node_api_nogc_env env,
56165680
napi_async_work work);
56175681
```
56185682

@@ -5816,7 +5880,7 @@ typedef struct {
58165880
const char* release;
58175881
} napi_node_version;
58185882

5819-
napi_status napi_get_node_version(napi_env env,
5883+
napi_status napi_get_node_version(node_api_nogc_env env,
58205884
const napi_node_version** version);
58215885
```
58225886

@@ -5839,7 +5903,7 @@ napiVersion: 1
58395903
-->
58405904

58415905
```c
5842-
napi_status napi_get_version(napi_env env,
5906+
napi_status napi_get_version(node_api_nogc_env env,
58435907
uint32_t* result);
58445908
```
58455909

@@ -5872,7 +5936,7 @@ napiVersion: 1
58725936
-->
58735937

58745938
```c
5875-
NAPI_EXTERN napi_status napi_adjust_external_memory(napi_env env,
5939+
NAPI_EXTERN napi_status napi_adjust_external_memory(node_api_nogc_env env,
58765940
int64_t change_in_bytes,
58775941
int64_t* result);
58785942
```
@@ -6089,7 +6153,7 @@ napiVersion: 2
60896153
-->
60906154

60916155
```c
6092-
NAPI_EXTERN napi_status napi_get_uv_event_loop(napi_env env,
6156+
NAPI_EXTERN napi_status napi_get_uv_event_loop(node_api_nogc_env env,
60936157
struct uv_loop_s** loop);
60946158
```
60956159

@@ -6403,7 +6467,7 @@ napiVersion: 4
64036467

64046468
```c
64056469
NAPI_EXTERN napi_status
6406-
napi_ref_threadsafe_function(napi_env env, napi_threadsafe_function func);
6470+
napi_ref_threadsafe_function(node_api_nogc_env env, napi_threadsafe_function func);
64076471
```
64086472

64096473
* `[in] env`: The environment that the API is invoked under.
@@ -6429,7 +6493,7 @@ napiVersion: 4
64296493

64306494
```c
64316495
NAPI_EXTERN napi_status
6432-
napi_unref_threadsafe_function(napi_env env, napi_threadsafe_function func);
6496+
napi_unref_threadsafe_function(node_api_nogc_env env, napi_threadsafe_function func);
64336497
```
64346498

64356499
* `[in] env`: The environment that the API is invoked under.
@@ -6455,7 +6519,7 @@ napiVersion: 9
64556519

64566520
```c
64576521
NAPI_EXTERN napi_status
6458-
node_api_get_module_file_name(napi_env env, const char** result);
6522+
node_api_get_module_file_name(node_api_nogc_env env, const char** result);
64596523

64606524
```
64616525

@@ -6519,6 +6583,7 @@ the add-on's file name during loading.
65196583
[`Number.MIN_SAFE_INTEGER`]: https://tc39.github.io/ecma262/#sec-number.min_safe_integer
65206584
[`Worker`]: worker_threads.md#class-worker
65216585
[`async_hooks.executionAsyncResource()`]: async_hooks.md#async_hooksexecutionasyncresource
6586+
[`build_with_cmake`]: https://github.com/nodejs/node-addon-examples/tree/main/build_with_cmake
65226587
[`global`]: globals.md#global
65236588
[`init` hooks]: async_hooks.md#initasyncid-type-triggerasyncid-resource
65246589
[`napi_add_async_cleanup_hook`]: #napi_add_async_cleanup_hook
@@ -6582,6 +6647,8 @@ the add-on's file name during loading.
65826647
[`node_api_create_external_string_latin1`]: #node_api_create_external_string_latin1
65836648
[`node_api_create_external_string_utf16`]: #node_api_create_external_string_utf16
65846649
[`node_api_create_syntax_error`]: #node_api_create_syntax_error
6650+
[`node_api_nogc_finalize`]: #node_api_nogc_finalize
6651+
[`node_api_post_finalizer`]: #node_api_post_finalizer
65856652
[`node_api_throw_syntax_error`]: #node_api_throw_syntax_error
65866653
[`process.release`]: process.md#processrelease
65876654
[`uv_ref`]: https://docs.libuv.org/en/v1.x/handle.html#c.uv_ref

doc/contributing/adding-new-napi-api.md

+13
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,16 @@ Node-API.
5151
to the decision to take an API out of experimental status.
5252
* The API **must** be implemented in a Node.js implementation with an
5353
alternate VM.
54+
55+
Since the adoption of the policy whereby moving to a later version of Node-API
56+
from an earlier version may entail rework of existing code, it is possible to
57+
introduce modifications to already-released Node-APIs, as long as the
58+
modifications affect neither the ABI nor the API of earlier versions. Such
59+
modifications **must** be accompanied by an opt-out flag. This provides add-on
60+
maintainers who take advantage of the initial compile-time flag to track
61+
impending changes to Node-API with
62+
63+
* a quick fix to the breakage caused,
64+
* a notification that such breakage is impending, and thus
65+
* a buffer to adoption above and beyond the one provided by the initial
66+
compile-time flag.

0 commit comments

Comments
 (0)