node-api: use c-based api for libnode embedding
This is a temporary spin off from the PR #43542. This separate PR is created to simplify merging and rebasing with the latest code while we discuss the new API design. When the code is ready it should be merged back to PR #43542.
The goal of the original PR is to enable C API and the Node-API for the embedded scenarios.
The C API allows using the shared libnode
from runtimes that do not interop with C++ such as WASM, C#, Java, etc.
This PR works towards the same goal with some changes to the original code.
This is the related issue #23265.
The API design principles
- Follow the best practices of the Node-API design and provide a way to interop with it.
- Prefix the new API constructs with
node_embedding_
. - Design the API for ABI safety and being future proof for new requirements.
- Follow the Builder pattern for the API design.
- The typical use is to create an object, configure it, initialize it based on the configuration, use it, and then delete it. The configuration changes are prohibited after the object is initialized.
- What if the initialization sequence must be customized? It means that we add a new configuration function and insert a customization hook into the initialization sequence. Thus, we can evolve the API by adding new configuration functions, and occasionally deprecating the old functions.
- All behavior changes must be associated with a new API version number.
The API usage
- To use the C embedding API, we must create, configure, and initialize the global
node_embedding_platform
. It initializes Node and V8 JS engine once per process and parses the CLI arguments. - Then, we create, configure, and initialize one or more
node_embedding_runtime
s. A runtime is responsible for running JavaScript code. - The runtime CLI arguments are initialized by default with the
args
andexec_args
from the result of the platform initialization. They can be overridden while configuring the runtime. - A runtime can run in its own thread, several runtimes can share the same thread, or the same runtime can be run from multiple threads.
- The runtime event loop APIs provide control over the runtime execution. These functions can be called many times because they do not destroy the runtime in the end.
- The runtime offers to specify version of Node-API and to retrieve the associated
napi_api
instance. Any Node-API code that uses thenapi_env
must be run in the runtime scope controlled bynode_embedding_runtime_open_scope
andnode_embedding_runtime_close_scope
functions.
The API overview
Based on the use scenarios, the API can be split up into six groups.
Node.js CLI API
-
node_embedding_run_nodejs_main
runs Node.js CLI without any customizations.
Error handling API
-
node_embedding_on_error
sets the global error handling hook.
Global platform API
node_embedding_create_platform
node_embedding_delete_platform
node_embedding_platform_is_initialized
node_embedding_platform_set_flags
node_embedding_platform_set_args
node_embedding_platform_initialize
node_embedding_platform_get_parsed_args
Runtime API
node_embedding_create_runtime
node_embedding_delete_runtime
node_embedding_runtime_is_initialized
node_embedding_runtime_set_flags
node_embedding_runtime_set_args
node_embedding_runtime_on_preload
node_embedding_runtime_add_module
node_embedding_runtime_initialize
Runtime API to run event loops
node_embedding_runtime_on_event_loop_run_request
node_embedding_runtime_run_event_loop
node_embedding_runtime_complete_event_loop
Runtime API to interop with Node-API
node_embedding_runtime_set_node_api_version
node_embedding_runtime_invoke_node_api
Documentation
- The new C embedding API is added to the existing
embedding.md
file after the C++ embedding API description. - The
index.md
is changed to indicate that theembedding.md
has docs for C++ and C APIs. -
TODO: complete the examples section.
Tests
- The new C embedding API tests pass the same scenarios as the C++ embedding API tests.
- The
embedtest
executable can be run in several modes controlled by the first CLI argument. It effectively contains severalmain
functions for different test scenarios. - The JS test code is changed to provide the test mode argument based on the scenario.
- Added several new test scenarios:
- run several Node.js runtimes each in its own thread;
- run several Node.js runtimes all in the same thread;
- run Node.js runtime from different threads.
- test that preload callback is called for the main and worker threads.
The PR status
The code is not 100% complete yet. There are still a few TODO items, but I would like to start a discussion with the Node-API team about the new API.
-
Address outstanding TODOs -
The main_script
must be an option for the runtime configuration. -
Add startup callback with process and require parameters. -
Consider generating the main script based on runtime configuration. -
Allow setting the global inspector for a selected runtime (there can be only one inspectable runtime per process). -
Start worker threads from C++. -
Worker threads to inherit parent inspector. -
Allow cancelling pending loop tasks on runtime deletion. -
Can we init platform again if it retuns early? -
Add simplified runtime mode without explicit open/close scope. -
Simplify API use for simple default cases. -
Enable embedded native modules that are a part of the main executable. -
Test passing the V8 thread pool size. -
Implement better error handling for function arguments. -
Clear up usage of args
vsexec_args
. It all looks quite confusing.
-
-
Review the API design -
Write docs