Skip to content

Commit

Permalink
node-api: segregate nogc APIs from rest via type system
Browse files Browse the repository at this point in the history
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>
Backport-PR-URL: #51804
(cherry picked from commit 7a216d5)
  • Loading branch information
gabrielschulhof authored and richardlau committed Mar 22, 2024
1 parent 1aa71c2 commit 32906dd
Show file tree
Hide file tree
Showing 16 changed files with 368 additions and 146 deletions.
139 changes: 103 additions & 36 deletions doc/api/n-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ it still gets the benefits of the ABI stability provided by the C API.
When using `node-addon-api` instead of the C APIs, start with the API [docs][]
for `node-addon-api`.

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

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

### Uploading precompiled binaries

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

Occasionally, experimental features are introduced that affect already-released
and stable APIs. These features can be disabled by an opt-out:

```c
#define NAPI_EXPERIMENTAL
#define NODE_API_EXPERIMENTAL_<FEATURE_NAME>_OPT_OUT
#include <node_api.h>
```

where `<FEATURE_NAME>` is the name of an experimental feature that affects both
experimental and stable APIs.

## Node-API version matrix

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

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

```c
napi_status napi_set_instance_data(napi_env env,
napi_status napi_set_instance_data(node_api_nogc_env env,
void* data,
napi_finalize finalize_cb,
void* finalize_hint);
Expand Down Expand Up @@ -475,7 +488,7 @@ napiVersion: 6
-->

```c
napi_status napi_get_instance_data(napi_env env,
napi_status napi_get_instance_data(node_api_nogc_env env,
void** data);
```

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

### `node_api_nogc_env`

> Stability: 1 - Experimental

This variant of `napi_env` is passed to synchronous finalizers
([`node_api_nogc_finalize`][]). There is a subset of Node-APIs which accept
a parameter of type `node_api_nogc_env` as their first argument. These APIs do
not access the state of the JavaScript engine and are thus safe to call from
synchronous finalizers. Passing a parameter of type `napi_env` to these APIs is
allowed, however, passing a parameter of type `node_api_nogc_env` to APIs that
access the JavaScript engine state is not allowed. Attempting to do so without
a cast will produce a compiler warning or an error when add-ons are compiled
with flags which cause them to emit warnings and/or errors when incorrect
pointer types are passed into a function. Calling such APIs from a synchronous
finalizer will ultimately result in the termination of the application.

### `napi_value`

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

#### `napi_finalize`
#### `node_api_nogc_finalize`

<!-- YAML
added: v8.0.0
napiVersion: 1
added: REPLACEME
-->

> Stability: 1 - Experimental

Function pointer type for add-on provided functions that allow the user to be
notified when externally-owned data is ready to be cleaned up because the
object with which it was associated with has been garbage-collected. The user
must provide a function satisfying the following signature which would get
called upon the object's collection. Currently, `napi_finalize` can be used for
object it was associated with has been garbage-collected. The user must provide
a function satisfying the following signature which would get called upon the
object's collection. Currently, `node_api_nogc_finalize` can be used for
finding out when objects that have external data are collected.

```c
typedef void (*napi_finalize)(napi_env env,
void* finalize_data,
void* finalize_hint);
typedef void (*node_api_nogc_finalize)(node_api_nogc_env env,
void* finalize_data,
void* finalize_hint);
```

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

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

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

Change History:

* experimental (`NAPI_EXPERIMENTAL`):

Only Node-API calls that accept a `node_api_nogc_env` as their first
parameter may be called, otherwise the application will be terminated with an
appropriate error message. This feature can be turned off by defining
`NODE_API_EXPERIMENTAL_NOGC_ENV_OPT_OUT`.

#### `napi_finalize`

<!-- YAML
added: v8.0.0
napiVersion: 1
-->

Function pointer type for add-on provided function that allow the user to
schedule a group of calls to Node-APIs in response to a garbage collection
event, after the garbage collection cycle has completed. These function
pointers can be used with [`node_api_post_finalizer`][].

```c
typedef void (*napi_finalize)(napi_env env,
void* finalize_data,
void* finalize_hint);
```

Change History:

* experimental (`NAPI_EXPERIMENTAL` is defined):

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

#### `napi_async_execute_callback`

Expand Down Expand Up @@ -979,7 +1040,7 @@ napiVersion: 1

```c
napi_status
napi_get_last_error_info(napi_env env,
napi_get_last_error_info(node_api_nogc_env env,
const napi_extended_error_info** result);
```

Expand Down Expand Up @@ -1798,7 +1859,7 @@ napiVersion: 3
-->

```c
NODE_EXTERN napi_status napi_add_env_cleanup_hook(napi_env env,
NODE_EXTERN napi_status napi_add_env_cleanup_hook(node_api_nogc_env env,
napi_cleanup_hook fun,
void* arg);
```
Expand Down Expand Up @@ -1828,7 +1889,7 @@ napiVersion: 3
-->

```c
NAPI_EXTERN napi_status napi_remove_env_cleanup_hook(napi_env env,
NAPI_EXTERN napi_status napi_remove_env_cleanup_hook(node_api_nogc_env env,
void (*fun)(void* arg),
void* arg);
```
Expand Down Expand Up @@ -1857,7 +1918,7 @@ changes:

```c
NAPI_EXTERN napi_status napi_add_async_cleanup_hook(
napi_env env,
node_api_nogc_env env,
napi_async_cleanup_hook hook,
void* arg,
napi_async_cleanup_hook_handle* remove_handle);
Expand Down Expand Up @@ -2015,7 +2076,7 @@ You can also use the `NAPI_MODULE_INIT` macro, which acts as a shorthand
for `NAPI_MODULE` and defining an `Init` function:

```c
NAPI_MODULE_INIT() {
NAPI_MODULE_INIT(/* napi_env env, napi_value exports */) {
napi_value answer;
napi_status result;

Expand All @@ -2029,6 +2090,9 @@ NAPI_MODULE_INIT() {
}
```

The parameters `env` and `exports` are provided to the body of the
`NAPI_MODULE_INIT` macro.

All Node-API addons are context-aware, meaning they may be loaded multiple
times. There are a few design considerations when declaring such a module.
The documentation on [context-aware addons][] provides more details.
Expand Down Expand Up @@ -5391,7 +5455,7 @@ napiVersion: 5
napi_status napi_add_finalizer(napi_env env,
napi_value js_object,
void* finalize_data,
napi_finalize finalize_cb,
node_api_nogc_finalize finalize_cb,
void* finalize_hint,
napi_ref* result);
```
Expand Down Expand Up @@ -5429,7 +5493,7 @@ added: v18.19.0
> Stability: 1 - Experimental

```c
napi_status node_api_post_finalizer(napi_env env,
napi_status node_api_post_finalizer(node_api_nogc_env env,
napi_finalize finalize_cb,
void* finalize_data,
void* finalize_hint);
Expand Down Expand Up @@ -5499,7 +5563,7 @@ Once created the async worker can be queued
for execution using the [`napi_queue_async_work`][] function:

```c
napi_status napi_queue_async_work(napi_env env,
napi_status napi_queue_async_work(node_api_nogc_env env,
napi_async_work work);
```

Expand Down Expand Up @@ -5591,7 +5655,7 @@ napiVersion: 1
-->

```c
napi_status napi_queue_async_work(napi_env env,
napi_status napi_queue_async_work(node_api_nogc_env env,
napi_async_work work);
```

Expand All @@ -5612,7 +5676,7 @@ napiVersion: 1
-->

```c
napi_status napi_cancel_async_work(napi_env env,
napi_status napi_cancel_async_work(node_api_nogc_env env,
napi_async_work work);
```

Expand Down Expand Up @@ -5816,7 +5880,7 @@ typedef struct {
const char* release;
} napi_node_version;

napi_status napi_get_node_version(napi_env env,
napi_status napi_get_node_version(node_api_nogc_env env,
const napi_node_version** version);
```

Expand All @@ -5839,7 +5903,7 @@ napiVersion: 1
-->

```c
napi_status napi_get_version(napi_env env,
napi_status napi_get_version(node_api_nogc_env env,
uint32_t* result);
```

Expand Down Expand Up @@ -5872,7 +5936,7 @@ napiVersion: 1
-->

```c
NAPI_EXTERN napi_status napi_adjust_external_memory(napi_env env,
NAPI_EXTERN napi_status napi_adjust_external_memory(node_api_nogc_env env,
int64_t change_in_bytes,
int64_t* result);
```
Expand Down Expand Up @@ -6089,7 +6153,7 @@ napiVersion: 2
-->

```c
NAPI_EXTERN napi_status napi_get_uv_event_loop(napi_env env,
NAPI_EXTERN napi_status napi_get_uv_event_loop(node_api_nogc_env env,
struct uv_loop_s** loop);
```

Expand Down Expand Up @@ -6403,7 +6467,7 @@ napiVersion: 4

```c
NAPI_EXTERN napi_status
napi_ref_threadsafe_function(napi_env env, napi_threadsafe_function func);
napi_ref_threadsafe_function(node_api_nogc_env env, napi_threadsafe_function func);
```

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

```c
NAPI_EXTERN napi_status
napi_unref_threadsafe_function(napi_env env, napi_threadsafe_function func);
napi_unref_threadsafe_function(node_api_nogc_env env, napi_threadsafe_function func);
```

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

```c
NAPI_EXTERN napi_status
node_api_get_module_file_name(napi_env env, const char** result);
node_api_get_module_file_name(node_api_nogc_env env, const char** result);

```

Expand Down Expand Up @@ -6519,6 +6583,7 @@ the add-on's file name during loading.
[`Number.MIN_SAFE_INTEGER`]: https://tc39.github.io/ecma262/#sec-number.min_safe_integer
[`Worker`]: worker_threads.md#class-worker
[`async_hooks.executionAsyncResource()`]: async_hooks.md#async_hooksexecutionasyncresource
[`build_with_cmake`]: https://github.com/nodejs/node-addon-examples/tree/main/build_with_cmake
[`global`]: globals.md#global
[`init` hooks]: async_hooks.md#initasyncid-type-triggerasyncid-resource
[`napi_add_async_cleanup_hook`]: #napi_add_async_cleanup_hook
Expand Down Expand Up @@ -6582,6 +6647,8 @@ the add-on's file name during loading.
[`node_api_create_external_string_latin1`]: #node_api_create_external_string_latin1
[`node_api_create_external_string_utf16`]: #node_api_create_external_string_utf16
[`node_api_create_syntax_error`]: #node_api_create_syntax_error
[`node_api_nogc_finalize`]: #node_api_nogc_finalize
[`node_api_post_finalizer`]: #node_api_post_finalizer
[`node_api_throw_syntax_error`]: #node_api_throw_syntax_error
[`process.release`]: process.md#processrelease
[`uv_ref`]: https://docs.libuv.org/en/v1.x/handle.html#c.uv_ref
Expand Down
13 changes: 13 additions & 0 deletions doc/contributing/adding-new-napi-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,16 @@ Node-API.
to the decision to take an API out of experimental status.
* The API **must** be implemented in a Node.js implementation with an
alternate VM.

Since the adoption of the policy whereby moving to a later version of Node-API
from an earlier version may entail rework of existing code, it is possible to
introduce modifications to already-released Node-APIs, as long as the
modifications affect neither the ABI nor the API of earlier versions. Such
modifications **must** be accompanied by an opt-out flag. This provides add-on
maintainers who take advantage of the initial compile-time flag to track
impending changes to Node-API with

* a quick fix to the breakage caused,
* a notification that such breakage is impending, and thus
* a buffer to adoption above and beyond the one provided by the initial
compile-time flag.
Loading

0 comments on commit 32906dd

Please sign in to comment.