Skip to content

Commit b2135ae

Browse files
gabrielschulhofRafaelGSS
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 <legendecas@gmail.com> Reviewed-By: Vladimir Morozov <vmorozov@microsoft.com> Reviewed-By: Michael Dawson <midawson@redhat.com>
1 parent c96ef65 commit b2135ae

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
Up until version 9, Node-API versions were additive and versioned
@@ -419,7 +432,7 @@ napi_value create_addon(napi_env env) {
419432
#include <node_api.h>
420433
#include "addon.h"
421434

422-
NAPI_MODULE_INIT() {
435+
NAPI_MODULE_INIT(/* napi_env env, napi_value exports */) {
423436
// This function body is expected to return a `napi_value`.
424437
// The variables `napi_env env` and `napi_value exports` may be used within
425438
// the body, as they are provided by the definition of `NAPI_MODULE_INIT()`.
@@ -464,7 +477,7 @@ napiVersion: 6
464477
-->
465478

466479
```c
467-
napi_status napi_set_instance_data(napi_env env,
480+
napi_status napi_set_instance_data(node_api_nogc_env env,
468481
void* data,
469482
napi_finalize finalize_cb,
470483
void* finalize_hint);
@@ -496,7 +509,7 @@ napiVersion: 6
496509
-->
497510

498511
```c
499-
napi_status napi_get_instance_data(napi_env env,
512+
napi_status napi_get_instance_data(node_api_nogc_env env,
500513
void** data);
501514
```
502515

@@ -598,6 +611,22 @@ when an instance of a native addon is unloaded. Notification of this event is
598611
delivered through the callbacks given to [`napi_add_env_cleanup_hook`][] and
599612
[`napi_set_instance_data`][].
600613

614+
### `node_api_nogc_env`
615+
616+
> Stability: 1 - Experimental
617+
618+
This variant of `napi_env` is passed to synchronous finalizers
619+
([`node_api_nogc_finalize`][]). There is a subset of Node-APIs which accept
620+
a parameter of type `node_api_nogc_env` as their first argument. These APIs do
621+
not access the state of the JavaScript engine and are thus safe to call from
622+
synchronous finalizers. Passing a parameter of type `napi_env` to these APIs is
623+
allowed, however, passing a parameter of type `node_api_nogc_env` to APIs that
624+
access the JavaScript engine state is not allowed. Attempting to do so without
625+
a cast will produce a compiler warning or an error when add-ons are compiled
626+
with flags which cause them to emit warnings and/or errors when incorrect
627+
pointer types are passed into a function. Calling such APIs from a synchronous
628+
finalizer will ultimately result in the termination of the application.
629+
601630
### `napi_value`
602631

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

765-
#### `napi_finalize`
794+
#### `node_api_nogc_finalize`
766795

767796
<!-- YAML
768-
added: v8.0.0
769-
napiVersion: 1
797+
added: REPLACEME
770798
-->
771799

800+
> Stability: 1 - Experimental
801+
772802
Function pointer type for add-on provided functions that allow the user to be
773803
notified when externally-owned data is ready to be cleaned up because the
774-
object with which it was associated with has been garbage-collected. The user
775-
must provide a function satisfying the following signature which would get
776-
called upon the object's collection. Currently, `napi_finalize` can be used for
804+
object it was associated with has been garbage-collected. The user must provide
805+
a function satisfying the following signature which would get called upon the
806+
object's collection. Currently, `node_api_nogc_finalize` can be used for
777807
finding out when objects that have external data are collected.
778808

779809
```c
780-
typedef void (*napi_finalize)(napi_env env,
781-
void* finalize_data,
782-
void* finalize_hint);
810+
typedef void (*node_api_nogc_finalize)(node_api_nogc_env env,
811+
void* finalize_data,
812+
void* finalize_hint);
783813
```
784814

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

788818
Since these functions may be called while the JavaScript engine is in a state
789-
where it cannot execute JavaScript code, some Node-API calls may return
790-
`napi_pending_exception` even when there is no exception pending.
819+
where it cannot execute JavaScript code, only Node-APIs which accept a
820+
`node_api_nogc_env` as their first parameter may be called.
821+
[`node_api_post_finalizer`][] can be used to schedule Node-API calls that
822+
require access to the JavaScript engine's state to run after the current
823+
garbage collection cycle has completed.
791824

792825
In the case of [`node_api_create_external_string_latin1`][] and
793826
[`node_api_create_external_string_utf16`][] the `env` parameter may be null,
@@ -796,11 +829,39 @@ shutdown.
796829

797830
Change History:
798831

832+
* experimental (`NAPI_EXPERIMENTAL`):
833+
834+
Only Node-API calls that accept a `node_api_nogc_env` as their first
835+
parameter may be called, otherwise the application will be terminated with an
836+
appropriate error message. This feature can be turned off by defining
837+
`NODE_API_EXPERIMENTAL_NOGC_ENV_OPT_OUT`.
838+
839+
#### `napi_finalize`
840+
841+
<!-- YAML
842+
added: v8.0.0
843+
napiVersion: 1
844+
-->
845+
846+
Function pointer type for add-on provided function that allow the user to
847+
schedule a group of calls to Node-APIs in response to a garbage collection
848+
event, after the garbage collection cycle has completed. These function
849+
pointers can be used with [`node_api_post_finalizer`][].
850+
851+
```c
852+
typedef void (*napi_finalize)(napi_env env,
853+
void* finalize_data,
854+
void* finalize_hint);
855+
```
856+
857+
Change History:
858+
799859
* experimental (`NAPI_EXPERIMENTAL` is defined):
800860

801-
Node-API calls made from a finalizer will return `napi_cannot_run_js` when
802-
the JavaScript engine is unable to execute JavaScript, and will return
803-
`napi_exception_pending` if there is a pending exception.
861+
A function of this type may no longer be used as a finalizer, except with
862+
[`node_api_post_finalizer`][]. [`node_api_nogc_finalize`][] must be used
863+
instead. This feature can be turned off by defining
864+
`NODE_API_EXPERIMENTAL_NOGC_ENV_OPT_OUT`.
804865

805866
#### `napi_async_execute_callback`
806867

@@ -1002,7 +1063,7 @@ napiVersion: 1
10021063

10031064
```c
10041065
napi_status
1005-
napi_get_last_error_info(napi_env env,
1066+
napi_get_last_error_info(node_api_nogc_env env,
10061067
const napi_extended_error_info** result);
10071068
```
10081069

@@ -1821,7 +1882,7 @@ napiVersion: 3
18211882
-->
18221883

18231884
```c
1824-
NODE_EXTERN napi_status napi_add_env_cleanup_hook(napi_env env,
1885+
NODE_EXTERN napi_status napi_add_env_cleanup_hook(node_api_nogc_env env,
18251886
napi_cleanup_hook fun,
18261887
void* arg);
18271888
```
@@ -1851,7 +1912,7 @@ napiVersion: 3
18511912
-->
18521913

18531914
```c
1854-
NAPI_EXTERN napi_status napi_remove_env_cleanup_hook(napi_env env,
1915+
NAPI_EXTERN napi_status napi_remove_env_cleanup_hook(node_api_nogc_env env,
18551916
void (*fun)(void* arg),
18561917
void* arg);
18571918
```
@@ -1880,7 +1941,7 @@ changes:
18801941

18811942
```c
18821943
NAPI_EXTERN napi_status napi_add_async_cleanup_hook(
1883-
napi_env env,
1944+
node_api_nogc_env env,
18841945
napi_async_cleanup_hook hook,
18851946
void* arg,
18861947
napi_async_cleanup_hook_handle* remove_handle);
@@ -2038,7 +2099,7 @@ You can also use the `NAPI_MODULE_INIT` macro, which acts as a shorthand
20382099
for `NAPI_MODULE` and defining an `Init` function:
20392100

20402101
```c
2041-
NAPI_MODULE_INIT() {
2102+
NAPI_MODULE_INIT(/* napi_env env, napi_value exports */) {
20422103
napi_value answer;
20432104
napi_status result;
20442105

@@ -2052,6 +2113,9 @@ NAPI_MODULE_INIT() {
20522113
}
20532114
```
20542115

2116+
The parameters `env` and `exports` are provided to the body of the
2117+
`NAPI_MODULE_INIT` macro.
2118+
20552119
All Node-API addons are context-aware, meaning they may be loaded multiple
20562120
times. There are a few design considerations when declaring such a module.
20572121
The documentation on [context-aware addons][] provides more details.
@@ -5420,7 +5484,7 @@ napiVersion: 5
54205484
napi_status napi_add_finalizer(napi_env env,
54215485
napi_value js_object,
54225486
void* finalize_data,
5423-
napi_finalize finalize_cb,
5487+
node_api_nogc_finalize finalize_cb,
54245488
void* finalize_hint,
54255489
napi_ref* result);
54265490
```
@@ -5458,7 +5522,7 @@ added: v21.0.0
54585522
> Stability: 1 - Experimental
54595523

54605524
```c
5461-
napi_status node_api_post_finalizer(napi_env env,
5525+
napi_status node_api_post_finalizer(node_api_nogc_env env,
54625526
napi_finalize finalize_cb,
54635527
void* finalize_data,
54645528
void* finalize_hint);
@@ -5528,7 +5592,7 @@ Once created the async worker can be queued
55285592
for execution using the [`napi_queue_async_work`][] function:
55295593

55305594
```c
5531-
napi_status napi_queue_async_work(napi_env env,
5595+
napi_status napi_queue_async_work(node_api_nogc_env env,
55325596
napi_async_work work);
55335597
```
55345598

@@ -5620,7 +5684,7 @@ napiVersion: 1
56205684
-->
56215685

56225686
```c
5623-
napi_status napi_queue_async_work(napi_env env,
5687+
napi_status napi_queue_async_work(node_api_nogc_env env,
56245688
napi_async_work work);
56255689
```
56265690

@@ -5641,7 +5705,7 @@ napiVersion: 1
56415705
-->
56425706

56435707
```c
5644-
napi_status napi_cancel_async_work(napi_env env,
5708+
napi_status napi_cancel_async_work(node_api_nogc_env env,
56455709
napi_async_work work);
56465710
```
56475711

@@ -5845,7 +5909,7 @@ typedef struct {
58455909
const char* release;
58465910
} napi_node_version;
58475911

5848-
napi_status napi_get_node_version(napi_env env,
5912+
napi_status napi_get_node_version(node_api_nogc_env env,
58495913
const napi_node_version** version);
58505914
```
58515915

@@ -5868,7 +5932,7 @@ napiVersion: 1
58685932
-->
58695933

58705934
```c
5871-
napi_status napi_get_version(napi_env env,
5935+
napi_status napi_get_version(node_api_nogc_env env,
58725936
uint32_t* result);
58735937
```
58745938

@@ -5901,7 +5965,7 @@ napiVersion: 1
59015965
-->
59025966

59035967
```c
5904-
NAPI_EXTERN napi_status napi_adjust_external_memory(napi_env env,
5968+
NAPI_EXTERN napi_status napi_adjust_external_memory(node_api_nogc_env env,
59055969
int64_t change_in_bytes,
59065970
int64_t* result);
59075971
```
@@ -6118,7 +6182,7 @@ napiVersion: 2
61186182
-->
61196183

61206184
```c
6121-
NAPI_EXTERN napi_status napi_get_uv_event_loop(napi_env env,
6185+
NAPI_EXTERN napi_status napi_get_uv_event_loop(node_api_nogc_env env,
61226186
struct uv_loop_s** loop);
61236187
```
61246188

@@ -6432,7 +6496,7 @@ napiVersion: 4
64326496

64336497
```c
64346498
NAPI_EXTERN napi_status
6435-
napi_ref_threadsafe_function(napi_env env, napi_threadsafe_function func);
6499+
napi_ref_threadsafe_function(node_api_nogc_env env, napi_threadsafe_function func);
64366500
```
64376501

64386502
* `[in] env`: The environment that the API is invoked under.
@@ -6458,7 +6522,7 @@ napiVersion: 4
64586522

64596523
```c
64606524
NAPI_EXTERN napi_status
6461-
napi_unref_threadsafe_function(napi_env env, napi_threadsafe_function func);
6525+
napi_unref_threadsafe_function(node_api_nogc_env env, napi_threadsafe_function func);
64626526
```
64636527

64646528
* `[in] env`: The environment that the API is invoked under.
@@ -6484,7 +6548,7 @@ napiVersion: 9
64846548

64856549
```c
64866550
NAPI_EXTERN napi_status
6487-
node_api_get_module_file_name(napi_env env, const char** result);
6551+
node_api_get_module_file_name(node_api_nogc_env env, const char** result);
64886552

64896553
```
64906554

@@ -6548,6 +6612,7 @@ the add-on's file name during loading.
65486612
[`Number.MIN_SAFE_INTEGER`]: https://tc39.github.io/ecma262/#sec-number.min_safe_integer
65496613
[`Worker`]: worker_threads.md#class-worker
65506614
[`async_hooks.executionAsyncResource()`]: async_hooks.md#async_hooksexecutionasyncresource
6615+
[`build_with_cmake`]: https://github.com/nodejs/node-addon-examples/tree/main/build_with_cmake
65516616
[`global`]: globals.md#global
65526617
[`init` hooks]: async_hooks.md#initasyncid-type-triggerasyncid-resource
65536618
[`napi_add_async_cleanup_hook`]: #napi_add_async_cleanup_hook
@@ -6611,6 +6676,8 @@ the add-on's file name during loading.
66116676
[`node_api_create_external_string_latin1`]: #node_api_create_external_string_latin1
66126677
[`node_api_create_external_string_utf16`]: #node_api_create_external_string_utf16
66136678
[`node_api_create_syntax_error`]: #node_api_create_syntax_error
6679+
[`node_api_nogc_finalize`]: #node_api_nogc_finalize
6680+
[`node_api_post_finalizer`]: #node_api_post_finalizer
66146681
[`node_api_throw_syntax_error`]: #node_api_throw_syntax_error
66156682
[`process.release`]: process.md#processrelease
66166683
[`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)