Skip to content

Node-API Runtime Semantics Versioning #45657

Closed
@legendecas

Description

@legendecas

The Problem

Today Node-API has no runtime semantics based on the Node-API version targeted by the addon. This means that regardless of the Node-API version specified by the package, the same behavior is provided for a given Node-API function.

To maintain strict forward compatibility on future versions of Node.js for existing Node-API add-ons, the Node-API team has avoided introducing major bug fixes and features, like:

This is because bug fixes and features can break existing add-ons that rely on the current behavior (even if it is incorrect). For instance, if we enable the flags introduced in #36510 by default, add-ons that don’t handle the exception thrown in the context of thread safe function callbacks will crash the Node.js process.

This makes it hard for Node-API to evolve and address problems that cannot be solved without changes in behavior. We have addressed this in the past by adding duplicate functions with the modified behavior. This can be confusing to module owners and makes it harder to move packages towards using the improved/fixed behavior.

Proposed Solution

In order to address this challenge the Node-API team is considering introducing runtime semantics on Node-API versions.

What this means is that at runtime the version of Node-API targeted by a package when it is built will be used to determine how Node-API functions work. Since Node.js will continue to support older Node-API versions, the API will be stable for a given Node-API version. For example, a package that set Node-API version 8 as its target will continue to compile and run on newer versions of Node.js.

If this proposal is adopted this is what would be different:

  1. Packages would no longer get the latest Node-API version supported by the version against which they are compiled by default. The Node-API version will be pinned at Node-API version 8. Package authors will need to explicitly opt-in to a newer Node-API version.
  2. Opting into a newer Node-API version could result in changes in behavior which in turn may require changes in the code for the package.
  3. Opting into a version of Node-API later than 8 may require that the package use different macros/functions for registration.

This would allow us to better evolve Node-API while at the same time maintaining ABI compatibility for packages at a given Node-API version. It does, however, mean that changes in the code for a package might be required in order to use new features in later Node-API versions. We currently consider this a reasonable trade-off as the package author is likely already changing/testing the code if they want to use new features.

Implementation Discussion

Currently, ABI-safe Node-API modules have two ways to register modules:

  1. Create a napi_module struct and call napi_module_register on the module library (.so or .dll) load.
  2. Define the well-known symbol napi_register_module_v1 function which is called on-demand to initialize module exports.

Those two have no significant differences. However, well-known symbol registration is used in various language bindings (Rust, go, etc). Even the C/C++ WASM bindings use symbol-based registration. It is easier to implement, and it does not need calling code on module library load.

Thus, we are going to deprecate napi_module struct registration in #46319. napi_module struct registration is still supported and its support is not going to be removed. Add-ons defining their entries with NAPI_MODULE and NAPI_MODULE_INIT are switching to the well-known symbol registration once they are compiled with the latest node_api.h headers.

Declare target node-api version

Additionally, A new well-known symbol napi_module_get_api_version_v1 is added to declare add-ons' target node-api version.

typedef int32_t(NAPI_CDECL* napi_addon_get_api_version_func)();

If an add-on's napi_module_get_api_version_v1 returned a version that is greater than the node-api version that the current Node.js is built with, Node.js should reject to load it.

If an add-on didn't define such a symbol, its target node-api version is pinned at node-api version 8 -- the last version we introduce runtime semantics versioning.

Add-on can assert the node-api version returned by napi_get_version is greater than or equal to its target version.

Future Plans

Before we ship these changes, we need to reach out to add-on authors and major Node-API language library (e.g. Rust) authors to see their opinions on the approach.

Future plans and improvements with the runtime semantics versioning:

  • Enable uncaught exception emitting by default for thread-safe functions.
  • Allow napi_create_reference to create strong references on the primitive values.
  • Eagerly invoke finalize callback, with JavaScript execution disabled, to improve the performance of native data finalization.

/cc @nodejs/node-api

Metadata

Metadata

Assignees

No one assigned

    Labels

    discussIssues opened for discussions and feedbacks.node-apiIssues and PRs related to the Node-API.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions