Skip to content

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_runtimes. A runtime is responsible for running JavaScript code.
  • The runtime CLI arguments are initialized by default with the args and exec_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 the napi_env must be run in the runtime scope controlled by node_embedding_runtime_open_scope and node_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 the embedding.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 several main 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 vs exec_args. It all looks quite confusing.
  • Review the API design
  • Write docs

Merge request reports

Loading