node
node copied to clipboard
node-api: use c-based api for libnode embedding
Note: this is an active work in progress and there are still a lot of code churning. You are welcome to comment on the code and share your thoughts, but please be aware that the code is not final yet.
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.
Error handling API
-
node_embedding_on_error
sets the global error handling hook.
Global platform API
-
node_embedding_set_api_version
-
node_embedding_run_main
-
node_embedding_create_platform
-
node_embedding_delete_platform
-
node_embedding_platform_set_flags
-
node_embedding_platform_get_parsed_args
Runtime API
-
node_embedding_run_runtime
-
node_embedding_create_runtime
-
node_embedding_delete_runtime
-
node_embedding_runtime_set_flags
-
node_embedding_runtime_set_args
-
node_embedding_runtime_on_preload
-
node_embedding_runtime_on_start_execution
-
node_embedding_runtime_add_module
- [ ] add API to handle unhandled exceptions
Runtime API to run event loops
-
node_embedding_on_wake_up_event_loop
-
node_embedding_run_event_loop
-
node_embedding_complete_event_loop
- [ ] add API for emitting
beforeExit
event - [ ] add API for emitting
exit
event - [ ] add API to stop the event loop
Runtime API to interop with Node-API
-
node_embedding_run_node_api
-
node_embedding_open_node_api_scope
-
node_embedding_close_node_api_scope
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
- [ ] Allow running Node.js uv_loop from UI loop. Follow the Electron implementation. - Complete implementation for non-Windows.
- [ ] Can we use some kind of waiter concept instead of the observer thread?
- [ ] Generate the main script based on the runtime settings.
- [ ] Set the global Inspector for he main runtime.
- [ ] Start workers from C++.
- [ ] Worker to inherit parent Inspector.
- [ ] Cancel pending event loop tasks on runtime deletion.
- [ ] Can we initialize platform again if it returns early?
- [ ] Test passing the V8 thread pool size.
- [ ] Add a way to terminate the runtime.
- [ ] Allow to provide custom thread pool from the app.
- [ ] Consider adding a v-table for the API functions to simplify binding with other languages.
- [ ] We must not exit the process on node::Environment errors.
- [ ] Be explicit about the recoverable errors.
- [ ] Store IsolateScope in TLS.
- [ ] Review the API design
- [ ] Write docs