From 19e8206643ebdb9398716d22292d3ba61a126a0d Mon Sep 17 00:00:00 2001 From: Vladimir Morozov Date: Fri, 30 Aug 2024 07:30:05 -0700 Subject: [PATCH 01/22] node-api: use c-based api for libnode embedding --- doc/api/embedding.md | 39 ++ doc/api/n-api.md | 139 ++++ node.gyp | 8 + src/api/embed_helpers.cc | 150 ++-- src/node_api_embedding.cc | 641 ++++++++++++++++++ src/node_api_embedding.h | 126 ++++ test/embedding/.eslintrc.yaml | 3 + test/embedding/cjs.cjs | 3 + test/embedding/embedtest.cc | 6 +- .../embedtest_concurrent_node_api.cc | 231 +++++++ test/embedding/embedtest_main.cc | 46 ++ test/embedding/embedtest_modules_node_api.c | 65 ++ test/embedding/embedtest_node_api.c | 223 ++++++ test/embedding/embedtest_node_api.h | 53 ++ test/embedding/embedtest_snapshot_node_api.cc | 213 ++++++ test/embedding/es6.mjs | 1 + test/embedding/test-embedding.js | 406 +++++++---- 17 files changed, 2170 insertions(+), 183 deletions(-) create mode 100644 src/node_api_embedding.cc create mode 100644 src/node_api_embedding.h create mode 100644 test/embedding/.eslintrc.yaml create mode 100644 test/embedding/cjs.cjs create mode 100644 test/embedding/embedtest_concurrent_node_api.cc create mode 100644 test/embedding/embedtest_main.cc create mode 100644 test/embedding/embedtest_modules_node_api.c create mode 100644 test/embedding/embedtest_node_api.c create mode 100644 test/embedding/embedtest_node_api.h create mode 100644 test/embedding/embedtest_snapshot_node_api.cc create mode 100644 test/embedding/es6.mjs diff --git a/doc/api/embedding.md b/doc/api/embedding.md index d4ae090c255f97..6a2678ff91d7f6 100644 --- a/doc/api/embedding.md +++ b/doc/api/embedding.md @@ -166,8 +166,47 @@ int RunNodeInstance(MultiIsolatePlatform* platform, } ``` +## Node-API Embedding + + + +As an alternative, an embedded Node.js can also be fully controlled through +Node-API. This API supports both C and C++ through [node-addon-api][]. + +An example can be found [in the Node.js source tree][napi_embedding.c]. + +```c + napi_platform platform; + napi_env env; + const char *main_script = "console.log('hello world')"; + + if (napi_create_platform(0, NULL, NULL, &platform) != napi_ok) { + fprintf(stderr, "Failed creating the platform\n"); + return -1; + } + + if (napi_create_environment(platform, NULL, main_script, + (napi_stdio){NULL, NULL, NULL}, NAPI_VERSION, &env) != napi_ok) { + fprintf(stderr, "Failed running JS\n"); + return -1; + } + + // Here you can interact with the environment through Node-API env + + if (napi_destroy_environment(env, NULL) != napi_ok) { + return -1; + } + + if (napi_destroy_platform(platform) != napi_ok) { + fprintf(stderr, "Failed destroying the platform\n"); + return -1; + } +``` + [CLI options]: cli.md [`process.memoryUsage()`]: process.md#processmemoryusage [deprecation policy]: deprecations.md [embedtest.cc]: https://github.com/nodejs/node/blob/HEAD/test/embedding/embedtest.cc +[napi_embedding.c]: https://github.com/nodejs/node/blob/HEAD/test/embedding/napi_embedding.c +[node-addon-api]: https://github.com/nodejs/node-addon-api [src/node.h]: https://github.com/nodejs/node/blob/HEAD/src/node.h diff --git a/doc/api/n-api.md b/doc/api/n-api.md index 0727ca74d2f986..d838a520142606 100644 --- a/doc/api/n-api.md +++ b/doc/api/n-api.md @@ -6592,6 +6592,145 @@ idempotent. This API may only be called from the main thread. +## Using embedded Node.js + +### `napi_create_platform` + + + +> Stability: 1 - Experimental + +```c +napi_status napi_create_platform(int argc, + char** argv, + napi_error_message_handler err_handler, + napi_platform* result); +``` + +* `[in] argc`: CLI argument count, pass 0 for autofilling. +* `[in] argv`: CLI arguments, pass NULL for autofilling. +* `[in] err_handler`: If different than NULL, will be called back with each + error message. There can be multiple error messages but the API guarantees + that no calls will be made after the `napi_create_platform` has returned. + In practice, the implementation of graceful recovery is still in progress, + and many errors will be fatal, resulting in an `abort()`. +* `[out] result`: A `napi_platform` result. + +This function must be called once to initialize V8 and Node.js when using as a +shared library. + +### `napi_destroy_platform` + + + +> Stability: 1 - Experimental + +```c +napi_status napi_destroy_platform(napi_platform platform); +``` + +* `[in] platform`: platform handle. + +Destroy the Node.js / V8 processes. + +### `napi_create_environment` + + + +> Stability: 1 - Experimental + +```c +napi_status napi_create_environment(napi_platform platform, + napi_error_message_handler err_handler, + const char* main_script, + int32_t api_version, + napi_env* result); +``` + +* `[in] platform`: platform handle. +* `[in] err_handler`: If different than NULL, will be called back with each + error message. There can be multiple error messages but the API guarantees + that no calls will be made after the `napi_create_platform` has returned. + In practice, the implementation of graceful recovery is still in progress, + and many errors will be fatal, resulting in an `abort()`. +* `[in] main_script`: If different than NULL, custom JavaScript to run in + addition to the default bootstrap that creates an empty + ready-to-use CJS/ES6 environment with `global.require()` and + `global.import()` functions that resolve modules from the directory of + the compiled binary. + It can be used to redirect `process.stdin`/ `process.stdout` streams + since Node.js might switch these file descriptors to non-blocking mode. +* `[in] api_version`: Node-API version to conform to, pass `NAPI_VERSION` + for the latest available. +* `[out] result`: A `napi_env` result. + +Initialize a new environment. A single platform can hold multiple Node.js +environments that will run in a separate V8 isolate each. If the returned +value is `napi_ok` or `napi_pending_exception`, the environment must be +destroyed with `napi_destroy_environment` to free all allocated memory. + +### `napi_run_environment` + + + +> Stability: 1 - Experimental + +```c +napi_status napi_run_environment(napi_env env); +``` + +* `[in] env`: environment handle. + +Iterate the event loop of the environment. Executes all pending +JavaScript callbacks. Cannot be called with JavaScript on the +stack (ie in a JavaScript callback). + +### `napi_await_promise` + + + +> Stability: 1 - Experimental + +```c +napi_status napi_await_promise(napi_env env, + napi_value promise, + napi_value *result); +``` + +* `[in] env`: environment handle. +* `[in] promise`: JS Promise. +* `[out] result`: Will receive the value that the Promise resolved with. + +Iterate the event loop of the environment until the `promise` has been +resolved. Returns `napi_pending_exception` on rejection. Cannot be called +with JavaScript on the stack (ie in a JavaScript callback). + +### `napi_destroy_environment` + + + +> Stability: 1 - Experimental + +```c +napi_status napi_destroy_environment(napi_env env); +``` + +* `[in] env`: environment handle. + +Destroy the Node.js environment / V8 isolate. + ## Miscellaneous utilities ### `node_api_get_module_file_name` diff --git a/node.gyp b/node.gyp index c69670c49b660d..a5c1df550de174 100644 --- a/node.gyp +++ b/node.gyp @@ -99,6 +99,7 @@ 'src/module_wrap.cc', 'src/node.cc', 'src/node_api.cc', + 'src/node_api_embedding.cc', 'src/node_binding.cc', 'src/node_blob.cc', 'src/node_buffer.cc', @@ -222,6 +223,7 @@ 'src/module_wrap.h', 'src/node.h', 'src/node_api.h', + 'src/node_api_embedding.h', 'src/node_api_types.h', 'src/node_binding.h', 'src/node_blob.h', @@ -1285,6 +1287,12 @@ 'sources': [ 'src/node_snapshot_stub.cc', 'test/embedding/embedtest.cc', + 'test/embedding/embedtest_concurrent_node_api.cc', + 'test/embedding/embedtest_main.cc', + 'test/embedding/embedtest_modules_node_api.c', + 'test/embedding/embedtest_node_api.c', + 'test/embedding/embedtest_node_api.h', + 'test/embedding/embedtest_snapshot_node_api.cc', ], 'conditions': [ diff --git a/src/api/embed_helpers.cc b/src/api/embed_helpers.cc index 34de89a8dc0398..ec9bf95eb779b0 100644 --- a/src/api/embed_helpers.cc +++ b/src/api/embed_helpers.cc @@ -19,7 +19,24 @@ using v8::TryCatch; namespace node { -Maybe SpinEventLoopInternal(Environment* env) { +enum class SpinEventLoopCleanupMode { + kNormal, + kNoCleanup, +}; + +/** + * Spin the event loop until there are no pending callbacks and + * then shutdown the environment. Returns a reference to the + * exit value or an empty reference on unexpected exit. + * If cleanupMode is kNoCleanup, then the environment will not be cleaned up. + * If shouldContinue is an invocable that returns bool, then the loop will + * break after shouldContinue returns false. + */ +template < + SpinEventLoopCleanupMode cleanupMode = SpinEventLoopCleanupMode::kNormal, + typename ShouldContinuePredicate = int> +Maybe SpinEventLoopInternalImpl( + Environment* env, const ShouldContinuePredicate& shouldContinue = 0) { CHECK_NOT_NULL(env); MultiIsolatePlatform* platform = GetMultiIsolatePlatform(env); CHECK_NOT_NULL(platform); @@ -31,64 +48,103 @@ Maybe SpinEventLoopInternal(Environment* env) { if (env->is_stopping()) return Nothing(); - env->set_trace_sync_io(env->options()->trace_sync_io); + // Run the UV loop { - bool more; + env->set_trace_sync_io(env->options()->trace_sync_io); + auto clear_set_trace_sync_io = + OnScopeLeave([env] { env->set_trace_sync_io(false); }); + env->performance_state()->Mark( node::performance::NODE_PERFORMANCE_MILESTONE_LOOP_START); + auto mark_loop_exit = OnScopeLeave([env] { + env->performance_state()->Mark( + node::performance::NODE_PERFORMANCE_MILESTONE_LOOP_EXIT); + }); + + bool more; do { - if (env->is_stopping()) break; - uv_run(env->event_loop(), UV_RUN_DEFAULT); + if constexpr (std::is_invocable_r_v) { + do { + if (env->is_stopping()) break; + more = uv_run(env->event_loop(), UV_RUN_NOWAIT); + } while (more && shouldContinue()); + } else { + if (env->is_stopping()) break; + uv_run(env->event_loop(), UV_RUN_DEFAULT); + } if (env->is_stopping()) break; platform->DrainTasks(isolate); more = uv_loop_alive(env->event_loop()); - if (more && !env->is_stopping()) continue; + if constexpr (cleanupMode == SpinEventLoopCleanupMode::kNormal) { + if (more) continue; - if (EmitProcessBeforeExit(env).IsNothing()) - break; + if (EmitProcessBeforeExit(env).IsNothing()) break; - { - HandleScope handle_scope(isolate); - if (env->RunSnapshotSerializeCallback().IsEmpty()) { - break; + { + HandleScope handle_scope(isolate); + if (env->RunSnapshotSerializeCallback().IsEmpty()) { + break; + } } - } - // Emit `beforeExit` if the loop became alive either after emitting - // event, or after running some callbacks. - more = uv_loop_alive(env->event_loop()); - } while (more == true && !env->is_stopping()); - env->performance_state()->Mark( - node::performance::NODE_PERFORMANCE_MILESTONE_LOOP_EXIT); + // Emit `beforeExit` again if the loop became alive either after + // emitting event, or after running some callbacks. + more = uv_loop_alive(env->event_loop()); + } + } while (more); } if (env->is_stopping()) return Nothing(); - env->set_trace_sync_io(false); - // Clear the serialize callback even though the JS-land queue should - // be empty this point so that the deserialized instance won't - // attempt to call into JS again. - env->set_snapshot_serialize_callback(Local()); - - env->PrintInfoForSnapshotIfDebug(); - env->ForEachRealm([](Realm* realm) { realm->VerifyNoStrongBaseObjects(); }); - Maybe exit_code = EmitProcessExitInternal(env); - if (exit_code.FromMaybe(ExitCode::kGenericUserError) != - ExitCode::kNoFailure) { - return exit_code; - } + if constexpr (cleanupMode == SpinEventLoopCleanupMode::kNormal) { + // Clear the serialize callback even though the JS-land queue should + // be empty this point so that the deserialized instance won't + // attempt to call into JS again. + env->set_snapshot_serialize_callback(Local()); + + env->PrintInfoForSnapshotIfDebug(); + env->ForEachRealm([](Realm* realm) { realm->VerifyNoStrongBaseObjects(); }); + Maybe exit_code = EmitProcessExitInternal(env); + if (exit_code.FromMaybe(ExitCode::kGenericUserError) != + ExitCode::kNoFailure) { + return exit_code; + } - auto unsettled_tla = env->CheckUnsettledTopLevelAwait(); - if (unsettled_tla.IsNothing()) { - return Nothing(); - } - if (!unsettled_tla.FromJust()) { - return Just(ExitCode::kUnsettledTopLevelAwait); + auto unsettled_tla = env->CheckUnsettledTopLevelAwait(); + if (unsettled_tla.IsNothing()) { + return Nothing(); + } + if (!unsettled_tla.FromJust()) { + return Just(ExitCode::kUnsettledTopLevelAwait); + } } return Just(ExitCode::kNoFailure); } +v8::Maybe SpinEventLoopInternal(Environment* env) { + return SpinEventLoopInternalImpl(env); +} + +Maybe ExitCodeToInt(Maybe value) { + if (value.IsNothing()) return Nothing(); + return Just(static_cast(value.FromJust())); +} + +Maybe SpinEventLoop(Environment* env) { + return ExitCodeToInt(SpinEventLoopInternalImpl(env)); +} + +v8::Maybe SpinEventLoopWithoutCleanup(Environment* env) { + return SpinEventLoopInternalImpl(env); +} + +v8::Maybe SpinEventLoopWithoutCleanup( + Environment* env, const std::function& shouldContinue) { + return SpinEventLoopInternalImpl( + env, shouldContinue); +} + struct CommonEnvironmentSetup::Impl { MultiIsolatePlatform* platform = nullptr; uv_loop_t loop; @@ -229,9 +285,10 @@ CommonEnvironmentSetup::~CommonEnvironmentSetup() { } bool platform_finished = false; - impl_->platform->AddIsolateFinishedCallback(isolate, [](void* data) { - *static_cast(data) = true; - }, &platform_finished); + impl_->platform->AddIsolateFinishedCallback( + isolate, + [](void* data) { *static_cast(data) = true; }, + &platform_finished); impl_->platform->UnregisterIsolate(isolate); if (impl_->snapshot_creator.has_value()) impl_->snapshot_creator.reset(); @@ -239,8 +296,7 @@ CommonEnvironmentSetup::~CommonEnvironmentSetup() { isolate->Dispose(); // Wait until the platform has cleaned up all relevant resources. - while (!platform_finished) - uv_run(&impl_->loop, UV_RUN_ONCE); + while (!platform_finished) uv_run(&impl_->loop, UV_RUN_ONCE); } if (impl_->isolate || impl_->loop.data != nullptr) @@ -261,14 +317,6 @@ EmbedderSnapshotData::Pointer CommonEnvironmentSetup::CreateSnapshot() { return result; } -Maybe SpinEventLoop(Environment* env) { - Maybe result = SpinEventLoopInternal(env); - if (result.IsNothing()) { - return Nothing(); - } - return Just(static_cast(result.FromJust())); -} - uv_loop_t* CommonEnvironmentSetup::event_loop() const { return &impl_->loop; } diff --git a/src/node_api_embedding.cc b/src/node_api_embedding.cc new file mode 100644 index 00000000000000..c3c5e17837ffbb --- /dev/null +++ b/src/node_api_embedding.cc @@ -0,0 +1,641 @@ +#include +#include // INT_MAX +#include +#define NAPI_EXPERIMENTAL +#include "env-inl.h" +#include "js_native_api.h" +#include "js_native_api_v8.h" +#include "node_api_embedding.h" +#include "node_api_internals.h" +#include "simdutf.h" +#include "util-inl.h" + +namespace node { + +// Declare functions implemented in embed_helpers.cc +v8::Maybe SpinEventLoopWithoutCleanup(Environment* env); +v8::Maybe SpinEventLoopWithoutCleanup( + Environment* env, const std::function& shouldContinue); + +} // end of namespace node + +namespace v8impl { +namespace { + +class CStringArray { + public: + explicit CStringArray(const std::vector& strings) noexcept + : size_(strings.size()) { + if (size_ < inplace_buffer_.size()) { + cstrings_ = inplace_buffer_.data(); + } else { + allocated_buffer_ = std::make_unique(size_); + cstrings_ = allocated_buffer_.get(); + } + for (size_t i = 0; i < size_; ++i) { + cstrings_[i] = strings[i].c_str(); + } + } + + CStringArray() = delete; + CStringArray(const CStringArray&) = delete; + CStringArray& operator=(const CStringArray&) = delete; + + const char** cstrings() const { return cstrings_; } + size_t size() const { return size_; } + + private: + const char** cstrings_; + size_t size_; + std::array inplace_buffer_; + std::unique_ptr allocated_buffer_; +}; + +class EmbeddedPlatform { + public: + static bool InitOncePerProcess() noexcept { + return !is_initialized_.test_and_set(); + } + + static bool UninitOncePerProcess() noexcept { + return is_initialized_.test() && !is_uninitialized_.test_and_set(); + } + + static EmbeddedPlatform* GetInstance() noexcept { return platform_.get(); } + + static EmbeddedPlatform* CreateInstance( + std::shared_ptr&& + platform_init_result) noexcept { + platform_ = + std::make_unique(std::move(platform_init_result)); + return platform_.get(); + } + + static void DeleteInstance() noexcept { platform_ = nullptr; } + + static void set_v8_platform( + std::unique_ptr&& v8_platform) { + platform_->v8_platform_ = std::move(v8_platform); + } + + static node::MultiIsolatePlatform* get_v8_platform() noexcept { + return platform_->v8_platform_.get(); + } + + const std::vector& args() const { + return platform_init_result_->args(); + } + + const std::vector& exec_args() const { + return platform_init_result_->exec_args(); + } + + explicit EmbeddedPlatform(std::shared_ptr&& + platform_init_result) noexcept + : platform_init_result_(std::move(platform_init_result)) {} + + EmbeddedPlatform(const EmbeddedPlatform&) = delete; + EmbeddedPlatform& operator=(const EmbeddedPlatform&) = delete; + + private: + std::shared_ptr platform_init_result_; + std::unique_ptr v8_platform_; + + static std::atomic_flag is_initialized_; + static std::atomic_flag is_uninitialized_; + static std::unique_ptr platform_; +}; + +std::atomic_flag EmbeddedPlatform::is_initialized_{}; +std::atomic_flag EmbeddedPlatform::is_uninitialized_{}; +std::unique_ptr EmbeddedPlatform::platform_{}; + +struct EmbeddedEnvironmentOptions { + explicit EmbeddedEnvironmentOptions() noexcept + : args_(EmbeddedPlatform::GetInstance()->args()), + exec_args_(EmbeddedPlatform::GetInstance()->exec_args()) {} + + EmbeddedEnvironmentOptions(const EmbeddedEnvironmentOptions&) = delete; + EmbeddedEnvironmentOptions& operator=(const EmbeddedEnvironmentOptions&) = + delete; + + std::vector args_; + std::vector exec_args_; + node::EmbedderSnapshotData::Pointer snapshot_; + std::function create_snapshot_; + std::optional snapshot_config_; +}; + +struct IsolateLocker { + IsolateLocker(node::CommonEnvironmentSetup* env_setup) + : v8_locker_(env_setup->isolate()), + isolate_scope_(env_setup->isolate()), + handle_scope_(env_setup->isolate()), + context_scope_(env_setup->context()) {} + + bool IsLocked() const { + return v8::Locker::IsLocked(v8::Isolate::GetCurrent()); + } + + void IncrementLockCount() { ++lock_count_; } + + bool DecrementLockCount() { + --lock_count_; + return lock_count_ == 0; + } + + private: + int32_t lock_count_ = 1; + v8::Locker v8_locker_; + v8::Isolate::Scope isolate_scope_; + v8::HandleScope handle_scope_; + v8::Context::Scope context_scope_; +}; + +class EmbeddedEnvironment final : public node_napi_env__ { + public: + explicit EmbeddedEnvironment( + std::unique_ptr&& env_setup, + v8::Local context, + const std::string& module_filename, + int32_t module_api_version) + : node_napi_env__(context, module_filename, module_api_version), + env_setup_(std::move(env_setup)) {} + + static EmbeddedEnvironment* FromNapiEnv(napi_env env) { + return static_cast(env); + } + + node::CommonEnvironmentSetup* env_setup() { return env_setup_.get(); } + + std::unique_ptr ResetEnvSetup() { + return std::move(env_setup_); + } + + napi_status OpenScope() { + if (isolate_locker_.has_value()) { + if (!isolate_locker_->IsLocked()) return napi_generic_failure; + isolate_locker_->IncrementLockCount(); + } else { + isolate_locker_.emplace(env_setup_.get()); + } + return napi_ok; + } + + napi_status CloseScope() { + if (!isolate_locker_.has_value()) return napi_generic_failure; + if (!isolate_locker_->IsLocked()) return napi_generic_failure; + if (isolate_locker_->DecrementLockCount()) isolate_locker_.reset(); + return napi_ok; + } + + bool IsScopeOpened() const { return isolate_locker_.has_value(); } + + std::function create_snapshot_; + + private: + std::unique_ptr env_setup_; + std::optional isolate_locker_; +}; + +std::vector ToCStringVector(const std::vector& vec) { + std::vector result; + result.reserve(vec.size()); + for (const std::string& str : vec) { + result.push_back(str.c_str()); + } + return result; +} + +node::ProcessInitializationFlags::Flags GetProcessInitializationFlags( + node_api_platform_flags flags) { + uint32_t result = node::ProcessInitializationFlags::kNoFlags; + if ((flags & node_api_platform_enable_env_var) == 0) { + // Disable reading the NODE_OPTIONS environment variable. + result |= node::ProcessInitializationFlags::kDisableNodeOptionsEnv; + } + if ((flags & node_api_platform_init_icu) == 0) { + // Do not initialize ICU. + result |= node::ProcessInitializationFlags::kNoICU; + } + // Do not modify stdio file descriptor or TTY state. + result |= node::ProcessInitializationFlags::kNoStdioInitialization; + // Do not register Node.js-specific signal handlers + // and reset other signal handlers to default state. + result |= node::ProcessInitializationFlags::kNoDefaultSignalHandling; + // Do not perform V8 initialization. + result |= node::ProcessInitializationFlags::kNoInitializeV8; + // Do not initialize a default Node.js-provided V8 platform instance. + result |= node::ProcessInitializationFlags::kNoInitializeNodeV8Platform; + if ((flags & node_api_platform_init_openssl) == 0) { + // Do not initialize OpenSSL config. + result |= node::ProcessInitializationFlags::kNoInitOpenSSL; + } + if ((flags & node_api_platform_parse_global_debug_vars) == 0) { + // Do not initialize Node.js debugging based on environment variables. + result |= node::ProcessInitializationFlags::kNoParseGlobalDebugVariables; + } + if ((flags & node_api_platform_adjust_resource_limits) == 0) { + // Do not adjust OS resource limits for this process. + result |= node::ProcessInitializationFlags::kNoAdjustResourceLimits; + } + if ((flags & node_api_platform_no_large_pages) != 0) { + // Do not map code segments into large pages for this process. + result |= node::ProcessInitializationFlags::kNoUseLargePages; + } + if ((flags & node_api_platform_print_help_or_version) == 0) { + // Skip printing output for --help, --version, --v8-options. + result |= node::ProcessInitializationFlags::kNoPrintHelpOrVersionOutput; + } + if ((flags & node_api_platform_generate_predictable_snapshot) == 0) { + // Initialize the process for predictable snapshot generation. + result |= node::ProcessInitializationFlags::kGeneratePredictableSnapshot; + } + return static_cast(result); +} + +} // end of anonymous namespace +} // end of namespace v8impl + +napi_status NAPI_CDECL +node_api_init_once_per_process(int32_t argc, + char* argv[], + node_api_platform_flags flags, + node_api_get_strings_callback get_errors_cb, + void* errors_data, + bool* early_return, + int32_t* exit_code) { + if (argc == 0) return napi_invalid_arg; + if (argv == nullptr) return napi_invalid_arg; + if (!v8impl::EmbeddedPlatform::InitOncePerProcess()) + return napi_generic_failure; + + std::vector args(argv, argv + argc); + + std::shared_ptr platform_init_result = + node::InitializeOncePerProcess( + args, v8impl::GetProcessInitializationFlags(flags)); + + if (get_errors_cb != nullptr && !platform_init_result->errors().empty()) { + v8impl::CStringArray errors(platform_init_result->errors()); + get_errors_cb(errors_data, errors.size(), errors.cstrings()); + } + + if (early_return != nullptr) { + *early_return = platform_init_result->early_return(); + } + + if (exit_code != nullptr) { + *exit_code = platform_init_result->exit_code(); + } + + if (platform_init_result->early_return()) { + return platform_init_result->exit_code() == 0 ? napi_ok + : napi_generic_failure; + } + + v8impl::EmbeddedPlatform* platform = + v8impl::EmbeddedPlatform::CreateInstance(std::move(platform_init_result)); + + int32_t thread_pool_size = + static_cast(node::per_process::cli_options->v8_thread_pool_size); + platform->set_v8_platform( + node::MultiIsolatePlatform::Create(thread_pool_size)); + v8::V8::InitializePlatform(platform->get_v8_platform()); + v8::V8::Initialize(); + + return napi_ok; +} + +napi_status NAPI_CDECL node_api_uninit_once_per_process() { + if (!v8impl::EmbeddedPlatform::UninitOncePerProcess()) + return napi_generic_failure; + v8::V8::Dispose(); + v8::V8::DisposePlatform(); + node::TearDownOncePerProcess(); + v8impl::EmbeddedPlatform::DeleteInstance(); + return napi_ok; +} + +napi_status NAPI_CDECL +node_api_create_env_options(node_api_env_options* result) { + if (result == nullptr) return napi_invalid_arg; + std::unique_ptr options = + std::make_unique(); + // Transfer ownership of the options object to the caller. + *result = reinterpret_cast(options.release()); + return napi_ok; +} + +napi_status NAPI_CDECL +node_api_delete_env_options(node_api_env_options options) { + if (options == nullptr) return napi_invalid_arg; + // Acquire ownership of the options object to delete it. + std::unique_ptr options_ptr{ + reinterpret_cast(options)}; + return napi_ok; +} + +napi_status NAPI_CDECL +node_api_env_options_get_args(node_api_env_options options, + node_api_get_strings_callback get_strings_cb, + void* strings_data) { + if (options == nullptr) return napi_invalid_arg; + if (get_strings_cb == nullptr) return napi_invalid_arg; + + v8impl::EmbeddedEnvironmentOptions* env_options = + reinterpret_cast(options); + v8impl::CStringArray args(env_options->args_); + get_strings_cb(strings_data, args.size(), args.cstrings()); + + return napi_ok; +} + +napi_status NAPI_CDECL node_api_env_options_set_args( + node_api_env_options options, size_t argc, const char* argv[]) { + if (options == nullptr) return napi_invalid_arg; + if (argv == nullptr) return napi_invalid_arg; + + v8impl::EmbeddedEnvironmentOptions* env_options = + reinterpret_cast(options); + env_options->args_.assign(argv, argv + argc); + + return napi_ok; +} + +napi_status NAPI_CDECL +node_api_env_options_get_exec_args(node_api_env_options options, + node_api_get_strings_callback get_strings_cb, + void* strings_data) { + if (options == nullptr) return napi_invalid_arg; + if (get_strings_cb == nullptr) return napi_invalid_arg; + + v8impl::EmbeddedEnvironmentOptions* env_options = + reinterpret_cast(options); + v8impl::CStringArray exec_args(env_options->exec_args_); + get_strings_cb(strings_data, exec_args.size(), exec_args.cstrings()); + + return napi_ok; +} + +napi_status NAPI_CDECL node_api_env_options_set_exec_args( + node_api_env_options options, size_t argc, const char* argv[]) { + if (options == nullptr) return napi_invalid_arg; + if (argv == nullptr) return napi_invalid_arg; + + v8impl::EmbeddedEnvironmentOptions* env_options = + reinterpret_cast(options); + env_options->exec_args_.assign(argv, argv + argc); + + return napi_ok; +} + +napi_status NAPI_CDECL +node_api_env_options_set_snapshot(node_api_env_options options, + const char* snapshot_data, + size_t snapshot_size) { + if (options == nullptr) return napi_invalid_arg; + if (snapshot_data == nullptr) return napi_invalid_arg; + + v8impl::EmbeddedEnvironmentOptions* env_options = + reinterpret_cast(options); + env_options->snapshot_ = node::EmbedderSnapshotData::FromBlob( + std::string_view(snapshot_data, snapshot_size)); + + return napi_ok; +} + +napi_status NAPI_CDECL +node_api_env_options_create_snapshot(node_api_env_options options, + node_api_get_string_callback get_blob_cb, + void* blob_cb_data, + node_api_snapshot_flags snapshot_flags) { + if (options == nullptr) return napi_invalid_arg; + if (get_blob_cb == nullptr) return napi_invalid_arg; + + v8impl::EmbeddedEnvironmentOptions* env_options = + reinterpret_cast(options); + env_options->create_snapshot_ = + [get_blob_cb, blob_cb_data](const node::EmbedderSnapshotData* snapshot) { + std::vector blob = snapshot->ToBlob(); + get_blob_cb(blob_cb_data, blob.data(), blob.size()); + }; + + if ((snapshot_flags & node_api_snapshot_no_code_cache) != 0) { + if (!env_options->snapshot_config_.has_value()) { + env_options->snapshot_config_ = node::SnapshotConfig{}; + } + env_options->snapshot_config_.value().flags = + static_cast( + static_cast(env_options->snapshot_config_.value().flags) | + static_cast(node::SnapshotFlags::kWithoutCodeCache)); + } + + return napi_ok; +} + +napi_status NAPI_CDECL +node_api_create_env(node_api_env_options options, + node_api_get_strings_callback get_errors_cb, + void* errors_data, + const char* main_script, + int32_t api_version, + napi_env* result) { + if (options == nullptr) return napi_invalid_arg; + if (result == nullptr) return napi_invalid_arg; + if (api_version == 0) api_version = NODE_API_DEFAULT_MODULE_API_VERSION; + + v8impl::EmbeddedEnvironmentOptions* env_options = + reinterpret_cast(options); + std::vector errors; + + std::unique_ptr env_setup; + node::MultiIsolatePlatform* platform = + v8impl::EmbeddedPlatform::GetInstance()->get_v8_platform(); + node::EnvironmentFlags::Flags flags = + static_cast( + node::EnvironmentFlags::kDefaultFlags | + node::EnvironmentFlags::kNoCreateInspector); + if (env_options->snapshot_) { + env_setup = node::CommonEnvironmentSetup::CreateFromSnapshot( + platform, + &errors, + env_options->snapshot_.get(), + env_options->args_, + env_options->exec_args_, + flags); + } else if (env_options->create_snapshot_) { + if (env_options->snapshot_config_.has_value()) { + env_setup = node::CommonEnvironmentSetup::CreateForSnapshotting( + platform, + &errors, + env_options->args_, + env_options->exec_args_, + env_options->snapshot_config_.value()); + } else { + env_setup = node::CommonEnvironmentSetup::CreateForSnapshotting( + platform, &errors, env_options->args_, env_options->exec_args_); + } + } else { + env_setup = node::CommonEnvironmentSetup::Create( + platform, &errors, env_options->args_, env_options->exec_args_, flags); + } + + if (get_errors_cb != nullptr && !errors.empty()) { + v8impl::CStringArray cerrors(errors); + get_errors_cb(errors_data, cerrors.size(), cerrors.cstrings()); + } + + if (env_setup == nullptr) { + return napi_generic_failure; + } + + std::string filename = + env_options->args_.size() > 1 ? env_options->args_[1] : ""; + node::CommonEnvironmentSetup* env_setup_ptr = env_setup.get(); + + v8impl::IsolateLocker isolate_locker(env_setup_ptr); + v8impl::EmbeddedEnvironment* embedded_env = new v8impl::EmbeddedEnvironment( + std::move(env_setup), env_setup_ptr->context(), filename, api_version); + embedded_env->create_snapshot_ = env_options->create_snapshot_; + embedded_env->node_env()->AddCleanupHook( + [](void* arg) { static_cast(arg)->Unref(); }, + static_cast(embedded_env)); + *result = embedded_env; + + node::Environment* node_env = env_setup_ptr->env(); + + v8::MaybeLocal ret = + env_options->snapshot_ + ? node::LoadEnvironment(node_env, node::StartExecutionCallback{}) + : node::LoadEnvironment(node_env, std::string_view(main_script)); + + if (ret.IsEmpty()) return napi_pending_exception; + + return napi_ok; +} + +napi_status NAPI_CDECL node_api_delete_env(napi_env env, int* exit_code) { + CHECK_ENV(env); + v8impl::EmbeddedEnvironment* embedded_env = + v8impl::EmbeddedEnvironment::FromNapiEnv(env); + + if (embedded_env->IsScopeOpened()) return napi_generic_failure; + + { + v8impl::IsolateLocker isolate_locker(embedded_env->env_setup()); + + int ret = node::SpinEventLoop(embedded_env->node_env()).FromMaybe(1); + if (exit_code != nullptr) *exit_code = ret; + } + + std::unique_ptr env_setup = + embedded_env->ResetEnvSetup(); + + if (embedded_env->create_snapshot_) { + node::EmbedderSnapshotData::Pointer snapshot = env_setup->CreateSnapshot(); + assert(snapshot); + embedded_env->create_snapshot_(snapshot.get()); + } + + node::Stop(embedded_env->node_env()); + + return napi_ok; +} + +napi_status NAPI_CDECL node_api_open_env_scope(napi_env env) { + CHECK_ENV(env); + v8impl::EmbeddedEnvironment* embedded_env = + v8impl::EmbeddedEnvironment::FromNapiEnv(env); + + return embedded_env->OpenScope(); +} + +napi_status NAPI_CDECL node_api_close_env_scope(napi_env env) { + CHECK_ENV(env); + v8impl::EmbeddedEnvironment* embedded_env = + v8impl::EmbeddedEnvironment::FromNapiEnv(env); + + return embedded_env->CloseScope(); +} + +napi_status NAPI_CDECL node_api_run_env(napi_env env) { + CHECK_ENV(env); + v8impl::EmbeddedEnvironment* embedded_env = + v8impl::EmbeddedEnvironment::FromNapiEnv(env); + + if (node::SpinEventLoopWithoutCleanup(embedded_env->node_env()).IsNothing()) { + return napi_closing; + } + + return napi_ok; +} + +napi_status NAPI_CDECL node_api_run_env_while(napi_env env, + node_api_run_predicate predicate, + void* predicate_data, + bool* has_more_work) { + CHECK_ENV(env); + CHECK_ARG(env, predicate); + v8impl::EmbeddedEnvironment* embedded_env = + v8impl::EmbeddedEnvironment::FromNapiEnv(env); + + if (predicate(predicate_data)) { + if (node::SpinEventLoopWithoutCleanup( + embedded_env->node_env(), + [predicate, predicate_data]() { return predicate(predicate_data); }) + .IsNothing()) { + return napi_closing; + } + } + + if (has_more_work != nullptr) { + *has_more_work = uv_loop_alive(embedded_env->node_env()->event_loop()); + } + + return napi_ok; +} + +napi_status NAPI_CDECL node_api_await_promise(napi_env env, + napi_value promise, + napi_value* result) { + NAPI_PREAMBLE(env); + CHECK_ARG(env, result); + + v8impl::EmbeddedEnvironment* embedded_env = + v8impl::EmbeddedEnvironment::FromNapiEnv(env); + + v8::EscapableHandleScope scope(env->isolate); + + v8::Local promise_value = v8impl::V8LocalValueFromJsValue(promise); + if (promise_value.IsEmpty() || !promise_value->IsPromise()) + return napi_invalid_arg; + v8::Local promise_object = promise_value.As(); + + v8::Local rejected = v8::Boolean::New(env->isolate, false); + v8::Local err_handler = + v8::Function::New( + env->context(), + [](const v8::FunctionCallbackInfo& info) { return; }, + rejected) + .ToLocalChecked(); + + if (promise_object->Catch(env->context(), err_handler).IsEmpty()) + return napi_pending_exception; + + if (node::SpinEventLoopWithoutCleanup( + embedded_env->node_env(), + [&promise_object]() { + return promise_object->State() == + v8::Promise::PromiseState::kPending; + }) + .IsNothing()) + return napi_closing; + + *result = + v8impl::JsValueFromV8LocalValue(scope.Escape(promise_object->Result())); + if (promise_object->State() == v8::Promise::PromiseState::kRejected) + return napi_pending_exception; + + return napi_ok; +} diff --git a/src/node_api_embedding.h b/src/node_api_embedding.h new file mode 100644 index 00000000000000..2ddb9d8b1e5641 --- /dev/null +++ b/src/node_api_embedding.h @@ -0,0 +1,126 @@ +#ifndef SRC_NODE_API_EMBEDDING_H_ +#define SRC_NODE_API_EMBEDDING_H_ + +#include "node_api.h" + +EXTERN_C_START + +typedef struct node_api_env_options__* node_api_env_options; + +typedef enum { + node_api_platform_no_flags = 0, + // Enable reading the NODE_OPTIONS environment variable. + node_api_platform_enable_env_var = 1 << 0, + // Initialize ICU. + node_api_platform_init_icu = 1 << 1, + // Initialize OpenSSL config. + node_api_platform_init_openssl = 1 << 2, + // Initialize Node.js debugging based on environment variables. + node_api_platform_parse_global_debug_vars = 1 << 3, + // Adjust OS resource limits for this process. + node_api_platform_adjust_resource_limits = 1 << 4, + // Do not map code segments into large pages for this process. + node_api_platform_no_large_pages = 1 << 5, + // Allow printing output for --help, --version, --v8-options. + node_api_platform_print_help_or_version = 1 << 6, + // Initialize the process for predictable snapshot generation. + node_api_platform_generate_predictable_snapshot = 1 << 7, +} node_api_platform_flags; + +typedef enum { + node_api_snapshot_no_flags = 0, + // Whether code cache should be generated as part of the snapshot. + // Code cache reduces the time spent on compiling functions included + // in the snapshot at the expense of a bigger snapshot size and + // potentially breaking portability of the snapshot. + node_api_snapshot_no_code_cache = 1 << 0, +} node_api_snapshot_flags; + +typedef void(NAPI_CDECL* node_api_get_strings_callback)( + void* data, size_t str_count, const char* str_array[]); + +typedef void(NAPI_CDECL* node_api_get_string_callback)(void* cb_data, + const char* str, + size_t size); + +typedef bool(NAPI_CDECL* node_api_run_predicate)(void* predicate_data); + +NAPI_EXTERN napi_status NAPI_CDECL +node_api_init_once_per_process(int32_t argc, + char* argv[], + node_api_platform_flags flags, + node_api_get_strings_callback get_errors_cb, + void* errors_data, + bool* early_return, + int32_t* exit_code); + +NAPI_EXTERN napi_status NAPI_CDECL node_api_uninit_once_per_process(); + +NAPI_EXTERN napi_status NAPI_CDECL +node_api_create_env_options(node_api_env_options* result); + +NAPI_EXTERN napi_status NAPI_CDECL +node_api_delete_env_options(node_api_env_options options); + +NAPI_EXTERN napi_status NAPI_CDECL +node_api_env_options_get_args(node_api_env_options options, + node_api_get_strings_callback get_strings_cb, + void* strings_data); + +NAPI_EXTERN napi_status NAPI_CDECL node_api_env_options_set_args( + node_api_env_options options, size_t argc, const char* argv[]); + +NAPI_EXTERN napi_status NAPI_CDECL +node_api_env_options_get_exec_args(node_api_env_options options, + node_api_get_strings_callback get_strings_cb, + void* strings_data); + +NAPI_EXTERN napi_status NAPI_CDECL node_api_env_options_set_exec_args( + node_api_env_options options, size_t argc, const char* argv[]); + +NAPI_EXTERN napi_status NAPI_CDECL +node_api_env_options_set_snapshot(node_api_env_options options, + const char* snapshot_data, + size_t snapshot_size); + +NAPI_EXTERN napi_status NAPI_CDECL +node_api_env_options_create_snapshot(node_api_env_options options, + node_api_get_string_callback get_blob_cb, + void* blob_cb_data, + node_api_snapshot_flags snapshot_flags); + +// TODO(vmoroz): Remove the main_script parameter. +// TODO(vmoroz): Add ABI-safe way to access internal module functionality. +// TODO(vmoroz): Add ability to create snapshots and to load them. +// TODO(vmoroz): Pass EnvironmentFlags +// TODO(vmoroz): Allow setting the global inspector for a specific environment. +NAPI_EXTERN napi_status NAPI_CDECL +node_api_create_env(node_api_env_options options, + node_api_get_strings_callback get_errors_cb, + void* errors_data, + const char* main_script, + int32_t api_version, + napi_env* result); + +NAPI_EXTERN napi_status NAPI_CDECL node_api_delete_env(napi_env env, + int* exit_code); + +NAPI_EXTERN napi_status NAPI_CDECL node_api_open_env_scope(napi_env env); + +NAPI_EXTERN napi_status NAPI_CDECL node_api_close_env_scope(napi_env env); + +NAPI_EXTERN napi_status NAPI_CDECL node_api_run_env(napi_env env); + +NAPI_EXTERN napi_status NAPI_CDECL +node_api_run_env_while(napi_env env, + node_api_run_predicate predicate, + void* predicate_data, + bool* has_more_work); + +NAPI_EXTERN napi_status NAPI_CDECL node_api_await_promise(napi_env env, + napi_value promise, + napi_value* result); + +EXTERN_C_END + +#endif // SRC_NODE_API_EMBEDDING_H_ diff --git a/test/embedding/.eslintrc.yaml b/test/embedding/.eslintrc.yaml new file mode 100644 index 00000000000000..b3981bdd272eca --- /dev/null +++ b/test/embedding/.eslintrc.yaml @@ -0,0 +1,3 @@ +rules: + node-core/required-modules: off + node-core/require-common-first: off diff --git a/test/embedding/cjs.cjs b/test/embedding/cjs.cjs new file mode 100644 index 00000000000000..df0ddbf40cf291 --- /dev/null +++ b/test/embedding/cjs.cjs @@ -0,0 +1,3 @@ +module.exports = { + value: "original" +}; diff --git a/test/embedding/embedtest.cc b/test/embedding/embedtest.cc index f481efc59a6eba..251c1b3c53859a 100644 --- a/test/embedding/embedtest.cc +++ b/test/embedding/embedtest.cc @@ -2,7 +2,6 @@ #undef NDEBUG #endif #include -#include "executable_wrapper.h" #include "node.h" #include @@ -27,10 +26,7 @@ static int RunNodeInstance(MultiIsolatePlatform* platform, const std::vector& args, const std::vector& exec_args); -NODE_MAIN(int argc, node::argv_type raw_argv[]) { - char** argv = nullptr; - node::FixupMain(argc, raw_argv, &argv); - +extern "C" int test_main_cpp_api(size_t argc, const char* argv[]) { std::vector args(argv, argv + argc); std::shared_ptr result = node::InitializeOncePerProcess( diff --git a/test/embedding/embedtest_concurrent_node_api.cc b/test/embedding/embedtest_concurrent_node_api.cc new file mode 100644 index 00000000000000..593e7961ef7f60 --- /dev/null +++ b/test/embedding/embedtest_concurrent_node_api.cc @@ -0,0 +1,231 @@ +#include "embedtest_node_api.h" + +#define NAPI_EXPERIMENTAL +#include + +#include +#include + +const char* main_script = + "globalThis.require = require('module').createRequire(process.execPath);\n" + "require('vm').runInThisContext(process.argv[1]);"; + +// We can use multiple environments at the same time on their own threads. +extern "C" int32_t test_main_concurrent_node_api(int32_t argc, char* argv[]) { + std::atomic global_count{0}; + std::atomic global_exit_code{0}; + CHECK(node_api_init_once_per_process(argc, + argv, + node_api_platform_no_flags, + nullptr, + nullptr, + nullptr, + nullptr)); + + const size_t thread_count = 12; + std::vector threads; + threads.reserve(thread_count); + for (size_t i = 0; i < thread_count; i++) { + threads.emplace_back([&global_count, &global_exit_code] { + int32_t exit_code = [&]() { + node_api_env_options options; + CHECK(node_api_create_env_options(&options)); + napi_env env; + CHECK(node_api_create_env( + options, nullptr, nullptr, main_script, NAPI_VERSION, &env)); + CHECK(node_api_delete_env_options(options)); + + CHECK(node_api_open_env_scope(env)); + + napi_value global, my_count; + CHECK(napi_get_global(env, &global)); + CHECK(napi_get_named_property(env, global, "myCount", &my_count)); + int32_t count; + CHECK(napi_get_value_int32(env, my_count, &count)); + global_count.fetch_add(count); + + CHECK(node_api_close_env_scope(env)); + CHECK(node_api_delete_env(env, nullptr)); + return 0; + }(); + if (exit_code != 0) { + global_exit_code.store(exit_code); + } + }); + } + + for (size_t i = 0; i < thread_count; i++) { + threads[i].join(); + } + + CHECK_EXIT_CODE(global_exit_code.load()); + + CHECK(node_api_uninit_once_per_process()); + + fprintf(stdout, "%d\n", global_count.load()); + + return 0; +} + +// We can use multiple environments at the same thread. +// For each use we must open and close the environment scope. +extern "C" int32_t test_main_multi_env_node_api(int32_t argc, char* argv[]) { + CHECK(node_api_init_once_per_process(argc, + argv, + node_api_platform_no_flags, + nullptr, + nullptr, + nullptr, + nullptr)); + + node_api_env_options options; + CHECK(node_api_create_env_options(&options)); + const size_t env_count = 12; + std::vector envs; + envs.reserve(env_count); + for (size_t i = 0; i < env_count; i++) { + napi_env env; + CHECK(node_api_create_env( + options, nullptr, nullptr, main_script, NAPI_VERSION, &env)); + envs.push_back(env); + + CHECK(node_api_open_env_scope(env)); + + napi_value undefined, global, func; + CHECK(napi_get_undefined(env, &undefined)); + CHECK(napi_get_global(env, &global)); + CHECK(napi_get_named_property(env, global, "incMyCount", &func)); + + napi_valuetype func_type; + CHECK(napi_typeof(env, func, &func_type)); + CHECK_TRUE(func_type == napi_function); + CHECK(napi_call_function(env, undefined, func, 0, nullptr, nullptr)); + + CHECK(node_api_close_env_scope(env)); + } + CHECK(node_api_delete_env_options(options)); + + bool more_work = false; + do { + more_work = false; + for (napi_env env : envs) { + CHECK(node_api_open_env_scope(env)); + + bool has_more_work = false; + bool had_run_once = false; + CHECK(node_api_run_env_while( + env, + [](void* predicate_data) { + bool* had_run_once = static_cast(predicate_data); + if (*had_run_once) { + return false; + } + *had_run_once = true; + return true; + }, + &had_run_once, + &has_more_work)); + more_work |= has_more_work; + + CHECK(node_api_close_env_scope(env)); + } + } while (more_work); + + int32_t global_count = 0; + for (size_t i = 0; i < env_count; i++) { + napi_env env = envs[i]; + CHECK(node_api_open_env_scope(env)); + + napi_value global, my_count; + CHECK(napi_get_global(env, &global)); + CHECK(napi_get_named_property(env, global, "myCount", &my_count)); + + napi_valuetype my_count_type; + CHECK(napi_typeof(env, my_count, &my_count_type)); + CHECK_TRUE(my_count_type == napi_number); + int32_t count; + CHECK(napi_get_value_int32(env, my_count, &count)); + + global_count += count; + + CHECK(node_api_close_env_scope(env)); + CHECK(node_api_delete_env(env, nullptr)); + } + + CHECK(node_api_uninit_once_per_process()); + + fprintf(stdout, "%d\n", global_count); + + return 0; +} + +// We can use the environment from different threads as long as only one thread +// at a time is using it. +extern "C" int32_t test_main_multi_thread_node_api(int32_t argc, char* argv[]) { + CHECK(node_api_init_once_per_process(argc, + argv, + node_api_platform_no_flags, + nullptr, + nullptr, + nullptr, + nullptr)); + + node_api_env_options options; + CHECK(node_api_create_env_options(&options)); + napi_env env; + CHECK(node_api_create_env( + options, nullptr, nullptr, main_script, NAPI_VERSION, &env)); + CHECK(node_api_delete_env_options(options)); + + std::mutex mutex; + std::atomic result_count{0}; + std::atomic result_exit_code{0}; + const size_t thread_count = 5; + std::vector threads; + threads.reserve(thread_count); + for (size_t i = 0; i < thread_count; i++) { + threads.emplace_back([env, &result_count, &result_exit_code, &mutex] { + int32_t exit_code = [&]() { + std::scoped_lock lock(mutex); + CHECK(node_api_open_env_scope(env)); + + napi_value undefined, global, func, my_count; + CHECK(napi_get_undefined(env, &undefined)); + CHECK(napi_get_global(env, &global)); + CHECK(napi_get_named_property(env, global, "incMyCount", &func)); + + napi_valuetype func_type; + CHECK(napi_typeof(env, func, &func_type)); + CHECK_TRUE(func_type == napi_function); + CHECK(napi_call_function(env, undefined, func, 0, nullptr, nullptr)); + + CHECK(napi_get_named_property(env, global, "myCount", &my_count)); + napi_valuetype count_type; + CHECK(napi_typeof(env, my_count, &count_type)); + CHECK_TRUE(count_type == napi_number); + int32_t count; + CHECK(napi_get_value_int32(env, my_count, &count)); + result_count.store(count); + + CHECK(node_api_close_env_scope(env)); + return 0; + }(); + if (exit_code != 0) { + result_exit_code.store(exit_code); + } + }); + } + + for (size_t i = 0; i < thread_count; i++) { + threads[i].join(); + } + + CHECK_EXIT_CODE(result_exit_code.load()); + + CHECK(node_api_delete_env(env, nullptr)); + CHECK(node_api_uninit_once_per_process()); + + fprintf(stdout, "%d\n", result_count.load()); + + return 0; +} diff --git a/test/embedding/embedtest_main.cc b/test/embedding/embedtest_main.cc new file mode 100644 index 00000000000000..9e11bd54c2f98a --- /dev/null +++ b/test/embedding/embedtest_main.cc @@ -0,0 +1,46 @@ +#include +#include "executable_wrapper.h" +#include "node.h" + +extern "C" int32_t test_main_cpp_api(int32_t argc, char* argv[]); +extern "C" int32_t test_main_node_api(int32_t argc, char* argv[]); +extern "C" int32_t test_main_modules_node_api(int32_t argc, char* argv[]); +extern "C" int32_t test_main_concurrent_node_api(int32_t argc, char* argv[]); +extern "C" int32_t test_main_multi_env_node_api(int32_t argc, char* argv[]); +extern "C" int32_t test_main_multi_thread_node_api(int32_t argc, char* argv[]); +extern "C" int32_t test_main_snapshot_node_api(int32_t argc, char* argv[]); + +typedef int32_t (*main_callback)(int32_t argc, char* argv[]); + +int32_t CallWithoutArg1(main_callback main, int32_t argc, char** argv) { + for (int32_t i = 2; i < argc; i++) { + argv[i - 1] = argv[i]; + } + argv[--argc] = nullptr; + return main(argc, argv); +} + +NODE_MAIN(int32_t argc, node::argv_type raw_argv[]) { + char** argv = nullptr; + node::FixupMain(argc, raw_argv, &argv); + + if (argc > 1) { + const char* arg1 = argv[1]; + if (strcmp(arg1, "cpp-api") == 0) { + return CallWithoutArg1(test_main_cpp_api, argc, argv); + } else if (strcmp(arg1, "node-api") == 0) { + return CallWithoutArg1(test_main_node_api, argc, argv); + } else if (strcmp(arg1, "modules-node-api") == 0) { + return CallWithoutArg1(test_main_modules_node_api, argc, argv); + } else if (strcmp(arg1, "concurrent-node-api") == 0) { + return CallWithoutArg1(test_main_concurrent_node_api, argc, argv); + } else if (strcmp(arg1, "multi-env-node-api") == 0) { + return CallWithoutArg1(test_main_multi_env_node_api, argc, argv); + } else if (strcmp(arg1, "multi-thread-node-api") == 0) { + return CallWithoutArg1(test_main_multi_thread_node_api, argc, argv); + } else if (strcmp(arg1, "snapshot-node-api") == 0) { + return CallWithoutArg1(test_main_snapshot_node_api, argc, argv); + } + } + return test_main_cpp_api(argc, argv); +} diff --git a/test/embedding/embedtest_modules_node_api.c b/test/embedding/embedtest_modules_node_api.c new file mode 100644 index 00000000000000..14592e1d1058d2 --- /dev/null +++ b/test/embedding/embedtest_modules_node_api.c @@ -0,0 +1,65 @@ +#include "embedtest_node_api.h" + +#define NAPI_EXPERIMENTAL +#include + +#include +#include + +int32_t test_main_modules_node_api(int32_t argc, char* argv[]) { + if (argc < 3) { + fprintf(stderr, "node_api_modules \n"); + return 2; + } + + CHECK(node_api_init_once_per_process( + argc, argv, node_api_platform_no_flags, NULL, NULL, NULL, NULL)); + + node_api_env_options options; + CHECK(node_api_create_env_options(&options)); + napi_env env; + CHECK(node_api_create_env(options, NULL, NULL, NULL, NAPI_VERSION, &env)); + CHECK(node_api_delete_env_options(options)); + + CHECK(node_api_open_env_scope(env)); + + napi_value global, import_name, require_name, import, require, cjs, es6, + value; + CHECK(napi_get_global(env, &global)); + CHECK(napi_create_string_utf8(env, "import", strlen("import"), &import_name)); + CHECK(napi_create_string_utf8( + env, "require", strlen("require"), &require_name)); + CHECK(napi_get_property(env, global, import_name, &import)); + CHECK(napi_get_property(env, global, require_name, &require)); + + CHECK(napi_create_string_utf8(env, argv[1], strlen(argv[1]), &cjs)); + CHECK(napi_create_string_utf8(env, argv[2], strlen(argv[2]), &es6)); + CHECK(napi_create_string_utf8(env, "value", strlen("value"), &value)); + + napi_value es6_module, es6_promise, cjs_module, es6_result, cjs_result; + char buffer[32]; + size_t bufferlen; + + CHECK(napi_call_function(env, global, import, 1, &es6, &es6_promise)); + CHECK(node_api_await_promise(env, es6_promise, &es6_module)); + + CHECK(napi_get_property(env, es6_module, value, &es6_result)); + CHECK(napi_get_value_string_utf8( + env, es6_result, buffer, sizeof(buffer), &bufferlen)); + if (strncmp(buffer, "genuine", bufferlen) != 0) { + FAIL("Unexpected value: %s\n", buffer); + } + + CHECK(napi_call_function(env, global, require, 1, &cjs, &cjs_module)); + CHECK(napi_get_property(env, cjs_module, value, &cjs_result)); + CHECK(napi_get_value_string_utf8( + env, cjs_result, buffer, sizeof(buffer), &bufferlen)); + if (strncmp(buffer, "original", bufferlen) != 0) { + FAIL("Unexpected value: %s\n", buffer); + } + + CHECK(node_api_close_env_scope(env)); + CHECK(node_api_delete_env(env, NULL)); + CHECK(node_api_uninit_once_per_process()); + return 0; +} diff --git a/test/embedding/embedtest_node_api.c b/test/embedding/embedtest_node_api.c new file mode 100644 index 00000000000000..0ce98ac4e4922a --- /dev/null +++ b/test/embedding/embedtest_node_api.c @@ -0,0 +1,223 @@ +#include "embedtest_node_api.h" + +#define NAPI_EXPERIMENTAL +#include + +#include +#include +#include + +// Note: This file is being referred to from doc/api/embedding.md, and excerpts +// from it are included in the documentation. Try to keep these in sync. + +static int32_t RunNodeInstance(); + +const char* main_script = + "globalThis.require = require('module').createRequire(process.execPath);\n" + "globalThis.embedVars = { nön_ascıı: '🏳️‍🌈' };\n" + "require('vm').runInThisContext(process.argv[1]);"; + +static const char* exe_name; + +static void NAPI_CDECL get_errors(void* data, + size_t count, + const char* errors[]) { + for (size_t i = 0; i < count && i < 30; ++i) { + fprintf(stderr, "%s: %s\n", exe_name, errors[i]); + } +} + +int32_t test_main_node_api(int32_t argc, char* argv[]) { + exe_name = argv[0]; + bool early_return = false; + int32_t exit_code = 0; + node_api_init_once_per_process(argc, + argv, + node_api_platform_no_flags, + get_errors, + NULL, + &early_return, + &exit_code); + if (early_return) { + return exit_code; + } + + CHECK_EXIT_CODE(RunNodeInstance()); + + CHECK(node_api_uninit_once_per_process()); + return 0; +} + +int32_t callMe(napi_env env) { + napi_value global; + napi_value cb; + napi_value key; + + CHECK(node_api_open_env_scope(env)); + + CHECK(napi_get_global(env, &global)); + CHECK(napi_create_string_utf8(env, "callMe", NAPI_AUTO_LENGTH, &key)); + CHECK(napi_get_property(env, global, key, &cb)); + + napi_valuetype cb_type; + CHECK(napi_typeof(env, cb, &cb_type)); + + // Only evaluate callMe if it was registered as a function. + if (cb_type == napi_function) { + napi_value undef; + CHECK(napi_get_undefined(env, &undef)); + napi_value arg; + CHECK(napi_create_string_utf8(env, "called", NAPI_AUTO_LENGTH, &arg)); + napi_value result; + CHECK(napi_call_function(env, undef, cb, 1, &arg, &result)); + + char buf[32]; + size_t len; + CHECK(napi_get_value_string_utf8(env, result, buf, 32, &len)); + if (strcmp(buf, "called you") != 0) { + FAIL("Invalid value received: %s\n", buf); + } + printf("%s", buf); + } else if (cb_type != napi_undefined) { + FAIL("Invalid callMe value\n"); + } + + CHECK(node_api_close_env_scope(env)); + return 0; +} + +char callback_buf[32]; +size_t callback_buf_len; +napi_value c_cb(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value arg; + + napi_get_cb_info(env, info, &argc, &arg, NULL, NULL); + napi_get_value_string_utf8(env, arg, callback_buf, 32, &callback_buf_len); + return NULL; +} + +int32_t waitMe(napi_env env) { + napi_value global; + napi_value cb; + napi_value key; + + CHECK(node_api_open_env_scope(env)); + + CHECK(napi_get_global(env, &global)); + CHECK(napi_create_string_utf8(env, "waitMe", NAPI_AUTO_LENGTH, &key)); + CHECK(napi_get_property(env, global, key, &cb)); + + napi_valuetype cb_type; + CHECK(napi_typeof(env, cb, &cb_type)); + + // Only evaluate waitMe if it was registered as a function. + if (cb_type == napi_function) { + napi_value undef; + CHECK(napi_get_undefined(env, &undef)); + napi_value args[2]; + CHECK(napi_create_string_utf8(env, "waited", NAPI_AUTO_LENGTH, &args[0])); + CHECK(napi_create_function( + env, "wait_cb", strlen("wait_cb"), c_cb, NULL, &args[1])); + + napi_value result; + memset(callback_buf, 0, 32); + CHECK(napi_call_function(env, undef, cb, 2, args, &result)); + if (strcmp(callback_buf, "waited you") == 0) { + FAIL("Anachronism detected: %s\n", callback_buf); + } + + CHECK(node_api_run_env(env)); + + if (strcmp(callback_buf, "waited you") != 0) { + FAIL("Invalid value received: %s\n", callback_buf); + } + printf("%s", callback_buf); + } else if (cb_type != napi_undefined) { + FAIL("Invalid waitMe value\n"); + } + + CHECK(node_api_close_env_scope(env)); + return 0; +} + +int32_t waitMeWithCheese(napi_env env) { + napi_value global; + napi_value cb; + napi_value key; + + CHECK(node_api_open_env_scope(env)); + + CHECK(napi_get_global(env, &global)); + CHECK(napi_create_string_utf8(env, "waitPromise", NAPI_AUTO_LENGTH, &key)); + CHECK(napi_get_property(env, global, key, &cb)); + + napi_valuetype cb_type; + CHECK(napi_typeof(env, cb, &cb_type)); + + // Only evaluate waitPromise if it was registered as a function. + if (cb_type == napi_function) { + napi_value undef; + napi_get_undefined(env, &undef); + napi_value arg; + bool result_type; + + CHECK(napi_create_string_utf8(env, "waited", NAPI_AUTO_LENGTH, &arg)); + + memset(callback_buf, 0, 32); + napi_value promise; + napi_value result; + CHECK(napi_call_function(env, undef, cb, 1, &arg, &promise)); + + if (strcmp(callback_buf, "waited with cheese") == 0) { + FAIL("Anachronism detected: %s\n", callback_buf); + } + + CHECK(napi_is_promise(env, promise, &result_type)); + + if (!result_type) { + FAIL("Result is not a Promise\n"); + } + + napi_status r = node_api_await_promise(env, promise, &result); + if (r != napi_ok && r != napi_pending_exception) { + FAIL("Failed awaiting promise: %d\n", r); + } + + const char* expected; + if (r == napi_ok) + expected = "waited with cheese"; + else + expected = "waited without cheese"; + + CHECK(napi_get_value_string_utf8( + env, result, callback_buf, 32, &callback_buf_len)); + if (strcmp(callback_buf, expected) != 0) { + FAIL("Invalid value received: %s\n", callback_buf); + } + printf("%s", callback_buf); + } else if (cb_type != napi_undefined) { + FAIL("Invalid waitPromise value\n"); + } + + CHECK(node_api_close_env_scope(env)); + return 0; +} + +int32_t RunNodeInstance() { + node_api_env_options options; + CHECK(node_api_create_env_options(&options)); + napi_env env; + CHECK(node_api_create_env( + options, NULL, NULL, main_script, NAPI_VERSION, &env)); + CHECK(node_api_delete_env_options(options)); + + CHECK_EXIT_CODE(callMe(env)); + CHECK_EXIT_CODE(waitMe(env)); + CHECK_EXIT_CODE(waitMeWithCheese(env)); + + int32_t exit_code; + CHECK(node_api_delete_env(env, &exit_code)); + + return exit_code; +} diff --git a/test/embedding/embedtest_node_api.h b/test/embedding/embedtest_node_api.h new file mode 100644 index 00000000000000..dcd7686b01f4d0 --- /dev/null +++ b/test/embedding/embedtest_node_api.h @@ -0,0 +1,53 @@ +#ifndef TEST_EMBEDDING_NODE_API_EMBEDTEST_H_ +#define TEST_EMBEDDING_NODE_API_EMBEDTEST_H_ + +#include + +#ifdef __cplusplus + +#include +#include + +extern "C" inline void NAPI_CDECL GetStringVector(void* data, + size_t count, + const char** strs) { + static_cast*>(data)->assign(strs, strs + count); +} + +#endif + +#define CHECK(expr) \ + do { \ + if ((expr) != napi_ok) { \ + fprintf(stderr, "Failed: %s\n", #expr); \ + fprintf(stderr, "File: %s\n", __FILE__); \ + fprintf(stderr, "Line: %d\n", __LINE__); \ + return 1; \ + } \ + } while (0) + +#define CHECK_TRUE(expr) \ + do { \ + if (!(expr)) { \ + fprintf(stderr, "Failed: %s\n", #expr); \ + fprintf(stderr, "File: %s\n", __FILE__); \ + fprintf(stderr, "Line: %d\n", __LINE__); \ + return 1; \ + } \ + } while (0) + +#define FAIL(...) \ + do { \ + fprintf(stderr, __VA_ARGS__); \ + return 1; \ + } while (0) + +#define CHECK_EXIT_CODE(code) \ + do { \ + int exit_code = (code); \ + if (exit_code != 0) { \ + return exit_code; \ + } \ + } while (0) + +#endif // TEST_EMBEDDING_NODE_API_EMBEDTEST_H_ diff --git a/test/embedding/embedtest_snapshot_node_api.cc b/test/embedding/embedtest_snapshot_node_api.cc new file mode 100644 index 00000000000000..c4fd43bc159187 --- /dev/null +++ b/test/embedding/embedtest_snapshot_node_api.cc @@ -0,0 +1,213 @@ +#define NAPI_EXPERIMENTAL +#include "embedtest_node_api.h" + +#include +#include +#include + +#include + +class CStringArray { + public: + explicit CStringArray(const std::vector& strings) noexcept + : size_(strings.size()) { + if (size_ < inplace_buffer_.size()) { + cstrings_ = inplace_buffer_.data(); + } else { + allocated_buffer_ = std::make_unique(size_); + cstrings_ = allocated_buffer_.get(); + } + for (size_t i = 0; i < size_; ++i) { + cstrings_[i] = strings[i].c_str(); + } + } + + const char** cstrings() const { return cstrings_; } + size_t size() const { return size_; } + + private: + const char** cstrings_; + size_t size_; + std::array inplace_buffer_; + std::unique_ptr allocated_buffer_; +}; + +static int32_t RunNodeInstance(); + +extern "C" int32_t test_main_snapshot_node_api(int32_t argc, char* argv[]) { + std::vector args(argv, argv + argc); + bool early_return = false; + int32_t exit_code = 0; + + std::vector errors; + node_api_init_once_per_process(argc, + argv, + node_api_platform_no_flags, + GetStringVector, + &errors, + &early_return, + &exit_code); + if (early_return) { + if (exit_code != 0) { + for (const std::string& err : errors) + fprintf(stderr, "%s: %s\n", args[0].c_str(), err.c_str()); + } else { + for (const std::string& err : errors) printf("%s\n", err.c_str()); + } + return exit_code; + } + + CHECK_EXIT_CODE(RunNodeInstance()); + + CHECK(node_api_uninit_once_per_process()); + return 0; +} + +static const char* exe_name; + +static void NAPI_CDECL get_errors(void* data, + size_t count, + const char* errors[]) { + for (size_t i = 0; i < count && i < 30; ++i) { + fprintf(stderr, "%s: %s\n", exe_name, errors[i]); + } +} + +int32_t RunNodeInstance() { + // Format of the arguments of this binary: + // Building snapshot: + // embedtest js_code_to_eval arg1 arg2... + // --embedder-snapshot-blob blob-path + // --embedder-snapshot-create + // [--embedder-snapshot-as-file] + // [--without-code-cache] + // Running snapshot: + // embedtest --embedder-snapshot-blob blob-path + // [--embedder-snapshot-as-file] + // arg1 arg2... + // No snapshot: + // embedtest arg1 arg2... + + int32_t exit_code = 0; + + node_api_env_options options; + CHECK(node_api_create_env_options(&options)); + std::vector args, exec_args; + CHECK(node_api_env_options_get_args(options, GetStringVector, &args)); + CHECK( + node_api_env_options_get_exec_args(options, GetStringVector, &exec_args)); + + exe_name = args[0].c_str(); + std::vector filtered_args; + bool is_building_snapshot = false; + node_api_snapshot_flags snapshot_flags = node_api_snapshot_no_flags; + std::string snapshot_blob_path; + for (size_t i = 0; i < args.size(); ++i) { + const std::string& arg = args[i]; + if (arg == "--embedder-snapshot-create") { + is_building_snapshot = true; + } else if (arg == "--embedder-snapshot-as-file") { + // This parameter is not implemented by the Node-API, and we must not + // include it in the filtered_args. + } else if (arg == "--without-code-cache") { + snapshot_flags = static_cast( + snapshot_flags | node_api_snapshot_no_code_cache); + } else if (arg == "--embedder-snapshot-blob") { + assert(i + 1 < args.size()); + snapshot_blob_path = args[i + 1]; + i++; + } else { + filtered_args.push_back(arg); + } + } + + bool use_snapshot = false; + if (!snapshot_blob_path.empty() && !is_building_snapshot) { + use_snapshot = true; + FILE* fp = fopen(snapshot_blob_path.c_str(), "rb"); + assert(fp != nullptr); + // Node-API only supports loading snapshots from blobs. + uv_fs_t req = uv_fs_t(); + int32_t statret = + uv_fs_stat(nullptr, &req, snapshot_blob_path.c_str(), nullptr); + assert(statret == 0); + size_t filesize = req.statbuf.st_size; + uv_fs_req_cleanup(&req); + + std::vector vec(filesize); + size_t read = fread(vec.data(), filesize, 1, fp); + assert(read == 1); + assert(snapshot); + int32_t ret = fclose(fp); + assert(ret == 0); + + CHECK(node_api_env_options_set_snapshot(options, vec.data(), vec.size())); + } + + if (is_building_snapshot) { + // It contains at least the binary path, the code to snapshot, + // and --embedder-snapshot-create (which is filtered, so at least + // 2 arguments should remain after filtering). + assert(filtered_args.size() >= 2); + // Insert an anonymous filename as process.argv[1]. + filtered_args.insert(filtered_args.begin() + 1, "__node_anonymous_main"); + } + + CStringArray filtered_args_arr(filtered_args); + CHECK(node_api_env_options_set_args( + options, filtered_args_arr.size(), filtered_args_arr.cstrings())); + + if (!snapshot_blob_path.empty() && is_building_snapshot) { + CHECK(node_api_env_options_create_snapshot( + options, + [](void* cb_data, const char* snapshot_data, size_t snapshot_size) { + const char* snapshot_blob_path = static_cast(cb_data); + FILE* fp = fopen(snapshot_blob_path, "wb"); + assert(fp != nullptr); + + size_t written = fwrite(snapshot_data, snapshot_size, 1, fp); + assert(written == 1); + + int32_t ret = fclose(fp); + assert(ret == 0); + }, + const_cast(snapshot_blob_path.c_str()), + snapshot_flags)); + } + + napi_env env; + if (use_snapshot) { + CHECK(node_api_create_env( + options, get_errors, nullptr, nullptr, NAPI_VERSION, &env)); + } else if (is_building_snapshot) { + // Environment created for snapshotting must set process.argv[1] to + // the name of the main script, which was inserted above. + CHECK(node_api_create_env( + options, + get_errors, + nullptr, + "const assert = require('assert');" + "assert(require('v8').startupSnapshot.isBuildingSnapshot());" + "globalThis.embedVars = { nön_ascıı: '🏳️‍🌈' };" + "globalThis.require = require;" + "require('vm').runInThisContext(process.argv[2]);", + NAPI_VERSION, + &env)); + } else { + CHECK(node_api_create_env( + options, + get_errors, + nullptr, + "const publicRequire = require('module').createRequire(process.cwd() + " + "'/');" + "globalThis.require = publicRequire;" + "globalThis.embedVars = { nön_ascıı: '🏳️‍🌈' };" + "require('vm').runInThisContext(process.argv[1]);", + NAPI_VERSION, + &env)); + } + + CHECK(node_api_delete_env(env, &exit_code)); + + return exit_code; +} diff --git a/test/embedding/es6.mjs b/test/embedding/es6.mjs new file mode 100644 index 00000000000000..01b505b2c5fb19 --- /dev/null +++ b/test/embedding/es6.mjs @@ -0,0 +1 @@ +export const value = 'genuine'; diff --git a/test/embedding/test-embedding.js b/test/embedding/test-embedding.js index 71c4f7f324c973..df17a82d8d7f3a 100644 --- a/test/embedding/test-embedding.js +++ b/test/embedding/test-embedding.js @@ -24,149 +24,301 @@ function resolveBuiltBinary(binary) { } const binary = resolveBuiltBinary('embedtest'); +assert.ok(fs.existsSync(binary)); -spawnSyncAndAssert( - binary, - ['console.log(42)'], - { - trim: true, - stdout: '42', - }); - -spawnSyncAndAssert( - binary, - ['console.log(embedVars.nön_ascıı)'], - { - trim: true, - stdout: '🏳️‍🌈', - }); +function runTest(testName, spawn, ...args) { + process.stdout.write(`Run test: ${testName} ... `); + spawn(binary, ...args); + console.log('ok'); +} -spawnSyncAndExit( - binary, - ['throw new Error()'], - { - status: 1, - signal: null, - }); +function runCommonApiTests(apiType) { + runTest( + `${apiType}: console.log`, + spawnSyncAndAssert, + [apiType, 'console.log(42)'], + { + trim: true, + stdout: '42', + } + ); -spawnSyncAndExit( - binary, - ['require("lib/internal/test/binding")'], - { - status: 1, - signal: null, - }); + runTest( + `${apiType}: console.log non-ascii`, + spawnSyncAndAssert, + [apiType, 'console.log(embedVars.nön_ascıı)'], + { + trim: true, + stdout: '🏳️‍🌈', + } + ); -spawnSyncAndExit( - binary, - ['process.exitCode = 8'], - { - status: 8, - signal: null, - }); - -const fixturePath = JSON.stringify(fixtures.path('exit.js')); -spawnSyncAndExit( - binary, - [`require(${fixturePath})`, 92], - { - status: 92, - signal: null, - }); + runTest( + `${apiType}: throw new Error()`, + spawnSyncAndExit, + [apiType, 'throw new Error()'], + { + status: 1, + signal: null, + } + ); -function getReadFileCodeForPath(path) { - return `(require("fs").readFileSync(${JSON.stringify(path)}, "utf8"))`; -} + runTest( + `${apiType}: require("lib/internal/test/binding")`, + spawnSyncAndExit, + [apiType, 'require("lib/internal/test/binding")'], + { + status: 1, + signal: null, + } + ); -// Basic snapshot support -for (const extraSnapshotArgs of [ - [], ['--embedder-snapshot-as-file'], ['--without-code-cache'], -]) { - // readSync + eval since snapshots don't support userland require() (yet) - const snapshotFixture = fixtures.path('snapshot', 'echo-args.js'); - const blobPath = tmpdir.resolve('embedder-snapshot.blob'); - const buildSnapshotExecArgs = [ - `eval(${getReadFileCodeForPath(snapshotFixture)})`, 'arg1', 'arg2', - ]; - const embedTestBuildArgs = [ - '--embedder-snapshot-blob', blobPath, '--embedder-snapshot-create', - ...extraSnapshotArgs, - ]; - const buildSnapshotArgs = [ - ...buildSnapshotExecArgs, - ...embedTestBuildArgs, - ]; - - const runSnapshotExecArgs = [ - 'arg3', 'arg4', - ]; - const embedTestRunArgs = [ - '--embedder-snapshot-blob', blobPath, - ...extraSnapshotArgs, - ]; - const runSnapshotArgs = [ - ...runSnapshotExecArgs, - ...embedTestRunArgs, - ]; - - fs.rmSync(blobPath, { force: true }); - spawnSyncAndExitWithoutError( - binary, - [ '--', ...buildSnapshotArgs ], - { cwd: tmpdir.path }); - spawnSyncAndAssert( - binary, - [ '--', ...runSnapshotArgs ], - { cwd: tmpdir.path }, + runTest( + `${apiType}: process.exitCode = 8`, + spawnSyncAndExit, + [apiType, 'process.exitCode = 8'], { - stdout(output) { - assert.deepStrictEqual(JSON.parse(output), { - originalArgv: [binary, '__node_anonymous_main', ...buildSnapshotExecArgs], - currentArgv: [binary, ...runSnapshotExecArgs], - }); - return true; - }, - }); -} + status: 8, + signal: null, + } + ); -// Create workers and vm contexts after deserialization -{ - const snapshotFixture = fixtures.path('snapshot', 'create-worker-and-vm.js'); - const blobPath = tmpdir.resolve('embedder-snapshot.blob'); - const buildSnapshotArgs = [ - `eval(${getReadFileCodeForPath(snapshotFixture)})`, - '--embedder-snapshot-blob', blobPath, '--embedder-snapshot-create', - ]; - const runEmbeddedArgs = [ - '--embedder-snapshot-blob', blobPath, - ]; - - fs.rmSync(blobPath, { force: true }); - - spawnSyncAndExitWithoutError( - binary, - [ '--', ...buildSnapshotArgs ], - { cwd: tmpdir.path }); - spawnSyncAndExitWithoutError( - binary, - [ '--', ...runEmbeddedArgs ], - { cwd: tmpdir.path }); -} + { + const fixturePath = JSON.stringify(fixtures.path('exit.js')); + runTest( + `${apiType}: require(fixturePath)`, + spawnSyncAndExit, + [apiType, `require(${fixturePath})`, 92], + { + status: 92, + signal: null, + } + ); + } -// Guarantee NODE_REPL_EXTERNAL_MODULE won't bypass kDisableNodeOptionsEnv -{ - spawnSyncAndExit( - binary, - ['require("os")'], + runTest( + `${apiType}: syntax error`, + spawnSyncAndExit, + [apiType, '0syntax_error'], + { + status: 1, + stderr: /SyntaxError: Invalid or unexpected token/, + } + ); + + // Guarantee NODE_REPL_EXTERNAL_MODULE won't bypass kDisableNodeOptionsEnv + runTest( + `${apiType}: check kDisableNodeOptionsEnv`, + spawnSyncAndExit, + [apiType, 'require("os")'], { env: { ...process.env, - 'NODE_REPL_EXTERNAL_MODULE': 'fs', + NODE_REPL_EXTERNAL_MODULE: 'fs', }, }, { status: 9, signal: null, stderr: `${binary}: NODE_REPL_EXTERNAL_MODULE can't be used with kDisableNodeOptionsEnv${os.EOL}`, - }); + } + ); } + +runCommonApiTests('cpp-api'); +runCommonApiTests('node-api'); + +function getReadFileCodeForPath(path) { + return `(require("fs").readFileSync(${JSON.stringify(path)}, "utf8"))`; +} + +function runSnapshotTests(apiType) { + // Basic snapshot support + for (const extraSnapshotArgs of [ + [], + ['--embedder-snapshot-as-file'], + ['--without-code-cache'], + ]) { + // readSync + eval since snapshots don't support userland require() (yet) + const snapshotFixture = fixtures.path('snapshot', 'echo-args.js'); + const blobPath = tmpdir.resolve('embedder-snapshot.blob'); + const buildSnapshotExecArgs = [ + `eval(${getReadFileCodeForPath(snapshotFixture)})`, + 'arg1', + 'arg2', + ]; + const embedTestBuildArgs = [ + '--embedder-snapshot-blob', + blobPath, + '--embedder-snapshot-create', + ...extraSnapshotArgs, + ]; + const buildSnapshotArgs = [...buildSnapshotExecArgs, ...embedTestBuildArgs]; + + const runSnapshotExecArgs = ['arg3', 'arg4']; + const embedTestRunArgs = [ + '--embedder-snapshot-blob', + blobPath, + ...extraSnapshotArgs, + ]; + const runSnapshotArgs = [...runSnapshotExecArgs, ...embedTestRunArgs]; + + fs.rmSync(blobPath, { force: true }); + runTest( + `${apiType}: build basic snapshot ${extraSnapshotArgs.join(' ')}`, + spawnSyncAndExitWithoutError, + [apiType, '--', ...buildSnapshotArgs], + { + cwd: tmpdir.path, + } + ); + + runTest( + `${apiType}: run basic snapshot ${extraSnapshotArgs.join(' ')}`, + spawnSyncAndAssert, + [apiType, '--', ...runSnapshotArgs], + { cwd: tmpdir.path }, + { + stdout(output) { + assert.deepStrictEqual(JSON.parse(output), { + originalArgv: [ + binary, + '__node_anonymous_main', + ...buildSnapshotExecArgs, + ], + currentArgv: [binary, ...runSnapshotExecArgs], + }); + return true; + }, + } + ); + } + + // Create workers and vm contexts after deserialization + { + const snapshotFixture = fixtures.path( + 'snapshot', + 'create-worker-and-vm.js' + ); + const blobPath = tmpdir.resolve('embedder-snapshot.blob'); + const buildSnapshotArgs = [ + `eval(${getReadFileCodeForPath(snapshotFixture)})`, + '--embedder-snapshot-blob', + blobPath, + '--embedder-snapshot-create', + ]; + const runEmbeddedArgs = ['--embedder-snapshot-blob', blobPath]; + + fs.rmSync(blobPath, { force: true }); + + runTest( + `${apiType}: build create-worker-and-vm snapshot`, + spawnSyncAndExitWithoutError, + [apiType, '--', ...buildSnapshotArgs], + { + cwd: tmpdir.path, + } + ); + + runTest( + `${apiType}: run create-worker-and-vm snapshot`, + spawnSyncAndExitWithoutError, + [apiType, '--', ...runEmbeddedArgs], + { + cwd: tmpdir.path, + } + ); + } +} + +runSnapshotTests('cpp-api'); +runSnapshotTests('snapshot-node-api'); + +// Node-API specific tests +{ + runTest( + `node-api: callMe`, + spawnSyncAndAssert, + ['node-api', 'function callMe(text) { return text + " you"; }'], + { stdout: 'called you' } + ); + + runTest( + `node-api: waitMe`, + spawnSyncAndAssert, + [ + 'node-api', + 'function waitMe(text, cb) { setTimeout(() => cb(text + " you"), 1); }', + ], + { stdout: 'waited you' } + ); + + runTest( + `node-api: waitPromise`, + spawnSyncAndAssert, + [ + 'node-api', + 'function waitPromise(text)' + + '{ return new Promise((res) => setTimeout(() => res(text + " with cheese"), 1)); }', + ], + { stdout: 'waited with cheese' } + ); + + runTest( + `node-api: waitPromise reject`, + spawnSyncAndAssert, + [ + 'node-api', + 'function waitPromise(text)' + + '{ return new Promise((res, rej) => setTimeout(() => rej(text + " without cheese"), 1)); }', + ], + { stdout: 'waited without cheese' } + ); + + runTest( + `concurrent-node-api: run 12 environments concurrently`, + spawnSyncAndAssert, + ['concurrent-node-api', 'myCount = 1'], + { + trim: true, + stdout: '12', + } + ); + + runTest( + 'multi-env-node-api: run 12 environments in the same thread', + spawnSyncAndAssert, + [ + 'multi-env-node-api', + 'myCount = 0; function incMyCount() { ++myCount; if (myCount < 5) { setTimeout(incMyCount, 1); } }', + ], + { + trim: true, + stdout: '60', + } + ); + + runTest( + 'multi-thread-node-api: run and environment from multiple threads', + spawnSyncAndAssert, + [ + 'multi-thread-node-api', + 'myCount = 0; function incMyCount() { ++myCount; if (myCount < 5) { setTimeout(incMyCount, 1); } }', + ], + { + trim: true, + stdout: '5', + } + ); +} + +/* +runTest( + `modules-node-api: load modules`, + spawnSyncAndExitWithoutError, + ['modules-node-api', 'cjs.cjs', 'es6.mjs', ], + { + cwd: __dirname, + } +); +*/ From a4dd991ab9eb2102cadab53ed87d692378d3e808 Mon Sep 17 00:00:00 2001 From: Vladimir Morozov Date: Fri, 30 Aug 2024 12:06:08 -0700 Subject: [PATCH 02/22] remove node_api_delete_env_options --- node.gyp | 4 +- src/node_api_embedding.cc | 93 ++++++++++--------- src/node_api_embedding.h | 4 - test/embedding/embedtest.cc | 2 +- .../embedtest_concurrent_node_api.cc | 12 +-- ...de_api.c => embedtest_modules_node_api.cc} | 6 +- ...dtest_node_api.c => embedtest_node_api.cc} | 8 +- test/embedding/embedtest_node_api.h | 7 +- test/embedding/embedtest_snapshot_node_api.cc | 1 - 9 files changed, 63 insertions(+), 74 deletions(-) rename test/embedding/{embedtest_modules_node_api.c => embedtest_modules_node_api.cc} (92%) rename test/embedding/{embedtest_node_api.c => embedtest_node_api.cc} (97%) diff --git a/node.gyp b/node.gyp index a5c1df550de174..5f2f953dcc8904 100644 --- a/node.gyp +++ b/node.gyp @@ -1289,8 +1289,8 @@ 'test/embedding/embedtest.cc', 'test/embedding/embedtest_concurrent_node_api.cc', 'test/embedding/embedtest_main.cc', - 'test/embedding/embedtest_modules_node_api.c', - 'test/embedding/embedtest_node_api.c', + 'test/embedding/embedtest_modules_node_api.cc', + 'test/embedding/embedtest_node_api.cc', 'test/embedding/embedtest_node_api.h', 'test/embedding/embedtest_snapshot_node_api.cc', ], diff --git a/src/node_api_embedding.cc b/src/node_api_embedding.cc index c3c5e17837ffbb..64bc8590247329 100644 --- a/src/node_api_embedding.cc +++ b/src/node_api_embedding.cc @@ -119,11 +119,12 @@ struct EmbeddedEnvironmentOptions { EmbeddedEnvironmentOptions& operator=(const EmbeddedEnvironmentOptions&) = delete; + bool is_frozen_{false}; std::vector args_; std::vector exec_args_; node::EmbedderSnapshotData::Pointer snapshot_; std::function create_snapshot_; - std::optional snapshot_config_; + node::SnapshotConfig snapshot_config_{}; }; struct IsolateLocker { @@ -155,12 +156,16 @@ struct IsolateLocker { class EmbeddedEnvironment final : public node_napi_env__ { public: explicit EmbeddedEnvironment( + std::unique_ptr&& env_options, std::unique_ptr&& env_setup, v8::Local context, const std::string& module_filename, int32_t module_api_version) : node_napi_env__(context, module_filename, module_api_version), - env_setup_(std::move(env_setup)) {} + env_options_(std::move(env_options)), + env_setup_(std::move(env_setup)) { + env_options_->is_frozen_ = true; + } static EmbeddedEnvironment* FromNapiEnv(napi_env env) { return static_cast(env); @@ -191,9 +196,17 @@ class EmbeddedEnvironment final : public node_napi_env__ { bool IsScopeOpened() const { return isolate_locker_.has_value(); } - std::function create_snapshot_; + const node::EmbedderSnapshotData::Pointer& snapshot() const { + return env_options_->snapshot_; + } + + const std::function& + create_snapshot() { + return env_options_->create_snapshot_; + } private: + std::unique_ptr env_options_; std::unique_ptr env_setup_; std::optional isolate_locker_; }; @@ -327,15 +340,6 @@ node_api_create_env_options(node_api_env_options* result) { return napi_ok; } -napi_status NAPI_CDECL -node_api_delete_env_options(node_api_env_options options) { - if (options == nullptr) return napi_invalid_arg; - // Acquire ownership of the options object to delete it. - std::unique_ptr options_ptr{ - reinterpret_cast(options)}; - return napi_ok; -} - napi_status NAPI_CDECL node_api_env_options_get_args(node_api_env_options options, node_api_get_strings_callback get_strings_cb, @@ -358,8 +362,9 @@ napi_status NAPI_CDECL node_api_env_options_set_args( v8impl::EmbeddedEnvironmentOptions* env_options = reinterpret_cast(options); - env_options->args_.assign(argv, argv + argc); + if (env_options->is_frozen_) return napi_generic_failure; + env_options->args_.assign(argv, argv + argc); return napi_ok; } @@ -385,8 +390,9 @@ napi_status NAPI_CDECL node_api_env_options_set_exec_args( v8impl::EmbeddedEnvironmentOptions* env_options = reinterpret_cast(options); - env_options->exec_args_.assign(argv, argv + argc); + if (env_options->is_frozen_) return napi_generic_failure; + env_options->exec_args_.assign(argv, argv + argc); return napi_ok; } @@ -399,9 +405,10 @@ node_api_env_options_set_snapshot(node_api_env_options options, v8impl::EmbeddedEnvironmentOptions* env_options = reinterpret_cast(options); + if (env_options->is_frozen_) return napi_generic_failure; + env_options->snapshot_ = node::EmbedderSnapshotData::FromBlob( std::string_view(snapshot_data, snapshot_size)); - return napi_ok; } @@ -415,6 +422,8 @@ node_api_env_options_create_snapshot(node_api_env_options options, v8impl::EmbeddedEnvironmentOptions* env_options = reinterpret_cast(options); + if (env_options->is_frozen_) return napi_generic_failure; + env_options->create_snapshot_ = [get_blob_cb, blob_cb_data](const node::EmbedderSnapshotData* snapshot) { std::vector blob = snapshot->ToBlob(); @@ -422,13 +431,9 @@ node_api_env_options_create_snapshot(node_api_env_options options, }; if ((snapshot_flags & node_api_snapshot_no_code_cache) != 0) { - if (!env_options->snapshot_config_.has_value()) { - env_options->snapshot_config_ = node::SnapshotConfig{}; - } - env_options->snapshot_config_.value().flags = - static_cast( - static_cast(env_options->snapshot_config_.value().flags) | - static_cast(node::SnapshotFlags::kWithoutCodeCache)); + env_options->snapshot_config_.flags = static_cast( + static_cast(env_options->snapshot_config_.flags) | + static_cast(node::SnapshotFlags::kWithoutCodeCache)); } return napi_ok; @@ -445,8 +450,8 @@ node_api_create_env(node_api_env_options options, if (result == nullptr) return napi_invalid_arg; if (api_version == 0) api_version = NODE_API_DEFAULT_MODULE_API_VERSION; - v8impl::EmbeddedEnvironmentOptions* env_options = - reinterpret_cast(options); + std::unique_ptr env_options{ + reinterpret_cast(options)}; std::vector errors; std::unique_ptr env_setup; @@ -465,17 +470,12 @@ node_api_create_env(node_api_env_options options, env_options->exec_args_, flags); } else if (env_options->create_snapshot_) { - if (env_options->snapshot_config_.has_value()) { - env_setup = node::CommonEnvironmentSetup::CreateForSnapshotting( - platform, - &errors, - env_options->args_, - env_options->exec_args_, - env_options->snapshot_config_.value()); - } else { - env_setup = node::CommonEnvironmentSetup::CreateForSnapshotting( - platform, &errors, env_options->args_, env_options->exec_args_); - } + env_setup = node::CommonEnvironmentSetup::CreateForSnapshotting( + platform, + &errors, + env_options->args_, + env_options->exec_args_, + env_options->snapshot_config_); } else { env_setup = node::CommonEnvironmentSetup::Create( platform, &errors, env_options->args_, env_options->exec_args_, flags); @@ -495,21 +495,28 @@ node_api_create_env(node_api_env_options options, node::CommonEnvironmentSetup* env_setup_ptr = env_setup.get(); v8impl::IsolateLocker isolate_locker(env_setup_ptr); - v8impl::EmbeddedEnvironment* embedded_env = new v8impl::EmbeddedEnvironment( - std::move(env_setup), env_setup_ptr->context(), filename, api_version); - embedded_env->create_snapshot_ = env_options->create_snapshot_; + std::unique_ptr embedded_env = + std::make_unique(std::move(env_options), + std::move(env_setup), + env_setup_ptr->context(), + filename, + api_version); embedded_env->node_env()->AddCleanupHook( [](void* arg) { static_cast(arg)->Unref(); }, - static_cast(embedded_env)); - *result = embedded_env; + static_cast(embedded_env.get())); + *result = embedded_env.get(); node::Environment* node_env = env_setup_ptr->env(); + // TODO(vmoroz): If we return an error here, then it is not clear if the + // environment must be deleted after that or not. v8::MaybeLocal ret = - env_options->snapshot_ + embedded_env->snapshot() ? node::LoadEnvironment(node_env, node::StartExecutionCallback{}) : node::LoadEnvironment(node_env, std::string_view(main_script)); + embedded_env.release(); + if (ret.IsEmpty()) return napi_pending_exception; return napi_ok; @@ -532,10 +539,10 @@ napi_status NAPI_CDECL node_api_delete_env(napi_env env, int* exit_code) { std::unique_ptr env_setup = embedded_env->ResetEnvSetup(); - if (embedded_env->create_snapshot_) { + if (embedded_env->create_snapshot()) { node::EmbedderSnapshotData::Pointer snapshot = env_setup->CreateSnapshot(); assert(snapshot); - embedded_env->create_snapshot_(snapshot.get()); + embedded_env->create_snapshot()(snapshot.get()); } node::Stop(embedded_env->node_env()); diff --git a/src/node_api_embedding.h b/src/node_api_embedding.h index 2ddb9d8b1e5641..436f3eab6bd53f 100644 --- a/src/node_api_embedding.h +++ b/src/node_api_embedding.h @@ -59,9 +59,6 @@ NAPI_EXTERN napi_status NAPI_CDECL node_api_uninit_once_per_process(); NAPI_EXTERN napi_status NAPI_CDECL node_api_create_env_options(node_api_env_options* result); -NAPI_EXTERN napi_status NAPI_CDECL -node_api_delete_env_options(node_api_env_options options); - NAPI_EXTERN napi_status NAPI_CDECL node_api_env_options_get_args(node_api_env_options options, node_api_get_strings_callback get_strings_cb, @@ -91,7 +88,6 @@ node_api_env_options_create_snapshot(node_api_env_options options, // TODO(vmoroz): Remove the main_script parameter. // TODO(vmoroz): Add ABI-safe way to access internal module functionality. -// TODO(vmoroz): Add ability to create snapshots and to load them. // TODO(vmoroz): Pass EnvironmentFlags // TODO(vmoroz): Allow setting the global inspector for a specific environment. NAPI_EXTERN napi_status NAPI_CDECL diff --git a/test/embedding/embedtest.cc b/test/embedding/embedtest.cc index 251c1b3c53859a..a0aed752d3146d 100644 --- a/test/embedding/embedtest.cc +++ b/test/embedding/embedtest.cc @@ -26,7 +26,7 @@ static int RunNodeInstance(MultiIsolatePlatform* platform, const std::vector& args, const std::vector& exec_args); -extern "C" int test_main_cpp_api(size_t argc, const char* argv[]) { +extern "C" int32_t test_main_cpp_api(int32_t argc, char* argv[]) { std::vector args(argv, argv + argc); std::shared_ptr result = node::InitializeOncePerProcess( diff --git a/test/embedding/embedtest_concurrent_node_api.cc b/test/embedding/embedtest_concurrent_node_api.cc index 593e7961ef7f60..f7b77c9a5b54a8 100644 --- a/test/embedding/embedtest_concurrent_node_api.cc +++ b/test/embedding/embedtest_concurrent_node_api.cc @@ -1,12 +1,9 @@ #include "embedtest_node_api.h" -#define NAPI_EXPERIMENTAL -#include - #include #include -const char* main_script = +static const char* main_script = "globalThis.require = require('module').createRequire(process.execPath);\n" "require('vm').runInThisContext(process.argv[1]);"; @@ -33,7 +30,6 @@ extern "C" int32_t test_main_concurrent_node_api(int32_t argc, char* argv[]) { napi_env env; CHECK(node_api_create_env( options, nullptr, nullptr, main_script, NAPI_VERSION, &env)); - CHECK(node_api_delete_env_options(options)); CHECK(node_api_open_env_scope(env)); @@ -78,12 +74,12 @@ extern "C" int32_t test_main_multi_env_node_api(int32_t argc, char* argv[]) { nullptr, nullptr)); - node_api_env_options options; - CHECK(node_api_create_env_options(&options)); const size_t env_count = 12; std::vector envs; envs.reserve(env_count); for (size_t i = 0; i < env_count; i++) { + node_api_env_options options; + CHECK(node_api_create_env_options(&options)); napi_env env; CHECK(node_api_create_env( options, nullptr, nullptr, main_script, NAPI_VERSION, &env)); @@ -103,7 +99,6 @@ extern "C" int32_t test_main_multi_env_node_api(int32_t argc, char* argv[]) { CHECK(node_api_close_env_scope(env)); } - CHECK(node_api_delete_env_options(options)); bool more_work = false; do { @@ -175,7 +170,6 @@ extern "C" int32_t test_main_multi_thread_node_api(int32_t argc, char* argv[]) { napi_env env; CHECK(node_api_create_env( options, nullptr, nullptr, main_script, NAPI_VERSION, &env)); - CHECK(node_api_delete_env_options(options)); std::mutex mutex; std::atomic result_count{0}; diff --git a/test/embedding/embedtest_modules_node_api.c b/test/embedding/embedtest_modules_node_api.cc similarity index 92% rename from test/embedding/embedtest_modules_node_api.c rename to test/embedding/embedtest_modules_node_api.cc index 14592e1d1058d2..2bd65ddcb733c1 100644 --- a/test/embedding/embedtest_modules_node_api.c +++ b/test/embedding/embedtest_modules_node_api.cc @@ -1,12 +1,9 @@ #include "embedtest_node_api.h" -#define NAPI_EXPERIMENTAL -#include - #include #include -int32_t test_main_modules_node_api(int32_t argc, char* argv[]) { +extern "C" int32_t test_main_modules_node_api(int32_t argc, char* argv[]) { if (argc < 3) { fprintf(stderr, "node_api_modules \n"); return 2; @@ -19,7 +16,6 @@ int32_t test_main_modules_node_api(int32_t argc, char* argv[]) { CHECK(node_api_create_env_options(&options)); napi_env env; CHECK(node_api_create_env(options, NULL, NULL, NULL, NAPI_VERSION, &env)); - CHECK(node_api_delete_env_options(options)); CHECK(node_api_open_env_scope(env)); diff --git a/test/embedding/embedtest_node_api.c b/test/embedding/embedtest_node_api.cc similarity index 97% rename from test/embedding/embedtest_node_api.c rename to test/embedding/embedtest_node_api.cc index 0ce98ac4e4922a..2e3e9dce5005fd 100644 --- a/test/embedding/embedtest_node_api.c +++ b/test/embedding/embedtest_node_api.cc @@ -1,8 +1,5 @@ #include "embedtest_node_api.h" -#define NAPI_EXPERIMENTAL -#include - #include #include #include @@ -12,7 +9,7 @@ static int32_t RunNodeInstance(); -const char* main_script = +static const char* main_script = "globalThis.require = require('module').createRequire(process.execPath);\n" "globalThis.embedVars = { nön_ascıı: '🏳️‍🌈' };\n" "require('vm').runInThisContext(process.argv[1]);"; @@ -27,7 +24,7 @@ static void NAPI_CDECL get_errors(void* data, } } -int32_t test_main_node_api(int32_t argc, char* argv[]) { +extern "C" int32_t test_main_node_api(int32_t argc, char* argv[]) { exe_name = argv[0]; bool early_return = false; int32_t exit_code = 0; @@ -210,7 +207,6 @@ int32_t RunNodeInstance() { napi_env env; CHECK(node_api_create_env( options, NULL, NULL, main_script, NAPI_VERSION, &env)); - CHECK(node_api_delete_env_options(options)); CHECK_EXIT_CODE(callMe(env)); CHECK_EXIT_CODE(waitMe(env)); diff --git a/test/embedding/embedtest_node_api.h b/test/embedding/embedtest_node_api.h index dcd7686b01f4d0..5f0bc1cff867ab 100644 --- a/test/embedding/embedtest_node_api.h +++ b/test/embedding/embedtest_node_api.h @@ -1,6 +1,7 @@ -#ifndef TEST_EMBEDDING_NODE_API_EMBEDTEST_H_ -#define TEST_EMBEDDING_NODE_API_EMBEDTEST_H_ +#ifndef TEST_EMBEDDING_EMBEDTEST_NODE_API_H_ +#define TEST_EMBEDDING_EMBEDTEST_NODE_API_H_ +#define NAPI_EXPERIMENTAL #include #ifdef __cplusplus @@ -50,4 +51,4 @@ extern "C" inline void NAPI_CDECL GetStringVector(void* data, } \ } while (0) -#endif // TEST_EMBEDDING_NODE_API_EMBEDTEST_H_ +#endif // TEST_EMBEDDING_EMBEDTEST_NODE_API_H_ diff --git a/test/embedding/embedtest_snapshot_node_api.cc b/test/embedding/embedtest_snapshot_node_api.cc index c4fd43bc159187..300944d8a62d5b 100644 --- a/test/embedding/embedtest_snapshot_node_api.cc +++ b/test/embedding/embedtest_snapshot_node_api.cc @@ -1,4 +1,3 @@ -#define NAPI_EXPERIMENTAL #include "embedtest_node_api.h" #include From d70e2d9a57a005fac7e105aca815a38f349de3a1 Mon Sep 17 00:00:00 2001 From: Vladimir Morozov Date: Fri, 30 Aug 2024 13:20:49 -0700 Subject: [PATCH 03/22] change node_api_get_strings_callback signature --- src/node_api_embedding.cc | 10 +++++----- src/node_api_embedding.h | 4 ++-- test/embedding/embedtest_node_api.cc | 4 ++-- test/embedding/embedtest_node_api.h | 4 ++-- test/embedding/embedtest_snapshot_node_api.cc | 6 +++--- 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/node_api_embedding.cc b/src/node_api_embedding.cc index 64bc8590247329..6f5d7960f3bf03 100644 --- a/src/node_api_embedding.cc +++ b/src/node_api_embedding.cc @@ -291,7 +291,7 @@ node_api_init_once_per_process(int32_t argc, if (get_errors_cb != nullptr && !platform_init_result->errors().empty()) { v8impl::CStringArray errors(platform_init_result->errors()); - get_errors_cb(errors_data, errors.size(), errors.cstrings()); + get_errors_cb(errors_data, errors.cstrings(), errors.size()); } if (early_return != nullptr) { @@ -350,7 +350,7 @@ node_api_env_options_get_args(node_api_env_options options, v8impl::EmbeddedEnvironmentOptions* env_options = reinterpret_cast(options); v8impl::CStringArray args(env_options->args_); - get_strings_cb(strings_data, args.size(), args.cstrings()); + get_strings_cb(strings_data, args.cstrings(), args.size()); return napi_ok; } @@ -378,7 +378,7 @@ node_api_env_options_get_exec_args(node_api_env_options options, v8impl::EmbeddedEnvironmentOptions* env_options = reinterpret_cast(options); v8impl::CStringArray exec_args(env_options->exec_args_); - get_strings_cb(strings_data, exec_args.size(), exec_args.cstrings()); + get_strings_cb(strings_data, exec_args.cstrings(), exec_args.size()); return napi_ok; } @@ -397,7 +397,7 @@ napi_status NAPI_CDECL node_api_env_options_set_exec_args( } napi_status NAPI_CDECL -node_api_env_options_set_snapshot(node_api_env_options options, +node_api_env_options_use_snapshot(node_api_env_options options, const char* snapshot_data, size_t snapshot_size) { if (options == nullptr) return napi_invalid_arg; @@ -483,7 +483,7 @@ node_api_create_env(node_api_env_options options, if (get_errors_cb != nullptr && !errors.empty()) { v8impl::CStringArray cerrors(errors); - get_errors_cb(errors_data, cerrors.size(), cerrors.cstrings()); + get_errors_cb(errors_data, cerrors.cstrings(), cerrors.size()); } if (env_setup == nullptr) { diff --git a/src/node_api_embedding.h b/src/node_api_embedding.h index 436f3eab6bd53f..bf6c504c0eb64c 100644 --- a/src/node_api_embedding.h +++ b/src/node_api_embedding.h @@ -37,7 +37,7 @@ typedef enum { } node_api_snapshot_flags; typedef void(NAPI_CDECL* node_api_get_strings_callback)( - void* data, size_t str_count, const char* str_array[]); + void* cb_data, const char* str_array[], size_t arr_size); typedef void(NAPI_CDECL* node_api_get_string_callback)(void* cb_data, const char* str, @@ -76,7 +76,7 @@ NAPI_EXTERN napi_status NAPI_CDECL node_api_env_options_set_exec_args( node_api_env_options options, size_t argc, const char* argv[]); NAPI_EXTERN napi_status NAPI_CDECL -node_api_env_options_set_snapshot(node_api_env_options options, +node_api_env_options_use_snapshot(node_api_env_options options, const char* snapshot_data, size_t snapshot_size); diff --git a/test/embedding/embedtest_node_api.cc b/test/embedding/embedtest_node_api.cc index 2e3e9dce5005fd..9137c6c1258094 100644 --- a/test/embedding/embedtest_node_api.cc +++ b/test/embedding/embedtest_node_api.cc @@ -17,8 +17,8 @@ static const char* main_script = static const char* exe_name; static void NAPI_CDECL get_errors(void* data, - size_t count, - const char* errors[]) { + const char* errors[], + size_t count) { for (size_t i = 0; i < count && i < 30; ++i) { fprintf(stderr, "%s: %s\n", exe_name, errors[i]); } diff --git a/test/embedding/embedtest_node_api.h b/test/embedding/embedtest_node_api.h index 5f0bc1cff867ab..f23b6bcd15f3c9 100644 --- a/test/embedding/embedtest_node_api.h +++ b/test/embedding/embedtest_node_api.h @@ -10,8 +10,8 @@ #include extern "C" inline void NAPI_CDECL GetStringVector(void* data, - size_t count, - const char** strs) { + const char* strs[], + size_t count) { static_cast*>(data)->assign(strs, strs + count); } diff --git a/test/embedding/embedtest_snapshot_node_api.cc b/test/embedding/embedtest_snapshot_node_api.cc index 300944d8a62d5b..8b35769f5e99c1 100644 --- a/test/embedding/embedtest_snapshot_node_api.cc +++ b/test/embedding/embedtest_snapshot_node_api.cc @@ -65,8 +65,8 @@ extern "C" int32_t test_main_snapshot_node_api(int32_t argc, char* argv[]) { static const char* exe_name; static void NAPI_CDECL get_errors(void* data, - size_t count, - const char* errors[]) { + const char* errors[], + size_t count) { for (size_t i = 0; i < count && i < 30; ++i) { fprintf(stderr, "%s: %s\n", exe_name, errors[i]); } @@ -140,7 +140,7 @@ int32_t RunNodeInstance() { int32_t ret = fclose(fp); assert(ret == 0); - CHECK(node_api_env_options_set_snapshot(options, vec.data(), vec.size())); + CHECK(node_api_env_options_use_snapshot(options, vec.data(), vec.size())); } if (is_building_snapshot) { From 6bb82b8d7f760798990a67baf21dd9cca317122f Mon Sep 17 00:00:00 2001 From: Vladimir Morozov Date: Fri, 30 Aug 2024 17:01:09 -0700 Subject: [PATCH 04/22] rename functions and callbacks --- src/node_api_embedding.cc | 61 +++++++++-------- src/node_api_embedding.h | 66 ++++++++++++------- .../embedtest_concurrent_node_api.cc | 48 +++++++------- test/embedding/embedtest_modules_node_api.cc | 4 +- test/embedding/embedtest_node_api.cc | 16 ++--- test/embedding/embedtest_node_api.h | 15 +++-- test/embedding/embedtest_snapshot_node_api.cc | 25 ++++--- 7 files changed, 131 insertions(+), 104 deletions(-) diff --git a/src/node_api_embedding.cc b/src/node_api_embedding.cc index 6f5d7960f3bf03..300d754963c903 100644 --- a/src/node_api_embedding.cc +++ b/src/node_api_embedding.cc @@ -41,8 +41,9 @@ class CStringArray { CStringArray(const CStringArray&) = delete; CStringArray& operator=(const CStringArray&) = delete; - const char** cstrings() const { return cstrings_; } size_t size() const { return size_; } + int32_t argc() const { return static_cast(size_); } + const char** argv() const { return cstrings_; } private: const char** cstrings_; @@ -271,13 +272,13 @@ node::ProcessInitializationFlags::Flags GetProcessInitializationFlags( } // end of namespace v8impl napi_status NAPI_CDECL -node_api_init_once_per_process(int32_t argc, - char* argv[], - node_api_platform_flags flags, - node_api_get_strings_callback get_errors_cb, - void* errors_data, - bool* early_return, - int32_t* exit_code) { +node_api_initialize_platform(int32_t argc, + char* argv[], + node_api_platform_flags flags, + node_api_error_message_handler error_handler, + void* error_handler_data, + bool* early_return, + int32_t* exit_code) { if (argc == 0) return napi_invalid_arg; if (argv == nullptr) return napi_invalid_arg; if (!v8impl::EmbeddedPlatform::InitOncePerProcess()) @@ -289,9 +290,9 @@ node_api_init_once_per_process(int32_t argc, node::InitializeOncePerProcess( args, v8impl::GetProcessInitializationFlags(flags)); - if (get_errors_cb != nullptr && !platform_init_result->errors().empty()) { + if (error_handler != nullptr && !platform_init_result->errors().empty()) { v8impl::CStringArray errors(platform_init_result->errors()); - get_errors_cb(errors_data, errors.cstrings(), errors.size()); + error_handler(error_handler_data, errors.argv(), errors.size()); } if (early_return != nullptr) { @@ -320,7 +321,7 @@ node_api_init_once_per_process(int32_t argc, return napi_ok; } -napi_status NAPI_CDECL node_api_uninit_once_per_process() { +napi_status NAPI_CDECL node_api_dispose_platform() { if (!v8impl::EmbeddedPlatform::UninitOncePerProcess()) return napi_generic_failure; v8::V8::Dispose(); @@ -342,15 +343,15 @@ node_api_create_env_options(node_api_env_options* result) { napi_status NAPI_CDECL node_api_env_options_get_args(node_api_env_options options, - node_api_get_strings_callback get_strings_cb, - void* strings_data) { + node_api_get_args_callback get_args_cb, + void* cb_data) { if (options == nullptr) return napi_invalid_arg; - if (get_strings_cb == nullptr) return napi_invalid_arg; + if (get_args_cb == nullptr) return napi_invalid_arg; v8impl::EmbeddedEnvironmentOptions* env_options = reinterpret_cast(options); v8impl::CStringArray args(env_options->args_); - get_strings_cb(strings_data, args.cstrings(), args.size()); + get_args_cb(cb_data, args.argc(), args.argv()); return napi_ok; } @@ -370,15 +371,15 @@ napi_status NAPI_CDECL node_api_env_options_set_args( napi_status NAPI_CDECL node_api_env_options_get_exec_args(node_api_env_options options, - node_api_get_strings_callback get_strings_cb, - void* strings_data) { + node_api_get_args_callback get_args_cb, + void* cb_data) { if (options == nullptr) return napi_invalid_arg; - if (get_strings_cb == nullptr) return napi_invalid_arg; + if (get_args_cb == nullptr) return napi_invalid_arg; v8impl::EmbeddedEnvironmentOptions* env_options = reinterpret_cast(options); - v8impl::CStringArray exec_args(env_options->exec_args_); - get_strings_cb(strings_data, exec_args.cstrings(), exec_args.size()); + v8impl::CStringArray args(env_options->exec_args_); + get_args_cb(cb_data, args.argc(), args.argv()); return napi_ok; } @@ -414,20 +415,22 @@ node_api_env_options_use_snapshot(node_api_env_options options, napi_status NAPI_CDECL node_api_env_options_create_snapshot(node_api_env_options options, - node_api_get_string_callback get_blob_cb, - void* blob_cb_data, + node_api_store_blob_callback store_blob_cb, + void* cb_data, node_api_snapshot_flags snapshot_flags) { if (options == nullptr) return napi_invalid_arg; - if (get_blob_cb == nullptr) return napi_invalid_arg; + if (store_blob_cb == nullptr) return napi_invalid_arg; v8impl::EmbeddedEnvironmentOptions* env_options = reinterpret_cast(options); if (env_options->is_frozen_) return napi_generic_failure; env_options->create_snapshot_ = - [get_blob_cb, blob_cb_data](const node::EmbedderSnapshotData* snapshot) { + [store_blob_cb, cb_data](const node::EmbedderSnapshotData* snapshot) { std::vector blob = snapshot->ToBlob(); - get_blob_cb(blob_cb_data, blob.data(), blob.size()); + store_blob_cb(cb_data, + reinterpret_cast(blob.data()), + blob.size()); }; if ((snapshot_flags & node_api_snapshot_no_code_cache) != 0) { @@ -441,8 +444,8 @@ node_api_env_options_create_snapshot(node_api_env_options options, napi_status NAPI_CDECL node_api_create_env(node_api_env_options options, - node_api_get_strings_callback get_errors_cb, - void* errors_data, + node_api_error_message_handler error_handler, + void* error_handler_data, const char* main_script, int32_t api_version, napi_env* result) { @@ -481,9 +484,9 @@ node_api_create_env(node_api_env_options options, platform, &errors, env_options->args_, env_options->exec_args_, flags); } - if (get_errors_cb != nullptr && !errors.empty()) { + if (error_handler != nullptr && !errors.empty()) { v8impl::CStringArray cerrors(errors); - get_errors_cb(errors_data, cerrors.cstrings(), cerrors.size()); + error_handler(error_handler_data, cerrors.argv(), cerrors.size()); } if (env_setup == nullptr) { diff --git a/src/node_api_embedding.h b/src/node_api_embedding.h index bf6c504c0eb64c..396c5f1995dff7 100644 --- a/src/node_api_embedding.h +++ b/src/node_api_embedding.h @@ -36,41 +36,46 @@ typedef enum { node_api_snapshot_no_code_cache = 1 << 0, } node_api_snapshot_flags; -typedef void(NAPI_CDECL* node_api_get_strings_callback)( - void* cb_data, const char* str_array[], size_t arr_size); +typedef void(NAPI_CDECL* node_api_error_message_handler)(void* handler_data, + const char* messages[], + size_t size); -typedef void(NAPI_CDECL* node_api_get_string_callback)(void* cb_data, - const char* str, +typedef void(NAPI_CDECL* node_api_get_args_callback)(void* cb_data, + int32_t argc, + const char* size[]); + +typedef void(NAPI_CDECL* node_api_store_blob_callback)(void* cb_data, + const uint8_t* blob, size_t size); typedef bool(NAPI_CDECL* node_api_run_predicate)(void* predicate_data); NAPI_EXTERN napi_status NAPI_CDECL -node_api_init_once_per_process(int32_t argc, - char* argv[], - node_api_platform_flags flags, - node_api_get_strings_callback get_errors_cb, - void* errors_data, - bool* early_return, - int32_t* exit_code); +node_api_initialize_platform(int32_t argc, + char* argv[], + node_api_platform_flags flags, + node_api_error_message_handler error_handler, + void* error_handler_data, + bool* early_return, + int32_t* exit_code); -NAPI_EXTERN napi_status NAPI_CDECL node_api_uninit_once_per_process(); +NAPI_EXTERN napi_status NAPI_CDECL node_api_dispose_platform(); NAPI_EXTERN napi_status NAPI_CDECL node_api_create_env_options(node_api_env_options* result); NAPI_EXTERN napi_status NAPI_CDECL node_api_env_options_get_args(node_api_env_options options, - node_api_get_strings_callback get_strings_cb, - void* strings_data); + node_api_get_args_callback get_args_cb, + void* cb_data); NAPI_EXTERN napi_status NAPI_CDECL node_api_env_options_set_args( node_api_env_options options, size_t argc, const char* argv[]); NAPI_EXTERN napi_status NAPI_CDECL node_api_env_options_get_exec_args(node_api_env_options options, - node_api_get_strings_callback get_strings_cb, - void* strings_data); + node_api_get_args_callback get_args_cb, + void* cb_data); NAPI_EXTERN napi_status NAPI_CDECL node_api_env_options_set_exec_args( node_api_env_options options, size_t argc, const char* argv[]); @@ -82,18 +87,14 @@ node_api_env_options_use_snapshot(node_api_env_options options, NAPI_EXTERN napi_status NAPI_CDECL node_api_env_options_create_snapshot(node_api_env_options options, - node_api_get_string_callback get_blob_cb, - void* blob_cb_data, + node_api_store_blob_callback store_blob_cb, + void* cb_data, node_api_snapshot_flags snapshot_flags); -// TODO(vmoroz): Remove the main_script parameter. -// TODO(vmoroz): Add ABI-safe way to access internal module functionality. -// TODO(vmoroz): Pass EnvironmentFlags -// TODO(vmoroz): Allow setting the global inspector for a specific environment. NAPI_EXTERN napi_status NAPI_CDECL node_api_create_env(node_api_env_options options, - node_api_get_strings_callback get_errors_cb, - void* errors_data, + node_api_error_message_handler error_handler, + void* error_handler_data, const char* main_script, int32_t api_version, napi_env* result); @@ -120,3 +121,20 @@ NAPI_EXTERN napi_status NAPI_CDECL node_api_await_promise(napi_env env, EXTERN_C_END #endif // SRC_NODE_API_EMBEDDING_H_ + +// TODO: (vmoroz) Match node_api_platform_flags to the existing Node.js flags. +// TODO: (vmoroz) Remove the main_script parameter. +// TODO: (vmoroz) Add startup callback with process and require parameters. +// TODO: (vmoroz) Add ABI-safe way to access internal module functionality. +// TODO: (vmoroz) Add EnvironmentFlags to env_options. +// TODO: (vmoroz) Allow setting the global inspector for a specific environment. +// TODO: (vmoroz) Start workers from C++. +// TODO: (vmoroz) Worker to inherit parent inspector. +// TODO: (vmoroz) Cancel pending tasks on delete env. +// TODO: (vmoroz) await_promise -> add has_more_work parameter. +// TODO: (vmoroz) Can we init plat again if it retuns early? +// TODO: (vmoroz) Add simpler threading model - without open/close scope. +// TODO: (vmoroz) Simplify API use for simple default cases. +// TODO: (vmoroz) Add a way to add embedded modules. +// TODO: (vmoroz) Check how to pass the V8 thread pool size. +// TODO: (vmoroz) Provide better error handling for args. diff --git a/test/embedding/embedtest_concurrent_node_api.cc b/test/embedding/embedtest_concurrent_node_api.cc index f7b77c9a5b54a8..73a61bc0b67ed1 100644 --- a/test/embedding/embedtest_concurrent_node_api.cc +++ b/test/embedding/embedtest_concurrent_node_api.cc @@ -11,13 +11,13 @@ static const char* main_script = extern "C" int32_t test_main_concurrent_node_api(int32_t argc, char* argv[]) { std::atomic global_count{0}; std::atomic global_exit_code{0}; - CHECK(node_api_init_once_per_process(argc, - argv, - node_api_platform_no_flags, - nullptr, - nullptr, - nullptr, - nullptr)); + CHECK(node_api_initialize_platform(argc, + argv, + node_api_platform_no_flags, + nullptr, + nullptr, + nullptr, + nullptr)); const size_t thread_count = 12; std::vector threads; @@ -56,7 +56,7 @@ extern "C" int32_t test_main_concurrent_node_api(int32_t argc, char* argv[]) { CHECK_EXIT_CODE(global_exit_code.load()); - CHECK(node_api_uninit_once_per_process()); + CHECK(node_api_dispose_platform()); fprintf(stdout, "%d\n", global_count.load()); @@ -66,13 +66,13 @@ extern "C" int32_t test_main_concurrent_node_api(int32_t argc, char* argv[]) { // We can use multiple environments at the same thread. // For each use we must open and close the environment scope. extern "C" int32_t test_main_multi_env_node_api(int32_t argc, char* argv[]) { - CHECK(node_api_init_once_per_process(argc, - argv, - node_api_platform_no_flags, - nullptr, - nullptr, - nullptr, - nullptr)); + CHECK(node_api_initialize_platform(argc, + argv, + node_api_platform_no_flags, + nullptr, + nullptr, + nullptr, + nullptr)); const size_t env_count = 12; std::vector envs; @@ -147,7 +147,7 @@ extern "C" int32_t test_main_multi_env_node_api(int32_t argc, char* argv[]) { CHECK(node_api_delete_env(env, nullptr)); } - CHECK(node_api_uninit_once_per_process()); + CHECK(node_api_dispose_platform()); fprintf(stdout, "%d\n", global_count); @@ -157,13 +157,13 @@ extern "C" int32_t test_main_multi_env_node_api(int32_t argc, char* argv[]) { // We can use the environment from different threads as long as only one thread // at a time is using it. extern "C" int32_t test_main_multi_thread_node_api(int32_t argc, char* argv[]) { - CHECK(node_api_init_once_per_process(argc, - argv, - node_api_platform_no_flags, - nullptr, - nullptr, - nullptr, - nullptr)); + CHECK(node_api_initialize_platform(argc, + argv, + node_api_platform_no_flags, + nullptr, + nullptr, + nullptr, + nullptr)); node_api_env_options options; CHECK(node_api_create_env_options(&options)); @@ -217,7 +217,7 @@ extern "C" int32_t test_main_multi_thread_node_api(int32_t argc, char* argv[]) { CHECK_EXIT_CODE(result_exit_code.load()); CHECK(node_api_delete_env(env, nullptr)); - CHECK(node_api_uninit_once_per_process()); + CHECK(node_api_dispose_platform()); fprintf(stdout, "%d\n", result_count.load()); diff --git a/test/embedding/embedtest_modules_node_api.cc b/test/embedding/embedtest_modules_node_api.cc index 2bd65ddcb733c1..936fc977418ca3 100644 --- a/test/embedding/embedtest_modules_node_api.cc +++ b/test/embedding/embedtest_modules_node_api.cc @@ -9,7 +9,7 @@ extern "C" int32_t test_main_modules_node_api(int32_t argc, char* argv[]) { return 2; } - CHECK(node_api_init_once_per_process( + CHECK(node_api_initialize_platform( argc, argv, node_api_platform_no_flags, NULL, NULL, NULL, NULL)); node_api_env_options options; @@ -56,6 +56,6 @@ extern "C" int32_t test_main_modules_node_api(int32_t argc, char* argv[]) { CHECK(node_api_close_env_scope(env)); CHECK(node_api_delete_env(env, NULL)); - CHECK(node_api_uninit_once_per_process()); + CHECK(node_api_dispose_platform()); return 0; } diff --git a/test/embedding/embedtest_node_api.cc b/test/embedding/embedtest_node_api.cc index 9137c6c1258094..8857be56183b48 100644 --- a/test/embedding/embedtest_node_api.cc +++ b/test/embedding/embedtest_node_api.cc @@ -28,20 +28,20 @@ extern "C" int32_t test_main_node_api(int32_t argc, char* argv[]) { exe_name = argv[0]; bool early_return = false; int32_t exit_code = 0; - node_api_init_once_per_process(argc, - argv, - node_api_platform_no_flags, - get_errors, - NULL, - &early_return, - &exit_code); + node_api_initialize_platform(argc, + argv, + node_api_platform_no_flags, + get_errors, + NULL, + &early_return, + &exit_code); if (early_return) { return exit_code; } CHECK_EXIT_CODE(RunNodeInstance()); - CHECK(node_api_uninit_once_per_process()); + CHECK(node_api_dispose_platform()); return 0; } diff --git a/test/embedding/embedtest_node_api.h b/test/embedding/embedtest_node_api.h index f23b6bcd15f3c9..04e61dd0a144a6 100644 --- a/test/embedding/embedtest_node_api.h +++ b/test/embedding/embedtest_node_api.h @@ -9,10 +9,17 @@ #include #include -extern "C" inline void NAPI_CDECL GetStringVector(void* data, - const char* strs[], - size_t count) { - static_cast*>(data)->assign(strs, strs + count); +extern "C" inline void NAPI_CDECL GetMessageVector(void* data, + const char* messages[], + size_t size) { + static_cast*>(data)->assign(messages, + messages + size); +} + +extern "C" inline void NAPI_CDECL GetArgsVector(void* data, + int32_t argc, + const char* argv[]) { + static_cast*>(data)->assign(argv, argv + argc); } #endif diff --git a/test/embedding/embedtest_snapshot_node_api.cc b/test/embedding/embedtest_snapshot_node_api.cc index 8b35769f5e99c1..5cbe74e67c343f 100644 --- a/test/embedding/embedtest_snapshot_node_api.cc +++ b/test/embedding/embedtest_snapshot_node_api.cc @@ -39,13 +39,13 @@ extern "C" int32_t test_main_snapshot_node_api(int32_t argc, char* argv[]) { int32_t exit_code = 0; std::vector errors; - node_api_init_once_per_process(argc, - argv, - node_api_platform_no_flags, - GetStringVector, - &errors, - &early_return, - &exit_code); + node_api_initialize_platform(argc, + argv, + node_api_platform_no_flags, + GetMessageVector, + &errors, + &early_return, + &exit_code); if (early_return) { if (exit_code != 0) { for (const std::string& err : errors) @@ -58,7 +58,7 @@ extern "C" int32_t test_main_snapshot_node_api(int32_t argc, char* argv[]) { CHECK_EXIT_CODE(RunNodeInstance()); - CHECK(node_api_uninit_once_per_process()); + CHECK(node_api_dispose_platform()); return 0; } @@ -92,9 +92,8 @@ int32_t RunNodeInstance() { node_api_env_options options; CHECK(node_api_create_env_options(&options)); std::vector args, exec_args; - CHECK(node_api_env_options_get_args(options, GetStringVector, &args)); - CHECK( - node_api_env_options_get_exec_args(options, GetStringVector, &exec_args)); + CHECK(node_api_env_options_get_args(options, GetArgsVector, &args)); + CHECK(node_api_env_options_get_exec_args(options, GetArgsVector, &exec_args)); exe_name = args[0].c_str(); std::vector filtered_args; @@ -159,12 +158,12 @@ int32_t RunNodeInstance() { if (!snapshot_blob_path.empty() && is_building_snapshot) { CHECK(node_api_env_options_create_snapshot( options, - [](void* cb_data, const char* snapshot_data, size_t snapshot_size) { + [](void* cb_data, const uint8_t* blob, size_t size) { const char* snapshot_blob_path = static_cast(cb_data); FILE* fp = fopen(snapshot_blob_path, "wb"); assert(fp != nullptr); - size_t written = fwrite(snapshot_data, snapshot_size, 1, fp); + size_t written = fwrite(blob, size, 1, fp); assert(written == 1); int32_t ret = fclose(fp); From d174f2dd33c7982c04a933095729d9d26595f995 Mon Sep 17 00:00:00 2001 From: Vladimir Morozov Date: Fri, 30 Aug 2024 18:40:23 -0700 Subject: [PATCH 05/22] platform flags to match process init flags --- src/node_api_embedding.cc | 43 +++++++++---------- src/node_api_embedding.h | 39 ++++++++++------- test/embedding/embedtest_node_api.cc | 2 +- test/embedding/embedtest_snapshot_node_api.cc | 2 +- 4 files changed, 46 insertions(+), 40 deletions(-) diff --git a/src/node_api_embedding.cc b/src/node_api_embedding.cc index 300d754963c903..3e131ad4e1b5ba 100644 --- a/src/node_api_embedding.cc +++ b/src/node_api_embedding.cc @@ -224,45 +224,42 @@ std::vector ToCStringVector(const std::vector& vec) { node::ProcessInitializationFlags::Flags GetProcessInitializationFlags( node_api_platform_flags flags) { uint32_t result = node::ProcessInitializationFlags::kNoFlags; - if ((flags & node_api_platform_enable_env_var) == 0) { - // Disable reading the NODE_OPTIONS environment variable. + if ((flags & node_api_platform_enable_stdio_inheritance) != 0) { + result |= node::ProcessInitializationFlags::kEnableStdioInheritance; + } + if ((flags & node_api_platform_disable_node_options_env) != 0) { result |= node::ProcessInitializationFlags::kDisableNodeOptionsEnv; } - if ((flags & node_api_platform_init_icu) == 0) { - // Do not initialize ICU. + if ((flags & node_api_platform_disable_cli_options) != 0) { + result |= node::ProcessInitializationFlags::kDisableCLIOptions; + } + if ((flags & node_api_platform_no_icu) != 0) { result |= node::ProcessInitializationFlags::kNoICU; } - // Do not modify stdio file descriptor or TTY state. - result |= node::ProcessInitializationFlags::kNoStdioInitialization; - // Do not register Node.js-specific signal handlers - // and reset other signal handlers to default state. - result |= node::ProcessInitializationFlags::kNoDefaultSignalHandling; - // Do not perform V8 initialization. + if ((flags & node_api_platform_no_stdio_initialization) != 0) { + result |= node::ProcessInitializationFlags::kNoStdioInitialization; + } + if ((flags & node_api_platform_no_default_signal_handling) != 0) { + result |= node::ProcessInitializationFlags::kNoDefaultSignalHandling; + } result |= node::ProcessInitializationFlags::kNoInitializeV8; - // Do not initialize a default Node.js-provided V8 platform instance. result |= node::ProcessInitializationFlags::kNoInitializeNodeV8Platform; - if ((flags & node_api_platform_init_openssl) == 0) { - // Do not initialize OpenSSL config. + if ((flags & node_api_platform_no_init_openssl) != 0) { result |= node::ProcessInitializationFlags::kNoInitOpenSSL; } - if ((flags & node_api_platform_parse_global_debug_vars) == 0) { - // Do not initialize Node.js debugging based on environment variables. + if ((flags & node_api_platform_no_parse_global_debug_variables) != 0) { result |= node::ProcessInitializationFlags::kNoParseGlobalDebugVariables; } - if ((flags & node_api_platform_adjust_resource_limits) == 0) { - // Do not adjust OS resource limits for this process. + if ((flags & node_api_platform_no_adjust_resource_limits) != 0) { result |= node::ProcessInitializationFlags::kNoAdjustResourceLimits; } - if ((flags & node_api_platform_no_large_pages) != 0) { - // Do not map code segments into large pages for this process. + if ((flags & node_api_platform_no_use_large_pages) != 0) { result |= node::ProcessInitializationFlags::kNoUseLargePages; } - if ((flags & node_api_platform_print_help_or_version) == 0) { - // Skip printing output for --help, --version, --v8-options. + if ((flags & node_api_platform_no_print_help_or_version_output) != 0) { result |= node::ProcessInitializationFlags::kNoPrintHelpOrVersionOutput; } - if ((flags & node_api_platform_generate_predictable_snapshot) == 0) { - // Initialize the process for predictable snapshot generation. + if ((flags & node_api_platform_generate_predictable_snapshot) != 0) { result |= node::ProcessInitializationFlags::kGeneratePredictableSnapshot; } return static_cast(result); diff --git a/src/node_api_embedding.h b/src/node_api_embedding.h index 396c5f1995dff7..571735b3b763af 100644 --- a/src/node_api_embedding.h +++ b/src/node_api_embedding.h @@ -9,22 +9,32 @@ typedef struct node_api_env_options__* node_api_env_options; typedef enum { node_api_platform_no_flags = 0, - // Enable reading the NODE_OPTIONS environment variable. - node_api_platform_enable_env_var = 1 << 0, - // Initialize ICU. - node_api_platform_init_icu = 1 << 1, - // Initialize OpenSSL config. - node_api_platform_init_openssl = 1 << 2, - // Initialize Node.js debugging based on environment variables. - node_api_platform_parse_global_debug_vars = 1 << 3, - // Adjust OS resource limits for this process. - node_api_platform_adjust_resource_limits = 1 << 4, + // Enable stdio inheritance, which is disabled by default. + // This flag is also implied by node_api_platform_no_stdio_initialization. + node_api_platform_enable_stdio_inheritance = 1 << 0, + // Disable reading the NODE_OPTIONS environment variable. + node_api_platform_disable_node_options_env = 1 << 1, + // Do not parse CLI options. + node_api_platform_disable_cli_options = 1 << 2, + // Do not initialize ICU. + node_api_platform_no_icu = 1 << 3, + // Do not modify stdio file descriptor or TTY state. + node_api_platform_no_stdio_initialization = 1 << 4, + // Do not register Node.js-specific signal handlers + // and reset other signal handlers to default state. + node_api_platform_no_default_signal_handling = 1 << 5, + // Do not initialize OpenSSL config. + node_api_platform_no_init_openssl = 1 << 8, + // Do not initialize Node.js debugging based on environment variables. + node_api_platform_no_parse_global_debug_variables = 1 << 9, + // Do not adjust OS resource limits for this process. + node_api_platform_no_adjust_resource_limits = 1 << 10, // Do not map code segments into large pages for this process. - node_api_platform_no_large_pages = 1 << 5, - // Allow printing output for --help, --version, --v8-options. - node_api_platform_print_help_or_version = 1 << 6, + node_api_platform_no_use_large_pages = 1 << 11, + // Skip printing output for --help, --version, --v8-options. + node_api_platform_no_print_help_or_version_output = 1 << 12, // Initialize the process for predictable snapshot generation. - node_api_platform_generate_predictable_snapshot = 1 << 7, + node_api_platform_generate_predictable_snapshot = 1 << 14, } node_api_platform_flags; typedef enum { @@ -122,7 +132,6 @@ EXTERN_C_END #endif // SRC_NODE_API_EMBEDDING_H_ -// TODO: (vmoroz) Match node_api_platform_flags to the existing Node.js flags. // TODO: (vmoroz) Remove the main_script parameter. // TODO: (vmoroz) Add startup callback with process and require parameters. // TODO: (vmoroz) Add ABI-safe way to access internal module functionality. diff --git a/test/embedding/embedtest_node_api.cc b/test/embedding/embedtest_node_api.cc index 8857be56183b48..dcefaf6a996bbc 100644 --- a/test/embedding/embedtest_node_api.cc +++ b/test/embedding/embedtest_node_api.cc @@ -30,7 +30,7 @@ extern "C" int32_t test_main_node_api(int32_t argc, char* argv[]) { int32_t exit_code = 0; node_api_initialize_platform(argc, argv, - node_api_platform_no_flags, + node_api_platform_disable_node_options_env, get_errors, NULL, &early_return, diff --git a/test/embedding/embedtest_snapshot_node_api.cc b/test/embedding/embedtest_snapshot_node_api.cc index 5cbe74e67c343f..bde72af09443b5 100644 --- a/test/embedding/embedtest_snapshot_node_api.cc +++ b/test/embedding/embedtest_snapshot_node_api.cc @@ -41,7 +41,7 @@ extern "C" int32_t test_main_snapshot_node_api(int32_t argc, char* argv[]) { std::vector errors; node_api_initialize_platform(argc, argv, - node_api_platform_no_flags, + node_api_platform_disable_node_options_env, GetMessageVector, &errors, &early_return, From 6e4da25b883c0ed07ff9f754520079e5cd5f50c0 Mon Sep 17 00:00:00 2001 From: Vladimir Morozov Date: Sat, 31 Aug 2024 20:25:01 -0700 Subject: [PATCH 06/22] add environment flags --- src/node_api_embedding.cc | 97 ++++++++++++++----- src/node_api_embedding.h | 87 +++++++++++++++-- .../embedtest_concurrent_node_api.cc | 6 ++ test/embedding/embedtest_modules_node_api.cc | 2 +- test/embedding/embedtest_node_api.cc | 2 +- 5 files changed, 162 insertions(+), 32 deletions(-) diff --git a/src/node_api_embedding.cc b/src/node_api_embedding.cc index 3e131ad4e1b5ba..f21bb57ee2133e 100644 --- a/src/node_api_embedding.cc +++ b/src/node_api_embedding.cc @@ -121,6 +121,7 @@ struct EmbeddedEnvironmentOptions { delete; bool is_frozen_{false}; + node_api_env_flags flags_{node_api_env_default_flags}; std::vector args_; std::vector exec_args_; node::EmbedderSnapshotData::Pointer snapshot_; @@ -212,15 +213,6 @@ class EmbeddedEnvironment final : public node_napi_env__ { std::optional isolate_locker_; }; -std::vector ToCStringVector(const std::vector& vec) { - std::vector result; - result.reserve(vec.size()); - for (const std::string& str : vec) { - result.push_back(str.c_str()); - } - return result; -} - node::ProcessInitializationFlags::Flags GetProcessInitializationFlags( node_api_platform_flags flags) { uint32_t result = node::ProcessInitializationFlags::kNoFlags; @@ -265,6 +257,47 @@ node::ProcessInitializationFlags::Flags GetProcessInitializationFlags( return static_cast(result); } +node::EnvironmentFlags::Flags GetEnvironmentFlags(node_api_env_flags flags) { + uint64_t result = node::EnvironmentFlags::kNoFlags; + if ((flags & node_api_env_default_flags) != 0) { + result |= node::EnvironmentFlags::kDefaultFlags; + } + if ((flags & node_api_env_owns_process_state) != 0) { + result |= node::EnvironmentFlags::kOwnsProcessState; + } + if ((flags & node_api_env_owns_inspector) != 0) { + result |= node::EnvironmentFlags::kOwnsInspector; + } + if ((flags & node_api_env_no_register_esm_loader) != 0) { + result |= node::EnvironmentFlags::kNoRegisterESMLoader; + } + if ((flags & node_api_env_track_unmanaged_fds) != 0) { + result |= node::EnvironmentFlags::kTrackUnmanagedFds; + } + if ((flags & node_api_env_hide_console_windows) != 0) { + result |= node::EnvironmentFlags::kHideConsoleWindows; + } + if ((flags & node_api_env_no_native_addons) != 0) { + result |= node::EnvironmentFlags::kNoNativeAddons; + } + if ((flags & node_api_env_no_global_search_paths) != 0) { + result |= node::EnvironmentFlags::kNoGlobalSearchPaths; + } + if ((flags & node_api_env_no_browser_globals) != 0) { + result |= node::EnvironmentFlags::kNoBrowserGlobals; + } + if ((flags & node_api_env_no_create_inspector) != 0) { + result |= node::EnvironmentFlags::kNoCreateInspector; + } + if ((flags & node_api_env_no_start_debug_signal_handler) != 0) { + result |= node::EnvironmentFlags::kNoStartDebugSignalHandler; + } + if ((flags & node_api_env_no_wait_for_inspector_frontend) != 0) { + result |= node::EnvironmentFlags::kNoWaitForInspectorFrontend; + } + return static_cast(result); +} + } // end of anonymous namespace } // end of namespace v8impl @@ -353,31 +386,43 @@ node_api_env_options_get_args(node_api_env_options options, return napi_ok; } -napi_status NAPI_CDECL node_api_env_options_set_args( - node_api_env_options options, size_t argc, const char* argv[]) { +napi_status NAPI_CDECL +node_api_env_options_get_exec_args(node_api_env_options options, + node_api_get_args_callback get_args_cb, + void* cb_data) { + if (options == nullptr) return napi_invalid_arg; + if (get_args_cb == nullptr) return napi_invalid_arg; + + v8impl::EmbeddedEnvironmentOptions* env_options = + reinterpret_cast(options); + v8impl::CStringArray args(env_options->exec_args_); + get_args_cb(cb_data, args.argc(), args.argv()); + + return napi_ok; +} + +napi_status NAPI_CDECL node_api_env_options_set_flags( + node_api_env_options options, node_api_env_flags flags) { if (options == nullptr) return napi_invalid_arg; - if (argv == nullptr) return napi_invalid_arg; v8impl::EmbeddedEnvironmentOptions* env_options = reinterpret_cast(options); if (env_options->is_frozen_) return napi_generic_failure; - env_options->args_.assign(argv, argv + argc); + env_options->flags_ = flags; return napi_ok; } -napi_status NAPI_CDECL -node_api_env_options_get_exec_args(node_api_env_options options, - node_api_get_args_callback get_args_cb, - void* cb_data) { +napi_status NAPI_CDECL node_api_env_options_set_args( + node_api_env_options options, size_t argc, const char* argv[]) { if (options == nullptr) return napi_invalid_arg; - if (get_args_cb == nullptr) return napi_invalid_arg; + if (argv == nullptr) return napi_invalid_arg; v8impl::EmbeddedEnvironmentOptions* env_options = reinterpret_cast(options); - v8impl::CStringArray args(env_options->exec_args_); - get_args_cb(cb_data, args.argc(), args.argv()); + if (env_options->is_frozen_) return napi_generic_failure; + env_options->args_.assign(argv, argv + argc); return napi_ok; } @@ -458,9 +503,7 @@ node_api_create_env(node_api_env_options options, node::MultiIsolatePlatform* platform = v8impl::EmbeddedPlatform::GetInstance()->get_v8_platform(); node::EnvironmentFlags::Flags flags = - static_cast( - node::EnvironmentFlags::kDefaultFlags | - node::EnvironmentFlags::kNoCreateInspector); + v8impl::GetEnvironmentFlags(env_options->flags_); if (env_options->snapshot_) { env_setup = node::CommonEnvironmentSetup::CreateFromSnapshot( platform, @@ -605,7 +648,8 @@ napi_status NAPI_CDECL node_api_run_env_while(napi_env env, napi_status NAPI_CDECL node_api_await_promise(napi_env env, napi_value promise, - napi_value* result) { + napi_value* result, + bool* has_more_work) { NAPI_PREAMBLE(env); CHECK_ARG(env, result); @@ -641,6 +685,11 @@ napi_status NAPI_CDECL node_api_await_promise(napi_env env, *result = v8impl::JsValueFromV8LocalValue(scope.Escape(promise_object->Result())); + + if (has_more_work != nullptr) { + *has_more_work = uv_loop_alive(embedded_env->node_env()->event_loop()); + } + if (promise_object->State() == v8::Promise::PromiseState::kRejected) return napi_pending_exception; diff --git a/src/node_api_embedding.h b/src/node_api_embedding.h index 571735b3b763af..affb82562ed7a4 100644 --- a/src/node_api_embedding.h +++ b/src/node_api_embedding.h @@ -37,6 +37,57 @@ typedef enum { node_api_platform_generate_predictable_snapshot = 1 << 14, } node_api_platform_flags; +typedef enum : uint64_t { + node_api_env_no_flags = 0, + // Use the default behaviour for Node.js instances. + node_api_env_default_flags = 1 << 0, + // Controls whether this Environment is allowed to affect per-process state + // (e.g. cwd, process title, uid, etc.). + // This is set when using node_api_env_default_flags. + node_api_env_owns_process_state = 1 << 1, + // Set if this Environment instance is associated with the global inspector + // handling code (i.e. listening on SIGUSR1). + // This is set when using node_api_env_default_flags. + node_api_env_owns_inspector = 1 << 2, + // Set if Node.js should not run its own esm loader. This is needed by some + // embedders, because it's possible for the Node.js esm loader to conflict + // with another one in an embedder environment, e.g. Blink's in Chromium. + node_api_env_no_register_esm_loader = 1 << 3, + // Set this flag to make Node.js track "raw" file descriptors, i.e. managed + // by fs.open() and fs.close(), and close them during node_api_delete_env(). + node_api_env_track_unmanaged_fds = 1 << 4, + // Set this flag to force hiding console windows when spawning child + // processes. This is usually used when embedding Node.js in GUI programs on + // Windows. + node_api_env_hide_console_windows = 1 << 5, + // Set this flag to disable loading native addons via `process.dlopen`. + // This environment flag is especially important for worker threads + // so that a worker thread can't load a native addon even if `execArgv` + // is overwritten and `--no-addons` is not specified but was specified + // for this Environment instance. + node_api_env_no_native_addons = 1 << 6, + // Set this flag to disable searching modules from global paths like + // $HOME/.node_modules and $NODE_PATH. This is used by standalone apps that + // do not expect to have their behaviors changed because of globally + // installed modules. + node_api_env_no_global_search_paths = 1 << 7, + // Do not export browser globals like setTimeout, console, etc. + node_api_env_no_browser_globals = 1 << 8, + // Controls whether or not the Environment should call V8Inspector::create(). + // This control is needed by embedders who may not want to initialize the V8 + // inspector in situations where one has already been created, + // e.g. Blink's in Chromium. + node_api_env_no_create_inspector = 1 << 9, + // Controls whether or not the InspectorAgent for this Environment should + // call StartDebugSignalHandler. This control is needed by embedders who may + // not want to allow other processes to start the V8 inspector. + node_api_env_no_start_debug_signal_handler = 1 << 10, + // Controls whether the InspectorAgent created for this Environment waits for + // Inspector frontend events during the Environment creation. It's used to + // call node::Stop(env) on a Worker thread that is waiting for the events. + node_api_env_no_wait_for_inspector_frontend = 1 << 11 +} node_api_env_flags; + typedef enum { node_api_snapshot_no_flags = 0, // Whether code cache should be generated as part of the snapshot. @@ -79,14 +130,17 @@ node_api_env_options_get_args(node_api_env_options options, node_api_get_args_callback get_args_cb, void* cb_data); -NAPI_EXTERN napi_status NAPI_CDECL node_api_env_options_set_args( - node_api_env_options options, size_t argc, const char* argv[]); - NAPI_EXTERN napi_status NAPI_CDECL node_api_env_options_get_exec_args(node_api_env_options options, node_api_get_args_callback get_args_cb, void* cb_data); +NAPI_EXTERN napi_status NAPI_CDECL node_api_env_options_set_flags( + node_api_env_options options, node_api_env_flags flags); + +NAPI_EXTERN napi_status NAPI_CDECL node_api_env_options_set_args( + node_api_env_options options, size_t argc, const char* argv[]); + NAPI_EXTERN napi_status NAPI_CDECL node_api_env_options_set_exec_args( node_api_env_options options, size_t argc, const char* argv[]); @@ -126,21 +180,42 @@ node_api_run_env_while(napi_env env, NAPI_EXTERN napi_status NAPI_CDECL node_api_await_promise(napi_env env, napi_value promise, - napi_value* result); + napi_value* result, + bool* has_more_work); EXTERN_C_END +#ifdef __cplusplus + +inline node_api_platform_flags operator|(node_api_platform_flags lhs, + node_api_platform_flags rhs) { + return static_cast(static_cast(lhs) | + static_cast(rhs)); +} + +inline node_api_env_flags operator|(node_api_env_flags lhs, + node_api_env_flags rhs) { + return static_cast(static_cast(lhs) | + static_cast(rhs)); +} + +inline node_api_snapshot_flags operator|(node_api_snapshot_flags lhs, + node_api_snapshot_flags rhs) { + return static_cast(static_cast(lhs) | + static_cast(rhs)); +} + +#endif + #endif // SRC_NODE_API_EMBEDDING_H_ // TODO: (vmoroz) Remove the main_script parameter. // TODO: (vmoroz) Add startup callback with process and require parameters. // TODO: (vmoroz) Add ABI-safe way to access internal module functionality. -// TODO: (vmoroz) Add EnvironmentFlags to env_options. // TODO: (vmoroz) Allow setting the global inspector for a specific environment. // TODO: (vmoroz) Start workers from C++. // TODO: (vmoroz) Worker to inherit parent inspector. // TODO: (vmoroz) Cancel pending tasks on delete env. -// TODO: (vmoroz) await_promise -> add has_more_work parameter. // TODO: (vmoroz) Can we init plat again if it retuns early? // TODO: (vmoroz) Add simpler threading model - without open/close scope. // TODO: (vmoroz) Simplify API use for simple default cases. diff --git a/test/embedding/embedtest_concurrent_node_api.cc b/test/embedding/embedtest_concurrent_node_api.cc index 73a61bc0b67ed1..c04244d56c0a7f 100644 --- a/test/embedding/embedtest_concurrent_node_api.cc +++ b/test/embedding/embedtest_concurrent_node_api.cc @@ -27,6 +27,9 @@ extern "C" int32_t test_main_concurrent_node_api(int32_t argc, char* argv[]) { int32_t exit_code = [&]() { node_api_env_options options; CHECK(node_api_create_env_options(&options)); + CHECK(node_api_env_options_set_flags( + options, + node_api_env_default_flags | node_api_env_no_create_inspector)); napi_env env; CHECK(node_api_create_env( options, nullptr, nullptr, main_script, NAPI_VERSION, &env)); @@ -80,6 +83,9 @@ extern "C" int32_t test_main_multi_env_node_api(int32_t argc, char* argv[]) { for (size_t i = 0; i < env_count; i++) { node_api_env_options options; CHECK(node_api_create_env_options(&options)); + CHECK(node_api_env_options_set_flags( + options, + node_api_env_default_flags | node_api_env_no_create_inspector)); napi_env env; CHECK(node_api_create_env( options, nullptr, nullptr, main_script, NAPI_VERSION, &env)); diff --git a/test/embedding/embedtest_modules_node_api.cc b/test/embedding/embedtest_modules_node_api.cc index 936fc977418ca3..d1d03361444981 100644 --- a/test/embedding/embedtest_modules_node_api.cc +++ b/test/embedding/embedtest_modules_node_api.cc @@ -37,7 +37,7 @@ extern "C" int32_t test_main_modules_node_api(int32_t argc, char* argv[]) { size_t bufferlen; CHECK(napi_call_function(env, global, import, 1, &es6, &es6_promise)); - CHECK(node_api_await_promise(env, es6_promise, &es6_module)); + CHECK(node_api_await_promise(env, es6_promise, &es6_module, nullptr)); CHECK(napi_get_property(env, es6_module, value, &es6_result)); CHECK(napi_get_value_string_utf8( diff --git a/test/embedding/embedtest_node_api.cc b/test/embedding/embedtest_node_api.cc index dcefaf6a996bbc..42c04359d5c423 100644 --- a/test/embedding/embedtest_node_api.cc +++ b/test/embedding/embedtest_node_api.cc @@ -176,7 +176,7 @@ int32_t waitMeWithCheese(napi_env env) { FAIL("Result is not a Promise\n"); } - napi_status r = node_api_await_promise(env, promise, &result); + napi_status r = node_api_await_promise(env, promise, &result, nullptr); if (r != napi_ok && r != napi_pending_exception) { FAIL("Failed awaiting promise: %d\n", r); } From ee97722efdfdaab57e02486d607c85aacbc76d85 Mon Sep 17 00:00:00 2001 From: Vladimir Morozov Date: Tue, 3 Sep 2024 11:20:17 -0700 Subject: [PATCH 07/22] add environment preload callback --- node.gyp | 1 + src/node_api_embedding.cc | 106 +++++++++++++++---- src/node_api_embedding.h | 10 ++ test/README.md | 1 + test/embedding/embedtest_main.cc | 3 + test/embedding/embedtest_preload_node_api.cc | 43 ++++++++ test/embedding/preload-with-worker.js | 18 ++++ test/embedding/test-embedding.js | 17 ++- 8 files changed, 178 insertions(+), 21 deletions(-) create mode 100644 test/embedding/embedtest_preload_node_api.cc create mode 100644 test/embedding/preload-with-worker.js diff --git a/node.gyp b/node.gyp index 5f2f953dcc8904..cd85b8b55e2518 100644 --- a/node.gyp +++ b/node.gyp @@ -1292,6 +1292,7 @@ 'test/embedding/embedtest_modules_node_api.cc', 'test/embedding/embedtest_node_api.cc', 'test/embedding/embedtest_node_api.h', + 'test/embedding/embedtest_preload_node_api.cc', 'test/embedding/embedtest_snapshot_node_api.cc', ], diff --git a/src/node_api_embedding.cc b/src/node_api_embedding.cc index f21bb57ee2133e..06403aca2d5883 100644 --- a/src/node_api_embedding.cc +++ b/src/node_api_embedding.cc @@ -1,15 +1,16 @@ -#include -#include // INT_MAX -#include #define NAPI_EXPERIMENTAL +#include "node_api_embedding.h" + #include "env-inl.h" -#include "js_native_api.h" #include "js_native_api_v8.h" -#include "node_api_embedding.h" #include "node_api_internals.h" -#include "simdutf.h" #include "util-inl.h" +#include +#include // INT_MAX +#include +#include + namespace node { // Declare functions implemented in embed_helpers.cc @@ -22,33 +23,43 @@ v8::Maybe SpinEventLoopWithoutCleanup( namespace v8impl { namespace { +// A helper class to convert std::vector to an array of C strings. +// If the number of strings is less than kInplaceBufferSize, the strings are +// stored in the inplace_buffer_ array. Otherwise, the strings are stored in the +// allocated_buffer_ array. +// Ideally the class must be allocated on the stack. +// In any case it must not outlive the passed vector since it keeps only the +// string pointers returned by std::stirng::c_str() method. class CStringArray { + static constexpr size_t kInplaceBufferSize = 32; + public: explicit CStringArray(const std::vector& strings) noexcept : size_(strings.size()) { - if (size_ < inplace_buffer_.size()) { - cstrings_ = inplace_buffer_.data(); + if (size_ <= inplace_buffer_.size()) { + c_strs_ = inplace_buffer_.data(); } else { allocated_buffer_ = std::make_unique(size_); - cstrings_ = allocated_buffer_.get(); + c_strs_ = allocated_buffer_.get(); } for (size_t i = 0; i < size_; ++i) { - cstrings_[i] = strings[i].c_str(); + c_strs_[i] = strings[i].c_str(); } } - CStringArray() = delete; CStringArray(const CStringArray&) = delete; CStringArray& operator=(const CStringArray&) = delete; + const char** c_strs() const { return c_strs_; } size_t size() const { return size_; } + + const char** argv() const { return c_strs_; } int32_t argc() const { return static_cast(size_); } - const char** argv() const { return cstrings_; } private: - const char** cstrings_; - size_t size_; - std::array inplace_buffer_; + const char** c_strs_{}; + size_t size_{}; + std::array inplace_buffer_; std::unique_ptr allocated_buffer_; }; @@ -124,6 +135,7 @@ struct EmbeddedEnvironmentOptions { node_api_env_flags flags_{node_api_env_default_flags}; std::vector args_; std::vector exec_args_; + node::EmbedderPreloadCallback preload_cb_{}; node::EmbedderSnapshotData::Pointer snapshot_; std::function create_snapshot_; node::SnapshotConfig snapshot_config_{}; @@ -167,6 +179,22 @@ class EmbeddedEnvironment final : public node_napi_env__ { env_options_(std::move(env_options)), env_setup_(std::move(env_setup)) { env_options_->is_frozen_ = true; + + std::scoped_lock lock(shared_mutex_); + node_env_to_node_api_env_.emplace(env_setup_->env(), this); + } + + static node_napi_env GetOrCreateNodeApiEnv(node::Environment* node_env) { + std::scoped_lock lock(shared_mutex_); + auto it = node_env_to_node_api_env_.find(node_env); + if (it != node_env_to_node_api_env_.end()) return it->second; + // TODO: (vmoroz) propagate API version from the root environment. + node_napi_env env = new node_napi_env__( + node_env->context(), "", NAPI_VERSION_EXPERIMENTAL); + node_env->AddCleanupHook( + [](void* arg) { static_cast(arg)->Unref(); }, env); + node_env_to_node_api_env_.try_emplace(node_env, env); + return env; } static EmbeddedEnvironment* FromNapiEnv(napi_env env) { @@ -198,6 +226,8 @@ class EmbeddedEnvironment final : public node_napi_env__ { bool IsScopeOpened() const { return isolate_locker_.has_value(); } + const EmbeddedEnvironmentOptions& options() const { return *env_options_; } + const node::EmbedderSnapshotData::Pointer& snapshot() const { return env_options_->snapshot_; } @@ -211,8 +241,16 @@ class EmbeddedEnvironment final : public node_napi_env__ { std::unique_ptr env_options_; std::unique_ptr env_setup_; std::optional isolate_locker_; + + static std::mutex shared_mutex_; + static std::unordered_map + node_env_to_node_api_env_; }; +std::mutex EmbeddedEnvironment::shared_mutex_{}; +std::unordered_map + EmbeddedEnvironment::node_env_to_node_api_env_{}; + node::ProcessInitializationFlags::Flags GetProcessInitializationFlags( node_api_platform_flags flags) { uint32_t result = node::ProcessInitializationFlags::kNoFlags; @@ -322,7 +360,7 @@ node_api_initialize_platform(int32_t argc, if (error_handler != nullptr && !platform_init_result->errors().empty()) { v8impl::CStringArray errors(platform_init_result->errors()); - error_handler(error_handler_data, errors.argv(), errors.size()); + error_handler(error_handler_data, errors.c_strs(), errors.size()); } if (early_return != nullptr) { @@ -439,6 +477,34 @@ napi_status NAPI_CDECL node_api_env_options_set_exec_args( return napi_ok; } +napi_status NAPI_CDECL +node_api_env_options_set_preload_callback(node_api_env_options options, + node_api_preload_callback preload_cb, + void* cb_data) { + if (options == nullptr) return napi_invalid_arg; + + v8impl::EmbeddedEnvironmentOptions* env_options = + reinterpret_cast(options); + if (env_options->is_frozen_) return napi_generic_failure; + + if (preload_cb != nullptr) { + env_options->preload_cb_ = node::EmbedderPreloadCallback( + [preload_cb, cb_data](node::Environment* node_env, + v8::Local process, + v8::Local require) { + node_napi_env env = + v8impl::EmbeddedEnvironment::GetOrCreateNodeApiEnv(node_env); + napi_value process_value = v8impl::JsValueFromV8LocalValue(process); + napi_value require_value = v8impl::JsValueFromV8LocalValue(require); + preload_cb(env, process_value, require_value, cb_data); + }); + } else { + env_options->preload_cb_ = {}; + } + + return napi_ok; +} + napi_status NAPI_CDECL node_api_env_options_use_snapshot(node_api_env_options options, const char* snapshot_data, @@ -525,8 +591,8 @@ node_api_create_env(node_api_env_options options, } if (error_handler != nullptr && !errors.empty()) { - v8impl::CStringArray cerrors(errors); - error_handler(error_handler_data, cerrors.argv(), cerrors.size()); + v8impl::CStringArray error_arr(errors); + error_handler(error_handler_data, error_arr.c_strs(), error_arr.size()); } if (env_setup == nullptr) { @@ -556,7 +622,9 @@ node_api_create_env(node_api_env_options options, v8::MaybeLocal ret = embedded_env->snapshot() ? node::LoadEnvironment(node_env, node::StartExecutionCallback{}) - : node::LoadEnvironment(node_env, std::string_view(main_script)); + : node::LoadEnvironment(node_env, + std::string_view(main_script), + embedded_env->options().preload_cb_); embedded_env.release(); diff --git a/src/node_api_embedding.h b/src/node_api_embedding.h index affb82562ed7a4..ff9495f3ef32c4 100644 --- a/src/node_api_embedding.h +++ b/src/node_api_embedding.h @@ -109,6 +109,11 @@ typedef void(NAPI_CDECL* node_api_store_blob_callback)(void* cb_data, const uint8_t* blob, size_t size); +typedef void(NAPI_CDECL* node_api_preload_callback)(napi_env env, + napi_value process, + napi_value require, + void* cb_data); + typedef bool(NAPI_CDECL* node_api_run_predicate)(void* predicate_data); NAPI_EXTERN napi_status NAPI_CDECL @@ -144,6 +149,11 @@ NAPI_EXTERN napi_status NAPI_CDECL node_api_env_options_set_args( NAPI_EXTERN napi_status NAPI_CDECL node_api_env_options_set_exec_args( node_api_env_options options, size_t argc, const char* argv[]); +NAPI_EXTERN napi_status NAPI_CDECL +node_api_env_options_set_preload_callback(node_api_env_options options, + node_api_preload_callback preload_cb, + void* cb_data); + NAPI_EXTERN napi_status NAPI_CDECL node_api_env_options_use_snapshot(node_api_env_options options, const char* snapshot_data, diff --git a/test/README.md b/test/README.md index 1a3eec09373412..9252f5c9496ac6 100644 --- a/test/README.md +++ b/test/README.md @@ -23,6 +23,7 @@ For the tests to run on Windows, be sure to clone Node.js source code with the | `code-cache` | No | Tests for a Node.js binary compiled with V8 code cache. | | `common` | _N/A_ | Common modules shared among many tests.[^1] | | `doctool` | Yes | Tests for the documentation generator. | +| `embdedding` | Yes | Test Node.js embedding API | | `es-module` | Yes | Test ESM module loading. | | `fixtures` | _N/A_ | Test fixtures used in various tests throughout the test suite. | | `internet` | No | Tests that make real outbound network connections.[^2] | diff --git a/test/embedding/embedtest_main.cc b/test/embedding/embedtest_main.cc index 9e11bd54c2f98a..f2aa1305854b85 100644 --- a/test/embedding/embedtest_main.cc +++ b/test/embedding/embedtest_main.cc @@ -8,6 +8,7 @@ extern "C" int32_t test_main_modules_node_api(int32_t argc, char* argv[]); extern "C" int32_t test_main_concurrent_node_api(int32_t argc, char* argv[]); extern "C" int32_t test_main_multi_env_node_api(int32_t argc, char* argv[]); extern "C" int32_t test_main_multi_thread_node_api(int32_t argc, char* argv[]); +extern "C" int32_t test_main_preload_node_api(int32_t argc, char* argv[]); extern "C" int32_t test_main_snapshot_node_api(int32_t argc, char* argv[]); typedef int32_t (*main_callback)(int32_t argc, char* argv[]); @@ -40,6 +41,8 @@ NODE_MAIN(int32_t argc, node::argv_type raw_argv[]) { return CallWithoutArg1(test_main_multi_thread_node_api, argc, argv); } else if (strcmp(arg1, "snapshot-node-api") == 0) { return CallWithoutArg1(test_main_snapshot_node_api, argc, argv); + } else if (strcmp(arg1, "preload-node-api") == 0) { + return CallWithoutArg1(test_main_preload_node_api, argc, argv); } } return test_main_cpp_api(argc, argv); diff --git a/test/embedding/embedtest_preload_node_api.cc b/test/embedding/embedtest_preload_node_api.cc new file mode 100644 index 00000000000000..18694b663f267d --- /dev/null +++ b/test/embedding/embedtest_preload_node_api.cc @@ -0,0 +1,43 @@ +#include "embedtest_node_api.h" + +#include +#include + +static const char* main_script = + "globalThis.require = require('module').createRequire(process.execPath);\n" + "require('vm').runInThisContext(process.argv[1]);"; + +// Test the preload callback being called. +extern "C" int32_t test_main_preload_node_api(int32_t argc, char* argv[]) { + CHECK(node_api_initialize_platform(argc, + argv, + node_api_platform_no_flags, + nullptr, + nullptr, + nullptr, + nullptr)); + + node_api_env_options options; + CHECK(node_api_create_env_options(&options)); + CHECK(node_api_env_options_set_preload_callback( + options, + [](napi_env env, + napi_value /*process*/, + napi_value /*require*/, + void* /*cb_data*/) { + napi_value global, value; + napi_get_global(env, &global); + napi_create_int32(env, 42, &value); + napi_set_named_property(env, global, "preloadValue", value); + }, + nullptr)); + napi_env env; + CHECK(node_api_create_env( + options, nullptr, nullptr, main_script, NAPI_VERSION, &env)); + + CHECK(node_api_delete_env(env, nullptr)); + + CHECK(node_api_dispose_platform()); + + return 0; +} diff --git a/test/embedding/preload-with-worker.js b/test/embedding/preload-with-worker.js new file mode 100644 index 00000000000000..d53fe5a894cfbc --- /dev/null +++ b/test/embedding/preload-with-worker.js @@ -0,0 +1,18 @@ +// Print the globalThis.preloadValue set by the preload script. +const mainPreloadValue = globalThis.preloadValue; + +// Test that the preload script is executed in the worker thread. +const { Worker } = require('worker_threads'); +const worker = new Worker( + 'require("worker_threads").parentPort.postMessage({value: globalThis.preloadValue})', + { eval: true } +); + +const messages = []; +worker.on('message', (message) => messages.push(message)); + +process.on('beforeExit', () => { + console.log( + `preloadValue=${mainPreloadValue}; worker preloadValue=${messages[0].value}` + ); +}); diff --git a/test/embedding/test-embedding.js b/test/embedding/test-embedding.js index df17a82d8d7f3a..fc325c530bb192 100644 --- a/test/embedding/test-embedding.js +++ b/test/embedding/test-embedding.js @@ -10,7 +10,6 @@ const { } = require('../common/child_process'); const path = require('path'); const fs = require('fs'); -const os = require('os'); tmpdir.refresh(); common.allowGlobals(global.require); @@ -120,7 +119,8 @@ function runCommonApiTests(apiType) { { status: 9, signal: null, - stderr: `${binary}: NODE_REPL_EXTERNAL_MODULE can't be used with kDisableNodeOptionsEnv${os.EOL}`, + trim: true, + stderr: `${binary}: NODE_REPL_EXTERNAL_MODULE can't be used with kDisableNodeOptionsEnv`, } ); } @@ -310,6 +310,19 @@ runSnapshotTests('snapshot-node-api'); stdout: '5', } ); + + const preloadScriptPath = path.join(__dirname, 'preload-with-worker.js'); + + runTest( + 'preload-node-api: run preload callback', + spawnSyncAndAssert, + ['preload-node-api', `eval(${getReadFileCodeForPath(preloadScriptPath)})`], + { + cwd: __dirname, + trim: true, + stdout: `preloadValue=42; worker preloadValue=42`, + } + ); } /* From 9387c56ea866c094290c36c1cbd11596e40f22b1 Mon Sep 17 00:00:00 2001 From: Vladimir Morozov Date: Tue, 3 Sep 2024 13:26:33 -0700 Subject: [PATCH 08/22] Update test/embedding/embedtest_concurrent_node_api.cc Co-authored-by: Michael Dawson --- test/embedding/embedtest_concurrent_node_api.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/embedding/embedtest_concurrent_node_api.cc b/test/embedding/embedtest_concurrent_node_api.cc index c04244d56c0a7f..bb13d58b881e77 100644 --- a/test/embedding/embedtest_concurrent_node_api.cc +++ b/test/embedding/embedtest_concurrent_node_api.cc @@ -66,7 +66,7 @@ extern "C" int32_t test_main_concurrent_node_api(int32_t argc, char* argv[]) { return 0; } -// We can use multiple environments at the same thread. +// We can use multiple environments on the same thread. // For each use we must open and close the environment scope. extern "C" int32_t test_main_multi_env_node_api(int32_t argc, char* argv[]) { CHECK(node_api_initialize_platform(argc, From 9a770b3c9846e581d3bc1d121f24ed40595e8b8c Mon Sep 17 00:00:00 2001 From: Vladimir Morozov Date: Tue, 3 Sep 2024 13:28:13 -0700 Subject: [PATCH 09/22] Update doc/api/embedding.md Co-authored-by: Michael Dawson --- doc/api/embedding.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/api/embedding.md b/doc/api/embedding.md index 6a2678ff91d7f6..f853847ab6818e 100644 --- a/doc/api/embedding.md +++ b/doc/api/embedding.md @@ -171,7 +171,9 @@ int RunNodeInstance(MultiIsolatePlatform* platform, As an alternative, an embedded Node.js can also be fully controlled through -Node-API. This API supports both C and C++ through [node-addon-api][]. +Node-API. This API supports both C and C++ through [node-addon-api][]. Although +the embedding API is not promised to be ABI stable at this time, it uses node-api types +and implementation so that it might be a some time in the future. An example can be found [in the Node.js source tree][napi_embedding.c]. From d236f28d25a570a363c7ca6bd4c271aee3e23ea2 Mon Sep 17 00:00:00 2001 From: Vladimir Morozov Date: Fri, 6 Sep 2024 22:35:24 -0700 Subject: [PATCH 10/22] update API based on the PR feedback --- doc/api/embedding.md | 1148 ++++++++++++++++- doc/api/index.md | 2 +- doc/api/n-api.md | 139 -- node.gyp | 5 +- src/api/embed_helpers.cc | 17 +- src/js_native_api.h | 2 +- src/node_api_embedding.cc | 765 ----------- src/node_api_embedding.h | 234 ---- src/node_embedding_api.cc | 1052 +++++++++++++++ src/node_embedding_api.h | 353 +++++ test/README.md | 2 +- .../embedtest_concurrent_node_api.cc | 172 +-- test/embedding/embedtest_main.cc | 4 +- test/embedding/embedtest_modules_node_api.cc | 35 +- test/embedding/embedtest_node_api.cc | 78 +- test/embedding/embedtest_node_api.h | 25 +- test/embedding/embedtest_preload_node_api.cc | 44 +- test/embedding/embedtest_snapshot_node_api.cc | 145 +-- 18 files changed, 2783 insertions(+), 1439 deletions(-) delete mode 100644 src/node_api_embedding.cc delete mode 100644 src/node_api_embedding.h create mode 100644 src/node_embedding_api.cc create mode 100644 src/node_embedding_api.h diff --git a/doc/api/embedding.md b/doc/api/embedding.md index f853847ab6818e..cda3481923add3 100644 --- a/doc/api/embedding.md +++ b/doc/api/embedding.md @@ -27,8 +27,8 @@ The full code can be found [in the Node.js source tree][embedtest.cc]. Node.js requires some per-process state management in order to run: -* Arguments parsing for Node.js [CLI options][], -* V8 per-process requirements, such as a `v8::Platform` instance. +- Arguments parsing for Node.js [CLI options][], +- V8 per-process requirements, such as a `v8::Platform` instance. The following example shows how these can be set up. Some class names are from the `node` and `v8` C++ namespaces, respectively. @@ -85,10 +85,10 @@ changes: Node.js has a concept of a “Node.js instance”, that is commonly being referred to as `node::Environment`. Each `node::Environment` is associated with: -* Exactly one `v8::Isolate`, i.e. one JS Engine instance, -* Exactly one `uv_loop_t`, i.e. one event loop, and -* A number of `v8::Context`s, but exactly one main `v8::Context`. -* One `node::IsolateData` instance that contains information that could be +- Exactly one `v8::Isolate`, i.e. one JS Engine instance, +- Exactly one `uv_loop_t`, i.e. one event loop, and +- A number of `v8::Context`s, but exactly one main `v8::Context`. +- One `node::IsolateData` instance that contains information that could be shared by multiple `node::Environment`s that use the same `v8::Isolate`. Currently, no testing is performed for this scenario. @@ -166,49 +166,1129 @@ int RunNodeInstance(MultiIsolatePlatform* platform, } ``` -## Node-API Embedding +# C embedder API -As an alternative, an embedded Node.js can also be fully controlled through -Node-API. This API supports both C and C++ through [node-addon-api][]. Although -the embedding API is not promised to be ABI stable at this time, it uses node-api types -and implementation so that it might be a some time in the future. +While Node.js provides an extensive C++ embedding API that can be used from C++ +applications, the C-based API is useful when Node.js is embedded as a shared +libnode library into C++ or non-C++ applications. -An example can be found [in the Node.js source tree][napi_embedding.c]. +The C embedding API is defined in [src/node_embedding_api.h][] in the Node.js +source tree. + +## API design overview + +One of the goals for the C based embedder API is to be ABI stable. It means that +applications must be able to use newer libnode versions without recompilation. +The following design principles are targeting to achieve that goal. + +- Follow the best practices for the [node-api][] design and build on top of + the [node-api][]. +- Use the [Builder pattern][] as the way to configure the global platform and + the instance environments. It enables us incrementally add new flags, + settings, callbacks, and behavior without changing the existing + functions. +- Use the API version as a way to add new or change existing behavior. +- Make the common scenarios simple and the complex scenarios possible. In some + cases we may provide some "shortcut" APIs that combine calls to multiple other + APIs to simplify some common scenarios. + +The C embedder API has the four major API function groups: + +- **Global platform APIs.** These are the global settings and initializations + that are done once per process. They include parsing CLI arguments, setting + the V8 platform, V8 thread pool, and initializing V8. +- **Runtime instance APIs.** This is the main Node.js working environment that + combines V8 `Isolate`, `Context`, and a UV loop. It is used run the Node.js + JavaScript code and modules. Each process we may have one or more runtime + environments. Its behavior is based on the global platform API. +- **Event loop APIs.** The event loop is one of the key concepts of Node.js. It + handles IO callbacks, timer jobs, and Promise continuations. These APIs are + related to a specific Node.js runtime instance and control handling of the + event loop tasks. The event loop tasks can be executed in the chosen thread. + The API controls how many tasks executed at one: all, one-by-one, or until a + predicate becomes false. We can also choose if the even loop must block the + current thread while waiting for a new task to arrive. +- **JavaScript/Native interop APIs.** They rely on the existing [node-api][] + set of functions. The embedding APIs provide access to functions that + retrieve or create `napi_env` instances related to a runtime instance. + + +## API reference + +The C embedder API is split up by the four major groups described above. + +### Global platform APIs + +#### Data types + +##### `node_embedding_platform` + + + +> Stability: 1 - Experimental + +This is an opaque pointer that represents a Node.js platform instance. +Node.js allows only a single platform instance per process. + + +##### `node_embedding_platform_flags` + + + +> Stability: 1 - Experimental + +Flags are used to initialize a Node.js platform instance. ```c - napi_platform platform; - napi_env env; - const char *main_script = "console.log('hello world')"; +typedef enum { + node_embedding_platform_no_flags = 0, + node_embedding_platform_enable_stdio_inheritance = 1 << 0, + node_embedding_platform_disable_node_options_env = 1 << 1, + node_embedding_platform_disable_cli_options = 1 << 2, + node_embedding_platform_no_icu = 1 << 3, + node_embedding_platform_no_stdio_initialization = 1 << 4, + node_embedding_platform_no_default_signal_handling = 1 << 5, + node_embedding_platform_no_init_openssl = 1 << 8, + node_embedding_platform_no_parse_global_debug_variables = 1 << 9, + node_embedding_platform_no_adjust_resource_limits = 1 << 10, + node_embedding_platform_no_use_large_pages = 1 << 11, + node_embedding_platform_no_print_help_or_version_output = 1 << 12, + node_embedding_platform_generate_predictable_snapshot = 1 << 14, +} node_embedding_platform_flags; +``` - if (napi_create_platform(0, NULL, NULL, &platform) != napi_ok) { - fprintf(stderr, "Failed creating the platform\n"); - return -1; - } +These flags match to the C++ `node::ProcessInitializationFlags` and control the +Node.js platform initialization. - if (napi_create_environment(platform, NULL, main_script, - (napi_stdio){NULL, NULL, NULL}, NAPI_VERSION, &env) != napi_ok) { - fprintf(stderr, "Failed running JS\n"); - return -1; - } +- `node_embedding_platform_no_flags` - The default flags. +- `node_embedding_platform_enable_stdio_inheritance` - Enable `stdio` + inheritance, which is disabled by default. This flag is also implied by the + `node_embedding_platform_no_stdio_initialization`. +- `node_embedding_platform_disable_node_options_env` - Disable reading the + `NODE_OPTIONS` environment variable. +- `node_embedding_platform_disable_cli_options` - Do not parse CLI options. +- `node_embedding_platform_no_icu` - Do not initialize ICU. +- `node_embedding_platform_no_stdio_initialization` - Do not modify `stdio` file + descriptor or TTY state. +- `node_embedding_platform_no_default_signal_handling` - Do not register + Node.js-specific signal handlers and reset other signal handlers to + default state. +- `node_embedding_platform_no_init_openssl` - Do not initialize OpenSSL config. +- `node_embedding_platform_no_parse_global_debug_variables` - Do not initialize + Node.js debugging based on environment variables. +- `node_embedding_platform_no_adjust_resource_limits` - Do not adjust OS + resource limits for this process. +- `node_embedding_platform_no_use_large_pages` - Do not map code segments into + large pages for this process. +- `node_embedding_platform_no_print_help_or_version_output` - Skip printing + output for `--help`, `--version`, `--v8-options`. +- `node_embedding_platform_generate_predictable_snapshot` - Initialize the + process for predictable snapshot generation. - // Here you can interact with the environment through Node-API env - if (napi_destroy_environment(env, NULL) != napi_ok) { - return -1; - } +#### Callback types - if (napi_destroy_platform(platform) != napi_ok) { - fprintf(stderr, "Failed destroying the platform\n"); - return -1; - } +##### `node_embedding_error_handler` + + + +> Stability: 1 - Experimental + +```c +typedef napi_status(NAPI_CDECL* node_embedding_error_handler)( + void* handler_data, + const char* messages[], + size_t messages_size, + int32_t exit_code, + napi_status status); +``` + +Function pointer type for user-provided native function that handles the list +of error messages and the exit code. + +The callback parameters: + +- `[in] handler_data`: The user data associated with this callback. +- `[in] messages`: Pointer to an array of zero terminating strings. +- `[in] messages_size`: Size of the `messages` string array. +- `[in] exit_code`: The suggested process exit code in case of error. If the + `exit_code` is zero, then the callback is used to output non-error messages. +- `[in] status`: Reported Node-API status. + + +##### `node_embedding_get_args_callback` + + + +> Stability: 1 - Experimental + +```c +typedef void(NAPI_CDECL* node_embedding_get_args_callback)(void* cb_data, + int32_t argc, + const char* argv[]); +``` + +Function pointer type for user-provided native function that receives list of +CLI arguments from the `node_embedding_platform`. + +The callback parameters: + +- `[in] cb_data`: The user data associated with this callback. +- `[in] argc`: Number of items in the `argv` array. +- `[in] argv`: CLI arguments as an array of zero terminating strings. + + +#### Functions + +##### `node_embedding_on_error` + + + +> Stability: 1 - Experimental + +Sets global custom error handler for the Node.js embedded code. + +```c +napi_status NAPI_CDECL +node_embedding_on_error(node_embedding_error_handler error_handler, + void* error_handler_data); +``` + +- `[in] error_handler`: The error handler callback. +- `[in] error_handler_data`: Optional. The error handler data that will be + passed to the `error_handler` callback. It can be removed after the + `node_embedding_delete_platform()` call. + +Returns `napi_ok` if there were no issues. + +It is recommended to call this function before the creation of the +`node_embedding_platform` instance to handle all error messages the same way. + +This function assigns a custom platform error handler. It replaces the default +error handler that outputs error messages to the `stderr` and exits the current +process with the `exit_code` when it is not zero. + +The zero `exit_code` indicates reported warnings or text messages. For example, +it can be Node.js help text returned in response to the `--help` CLI argument. + + +##### `node_embedding_create_platform` + + + +> Stability: 1 - Experimental + +Creates new Node.js platform instance. + +```c +napi_status NAPI_CDECL +node_embedding_create_platform(int32_t api_version, + node_embedding_platform* result); +``` + +- `[in] api_version`: The version of the C embedder API. +- `[out] result`: New Node.js platform instance. + +Returns `napi_ok` if there were no issues. + +It is a simple object allocation. It does not do any initialization or any +other complex work that may fail. It only checks the argument. + +Node.js allows only a single platform instance per process. + + +##### `node_embedding_delete_platform` + + + +> Stability: 1 - Experimental + +Deletes Node.js platform instance. + +```c +napi_status NAPI_CDECL +node_embedding_delete_platform(node_embedding_platform platform); +``` + +- `[in] platform`: The Node.js platform instance to delete. + +Returns `napi_ok` if there were no issues. + +If the platform was initialized before the deletion, then the method +uninitializes the platform before deletion. + + +##### `node_embedding_platform_is_initialized` + + + +> Stability: 1 - Experimental + +Checks if the Node.js platform instance is initialized. + +```c +napi_status NAPI_CDECL +node_embedding_platform_is_initialized(node_embedding_platform platform, + bool* result); +``` + +- `[in] platform`: The Node.js platform instance to check. +- `[out] result`: `true` if the platform is already initialized. + +Returns `napi_ok` if there were no issues. + +The platform instance settings can be changed until the platform is initialized. +After the `node_embedding_platform_initialize` function call any attempt to +change platform instance settings will fail. + + +##### `node_embedding_platform_set_flags` + + + +> Stability: 1 - Experimental + +Sets the Node.js platform instance flags. + +```c +napi_status NAPI_CDECL +node_embedding_platform_set_flags(node_embedding_platform platform, + node_embedding_platform_flags flags); +``` + +- `[in] platform`: The Node.js platform instance to configure. +- `[in] flags`: The platform flags that control the platform behavior. + +Returns `napi_ok` if there were no issues. + + +##### `node_embedding_platform_set_args` + + + +> Stability: 1 - Experimental + +Sets the CLI args for the Node.js platform instance. + +```c +napi_status NAPI_CDECL +node_embedding_platform_set_args(node_embedding_platform platform, + int32_t argc, + char* argv[]); +``` + +- `[in] platform`: The Node.js platform instance to configure. +- `[in] argc`: Number of items in the `argv` array. +- `[in] argv`: CLI arguments as an array of zero terminating strings. + +Returns `napi_ok` if there were no issues. + + +##### `node_embedding_platform_initialize` + + + +> Stability: 1 - Experimental + +Initializes the Node.js platform instance. + +```c +napi_status NAPI_CDECL +node_embedding_platform_initialize(node_platform platform, + bool* early_return); +``` + +- `[in] platform`: The Node.js platform instance to initialize. +- `[out] early_return`: Optional. `true` if the initialization result requires + early return either because of an error or the Node.js completed the work. + For example, it had printed Node.js version or the help text. + +Returns `napi_ok` if there were no issues. + +The Node.js platform instance initialization parses CLI args, initializes +Node.js internals and the V8 runtime. If the initial work such as printing the +Node.js version number is completed, then the `early_return` is set to `true`. + +After the initialization is completed the Node.js platform settings cannot be +changed anymore. The parsed arguments can be accessed by calling the +`node_embedding_platform_get_args` and `node_embedding_platform_get_exec_args` +functions. + + +##### `node_embedding_platform_get_args` + + + +> Stability: 1 - Experimental + +Gets the parsed list of non-Node.js arguments. + +```c +napi_status NAPI_CDECL +node_embedding_platform_get_args(node_embedding_platform platform, + node_embedding_get_args_callback get_args_cb, + void* get_args_cb_data); +``` + +- `[in] platform`: The Node.js platform instance. +- `[in] get_args_cb`: The callback to receive non-Node.js arguments. +- `[in] get_args_cb_data`: Optional. The callback data that will be passed to + the `get_args_cb` callback. It can be deleted right after the function call. + +Returns `napi_ok` if there were no issues. + + +##### `node_embedding_platform_get_exec_args` + + + +> Stability: 1 - Experimental + +Gets the parsed list of Node.js arguments. + +```c +napi_status NAPI_CDECL +node_embedding_platform_get_exec_args( + node_embedding_platform platform, + node_embedding_get_args_callback get_args_cb, + void* get_args_cb_data); +``` + +- `[in] platform`: The Node.js platform instance. +- `[in] get_args_cb`: The callback to receive Node.js arguments. +- `[in] get_args_cb_data`: Optional. The callback data that will be passed to + the `get_args_cb` callback. It can be deleted right after the function call. + +Returns `napi_ok` if there were no issues. + + +### Runtime instance APIs + +#### Data types + +##### `node_embedding_runtime` + + + +> Stability: 1 - Experimental + +This is an opaque pointer that represents a Node.js runtime instance. +It wraps up the C++ `node::Environment`. +There can be one or more runtime instances in the process. + +##### `node_embedding_runtime_flags` + + + +> Stability: 1 - Experimental + +Flags are used to initialize a Node.js runtime instance. + +```c +typedef enum { + node_embedding_runtime_no_flags = 0, + node_embedding_runtime_default_flags = 1 << 0, + node_embedding_runtime_owns_process_state = 1 << 1, + node_embedding_runtime_owns_inspector = 1 << 2, + node_embedding_runtime_no_register_esm_loader = 1 << 3, + node_embedding_runtime_track_unmanaged_fds = 1 << 4, + node_embedding_runtime_hide_console_windows = 1 << 5, + node_embedding_runtime_no_native_addons = 1 << 6, + node_embedding_runtime_no_global_search_paths = 1 << 7, + node_embedding_runtime_no_browser_globals = 1 << 8, + node_embedding_runtime_no_create_inspector = 1 << 9, + node_embedding_runtime_no_start_debug_signal_handler = 1 << 10, + node_embedding_runtime_no_wait_for_inspector_frontend = 1 << 11 +} node_embedding_runtime_flags; +``` + +These flags match to the C++ `node::EnvironmentFlags` and control the +Node.js runtime initialization. + +- `node_embedding_runtime_no_flags` - No flags set. +- `node_embedding_runtime_default_flags` - Use the default behavior for + Node.js instances. +- `node_embedding_runtime_owns_process_state` - Controls whether this runtime + is allowed to affect per-process state (e.g. cwd, process title, uid, etc.). + This is set when using `node_embedding_runtime_default_flags`. +- `node_embedding_runtime_owns_inspector` - Set if this runtime instance is + associated with the global inspector handling code (i.e. listening + on `SIGUSR1`). + This is set when using `node_embedding_runtime_default_flags`. +- `node_embedding_runtime_no_register_esm_loader` - Set if Node.js should not + run its own esm loader. This is needed by some embedders, because it's + possible for the Node.js esm loader to conflict with another one in an + embedder environment, e.g. Blink's in Chromium. +- `node_embedding_runtime_track_unmanaged_fds` - Set this flag to make Node.js + track "raw" file descriptors, i.e. managed by `fs.open()` and `fs.close()`, + and close them during `node_embedding_delete_runtime`. +- `node_embedding_runtime_hide_console_windows` - Set this flag to force hiding + console windows when spawning child processes. This is usually used when + embedding Node.js in GUI programs on Windows. +- `node_embedding_runtime_no_native_addons` - Set this flag to disable loading + native addons via `process.dlopen`. This runtime flag is especially important + for worker threads so that a worker thread can't load a native addon even if + `execArgv` is overwritten and `--no-addons` is not specified but was specified + for this runtime instance. +- `node_embedding_runtime_no_global_search_paths` - Set this flag to disable + searching modules from global paths like `$HOME/.node_modules` and + `$NODE_PATH`. This is used by standalone apps that do not expect to have their + behaviors changed because of globally installed modules. +- `node_embedding_runtime_no_browser_globals` - Do not export browser globals + like setTimeout, console, etc. +- `node_embedding_runtime_no_create_inspector` - Controls whether or not the + runtime should call `V8Inspector::create()`. This control is needed by + embedders who may not want to initialize the V8 inspector in situations where + one has already been created, e.g. Blink's in Chromium. +- `node_embedding_runtime_no_start_debug_signal_handler` - Controls whether or + not the `InspectorAgent` for this runtime should call + `StartDebugSignalHandler`. This control is needed by embedders who may not + want to allow other processes to start the V8 inspector. +- `node_embedding_runtime_no_wait_for_inspector_frontend` - Controls whether the + `InspectorAgent` created for this runtime waits for Inspector frontend events + during the runtime creation. It's used to call `node::Stop(env)` on a Worker + thread that is waiting for the events. + + +##### `node_embedding_snapshot_flags` + + + +> Stability: 1 - Experimental + +Flags are used to create a Node.js runtime JavaScript snapshot. + +```c +typedef enum { + node_embedding_snapshot_no_flags = 0, + node_embedding_snapshot_no_code_cache = 1 << 0, +} node_embedding_snapshot_flags; +``` + +These flags match to the C++ `node::SnapshotFlags` and control the +Node.js runtime snapshot creation. + +- `node_embedding_snapshot_no_flags` - No flags set. +- `node_embedding_snapshot_no_code_cache` - Whether code cache should be + generated as part of the snapshot. Code cache reduces the time spent on + compiling functions included in the snapshot at the expense of a bigger + snapshot size and potentially breaking portability of the snapshot. + + +#### Callback types + +##### `node_embedding_runtime_preload_callback` + + + +> Stability: 1 - Experimental + +```c +typedef void(NAPI_CDECL* node_embedding_runtime_preload_callback)( + void* cb_data, + napi_env env, + napi_value process, + napi_value require); +``` + +Function pointer type for user-provided native function that is called when the +runtime initially loads the JavaScript code. + +The callback parameters: + +- `[in] cb_data`: The user data associated with this callback. +- `[in] env`: Node-API environmentStart of the blob memory span. +- `[in] process`: The Node.js `process` object. +- `[in] require`: The internal `require` function. + + +##### `node_embedding_store_blob_callback` + + + +> Stability: 1 - Experimental + +```c +typedef void(NAPI_CDECL* node_embedding_store_blob_callback)( + void* cb_data, + const uint8_t* blob, + size_t size); +``` + +Function pointer type for user-provided native function that is called when the +runtime needs to store the snapshot blob. + +The callback parameters: + +- `[in] cb_data`: The user data associated with this callback. +- `[in] blob`: Start of the blob memory span. +- `[in] size`: Size of the blob memory span. + + +#### Functions + +##### `node_embedding_create_runtime` + + + +> Stability: 1 - Experimental + +Creates new Node.js runtime instance. + +```c +napi_status NAPI_CDECL +node_embedding_create_runtime(node_embedding_platform platform, + node_embedding_runtime* result); +``` + +- `[in] platform`: Optional. An initialized Node.js platform instance. +- `[out] result`: Upon return has a new Node.js runtime instance. + +Returns `napi_ok` if there were no issues. + +Creates new Node.js runtime instance based on the provided platform instance. + +It is a simple object allocation. It does not do any initialization or any +other complex work that may fail besides checking the arguments. + +If the platform instance is `NULL` then a default platform instance will be +created internally when the `node_embedding_platform_initialize` is called. +Since there can be only one platform instance per process, only one runtime +instance can be created this way per process. + +If it is planned to create more than one runtime instance or a non-default +platform configuration is required, then it is recommended to create the +Node.js platform instance explicitly. + + +##### `node_embedding_delete_runtime` + + + +> Stability: 1 - Experimental + +Deletes Node.js runtime instance. + +```c +napi_status NAPI_CDECL +node_embedding_delete_runtime(node_embedding_runtime runtime); +``` + +- `[in] runtime`: The Node.js runtime instance to delete. + +Returns `napi_ok` if there were no issues. + +If the runtime was initialized, then the method un-initializes the runtime +before the deletion. + +As a part of the un-initialization it can store created snapshot blob if the +`node_embedding_runtime_on_create_snapshot` set the callback to save the +snapshot blob. + + +##### `node_embedding_runtime_is_initialized` + + + +> Stability: 1 - Experimental + +Checks if the Node.js runtime instance is initialized. + +```c +napi_status NAPI_CDECL +node_embedding_runtime_is_initialized(node_embedding_runtime runtime, + bool* result); +``` + +- `[in] runtime`: The Node.js runtime instance to check. +- `[out] result`: `true` if the runtime is already initialized. + +Returns `napi_ok` if there were no issues. + +The runtime settings can be changed until the runtime is initialized. +After the `node_embedding_runtime_initialize` function is called any attempt to change +runtime settings will fail. + + +##### `node_embedding_runtime_set_flags` + + + +> Stability: 1 - Experimental + +Sets the Node.js runtime instance flags. + +```c +napi_status NAPI_CDECL +node_embedding_runtime_set_flags(node_embedding_runtime runtime, + node_embedding_runtime_flags flags); +``` + +- `[in] runtime`: The Node.js runtime instance to configure. +- `[in] flags`: The runtime flags that control the runtime behavior. + +Returns `napi_ok` if there were no issues. + + +##### `node_embedding_runtime_set_args` + +Sets the non-Node.js arguments for the Node.js runtime instance. + +```c +napi_status NAPI_CDECL +node_embedding_runtime_set_args(node_embedding_runtime runtime, + int32_t argc, + const char* argv[]); +``` + +- `[in] runtime`: The Node.js runtime instance to configure. +- `[in] argc`: Number of items in the `argv` array. +- `[in] argv`: non-Node.js arguments as an array of zero terminating strings. + +Returns `napi_ok` if there were no issues. + + +##### `node_embedding_runtime_set_exec_args` + +Sets the Node.js arguments for the Node.js runtime instance. + +```c +napi_status NAPI_CDECL +node_embedding_runtime_set_exec_args(node_embedding_runtime runtime, + int32_t argc, + const char* argv[]); +``` + +- `[in] runtime`: The Node.js runtime instance to configure. +- `[in] argc`: Number of items in the `argv` array. +- `[in] argv`: Node.js arguments as an array of zero terminating strings. + +Returns `napi_ok` if there were no issues. + + +##### `node_embedding_runtime_on_preload` + + + +> Stability: 1 - Experimental + +Sets a preload callback to call before Node.js runtime instance is loaded. + +```c +napi_status NAPI_CDECL +node_embedding_runtime_on_preload( + node_embedding_runtime runtime, + node_embedding_runtime_preload_callback preload_cb, + void* preload_cb_data); +``` + +- `[in] runtime`: The Node.js runtime instance. +- `[in] preload_cb`: The preload callback to be called before Node.js runtime + instance is loaded. +- `[in] preload_cb_data`: Optional. The preload callback data that will be + passed to the `preload_cb` callback. It can be removed after the + `node_embedding_delete_runtime` call. + +Returns `napi_ok` if there were no issues. + + +##### `node_embedding_runtime_use_snapshot` + + + +> Stability: 1 - Experimental + +Use a snapshot blob to load this Node.js runtime instance. + +```c +napi_status NAPI_CDECL +node_embedding_runtime_use_snapshot(node_embedding_runtime runtime, + const uint8_t* snapshot, + size_t size); +``` + +- `[in] runtime`: The Node.js runtime instance. +- `[in] snapshot`: Start of the snapshot memory span. +- `[in] size`: Size of the snapshot memory span. + +Returns `napi_ok` if there were no issues. + + +##### `node_embedding_runtime_on_create_snapshot` + + + +> Stability: 1 - Experimental + +Sets a callback to store created snapshot when Node.js runtime instance +finished execution. + +```c +napi_status NAPI_CDECL +node_embedding_runtime_on_create_snapshot( + node_embedding_runtime runtime, + node_embedding_store_blob_callback store_blob_cb, + void* store_blob_cb_data, + node_embedding_snapshot_flags snapshot_flags); +``` + +- `[in] runtime`: The Node.js runtime instance. +- `[in] store_blob_cb`: The store blob callback to be called before Node.js + runtime instance is deleted. +- `[in] store_blob_cb_data`: Optional. The store blob callback data that will be + passed to the `store_blob_cb` callback. It can be removed after the + `node_embedding_delete_runtime` call. +- `[in] snapshot_flags`: The flags controlling the snapshot creation. + +Returns `napi_ok` if there were no issues. + + +##### `node_embedding_runtime_initialize` + + + +> Stability: 1 - Experimental + +Initializes the Node.js runtime instance. + +```c +napi_status NAPI_CDECL +node_embedding_runtime_initialize(node_embedding_runtime runtime, + const char* main_script); +``` + +- `[in] runtime`: The Node.js runtime instance to initialize. +- `[in] main_script`: The main script to run. + +Returns `napi_ok` if there were no issues. + +The Node.js runtime initialization creates new Node.js environment associated +with a V8 `Isolate` and V8 `Context`. + +After the initialization is completed the Node.js runtime settings cannot be +changed anymore. + + +### Event loop APIs + +#### Data types + +##### `node_embedding_event_loop_run_mode` + + + +> Stability: 1 - Experimental + +The event loop run mode. + +```c +typedef enum { + node_embedding_event_loop_run_once = 1, + node_embedding_event_loop_run_nowait = 2, +} node_embedding_event_loop_run_mode; +``` + +These values match to UV library `uv_run_mode` enum and control the event loop +beahvior. + +- `node_embedding_event_loop_run_once` - Run the event loop once and wait if + there are no items. It matches the `UV_RUN_ONCE` behavior. +- `node_embedding_event_loop_run_nowait` - Run the event loop once and do not + wait if there are no items. It matches the `UV_RUN_NOWAIT` behavior. + + +#### Callback types + +##### `node_embedding_event_loop_predicate` + + + +> Stability: 1 - Experimental + +```c +typedef bool(NAPI_CDECL* node_embedding_event_loop_predicate)( + void* predicate_data, + bool has_work); +``` + +Function pointer type for user-provided predicate function that is checked +before each task execution in the Node.js runtime event loop. + +The callback parameters: + +- `[in] predicate_data`: The user data associated with this predicate callback. +- `[in] has_work`: `true` if the event loop has work to do. + +Returns `true` if the runtime loop must continue to run. + + +#### Functions + +##### `node_embedding_runtime_run_event_loop` + + + +> Stability: 1 - Experimental + +Runs Node.js runtime instance event loop. + +```c +napi_status NAPI_CDECL +node_embedding_runtime_run_event_loop(node_embedding_runtime runtime); +``` + +- `[in] runtime`: The Node.js runtime instance. + +Returns `napi_ok` if there were no issues. + +The function exits when there are no more tasks to process in the loop. + + +##### `node_embedding_runtime_run_event_loop_while` + + + +> Stability: 1 - Experimental + +Runs Node.js runtime instance event loop while there tasks to process and +the provided predicate returns true. + +```c +napi_status NAPI_CDECL +node_embedding_runtime_run_event_loop_while( + node_embedding_runtime runtime, + node_embedding_runtime_event_loop_predicate predicate, + void* predicate_data, + node_embedding_event_loop_run_mode run_mode, + bool* has_more_work); +``` + +- `[in] runtime`: The Node.js runtime instance. +- `[in] predicate`: The predicate to check before each runtime event loop + task invocation. +- `[in] predicate_data`: Optional. The predicate data that will be + passed to the `predicate` callback. It can be removed right after this + function call. +- `[in] run_mode`: Specifies behavior in case if the event loop does not have + items to process. The `node_embedding_event_loop_run_once` will block the + current thread and the `node_embedding_event_loop_run_nowait` will not wait + and exit. +- `[out] has_more_work`: `true` if the runtime event loop has more tasks after + returning from the function. + +Returns `napi_ok` if there were no issues. + + +##### `node_embedding_runtime_await_promise` + + + +> Stability: 1 - Experimental + +Runs Node.js runtime instance event loop until the provided promise is completed +with a success of a failure. It blocks the thread if there are to tasks in the +loop and the promise is not completed. + +```c +napi_status NAPI_CDECL +node_embedding_runtime_await_promise(node_embedding_runtime runtime, + napi_value promise, + napi_value* result, + bool* has_more_work); +``` + +- `[in] runtime`: The Node.js runtime instance. +- `[in] promise`: The promise to complete. +- `[out] result`: Result of the `promise` completion. +- `[out] has_more_work`: `true` if the runtime event loop has more tasks after + returning from the function. + +Returns `napi_ok` if there were no issues. + + +### JavaScript/Native interop APIs + +#### Functions + +##### `node_embedding_runtime_set_node_api_version` + + + +> Stability: 1 - Experimental + +Sets the Node-API version for the Node.js runtime instance. + +```c +napi_status NAPI_CDECL +node_runtime_set_node_api_version(node_embedding_runtime runtime, + int32_t node_api_version); +``` + +- `[in] runtime`: The Node.js runtime instance. +- `[in] node_api_version`: The version of the Node-API. + +Returns `napi_ok` if there were no issues. + +By default it is using the Node-API version 8. + + +##### `node_embedding_runtime_get_node_api_env` + + + +> Stability: 1 - Experimental + +Gets `napi_env` associated with the Node.js runtime instance. + +```c +napi_status NAPI_CDECL +node_embedding_runtime_get_node_api_env(node_runtime embedding_runtime, + napi_env* env); +``` + +- `[in] runtime`: The Node.js runtime instance. +- `[out] env`: An instance of `napi_env`. + +Returns `napi_ok` if there were no issues. + + +##### `node_embedding_runtime_open_scope` + + + +> Stability: 1 - Experimental + +Opens V8 Isolate and Context scope associated with the Node.js runtime instance. + +```c +napi_status NAPI_CDECL +node_embedding_runtime_open_scope(node_embedding_runtime runtime); +``` + +- `[in] runtime`: The Node.js runtime instance. + +Returns `napi_ok` if there were no issues. + +Any Node-API function call requires the runtime scope to be opened for the +current thread. + +This function lets the V8 Isolate enter the current thread and open the scope. +Then, it opens the V8 Handle scope and the V8 Context scope. +It enables use of V8 API and the Node APIs which is a C API on top of +the V8 API. + + +##### `node_runtime_close_scope` + + + +> Stability: 1 - Experimental + +Closes V8 Isolate and Context scope associated with the Node.js +runtime instance. + +```c +napi_status NAPI_CDECL +node_embedding_runtime_close_scope(node_embedding_runtime runtime); +``` + +- `[in] runtime`: The Node.js embedding_runtime instance. + +Returns `napi_ok` if there were no issues. + +Any Node-API function call requires the runtime scope to be opened for the +current thread. Each opened runtime scoped must be closed in the end. + +This function closes V8 Context scope, V8 Handle scope, V8 Isolate scope, and +then makes the V8 Isolate leave the current thread. + + +## Examples + +The examples listed here are part of the Node.js +[embedding unit tests][test_embedding]. + +```c + // TODO: add example here. ``` +[Builder pattern]: https://en.wikipedia.org/wiki/Builder_pattern [CLI options]: cli.md [`process.memoryUsage()`]: process.md#processmemoryusage [deprecation policy]: deprecations.md [embedtest.cc]: https://github.com/nodejs/node/blob/HEAD/test/embedding/embedtest.cc -[napi_embedding.c]: https://github.com/nodejs/node/blob/HEAD/test/embedding/napi_embedding.c -[node-addon-api]: https://github.com/nodejs/node-addon-api +[test_embedding]: https://github.com/nodejs/node/blob/HEAD/test/embedding +[node-api]: n-api.md [src/node.h]: https://github.com/nodejs/node/blob/HEAD/src/node.h diff --git a/doc/api/index.md b/doc/api/index.md index 0f3d4c8c4fec35..a778c37b3d0193 100644 --- a/doc/api/index.md +++ b/doc/api/index.md @@ -16,7 +16,7 @@ * [Buffer](buffer.md) * [C++ addons](addons.md) * [C/C++ addons with Node-API](n-api.md) -* [C++ embedder API](embedding.md) +* [C/C++ embedder API](embedding.md) * [Child processes](child_process.md) * [Cluster](cluster.md) * [Command-line options](cli.md) diff --git a/doc/api/n-api.md b/doc/api/n-api.md index d838a520142606..0727ca74d2f986 100644 --- a/doc/api/n-api.md +++ b/doc/api/n-api.md @@ -6592,145 +6592,6 @@ idempotent. This API may only be called from the main thread. -## Using embedded Node.js - -### `napi_create_platform` - - - -> Stability: 1 - Experimental - -```c -napi_status napi_create_platform(int argc, - char** argv, - napi_error_message_handler err_handler, - napi_platform* result); -``` - -* `[in] argc`: CLI argument count, pass 0 for autofilling. -* `[in] argv`: CLI arguments, pass NULL for autofilling. -* `[in] err_handler`: If different than NULL, will be called back with each - error message. There can be multiple error messages but the API guarantees - that no calls will be made after the `napi_create_platform` has returned. - In practice, the implementation of graceful recovery is still in progress, - and many errors will be fatal, resulting in an `abort()`. -* `[out] result`: A `napi_platform` result. - -This function must be called once to initialize V8 and Node.js when using as a -shared library. - -### `napi_destroy_platform` - - - -> Stability: 1 - Experimental - -```c -napi_status napi_destroy_platform(napi_platform platform); -``` - -* `[in] platform`: platform handle. - -Destroy the Node.js / V8 processes. - -### `napi_create_environment` - - - -> Stability: 1 - Experimental - -```c -napi_status napi_create_environment(napi_platform platform, - napi_error_message_handler err_handler, - const char* main_script, - int32_t api_version, - napi_env* result); -``` - -* `[in] platform`: platform handle. -* `[in] err_handler`: If different than NULL, will be called back with each - error message. There can be multiple error messages but the API guarantees - that no calls will be made after the `napi_create_platform` has returned. - In practice, the implementation of graceful recovery is still in progress, - and many errors will be fatal, resulting in an `abort()`. -* `[in] main_script`: If different than NULL, custom JavaScript to run in - addition to the default bootstrap that creates an empty - ready-to-use CJS/ES6 environment with `global.require()` and - `global.import()` functions that resolve modules from the directory of - the compiled binary. - It can be used to redirect `process.stdin`/ `process.stdout` streams - since Node.js might switch these file descriptors to non-blocking mode. -* `[in] api_version`: Node-API version to conform to, pass `NAPI_VERSION` - for the latest available. -* `[out] result`: A `napi_env` result. - -Initialize a new environment. A single platform can hold multiple Node.js -environments that will run in a separate V8 isolate each. If the returned -value is `napi_ok` or `napi_pending_exception`, the environment must be -destroyed with `napi_destroy_environment` to free all allocated memory. - -### `napi_run_environment` - - - -> Stability: 1 - Experimental - -```c -napi_status napi_run_environment(napi_env env); -``` - -* `[in] env`: environment handle. - -Iterate the event loop of the environment. Executes all pending -JavaScript callbacks. Cannot be called with JavaScript on the -stack (ie in a JavaScript callback). - -### `napi_await_promise` - - - -> Stability: 1 - Experimental - -```c -napi_status napi_await_promise(napi_env env, - napi_value promise, - napi_value *result); -``` - -* `[in] env`: environment handle. -* `[in] promise`: JS Promise. -* `[out] result`: Will receive the value that the Promise resolved with. - -Iterate the event loop of the environment until the `promise` has been -resolved. Returns `napi_pending_exception` on rejection. Cannot be called -with JavaScript on the stack (ie in a JavaScript callback). - -### `napi_destroy_environment` - - - -> Stability: 1 - Experimental - -```c -napi_status napi_destroy_environment(napi_env env); -``` - -* `[in] env`: environment handle. - -Destroy the Node.js environment / V8 isolate. - ## Miscellaneous utilities ### `node_api_get_module_file_name` diff --git a/node.gyp b/node.gyp index cd85b8b55e2518..066689aacd134b 100644 --- a/node.gyp +++ b/node.gyp @@ -99,7 +99,6 @@ 'src/module_wrap.cc', 'src/node.cc', 'src/node_api.cc', - 'src/node_api_embedding.cc', 'src/node_binding.cc', 'src/node_blob.cc', 'src/node_buffer.cc', @@ -111,6 +110,7 @@ 'src/node_debug.cc', 'src/node_dir.cc', 'src/node_dotenv.cc', + 'src/node_embedding_api.cc', 'src/node_env_var.cc', 'src/node_errors.cc', 'src/node_external_reference.cc', @@ -223,7 +223,7 @@ 'src/module_wrap.h', 'src/node.h', 'src/node_api.h', - 'src/node_api_embedding.h', + 'src/node_api_internals.h', 'src/node_api_types.h', 'src/node_binding.h', 'src/node_blob.h', @@ -235,6 +235,7 @@ 'src/node_debug.h', 'src/node_dir.h', 'src/node_dotenv.h', + 'src/node_embedding_api.h', 'src/node_errors.h', 'src/node_exit_code.h', 'src/node_external_reference.h', diff --git a/src/api/embed_helpers.cc b/src/api/embed_helpers.cc index ec9bf95eb779b0..5f8fadc317a3c8 100644 --- a/src/api/embed_helpers.cc +++ b/src/api/embed_helpers.cc @@ -36,7 +36,9 @@ template < SpinEventLoopCleanupMode cleanupMode = SpinEventLoopCleanupMode::kNormal, typename ShouldContinuePredicate = int> Maybe SpinEventLoopInternalImpl( - Environment* env, const ShouldContinuePredicate& shouldContinue = 0) { + Environment* env, + uv_run_mode run_mode = UV_RUN_DEFAULT, + const ShouldContinuePredicate& shouldContinue = 0) { CHECK_NOT_NULL(env); MultiIsolatePlatform* platform = GetMultiIsolatePlatform(env); CHECK_NOT_NULL(platform); @@ -63,11 +65,12 @@ Maybe SpinEventLoopInternalImpl( bool more; do { - if constexpr (std::is_invocable_r_v) { + if constexpr (std:: + is_invocable_r_v) { do { if (env->is_stopping()) break; - more = uv_run(env->event_loop(), UV_RUN_NOWAIT); - } while (more && shouldContinue()); + more = uv_run(env->event_loop(), run_mode); + } while (more && shouldContinue(more)); } else { if (env->is_stopping()) break; uv_run(env->event_loop(), UV_RUN_DEFAULT); @@ -140,9 +143,11 @@ v8::Maybe SpinEventLoopWithoutCleanup(Environment* env) { } v8::Maybe SpinEventLoopWithoutCleanup( - Environment* env, const std::function& shouldContinue) { + Environment* env, + uv_run_mode run_mode, + const std::function& shouldContinue) { return SpinEventLoopInternalImpl( - env, shouldContinue); + env, run_mode, shouldContinue); } struct CommonEnvironmentSetup::Impl { diff --git a/src/js_native_api.h b/src/js_native_api.h index 1558a9f996a069..bbcb98772c3ea1 100644 --- a/src/js_native_api.h +++ b/src/js_native_api.h @@ -13,7 +13,7 @@ #else // The baseline version for N-API. // The NAPI_VERSION controls which version will be used by default when -// compilling a native addon. If the addon developer specifically wants to use +// compiling a native addon. If the addon developer specifically wants to use // functions available in a new version of N-API that is not yet ported in all // LTS versions, they can set NAPI_VERSION knowing that they have specifically // depended on that version. diff --git a/src/node_api_embedding.cc b/src/node_api_embedding.cc deleted file mode 100644 index 06403aca2d5883..00000000000000 --- a/src/node_api_embedding.cc +++ /dev/null @@ -1,765 +0,0 @@ -#define NAPI_EXPERIMENTAL -#include "node_api_embedding.h" - -#include "env-inl.h" -#include "js_native_api_v8.h" -#include "node_api_internals.h" -#include "util-inl.h" - -#include -#include // INT_MAX -#include -#include - -namespace node { - -// Declare functions implemented in embed_helpers.cc -v8::Maybe SpinEventLoopWithoutCleanup(Environment* env); -v8::Maybe SpinEventLoopWithoutCleanup( - Environment* env, const std::function& shouldContinue); - -} // end of namespace node - -namespace v8impl { -namespace { - -// A helper class to convert std::vector to an array of C strings. -// If the number of strings is less than kInplaceBufferSize, the strings are -// stored in the inplace_buffer_ array. Otherwise, the strings are stored in the -// allocated_buffer_ array. -// Ideally the class must be allocated on the stack. -// In any case it must not outlive the passed vector since it keeps only the -// string pointers returned by std::stirng::c_str() method. -class CStringArray { - static constexpr size_t kInplaceBufferSize = 32; - - public: - explicit CStringArray(const std::vector& strings) noexcept - : size_(strings.size()) { - if (size_ <= inplace_buffer_.size()) { - c_strs_ = inplace_buffer_.data(); - } else { - allocated_buffer_ = std::make_unique(size_); - c_strs_ = allocated_buffer_.get(); - } - for (size_t i = 0; i < size_; ++i) { - c_strs_[i] = strings[i].c_str(); - } - } - - CStringArray(const CStringArray&) = delete; - CStringArray& operator=(const CStringArray&) = delete; - - const char** c_strs() const { return c_strs_; } - size_t size() const { return size_; } - - const char** argv() const { return c_strs_; } - int32_t argc() const { return static_cast(size_); } - - private: - const char** c_strs_{}; - size_t size_{}; - std::array inplace_buffer_; - std::unique_ptr allocated_buffer_; -}; - -class EmbeddedPlatform { - public: - static bool InitOncePerProcess() noexcept { - return !is_initialized_.test_and_set(); - } - - static bool UninitOncePerProcess() noexcept { - return is_initialized_.test() && !is_uninitialized_.test_and_set(); - } - - static EmbeddedPlatform* GetInstance() noexcept { return platform_.get(); } - - static EmbeddedPlatform* CreateInstance( - std::shared_ptr&& - platform_init_result) noexcept { - platform_ = - std::make_unique(std::move(platform_init_result)); - return platform_.get(); - } - - static void DeleteInstance() noexcept { platform_ = nullptr; } - - static void set_v8_platform( - std::unique_ptr&& v8_platform) { - platform_->v8_platform_ = std::move(v8_platform); - } - - static node::MultiIsolatePlatform* get_v8_platform() noexcept { - return platform_->v8_platform_.get(); - } - - const std::vector& args() const { - return platform_init_result_->args(); - } - - const std::vector& exec_args() const { - return platform_init_result_->exec_args(); - } - - explicit EmbeddedPlatform(std::shared_ptr&& - platform_init_result) noexcept - : platform_init_result_(std::move(platform_init_result)) {} - - EmbeddedPlatform(const EmbeddedPlatform&) = delete; - EmbeddedPlatform& operator=(const EmbeddedPlatform&) = delete; - - private: - std::shared_ptr platform_init_result_; - std::unique_ptr v8_platform_; - - static std::atomic_flag is_initialized_; - static std::atomic_flag is_uninitialized_; - static std::unique_ptr platform_; -}; - -std::atomic_flag EmbeddedPlatform::is_initialized_{}; -std::atomic_flag EmbeddedPlatform::is_uninitialized_{}; -std::unique_ptr EmbeddedPlatform::platform_{}; - -struct EmbeddedEnvironmentOptions { - explicit EmbeddedEnvironmentOptions() noexcept - : args_(EmbeddedPlatform::GetInstance()->args()), - exec_args_(EmbeddedPlatform::GetInstance()->exec_args()) {} - - EmbeddedEnvironmentOptions(const EmbeddedEnvironmentOptions&) = delete; - EmbeddedEnvironmentOptions& operator=(const EmbeddedEnvironmentOptions&) = - delete; - - bool is_frozen_{false}; - node_api_env_flags flags_{node_api_env_default_flags}; - std::vector args_; - std::vector exec_args_; - node::EmbedderPreloadCallback preload_cb_{}; - node::EmbedderSnapshotData::Pointer snapshot_; - std::function create_snapshot_; - node::SnapshotConfig snapshot_config_{}; -}; - -struct IsolateLocker { - IsolateLocker(node::CommonEnvironmentSetup* env_setup) - : v8_locker_(env_setup->isolate()), - isolate_scope_(env_setup->isolate()), - handle_scope_(env_setup->isolate()), - context_scope_(env_setup->context()) {} - - bool IsLocked() const { - return v8::Locker::IsLocked(v8::Isolate::GetCurrent()); - } - - void IncrementLockCount() { ++lock_count_; } - - bool DecrementLockCount() { - --lock_count_; - return lock_count_ == 0; - } - - private: - int32_t lock_count_ = 1; - v8::Locker v8_locker_; - v8::Isolate::Scope isolate_scope_; - v8::HandleScope handle_scope_; - v8::Context::Scope context_scope_; -}; - -class EmbeddedEnvironment final : public node_napi_env__ { - public: - explicit EmbeddedEnvironment( - std::unique_ptr&& env_options, - std::unique_ptr&& env_setup, - v8::Local context, - const std::string& module_filename, - int32_t module_api_version) - : node_napi_env__(context, module_filename, module_api_version), - env_options_(std::move(env_options)), - env_setup_(std::move(env_setup)) { - env_options_->is_frozen_ = true; - - std::scoped_lock lock(shared_mutex_); - node_env_to_node_api_env_.emplace(env_setup_->env(), this); - } - - static node_napi_env GetOrCreateNodeApiEnv(node::Environment* node_env) { - std::scoped_lock lock(shared_mutex_); - auto it = node_env_to_node_api_env_.find(node_env); - if (it != node_env_to_node_api_env_.end()) return it->second; - // TODO: (vmoroz) propagate API version from the root environment. - node_napi_env env = new node_napi_env__( - node_env->context(), "", NAPI_VERSION_EXPERIMENTAL); - node_env->AddCleanupHook( - [](void* arg) { static_cast(arg)->Unref(); }, env); - node_env_to_node_api_env_.try_emplace(node_env, env); - return env; - } - - static EmbeddedEnvironment* FromNapiEnv(napi_env env) { - return static_cast(env); - } - - node::CommonEnvironmentSetup* env_setup() { return env_setup_.get(); } - - std::unique_ptr ResetEnvSetup() { - return std::move(env_setup_); - } - - napi_status OpenScope() { - if (isolate_locker_.has_value()) { - if (!isolate_locker_->IsLocked()) return napi_generic_failure; - isolate_locker_->IncrementLockCount(); - } else { - isolate_locker_.emplace(env_setup_.get()); - } - return napi_ok; - } - - napi_status CloseScope() { - if (!isolate_locker_.has_value()) return napi_generic_failure; - if (!isolate_locker_->IsLocked()) return napi_generic_failure; - if (isolate_locker_->DecrementLockCount()) isolate_locker_.reset(); - return napi_ok; - } - - bool IsScopeOpened() const { return isolate_locker_.has_value(); } - - const EmbeddedEnvironmentOptions& options() const { return *env_options_; } - - const node::EmbedderSnapshotData::Pointer& snapshot() const { - return env_options_->snapshot_; - } - - const std::function& - create_snapshot() { - return env_options_->create_snapshot_; - } - - private: - std::unique_ptr env_options_; - std::unique_ptr env_setup_; - std::optional isolate_locker_; - - static std::mutex shared_mutex_; - static std::unordered_map - node_env_to_node_api_env_; -}; - -std::mutex EmbeddedEnvironment::shared_mutex_{}; -std::unordered_map - EmbeddedEnvironment::node_env_to_node_api_env_{}; - -node::ProcessInitializationFlags::Flags GetProcessInitializationFlags( - node_api_platform_flags flags) { - uint32_t result = node::ProcessInitializationFlags::kNoFlags; - if ((flags & node_api_platform_enable_stdio_inheritance) != 0) { - result |= node::ProcessInitializationFlags::kEnableStdioInheritance; - } - if ((flags & node_api_platform_disable_node_options_env) != 0) { - result |= node::ProcessInitializationFlags::kDisableNodeOptionsEnv; - } - if ((flags & node_api_platform_disable_cli_options) != 0) { - result |= node::ProcessInitializationFlags::kDisableCLIOptions; - } - if ((flags & node_api_platform_no_icu) != 0) { - result |= node::ProcessInitializationFlags::kNoICU; - } - if ((flags & node_api_platform_no_stdio_initialization) != 0) { - result |= node::ProcessInitializationFlags::kNoStdioInitialization; - } - if ((flags & node_api_platform_no_default_signal_handling) != 0) { - result |= node::ProcessInitializationFlags::kNoDefaultSignalHandling; - } - result |= node::ProcessInitializationFlags::kNoInitializeV8; - result |= node::ProcessInitializationFlags::kNoInitializeNodeV8Platform; - if ((flags & node_api_platform_no_init_openssl) != 0) { - result |= node::ProcessInitializationFlags::kNoInitOpenSSL; - } - if ((flags & node_api_platform_no_parse_global_debug_variables) != 0) { - result |= node::ProcessInitializationFlags::kNoParseGlobalDebugVariables; - } - if ((flags & node_api_platform_no_adjust_resource_limits) != 0) { - result |= node::ProcessInitializationFlags::kNoAdjustResourceLimits; - } - if ((flags & node_api_platform_no_use_large_pages) != 0) { - result |= node::ProcessInitializationFlags::kNoUseLargePages; - } - if ((flags & node_api_platform_no_print_help_or_version_output) != 0) { - result |= node::ProcessInitializationFlags::kNoPrintHelpOrVersionOutput; - } - if ((flags & node_api_platform_generate_predictable_snapshot) != 0) { - result |= node::ProcessInitializationFlags::kGeneratePredictableSnapshot; - } - return static_cast(result); -} - -node::EnvironmentFlags::Flags GetEnvironmentFlags(node_api_env_flags flags) { - uint64_t result = node::EnvironmentFlags::kNoFlags; - if ((flags & node_api_env_default_flags) != 0) { - result |= node::EnvironmentFlags::kDefaultFlags; - } - if ((flags & node_api_env_owns_process_state) != 0) { - result |= node::EnvironmentFlags::kOwnsProcessState; - } - if ((flags & node_api_env_owns_inspector) != 0) { - result |= node::EnvironmentFlags::kOwnsInspector; - } - if ((flags & node_api_env_no_register_esm_loader) != 0) { - result |= node::EnvironmentFlags::kNoRegisterESMLoader; - } - if ((flags & node_api_env_track_unmanaged_fds) != 0) { - result |= node::EnvironmentFlags::kTrackUnmanagedFds; - } - if ((flags & node_api_env_hide_console_windows) != 0) { - result |= node::EnvironmentFlags::kHideConsoleWindows; - } - if ((flags & node_api_env_no_native_addons) != 0) { - result |= node::EnvironmentFlags::kNoNativeAddons; - } - if ((flags & node_api_env_no_global_search_paths) != 0) { - result |= node::EnvironmentFlags::kNoGlobalSearchPaths; - } - if ((flags & node_api_env_no_browser_globals) != 0) { - result |= node::EnvironmentFlags::kNoBrowserGlobals; - } - if ((flags & node_api_env_no_create_inspector) != 0) { - result |= node::EnvironmentFlags::kNoCreateInspector; - } - if ((flags & node_api_env_no_start_debug_signal_handler) != 0) { - result |= node::EnvironmentFlags::kNoStartDebugSignalHandler; - } - if ((flags & node_api_env_no_wait_for_inspector_frontend) != 0) { - result |= node::EnvironmentFlags::kNoWaitForInspectorFrontend; - } - return static_cast(result); -} - -} // end of anonymous namespace -} // end of namespace v8impl - -napi_status NAPI_CDECL -node_api_initialize_platform(int32_t argc, - char* argv[], - node_api_platform_flags flags, - node_api_error_message_handler error_handler, - void* error_handler_data, - bool* early_return, - int32_t* exit_code) { - if (argc == 0) return napi_invalid_arg; - if (argv == nullptr) return napi_invalid_arg; - if (!v8impl::EmbeddedPlatform::InitOncePerProcess()) - return napi_generic_failure; - - std::vector args(argv, argv + argc); - - std::shared_ptr platform_init_result = - node::InitializeOncePerProcess( - args, v8impl::GetProcessInitializationFlags(flags)); - - if (error_handler != nullptr && !platform_init_result->errors().empty()) { - v8impl::CStringArray errors(platform_init_result->errors()); - error_handler(error_handler_data, errors.c_strs(), errors.size()); - } - - if (early_return != nullptr) { - *early_return = platform_init_result->early_return(); - } - - if (exit_code != nullptr) { - *exit_code = platform_init_result->exit_code(); - } - - if (platform_init_result->early_return()) { - return platform_init_result->exit_code() == 0 ? napi_ok - : napi_generic_failure; - } - - v8impl::EmbeddedPlatform* platform = - v8impl::EmbeddedPlatform::CreateInstance(std::move(platform_init_result)); - - int32_t thread_pool_size = - static_cast(node::per_process::cli_options->v8_thread_pool_size); - platform->set_v8_platform( - node::MultiIsolatePlatform::Create(thread_pool_size)); - v8::V8::InitializePlatform(platform->get_v8_platform()); - v8::V8::Initialize(); - - return napi_ok; -} - -napi_status NAPI_CDECL node_api_dispose_platform() { - if (!v8impl::EmbeddedPlatform::UninitOncePerProcess()) - return napi_generic_failure; - v8::V8::Dispose(); - v8::V8::DisposePlatform(); - node::TearDownOncePerProcess(); - v8impl::EmbeddedPlatform::DeleteInstance(); - return napi_ok; -} - -napi_status NAPI_CDECL -node_api_create_env_options(node_api_env_options* result) { - if (result == nullptr) return napi_invalid_arg; - std::unique_ptr options = - std::make_unique(); - // Transfer ownership of the options object to the caller. - *result = reinterpret_cast(options.release()); - return napi_ok; -} - -napi_status NAPI_CDECL -node_api_env_options_get_args(node_api_env_options options, - node_api_get_args_callback get_args_cb, - void* cb_data) { - if (options == nullptr) return napi_invalid_arg; - if (get_args_cb == nullptr) return napi_invalid_arg; - - v8impl::EmbeddedEnvironmentOptions* env_options = - reinterpret_cast(options); - v8impl::CStringArray args(env_options->args_); - get_args_cb(cb_data, args.argc(), args.argv()); - - return napi_ok; -} - -napi_status NAPI_CDECL -node_api_env_options_get_exec_args(node_api_env_options options, - node_api_get_args_callback get_args_cb, - void* cb_data) { - if (options == nullptr) return napi_invalid_arg; - if (get_args_cb == nullptr) return napi_invalid_arg; - - v8impl::EmbeddedEnvironmentOptions* env_options = - reinterpret_cast(options); - v8impl::CStringArray args(env_options->exec_args_); - get_args_cb(cb_data, args.argc(), args.argv()); - - return napi_ok; -} - -napi_status NAPI_CDECL node_api_env_options_set_flags( - node_api_env_options options, node_api_env_flags flags) { - if (options == nullptr) return napi_invalid_arg; - - v8impl::EmbeddedEnvironmentOptions* env_options = - reinterpret_cast(options); - if (env_options->is_frozen_) return napi_generic_failure; - - env_options->flags_ = flags; - return napi_ok; -} - -napi_status NAPI_CDECL node_api_env_options_set_args( - node_api_env_options options, size_t argc, const char* argv[]) { - if (options == nullptr) return napi_invalid_arg; - if (argv == nullptr) return napi_invalid_arg; - - v8impl::EmbeddedEnvironmentOptions* env_options = - reinterpret_cast(options); - if (env_options->is_frozen_) return napi_generic_failure; - - env_options->args_.assign(argv, argv + argc); - return napi_ok; -} - -napi_status NAPI_CDECL node_api_env_options_set_exec_args( - node_api_env_options options, size_t argc, const char* argv[]) { - if (options == nullptr) return napi_invalid_arg; - if (argv == nullptr) return napi_invalid_arg; - - v8impl::EmbeddedEnvironmentOptions* env_options = - reinterpret_cast(options); - if (env_options->is_frozen_) return napi_generic_failure; - - env_options->exec_args_.assign(argv, argv + argc); - return napi_ok; -} - -napi_status NAPI_CDECL -node_api_env_options_set_preload_callback(node_api_env_options options, - node_api_preload_callback preload_cb, - void* cb_data) { - if (options == nullptr) return napi_invalid_arg; - - v8impl::EmbeddedEnvironmentOptions* env_options = - reinterpret_cast(options); - if (env_options->is_frozen_) return napi_generic_failure; - - if (preload_cb != nullptr) { - env_options->preload_cb_ = node::EmbedderPreloadCallback( - [preload_cb, cb_data](node::Environment* node_env, - v8::Local process, - v8::Local require) { - node_napi_env env = - v8impl::EmbeddedEnvironment::GetOrCreateNodeApiEnv(node_env); - napi_value process_value = v8impl::JsValueFromV8LocalValue(process); - napi_value require_value = v8impl::JsValueFromV8LocalValue(require); - preload_cb(env, process_value, require_value, cb_data); - }); - } else { - env_options->preload_cb_ = {}; - } - - return napi_ok; -} - -napi_status NAPI_CDECL -node_api_env_options_use_snapshot(node_api_env_options options, - const char* snapshot_data, - size_t snapshot_size) { - if (options == nullptr) return napi_invalid_arg; - if (snapshot_data == nullptr) return napi_invalid_arg; - - v8impl::EmbeddedEnvironmentOptions* env_options = - reinterpret_cast(options); - if (env_options->is_frozen_) return napi_generic_failure; - - env_options->snapshot_ = node::EmbedderSnapshotData::FromBlob( - std::string_view(snapshot_data, snapshot_size)); - return napi_ok; -} - -napi_status NAPI_CDECL -node_api_env_options_create_snapshot(node_api_env_options options, - node_api_store_blob_callback store_blob_cb, - void* cb_data, - node_api_snapshot_flags snapshot_flags) { - if (options == nullptr) return napi_invalid_arg; - if (store_blob_cb == nullptr) return napi_invalid_arg; - - v8impl::EmbeddedEnvironmentOptions* env_options = - reinterpret_cast(options); - if (env_options->is_frozen_) return napi_generic_failure; - - env_options->create_snapshot_ = - [store_blob_cb, cb_data](const node::EmbedderSnapshotData* snapshot) { - std::vector blob = snapshot->ToBlob(); - store_blob_cb(cb_data, - reinterpret_cast(blob.data()), - blob.size()); - }; - - if ((snapshot_flags & node_api_snapshot_no_code_cache) != 0) { - env_options->snapshot_config_.flags = static_cast( - static_cast(env_options->snapshot_config_.flags) | - static_cast(node::SnapshotFlags::kWithoutCodeCache)); - } - - return napi_ok; -} - -napi_status NAPI_CDECL -node_api_create_env(node_api_env_options options, - node_api_error_message_handler error_handler, - void* error_handler_data, - const char* main_script, - int32_t api_version, - napi_env* result) { - if (options == nullptr) return napi_invalid_arg; - if (result == nullptr) return napi_invalid_arg; - if (api_version == 0) api_version = NODE_API_DEFAULT_MODULE_API_VERSION; - - std::unique_ptr env_options{ - reinterpret_cast(options)}; - std::vector errors; - - std::unique_ptr env_setup; - node::MultiIsolatePlatform* platform = - v8impl::EmbeddedPlatform::GetInstance()->get_v8_platform(); - node::EnvironmentFlags::Flags flags = - v8impl::GetEnvironmentFlags(env_options->flags_); - if (env_options->snapshot_) { - env_setup = node::CommonEnvironmentSetup::CreateFromSnapshot( - platform, - &errors, - env_options->snapshot_.get(), - env_options->args_, - env_options->exec_args_, - flags); - } else if (env_options->create_snapshot_) { - env_setup = node::CommonEnvironmentSetup::CreateForSnapshotting( - platform, - &errors, - env_options->args_, - env_options->exec_args_, - env_options->snapshot_config_); - } else { - env_setup = node::CommonEnvironmentSetup::Create( - platform, &errors, env_options->args_, env_options->exec_args_, flags); - } - - if (error_handler != nullptr && !errors.empty()) { - v8impl::CStringArray error_arr(errors); - error_handler(error_handler_data, error_arr.c_strs(), error_arr.size()); - } - - if (env_setup == nullptr) { - return napi_generic_failure; - } - - std::string filename = - env_options->args_.size() > 1 ? env_options->args_[1] : ""; - node::CommonEnvironmentSetup* env_setup_ptr = env_setup.get(); - - v8impl::IsolateLocker isolate_locker(env_setup_ptr); - std::unique_ptr embedded_env = - std::make_unique(std::move(env_options), - std::move(env_setup), - env_setup_ptr->context(), - filename, - api_version); - embedded_env->node_env()->AddCleanupHook( - [](void* arg) { static_cast(arg)->Unref(); }, - static_cast(embedded_env.get())); - *result = embedded_env.get(); - - node::Environment* node_env = env_setup_ptr->env(); - - // TODO(vmoroz): If we return an error here, then it is not clear if the - // environment must be deleted after that or not. - v8::MaybeLocal ret = - embedded_env->snapshot() - ? node::LoadEnvironment(node_env, node::StartExecutionCallback{}) - : node::LoadEnvironment(node_env, - std::string_view(main_script), - embedded_env->options().preload_cb_); - - embedded_env.release(); - - if (ret.IsEmpty()) return napi_pending_exception; - - return napi_ok; -} - -napi_status NAPI_CDECL node_api_delete_env(napi_env env, int* exit_code) { - CHECK_ENV(env); - v8impl::EmbeddedEnvironment* embedded_env = - v8impl::EmbeddedEnvironment::FromNapiEnv(env); - - if (embedded_env->IsScopeOpened()) return napi_generic_failure; - - { - v8impl::IsolateLocker isolate_locker(embedded_env->env_setup()); - - int ret = node::SpinEventLoop(embedded_env->node_env()).FromMaybe(1); - if (exit_code != nullptr) *exit_code = ret; - } - - std::unique_ptr env_setup = - embedded_env->ResetEnvSetup(); - - if (embedded_env->create_snapshot()) { - node::EmbedderSnapshotData::Pointer snapshot = env_setup->CreateSnapshot(); - assert(snapshot); - embedded_env->create_snapshot()(snapshot.get()); - } - - node::Stop(embedded_env->node_env()); - - return napi_ok; -} - -napi_status NAPI_CDECL node_api_open_env_scope(napi_env env) { - CHECK_ENV(env); - v8impl::EmbeddedEnvironment* embedded_env = - v8impl::EmbeddedEnvironment::FromNapiEnv(env); - - return embedded_env->OpenScope(); -} - -napi_status NAPI_CDECL node_api_close_env_scope(napi_env env) { - CHECK_ENV(env); - v8impl::EmbeddedEnvironment* embedded_env = - v8impl::EmbeddedEnvironment::FromNapiEnv(env); - - return embedded_env->CloseScope(); -} - -napi_status NAPI_CDECL node_api_run_env(napi_env env) { - CHECK_ENV(env); - v8impl::EmbeddedEnvironment* embedded_env = - v8impl::EmbeddedEnvironment::FromNapiEnv(env); - - if (node::SpinEventLoopWithoutCleanup(embedded_env->node_env()).IsNothing()) { - return napi_closing; - } - - return napi_ok; -} - -napi_status NAPI_CDECL node_api_run_env_while(napi_env env, - node_api_run_predicate predicate, - void* predicate_data, - bool* has_more_work) { - CHECK_ENV(env); - CHECK_ARG(env, predicate); - v8impl::EmbeddedEnvironment* embedded_env = - v8impl::EmbeddedEnvironment::FromNapiEnv(env); - - if (predicate(predicate_data)) { - if (node::SpinEventLoopWithoutCleanup( - embedded_env->node_env(), - [predicate, predicate_data]() { return predicate(predicate_data); }) - .IsNothing()) { - return napi_closing; - } - } - - if (has_more_work != nullptr) { - *has_more_work = uv_loop_alive(embedded_env->node_env()->event_loop()); - } - - return napi_ok; -} - -napi_status NAPI_CDECL node_api_await_promise(napi_env env, - napi_value promise, - napi_value* result, - bool* has_more_work) { - NAPI_PREAMBLE(env); - CHECK_ARG(env, result); - - v8impl::EmbeddedEnvironment* embedded_env = - v8impl::EmbeddedEnvironment::FromNapiEnv(env); - - v8::EscapableHandleScope scope(env->isolate); - - v8::Local promise_value = v8impl::V8LocalValueFromJsValue(promise); - if (promise_value.IsEmpty() || !promise_value->IsPromise()) - return napi_invalid_arg; - v8::Local promise_object = promise_value.As(); - - v8::Local rejected = v8::Boolean::New(env->isolate, false); - v8::Local err_handler = - v8::Function::New( - env->context(), - [](const v8::FunctionCallbackInfo& info) { return; }, - rejected) - .ToLocalChecked(); - - if (promise_object->Catch(env->context(), err_handler).IsEmpty()) - return napi_pending_exception; - - if (node::SpinEventLoopWithoutCleanup( - embedded_env->node_env(), - [&promise_object]() { - return promise_object->State() == - v8::Promise::PromiseState::kPending; - }) - .IsNothing()) - return napi_closing; - - *result = - v8impl::JsValueFromV8LocalValue(scope.Escape(promise_object->Result())); - - if (has_more_work != nullptr) { - *has_more_work = uv_loop_alive(embedded_env->node_env()->event_loop()); - } - - if (promise_object->State() == v8::Promise::PromiseState::kRejected) - return napi_pending_exception; - - return napi_ok; -} diff --git a/src/node_api_embedding.h b/src/node_api_embedding.h deleted file mode 100644 index ff9495f3ef32c4..00000000000000 --- a/src/node_api_embedding.h +++ /dev/null @@ -1,234 +0,0 @@ -#ifndef SRC_NODE_API_EMBEDDING_H_ -#define SRC_NODE_API_EMBEDDING_H_ - -#include "node_api.h" - -EXTERN_C_START - -typedef struct node_api_env_options__* node_api_env_options; - -typedef enum { - node_api_platform_no_flags = 0, - // Enable stdio inheritance, which is disabled by default. - // This flag is also implied by node_api_platform_no_stdio_initialization. - node_api_platform_enable_stdio_inheritance = 1 << 0, - // Disable reading the NODE_OPTIONS environment variable. - node_api_platform_disable_node_options_env = 1 << 1, - // Do not parse CLI options. - node_api_platform_disable_cli_options = 1 << 2, - // Do not initialize ICU. - node_api_platform_no_icu = 1 << 3, - // Do not modify stdio file descriptor or TTY state. - node_api_platform_no_stdio_initialization = 1 << 4, - // Do not register Node.js-specific signal handlers - // and reset other signal handlers to default state. - node_api_platform_no_default_signal_handling = 1 << 5, - // Do not initialize OpenSSL config. - node_api_platform_no_init_openssl = 1 << 8, - // Do not initialize Node.js debugging based on environment variables. - node_api_platform_no_parse_global_debug_variables = 1 << 9, - // Do not adjust OS resource limits for this process. - node_api_platform_no_adjust_resource_limits = 1 << 10, - // Do not map code segments into large pages for this process. - node_api_platform_no_use_large_pages = 1 << 11, - // Skip printing output for --help, --version, --v8-options. - node_api_platform_no_print_help_or_version_output = 1 << 12, - // Initialize the process for predictable snapshot generation. - node_api_platform_generate_predictable_snapshot = 1 << 14, -} node_api_platform_flags; - -typedef enum : uint64_t { - node_api_env_no_flags = 0, - // Use the default behaviour for Node.js instances. - node_api_env_default_flags = 1 << 0, - // Controls whether this Environment is allowed to affect per-process state - // (e.g. cwd, process title, uid, etc.). - // This is set when using node_api_env_default_flags. - node_api_env_owns_process_state = 1 << 1, - // Set if this Environment instance is associated with the global inspector - // handling code (i.e. listening on SIGUSR1). - // This is set when using node_api_env_default_flags. - node_api_env_owns_inspector = 1 << 2, - // Set if Node.js should not run its own esm loader. This is needed by some - // embedders, because it's possible for the Node.js esm loader to conflict - // with another one in an embedder environment, e.g. Blink's in Chromium. - node_api_env_no_register_esm_loader = 1 << 3, - // Set this flag to make Node.js track "raw" file descriptors, i.e. managed - // by fs.open() and fs.close(), and close them during node_api_delete_env(). - node_api_env_track_unmanaged_fds = 1 << 4, - // Set this flag to force hiding console windows when spawning child - // processes. This is usually used when embedding Node.js in GUI programs on - // Windows. - node_api_env_hide_console_windows = 1 << 5, - // Set this flag to disable loading native addons via `process.dlopen`. - // This environment flag is especially important for worker threads - // so that a worker thread can't load a native addon even if `execArgv` - // is overwritten and `--no-addons` is not specified but was specified - // for this Environment instance. - node_api_env_no_native_addons = 1 << 6, - // Set this flag to disable searching modules from global paths like - // $HOME/.node_modules and $NODE_PATH. This is used by standalone apps that - // do not expect to have their behaviors changed because of globally - // installed modules. - node_api_env_no_global_search_paths = 1 << 7, - // Do not export browser globals like setTimeout, console, etc. - node_api_env_no_browser_globals = 1 << 8, - // Controls whether or not the Environment should call V8Inspector::create(). - // This control is needed by embedders who may not want to initialize the V8 - // inspector in situations where one has already been created, - // e.g. Blink's in Chromium. - node_api_env_no_create_inspector = 1 << 9, - // Controls whether or not the InspectorAgent for this Environment should - // call StartDebugSignalHandler. This control is needed by embedders who may - // not want to allow other processes to start the V8 inspector. - node_api_env_no_start_debug_signal_handler = 1 << 10, - // Controls whether the InspectorAgent created for this Environment waits for - // Inspector frontend events during the Environment creation. It's used to - // call node::Stop(env) on a Worker thread that is waiting for the events. - node_api_env_no_wait_for_inspector_frontend = 1 << 11 -} node_api_env_flags; - -typedef enum { - node_api_snapshot_no_flags = 0, - // Whether code cache should be generated as part of the snapshot. - // Code cache reduces the time spent on compiling functions included - // in the snapshot at the expense of a bigger snapshot size and - // potentially breaking portability of the snapshot. - node_api_snapshot_no_code_cache = 1 << 0, -} node_api_snapshot_flags; - -typedef void(NAPI_CDECL* node_api_error_message_handler)(void* handler_data, - const char* messages[], - size_t size); - -typedef void(NAPI_CDECL* node_api_get_args_callback)(void* cb_data, - int32_t argc, - const char* size[]); - -typedef void(NAPI_CDECL* node_api_store_blob_callback)(void* cb_data, - const uint8_t* blob, - size_t size); - -typedef void(NAPI_CDECL* node_api_preload_callback)(napi_env env, - napi_value process, - napi_value require, - void* cb_data); - -typedef bool(NAPI_CDECL* node_api_run_predicate)(void* predicate_data); - -NAPI_EXTERN napi_status NAPI_CDECL -node_api_initialize_platform(int32_t argc, - char* argv[], - node_api_platform_flags flags, - node_api_error_message_handler error_handler, - void* error_handler_data, - bool* early_return, - int32_t* exit_code); - -NAPI_EXTERN napi_status NAPI_CDECL node_api_dispose_platform(); - -NAPI_EXTERN napi_status NAPI_CDECL -node_api_create_env_options(node_api_env_options* result); - -NAPI_EXTERN napi_status NAPI_CDECL -node_api_env_options_get_args(node_api_env_options options, - node_api_get_args_callback get_args_cb, - void* cb_data); - -NAPI_EXTERN napi_status NAPI_CDECL -node_api_env_options_get_exec_args(node_api_env_options options, - node_api_get_args_callback get_args_cb, - void* cb_data); - -NAPI_EXTERN napi_status NAPI_CDECL node_api_env_options_set_flags( - node_api_env_options options, node_api_env_flags flags); - -NAPI_EXTERN napi_status NAPI_CDECL node_api_env_options_set_args( - node_api_env_options options, size_t argc, const char* argv[]); - -NAPI_EXTERN napi_status NAPI_CDECL node_api_env_options_set_exec_args( - node_api_env_options options, size_t argc, const char* argv[]); - -NAPI_EXTERN napi_status NAPI_CDECL -node_api_env_options_set_preload_callback(node_api_env_options options, - node_api_preload_callback preload_cb, - void* cb_data); - -NAPI_EXTERN napi_status NAPI_CDECL -node_api_env_options_use_snapshot(node_api_env_options options, - const char* snapshot_data, - size_t snapshot_size); - -NAPI_EXTERN napi_status NAPI_CDECL -node_api_env_options_create_snapshot(node_api_env_options options, - node_api_store_blob_callback store_blob_cb, - void* cb_data, - node_api_snapshot_flags snapshot_flags); - -NAPI_EXTERN napi_status NAPI_CDECL -node_api_create_env(node_api_env_options options, - node_api_error_message_handler error_handler, - void* error_handler_data, - const char* main_script, - int32_t api_version, - napi_env* result); - -NAPI_EXTERN napi_status NAPI_CDECL node_api_delete_env(napi_env env, - int* exit_code); - -NAPI_EXTERN napi_status NAPI_CDECL node_api_open_env_scope(napi_env env); - -NAPI_EXTERN napi_status NAPI_CDECL node_api_close_env_scope(napi_env env); - -NAPI_EXTERN napi_status NAPI_CDECL node_api_run_env(napi_env env); - -NAPI_EXTERN napi_status NAPI_CDECL -node_api_run_env_while(napi_env env, - node_api_run_predicate predicate, - void* predicate_data, - bool* has_more_work); - -NAPI_EXTERN napi_status NAPI_CDECL node_api_await_promise(napi_env env, - napi_value promise, - napi_value* result, - bool* has_more_work); - -EXTERN_C_END - -#ifdef __cplusplus - -inline node_api_platform_flags operator|(node_api_platform_flags lhs, - node_api_platform_flags rhs) { - return static_cast(static_cast(lhs) | - static_cast(rhs)); -} - -inline node_api_env_flags operator|(node_api_env_flags lhs, - node_api_env_flags rhs) { - return static_cast(static_cast(lhs) | - static_cast(rhs)); -} - -inline node_api_snapshot_flags operator|(node_api_snapshot_flags lhs, - node_api_snapshot_flags rhs) { - return static_cast(static_cast(lhs) | - static_cast(rhs)); -} - -#endif - -#endif // SRC_NODE_API_EMBEDDING_H_ - -// TODO: (vmoroz) Remove the main_script parameter. -// TODO: (vmoroz) Add startup callback with process and require parameters. -// TODO: (vmoroz) Add ABI-safe way to access internal module functionality. -// TODO: (vmoroz) Allow setting the global inspector for a specific environment. -// TODO: (vmoroz) Start workers from C++. -// TODO: (vmoroz) Worker to inherit parent inspector. -// TODO: (vmoroz) Cancel pending tasks on delete env. -// TODO: (vmoroz) Can we init plat again if it retuns early? -// TODO: (vmoroz) Add simpler threading model - without open/close scope. -// TODO: (vmoroz) Simplify API use for simple default cases. -// TODO: (vmoroz) Add a way to add embedded modules. -// TODO: (vmoroz) Check how to pass the V8 thread pool size. -// TODO: (vmoroz) Provide better error handling for args. diff --git a/src/node_embedding_api.cc b/src/node_embedding_api.cc new file mode 100644 index 00000000000000..5cbe1027d43f52 --- /dev/null +++ b/src/node_embedding_api.cc @@ -0,0 +1,1052 @@ +#define NAPI_EXPERIMENTAL +#include "node_embedding_api.h" + +#include "env-inl.h" +#include "js_native_api_v8.h" +#include "node_api_internals.h" +#include "util-inl.h" + +#include + +// Use macros to handle errors since they can record the failing argument name +// or expression and their location in the source code. + +#define EMBEDDED_PLATFORM(platform) \ + ((platform) == nullptr) \ + ? v8impl::EmbeddedErrorHandling::HandleError( \ + "Argument must not be null: " #platform, \ + __FILE__, \ + __LINE__, \ + 1, \ + napi_invalid_arg) \ + : reinterpret_cast(platform) + +#define EMBEDDED_RUNTIME(runtime) \ + (runtime) == nullptr ? v8impl::EmbeddedErrorHandling::HandleError( \ + "Argument must not be null: " #runtime, \ + __FILE__, \ + __LINE__, \ + 1, \ + napi_invalid_arg) \ + : reinterpret_cast(runtime) + +#define ARG_NOT_NULL(arg) \ + do { \ + if ((arg) == nullptr) { \ + return v8impl::EmbeddedErrorHandling::HandleError( \ + "Argument must not be null: " #arg, \ + __FILE__, \ + __LINE__, \ + 1, \ + napi_invalid_arg); \ + } \ + } while (false) + +#define ASSERT(expr) \ + do { \ + if (!(expr)) { \ + return v8impl::EmbeddedErrorHandling::HandleError( \ + "Expression returned false: " #expr, \ + __FILE__, \ + __LINE__, \ + 1, \ + napi_generic_failure); \ + } \ + } while (false) + +namespace node { + +// Declare functions implemented in embed_helpers.cc +v8::Maybe SpinEventLoopWithoutCleanup(Environment* env); +v8::Maybe SpinEventLoopWithoutCleanup( + Environment* env, + uv_run_mode run_mode, + const std::function& shouldContinue); + +} // end of namespace node + +namespace v8impl { +namespace { + +// A helper class to convert std::vector to an array of C strings. +// If the number of strings is less than kInplaceBufferSize, the strings are +// stored in the inplace_buffer_ array. Otherwise, the strings are stored in the +// allocated_buffer_ array. +// Ideally the class must be allocated on the stack. +// In any case it must not outlive the passed vector since it keeps only the +// string pointers returned by std::string::c_str() method. +class CStringArray { + static constexpr size_t kInplaceBufferSize = 32; + + public: + explicit CStringArray(const std::vector& strings) noexcept + : size_(strings.size()) { + if (size_ <= inplace_buffer_.size()) { + c_strs_ = inplace_buffer_.data(); + } else { + allocated_buffer_ = std::make_unique(size_); + c_strs_ = allocated_buffer_.get(); + } + for (size_t i = 0; i < size_; ++i) { + c_strs_[i] = strings[i].c_str(); + } + } + + CStringArray(const CStringArray&) = delete; + CStringArray& operator=(const CStringArray&) = delete; + + const char** c_strs() const { return c_strs_; } + size_t size() const { return size_; } + + const char** argv() const { return c_strs_; } + int32_t argc() const { return static_cast(size_); } + + private: + const char** c_strs_{}; + size_t size_{}; + std::array inplace_buffer_; + std::unique_ptr allocated_buffer_; +}; + +class EmbeddedErrorHandling { + public: + static napi_status SetErrorHandler(node_embedding_error_handler error_handler, + void* error_handler_data); + + static napi_status HandleError(const std::string& message, + int32_t exit_code, + napi_status status); + + static napi_status HandleError(const std::vector& messages, + int32_t exit_code, + napi_status status); + + static napi_status HandleError(const char* message, + const char* filename, + int32_t line, + int32_t exit_code, + napi_status status); + + static std::string FormatString(const char* format, ...); + + static napi_status DefaultErrorHandler(void* handler_data, + const char* messages[], + size_t messages_size, + int32_t exit_code, + napi_status status); + + static node_embedding_error_handler error_handler() { + return error_handler_ ? error_handler_ : DefaultErrorHandler; + } + + private: + static node_embedding_error_handler error_handler_; + static void* error_handler_data_; +}; + +node_embedding_error_handler EmbeddedErrorHandling::error_handler_{}; +void* EmbeddedErrorHandling::error_handler_data_{}; + +class EmbeddedPlatform { + public: + explicit EmbeddedPlatform(int32_t api_version) noexcept + : api_version_(api_version) {} + + EmbeddedPlatform(const EmbeddedPlatform&) = delete; + EmbeddedPlatform& operator=(const EmbeddedPlatform&) = delete; + + napi_status DeleteMe(); + + napi_status IsInitialized(bool* result); + + napi_status SetFlags(node_embedding_platform_flags flags); + + napi_status SetArgs(int32_t argc, char* argv[]); + + napi_status Initialize(bool* early_return); + + napi_status GetArgs(node_embedding_get_args_callback get_args, + void* get_args_data); + + napi_status GetExecArgs(node_embedding_get_args_callback get_args, + void* get_args_data); + + napi_status CreateRuntime(node_embedding_runtime* result); + + node::InitializationResult* init_result() { return init_result_.get(); } + + node::MultiIsolatePlatform* get_v8_platform() { return v8_platform_.get(); } + + private: + static node::ProcessInitializationFlags::Flags GetProcessInitializationFlags( + node_embedding_platform_flags flags); + + private: + int32_t api_version_{1}; + bool is_initialized_{false}; + bool v8_is_initialized_{false}; + bool v8_is_uninitialized_{false}; + node_embedding_platform_flags flags_; + std::vector args_; + struct { + bool flags : 1; + bool args : 1; + } optional_bits_{}; + + std::shared_ptr init_result_; + std::unique_ptr v8_platform_; + + static node_embedding_error_handler custom_error_handler_; + static void* custom_error_handler_data_; +}; + +node_embedding_error_handler EmbeddedPlatform::custom_error_handler_{}; +void* EmbeddedPlatform::custom_error_handler_data_{}; + +struct IsolateLocker { + IsolateLocker(node::CommonEnvironmentSetup* env_setup) + : v8_locker_(env_setup->isolate()), + isolate_scope_(env_setup->isolate()), + handle_scope_(env_setup->isolate()), + context_scope_(env_setup->context()) {} + + bool IsLocked() const { + return v8::Locker::IsLocked(v8::Isolate::GetCurrent()); + } + + void IncrementLockCount() { ++lock_count_; } + + bool DecrementLockCount() { return --lock_count_ == 0; } + + private: + int32_t lock_count_ = 1; + v8::Locker v8_locker_; + v8::Isolate::Scope isolate_scope_; + v8::HandleScope handle_scope_; + v8::Context::Scope context_scope_; +}; + +class EmbeddedRuntime { + public: + explicit EmbeddedRuntime(EmbeddedPlatform* platform); + + napi_status DeleteMe(); + + napi_status IsInitialized(bool* result); + + napi_status SetFlags(node_embedding_runtime_flags flags); + + napi_status SetArgs(int32_t argc, const char* argv[]); + + napi_status SetExecArgs(int32_t argc, const char* argv[]); + + napi_status SetPreloadCallback(node_embedding_preload_callback preload_cb, + void* preload_cb_data); + + napi_status SetSnapshotBlob(const uint8_t* snapshot, size_t size); + + napi_status OnCreateSnapshotBlob( + node_embedding_store_blob_callback store_blob_cb, + void* store_blob_cb_data, + node_embedding_snapshot_flags snapshot_flags); + + napi_status Initialize(const char* main_script); + + napi_status RunEventLoop(); + + napi_status RunEventLoopWhile(node_embedding_event_loop_predicate predicate, + void* predicate_data, + node_embedding_event_loop_run_mode run_mode, + bool* has_more_work); + + napi_status AwaitPromise(napi_value promise, + napi_value* result, + bool* has_more_work); + + napi_status SetNodeApiVersion(int32_t node_api_version); + + napi_status GetNodeApiEnv(napi_env* env); + + napi_status OpenScope(); + + napi_status CloseScope(); + + bool IsScopeOpened() const; + + static node_napi_env GetOrCreateNodeApiEnv( + node::Environment* node_env, + const std::string& module_filename = "", + int32_t node_api_version = 0); + + private: + static node::EnvironmentFlags::Flags GetEnvironmentFlags( + node_embedding_runtime_flags flags); + + private: + EmbeddedPlatform* platform_; + bool is_initialized_{false}; + node_embedding_runtime_flags flags_{node_embedding_runtime_default_flags}; + std::vector args_; + std::vector exec_args_; + node::EmbedderPreloadCallback preload_cb_{}; + node::EmbedderSnapshotData::Pointer snapshot_; + std::function create_snapshot_; + node::SnapshotConfig snapshot_config_{}; + int32_t node_api_version_{0}; + node_napi_env node_api_env_{}; + + struct { + bool flags : 1; + bool args : 1; + bool exec_args : 1; + } optional_bits_{}; + + std::unique_ptr env_setup_; + std::optional isolate_locker_; + + static std::mutex shared_mutex_; + static std::unordered_map + node_env_to_node_api_env_; +}; + +// TODO: (vmoroz) remove from the static initialization on module load. +std::mutex EmbeddedRuntime::shared_mutex_{}; +std::unordered_map + EmbeddedRuntime::node_env_to_node_api_env_{}; + +//----------------------------------------------------------------------------- +// EmbeddedErrorHandling implementation. +//----------------------------------------------------------------------------- + +napi_status EmbeddedErrorHandling::SetErrorHandler( + node_embedding_error_handler error_handler, void* error_handler_data) { + error_handler_ = error_handler; + error_handler_data_ = error_handler_data; + return napi_ok; +} + +napi_status EmbeddedErrorHandling::HandleError(const std::string& message, + int32_t exit_code, + napi_status status) { + const char* message_c_str = message.c_str(); + return error_handler()( + error_handler_data_, &message_c_str, 1, exit_code, status); +} + +napi_status EmbeddedErrorHandling::HandleError( + const std::vector& messages, + int32_t exit_code, + napi_status status) { + CStringArray message_arr(messages); + return error_handler()(error_handler_data_, + message_arr.c_strs(), + message_arr.size(), + exit_code, + status); +} + +napi_status EmbeddedErrorHandling::HandleError(const char* message, + const char* filename, + int32_t line, + int32_t exit_code, + napi_status status) { + return HandleError( + FormatString("Error: %s at %s:%d", message, filename, line), + exit_code, + status); +} + +napi_status EmbeddedErrorHandling::DefaultErrorHandler(void* /*handler_data*/, + const char* messages[], + size_t messages_size, + int32_t exit_code, + napi_status status) { + if (exit_code != 0) { + for (size_t i = 0; i < messages_size; ++i) { + fprintf(stderr, "%s\n", messages[i]); + } + fflush(stderr); + exit(exit_code); + } else { + for (size_t i = 0; i < messages_size; ++i) { + fprintf(stdout, "%s\n", messages[i]); + } + fflush(stdout); + } + return status; +} + +std::string EmbeddedErrorHandling::FormatString(const char* format, ...) { + va_list args1; + va_start(args1, format); + va_list args2; + va_copy(args2, args1); // Required for some compilers like GCC. + std::string result(std::vsnprintf(nullptr, 0, format, args1), '\0'); + va_end(args1); + std::vsnprintf(&result[0], result.size() + 1, format, args2); + va_end(args2); + return result; +} + +//----------------------------------------------------------------------------- +// EmbeddedPlatform implementation. +//----------------------------------------------------------------------------- + +napi_status EmbeddedPlatform::DeleteMe() { + if (v8_is_initialized_ && !v8_is_uninitialized_) { + v8::V8::Dispose(); + v8::V8::DisposePlatform(); + node::TearDownOncePerProcess(); + v8_is_uninitialized_ = false; + } + + delete this; + return napi_ok; +} + +napi_status EmbeddedPlatform::IsInitialized(bool* result) { + ARG_NOT_NULL(result); + *result = is_initialized_; + return napi_ok; +} + +napi_status EmbeddedPlatform::SetFlags(node_embedding_platform_flags flags) { + ASSERT(!is_initialized_); + flags_ = flags; + optional_bits_.flags = true; + return napi_ok; +} + +napi_status EmbeddedPlatform::SetArgs(int32_t argc, char* argv[]) { + ARG_NOT_NULL(argv); + ASSERT(!is_initialized_); + args_.assign(argv, argv + argc); + optional_bits_.args = true; + return napi_ok; +} + +napi_status EmbeddedPlatform::Initialize(bool* early_return) { + ASSERT(!is_initialized_); + + is_initialized_ = true; + + // TODO: (vmoroz) default initialize args_. + + if (!optional_bits_.flags) { + flags_ = node_embedding_platform_no_flags; + } + + init_result_ = node::InitializeOncePerProcess( + args_, GetProcessInitializationFlags(flags_)); + + if (init_result_->exit_code() != 0 || !init_result_->errors().empty()) { + return EmbeddedErrorHandling::HandleError(init_result_->errors(), + init_result_->exit_code(), + napi_generic_failure); + } + + if (early_return != nullptr) { + *early_return = init_result_->early_return(); + } else if (init_result_->early_return()) { + exit(init_result_->exit_code()); + } + + if (init_result_->early_return()) { + return init_result_->exit_code() == 0 ? napi_ok : napi_generic_failure; + } + + int32_t thread_pool_size = + static_cast(node::per_process::cli_options->v8_thread_pool_size); + v8_platform_ = node::MultiIsolatePlatform::Create(thread_pool_size); + v8::V8::InitializePlatform(v8_platform_.get()); + v8::V8::Initialize(); + + v8_is_initialized_ = true; + + return napi_ok; +} + +napi_status EmbeddedPlatform::GetArgs( + node_embedding_get_args_callback get_args_cb, void* get_args_cb_data) { + ARG_NOT_NULL(get_args_cb); + ASSERT(is_initialized_); + + v8impl::CStringArray args(init_result_->args()); + get_args_cb(get_args_cb_data, args.argc(), args.argv()); + + return napi_ok; +} + +napi_status EmbeddedPlatform::GetExecArgs( + node_embedding_get_args_callback get_args_cb, void* get_args_cb_data) { + ARG_NOT_NULL(get_args_cb); + ASSERT(is_initialized_); + + v8impl::CStringArray args(init_result_->exec_args()); + get_args_cb(get_args_cb_data, args.argc(), args.argv()); + + return napi_ok; +} + +napi_status EmbeddedPlatform::CreateRuntime(node_embedding_runtime* result) { + ARG_NOT_NULL(result); + ASSERT(is_initialized_); + ASSERT(v8_is_initialized_); + + std::unique_ptr runtime = + std::make_unique(this); + + *result = reinterpret_cast(runtime.release()); + + return napi_ok; +} + +node::ProcessInitializationFlags::Flags +EmbeddedPlatform::GetProcessInitializationFlags( + node_embedding_platform_flags flags) { + uint32_t result = node::ProcessInitializationFlags::kNoFlags; + if ((flags & node_embedding_platform_enable_stdio_inheritance) != 0) { + result |= node::ProcessInitializationFlags::kEnableStdioInheritance; + } + if ((flags & node_embedding_platform_disable_node_options_env) != 0) { + result |= node::ProcessInitializationFlags::kDisableNodeOptionsEnv; + } + if ((flags & node_embedding_platform_disable_cli_options) != 0) { + result |= node::ProcessInitializationFlags::kDisableCLIOptions; + } + if ((flags & node_embedding_platform_no_icu) != 0) { + result |= node::ProcessInitializationFlags::kNoICU; + } + if ((flags & node_embedding_platform_no_stdio_initialization) != 0) { + result |= node::ProcessInitializationFlags::kNoStdioInitialization; + } + if ((flags & node_embedding_platform_no_default_signal_handling) != 0) { + result |= node::ProcessInitializationFlags::kNoDefaultSignalHandling; + } + result |= node::ProcessInitializationFlags::kNoInitializeV8; + result |= node::ProcessInitializationFlags::kNoInitializeNodeV8Platform; + if ((flags & node_embedding_platform_no_init_openssl) != 0) { + result |= node::ProcessInitializationFlags::kNoInitOpenSSL; + } + if ((flags & node_embedding_platform_no_parse_global_debug_variables) != 0) { + result |= node::ProcessInitializationFlags::kNoParseGlobalDebugVariables; + } + if ((flags & node_embedding_platform_no_adjust_resource_limits) != 0) { + result |= node::ProcessInitializationFlags::kNoAdjustResourceLimits; + } + if ((flags & node_embedding_platform_no_use_large_pages) != 0) { + result |= node::ProcessInitializationFlags::kNoUseLargePages; + } + if ((flags & node_embedding_platform_no_print_help_or_version_output) != 0) { + result |= node::ProcessInitializationFlags::kNoPrintHelpOrVersionOutput; + } + if ((flags & node_embedding_platform_generate_predictable_snapshot) != 0) { + result |= node::ProcessInitializationFlags::kGeneratePredictableSnapshot; + } + return static_cast(result); +} + +//----------------------------------------------------------------------------- +// EmbeddedRuntime implementation. +//----------------------------------------------------------------------------- + +EmbeddedRuntime::EmbeddedRuntime(EmbeddedPlatform* platform) + : platform_(platform) {} + +napi_status EmbeddedRuntime::DeleteMe() { + ASSERT(!IsScopeOpened()); + + { + v8impl::IsolateLocker isolate_locker(env_setup_.get()); + + int32_t ret = node::SpinEventLoop(env_setup_->env()).FromMaybe(1); + if (ret != 0) { + return EmbeddedErrorHandling::HandleError( + "Failed while closing the runtime", ret, napi_generic_failure); + } + } + + std::unique_ptr env_setup = + std::move(env_setup_); + + if (create_snapshot_) { + node::EmbedderSnapshotData::Pointer snapshot = env_setup->CreateSnapshot(); + ASSERT(snapshot); + // TODO: (vmoroz) handle error conditions. + create_snapshot_(snapshot.get()); + } + + node::Stop(env_setup->env()); + + return napi_ok; +} + +napi_status EmbeddedRuntime::IsInitialized(bool* result) { + ARG_NOT_NULL(result); + *result = is_initialized_; + return napi_ok; +} + +napi_status EmbeddedRuntime::SetFlags(node_embedding_runtime_flags flags) { + ASSERT(!is_initialized_); + flags_ = flags; + optional_bits_.flags = true; + return napi_ok; +} + +napi_status EmbeddedRuntime::SetArgs(int32_t argc, const char* argv[]) { + ARG_NOT_NULL(argv); + ASSERT(!is_initialized_); + args_.assign(argv, argv + argc); + optional_bits_.args = true; + return napi_ok; +} + +napi_status EmbeddedRuntime::SetExecArgs(int32_t argc, const char* argv[]) { + ARG_NOT_NULL(argv); + ASSERT(!is_initialized_); + exec_args_.assign(argv, argv + argc); + optional_bits_.exec_args = true; + return napi_ok; +} + +napi_status EmbeddedRuntime::SetPreloadCallback( + node_embedding_preload_callback preload_cb, void* preload_cb_data) { + ASSERT(!is_initialized_); + + // TODO: (vmoroz) use CallIntoModule to handle errors. + if (preload_cb != nullptr) { + preload_cb_ = node::EmbedderPreloadCallback( + [preload_cb, preload_cb_data](node::Environment* node_env, + v8::Local process, + v8::Local require) { + // TODO: (vmoroz) propagate node_api_version from the parent env. + node_napi_env env = GetOrCreateNodeApiEnv(node_env); + napi_value process_value = v8impl::JsValueFromV8LocalValue(process); + napi_value require_value = v8impl::JsValueFromV8LocalValue(require); + preload_cb(preload_cb_data, env, process_value, require_value); + }); + } else { + preload_cb_ = {}; + } + + return napi_ok; +} + +napi_status EmbeddedRuntime::SetSnapshotBlob(const uint8_t* snapshot, + size_t size) { + ARG_NOT_NULL(snapshot); + ASSERT(!is_initialized_); + + snapshot_ = node::EmbedderSnapshotData::FromBlob( + std::string_view(reinterpret_cast(snapshot), size)); + return napi_ok; +} + +napi_status EmbeddedRuntime::OnCreateSnapshotBlob( + node_embedding_store_blob_callback store_blob_cb, + void* store_blob_cb_data, + node_embedding_snapshot_flags snapshot_flags) { + ARG_NOT_NULL(store_blob_cb); + ASSERT(!is_initialized_); + + create_snapshot_ = [store_blob_cb, store_blob_cb_data]( + const node::EmbedderSnapshotData* snapshot) { + std::vector blob = snapshot->ToBlob(); + store_blob_cb(store_blob_cb_data, + reinterpret_cast(blob.data()), + blob.size()); + }; + + if ((snapshot_flags & node_embedding_snapshot_no_code_cache) != 0) { + snapshot_config_.flags = static_cast( + static_cast(snapshot_config_.flags) | + static_cast(node::SnapshotFlags::kWithoutCodeCache)); + } + + return napi_ok; +} + +// TODO: (vmoroz) avoid passing the main_script this way. +napi_status EmbeddedRuntime::Initialize(const char* main_script) { + ASSERT(!is_initialized_); + + is_initialized_ = true; + + node::MultiIsolatePlatform* platform = platform_->get_v8_platform(); + + node::EnvironmentFlags::Flags flags = GetEnvironmentFlags( + optional_bits_.flags ? flags_ : node_embedding_runtime_default_flags); + + const std::vector& args = + optional_bits_.args ? args_ : platform_->init_result()->args(); + + const std::vector& exec_args = + optional_bits_.exec_args ? exec_args_ + : platform_->init_result()->exec_args(); + + std::vector errors; + if (snapshot_) { + env_setup_ = node::CommonEnvironmentSetup::CreateFromSnapshot( + platform, &errors, snapshot_.get(), args, exec_args, flags); + } else if (create_snapshot_) { + env_setup_ = node::CommonEnvironmentSetup::CreateForSnapshotting( + platform, &errors, args, exec_args, snapshot_config_); + } else { + env_setup_ = node::CommonEnvironmentSetup::Create( + platform, &errors, args, exec_args, flags); + } + + if (env_setup_ == nullptr || !errors.empty()) { + return EmbeddedErrorHandling::HandleError(errors, 1, napi_generic_failure); + } + + v8impl::IsolateLocker isolate_locker(env_setup_.get()); + + std::string filename = args_.size() > 1 ? args_[1] : ""; + node_api_env_ = + GetOrCreateNodeApiEnv(env_setup_->env(), filename, node_api_version_); + + node::Environment* node_env = env_setup_->env(); + + v8::MaybeLocal ret = + snapshot_ + ? node::LoadEnvironment(node_env, node::StartExecutionCallback{}) + : node::LoadEnvironment( + node_env, std::string_view(main_script), preload_cb_); + + if (ret.IsEmpty()) return napi_pending_exception; + + return napi_ok; +} + +napi_status EmbeddedRuntime::RunEventLoop() { + if (node::SpinEventLoopWithoutCleanup(env_setup_->env()).IsNothing()) { + return napi_closing; + } + + return napi_ok; +} + +// TODO: (vmoroz) add support for is_thread_blocking. +napi_status EmbeddedRuntime::RunEventLoopWhile( + node_embedding_event_loop_predicate predicate, + void* predicate_data, + node_embedding_event_loop_run_mode run_mode, + bool* has_more_work) { + ARG_NOT_NULL(predicate); + + if (predicate(predicate_data, + uv_loop_alive(env_setup_->env()->event_loop()))) { + if (node::SpinEventLoopWithoutCleanup( + env_setup_->env(), + static_cast(run_mode), + [predicate, predicate_data](bool has_work) { + return predicate(predicate_data, has_work); + }) + .IsNothing()) { + return napi_closing; + } + } + + if (has_more_work != nullptr) { + *has_more_work = uv_loop_alive(env_setup_->env()->event_loop()); + } + + return napi_ok; +} + +napi_status EmbeddedRuntime::AwaitPromise(napi_value promise, + napi_value* result, + bool* has_more_work) { + NAPI_PREAMBLE(node_api_env_); + CHECK_ARG(node_api_env_, result); + + v8::EscapableHandleScope scope(node_api_env_->isolate); + + v8::Local promise_value = v8impl::V8LocalValueFromJsValue(promise); + if (promise_value.IsEmpty() || !promise_value->IsPromise()) + return napi_invalid_arg; + v8::Local promise_object = promise_value.As(); + + v8::Local rejected = + v8::Boolean::New(node_api_env_->isolate, false); + v8::Local err_handler = + v8::Function::New( + node_api_env_->context(), + [](const v8::FunctionCallbackInfo& info) { return; }, + rejected) + .ToLocalChecked(); + + if (promise_object->Catch(node_api_env_->context(), err_handler).IsEmpty()) + return napi_pending_exception; + + if (node::SpinEventLoopWithoutCleanup( + env_setup_->env(), + UV_RUN_ONCE, + [&promise_object](bool /*has_work*/) { + return promise_object->State() == + v8::Promise::PromiseState::kPending; + }) + .IsNothing()) + return napi_closing; + + *result = + v8impl::JsValueFromV8LocalValue(scope.Escape(promise_object->Result())); + + if (has_more_work != nullptr) { + *has_more_work = uv_loop_alive(env_setup_->env()->event_loop()); + } + + if (promise_object->State() == v8::Promise::PromiseState::kRejected) + return napi_pending_exception; + + return napi_ok; +} + +napi_status EmbeddedRuntime::SetNodeApiVersion(int32_t node_api_version) { + ASSERT(!is_initialized_); + node_api_version_ = node_api_version; + return napi_ok; +} + +napi_status EmbeddedRuntime::GetNodeApiEnv(napi_env* env) { + ARG_NOT_NULL(env); + *env = node_api_env_; + return napi_ok; +} + +napi_status EmbeddedRuntime::OpenScope() { + if (isolate_locker_.has_value()) { + ASSERT(isolate_locker_->IsLocked()); + isolate_locker_->IncrementLockCount(); + } else { + isolate_locker_.emplace(env_setup_.get()); + } + return napi_ok; +} + +napi_status EmbeddedRuntime::CloseScope() { + ASSERT(isolate_locker_.has_value()); + ASSERT(isolate_locker_->IsLocked()); + if (isolate_locker_->DecrementLockCount()) isolate_locker_.reset(); + return napi_ok; +} + +bool EmbeddedRuntime::IsScopeOpened() const { + return isolate_locker_.has_value(); +} + +node_napi_env EmbeddedRuntime::GetOrCreateNodeApiEnv( + node::Environment* node_env, + const std::string& module_filename, + int32_t node_api_version) { + std::scoped_lock lock(shared_mutex_); + auto it = node_env_to_node_api_env_.find(node_env); + if (it != node_env_to_node_api_env_.end()) return it->second; + node_napi_env env = new node_napi_env__( + node_env->context(), module_filename, node_api_version); + node_env->AddCleanupHook( + [](void* arg) { static_cast(arg)->Unref(); }, env); + node_env_to_node_api_env_.try_emplace(node_env, env); + return env; +} + +node::EnvironmentFlags::Flags EmbeddedRuntime::GetEnvironmentFlags( + node_embedding_runtime_flags flags) { + uint64_t result = node::EnvironmentFlags::kNoFlags; + if ((flags & node_embedding_runtime_default_flags) != 0) { + result |= node::EnvironmentFlags::kDefaultFlags; + } + if ((flags & node_embedding_runtime_owns_process_state) != 0) { + result |= node::EnvironmentFlags::kOwnsProcessState; + } + if ((flags & node_embedding_runtime_owns_inspector) != 0) { + result |= node::EnvironmentFlags::kOwnsInspector; + } + if ((flags & node_embedding_runtime_no_register_esm_loader) != 0) { + result |= node::EnvironmentFlags::kNoRegisterESMLoader; + } + if ((flags & node_embedding_runtime_track_unmanaged_fds) != 0) { + result |= node::EnvironmentFlags::kTrackUnmanagedFds; + } + if ((flags & node_embedding_runtime_hide_console_windows) != 0) { + result |= node::EnvironmentFlags::kHideConsoleWindows; + } + if ((flags & node_embedding_runtime_no_native_addons) != 0) { + result |= node::EnvironmentFlags::kNoNativeAddons; + } + if ((flags & node_embedding_runtime_no_global_search_paths) != 0) { + result |= node::EnvironmentFlags::kNoGlobalSearchPaths; + } + if ((flags & node_embedding_runtime_no_browser_globals) != 0) { + result |= node::EnvironmentFlags::kNoBrowserGlobals; + } + if ((flags & node_embedding_runtime_no_create_inspector) != 0) { + result |= node::EnvironmentFlags::kNoCreateInspector; + } + if ((flags & node_embedding_runtime_no_start_debug_signal_handler) != 0) { + result |= node::EnvironmentFlags::kNoStartDebugSignalHandler; + } + if ((flags & node_embedding_runtime_no_wait_for_inspector_frontend) != 0) { + result |= node::EnvironmentFlags::kNoWaitForInspectorFrontend; + } + return static_cast(result); +} + +} // end of anonymous namespace +} // end of namespace v8impl + +napi_status NAPI_CDECL node_embedding_on_error( + node_embedding_error_handler error_handler, void* error_handler_data) { + return v8impl::EmbeddedErrorHandling::SetErrorHandler(error_handler, + error_handler_data); +} + +napi_status NAPI_CDECL node_embedding_create_platform( + int32_t api_version, node_embedding_platform* result) { + ARG_NOT_NULL(result); + *result = reinterpret_cast( + new v8impl::EmbeddedPlatform(api_version)); + return napi_ok; +} + +napi_status NAPI_CDECL +node_embedding_delete_platform(node_embedding_platform platform) { + return EMBEDDED_PLATFORM(platform)->DeleteMe(); +} + +napi_status NAPI_CDECL node_embedding_platform_is_initialized( + node_embedding_platform platform, bool* result) { + return EMBEDDED_PLATFORM(platform)->IsInitialized(result); +} + +napi_status NAPI_CDECL node_embedding_platform_set_flags( + node_embedding_platform platform, node_embedding_platform_flags flags) { + return EMBEDDED_PLATFORM(platform)->SetFlags(flags); +} + +napi_status NAPI_CDECL node_embedding_platform_set_args( + node_embedding_platform platform, int32_t argc, char* argv[]) { + return EMBEDDED_PLATFORM(platform)->SetArgs(argc, argv); +} + +napi_status NAPI_CDECL node_embedding_platform_initialize( + node_embedding_platform platform, bool* early_return) { + return EMBEDDED_PLATFORM(platform)->Initialize(early_return); +} + +napi_status NAPI_CDECL +node_embedding_platform_get_args(node_embedding_platform platform, + node_embedding_get_args_callback get_args, + void* get_args_data) { + return EMBEDDED_PLATFORM(platform)->GetArgs(get_args, get_args_data); +} + +napi_status NAPI_CDECL +node_embedding_platform_get_exec_args(node_embedding_platform platform, + node_embedding_get_args_callback get_args, + void* get_args_data) { + return EMBEDDED_PLATFORM(platform)->GetExecArgs(get_args, get_args_data); +} + +napi_status NAPI_CDECL node_embedding_create_runtime( + node_embedding_platform platform, node_embedding_runtime* result) { + return EMBEDDED_PLATFORM(platform)->CreateRuntime(result); +} + +napi_status NAPI_CDECL +node_embedding_delete_runtime(node_embedding_runtime runtime) { + return EMBEDDED_RUNTIME(runtime)->DeleteMe(); +} + +napi_status NAPI_CDECL node_embedding_runtime_is_initialized( + node_embedding_runtime runtime, bool* result) { + return EMBEDDED_RUNTIME(runtime)->IsInitialized(result); +} + +napi_status NAPI_CDECL node_embedding_runtime_set_flags( + node_embedding_runtime runtime, node_embedding_runtime_flags flags) { + return EMBEDDED_RUNTIME(runtime)->SetFlags(flags); +} + +napi_status NAPI_CDECL node_embedding_runtime_set_args( + node_embedding_runtime runtime, int32_t argc, const char* argv[]) { + return EMBEDDED_RUNTIME(runtime)->SetArgs(argc, argv); +} + +napi_status NAPI_CDECL node_embedding_runtime_set_exec_args( + node_embedding_runtime runtime, int32_t argc, const char* argv[]) { + return EMBEDDED_RUNTIME(runtime)->SetExecArgs(argc, argv); +} + +napi_status NAPI_CDECL +node_embedding_runtime_on_preload(node_embedding_runtime runtime, + node_embedding_preload_callback preload_cb, + void* preload_cb_data) { + return EMBEDDED_RUNTIME(runtime)->SetPreloadCallback(preload_cb, + preload_cb_data); +} + +napi_status NAPI_CDECL node_embedding_runtime_use_snapshot( + node_embedding_runtime runtime, const uint8_t* snapshot, size_t size) { + return EMBEDDED_RUNTIME(runtime)->SetSnapshotBlob(snapshot, size); +} + +napi_status NAPI_CDECL node_embedding_runtime_on_create_snapshot( + node_embedding_runtime runtime, + node_embedding_store_blob_callback store_blob_cb, + void* store_blob_cb_data, + node_embedding_snapshot_flags snapshot_flags) { + return EMBEDDED_RUNTIME(runtime)->OnCreateSnapshotBlob( + store_blob_cb, store_blob_cb_data, snapshot_flags); +} + +napi_status NAPI_CDECL node_embedding_runtime_initialize( + node_embedding_runtime runtime, const char* main_script) { + return EMBEDDED_RUNTIME(runtime)->Initialize(main_script); +} + +napi_status NAPI_CDECL +node_embedding_runtime_run_event_loop(node_embedding_runtime runtime) { + return EMBEDDED_RUNTIME(runtime)->RunEventLoop(); +} + +napi_status NAPI_CDECL node_embedding_runtime_run_event_loop_while( + node_embedding_runtime runtime, + node_embedding_event_loop_predicate predicate, + void* predicate_data, + node_embedding_event_loop_run_mode run_mode, + bool* has_more_work) { + return EMBEDDED_RUNTIME(runtime)->RunEventLoopWhile( + predicate, predicate_data, run_mode, has_more_work); +} + +napi_status NAPI_CDECL +node_embedding_runtime_await_promise(node_embedding_runtime runtime, + napi_value promise, + napi_value* result, + bool* has_more_work) { + return EMBEDDED_RUNTIME(runtime)->AwaitPromise( + promise, result, has_more_work); +} + +napi_status NAPI_CDECL node_embedding_runtime_set_node_api_version( + node_embedding_runtime runtime, int32_t node_api_version) { + return EMBEDDED_RUNTIME(runtime)->SetNodeApiVersion(node_api_version); +} + +napi_status NAPI_CDECL node_embedding_runtime_get_node_api_env( + node_embedding_runtime runtime, napi_env* env) { + return EMBEDDED_RUNTIME(runtime)->GetNodeApiEnv(env); +} + +napi_status NAPI_CDECL +node_embedding_runtime_open_scope(node_embedding_runtime runtime) { + return EMBEDDED_RUNTIME(runtime)->OpenScope(); +} + +napi_status NAPI_CDECL +node_embedding_runtime_close_scope(node_embedding_runtime runtime) { + return EMBEDDED_RUNTIME(runtime)->CloseScope(); +} diff --git a/src/node_embedding_api.h b/src/node_embedding_api.h new file mode 100644 index 00000000000000..418b8e53387c65 --- /dev/null +++ b/src/node_embedding_api.h @@ -0,0 +1,353 @@ +// +// Description: C-based API for embedding Node.js. +// +// !!! WARNING !!! WARNING !!! WARNING !!! +// This is a new API and is subject to change. +// While it is C-based, it is not ABI safe yet. +// Consider all functions and data structures as experimental. +// !!! WARNING !!! WARNING !!! WARNING !!! +// +// This file contains the C-based API for embedding Node.js in a host +// application. The API is designed to be used by applications that want to +// embed Node.js as a library and can interop with C-based API. +// + +#ifndef SRC_NODE_EMBEDDING_API_H_ +#define SRC_NODE_EMBEDDING_API_H_ + +#include "node_api.h" + +#define NODE_EMBEDDING_VERSION 1 + +EXTERN_C_START + +//============================================================================== +// Data types +//============================================================================== + +typedef struct node_embedding_platform__* node_embedding_platform; +typedef struct node_embedding_runtime__* node_embedding_runtime; + +typedef enum { + node_embedding_platform_no_flags = 0, + // Enable stdio inheritance, which is disabled by default. + // This flag is also implied by + // node_embedding_platform_no_stdio_initialization. + node_embedding_platform_enable_stdio_inheritance = 1 << 0, + // Disable reading the NODE_OPTIONS environment variable. + node_embedding_platform_disable_node_options_env = 1 << 1, + // Do not parse CLI options. + node_embedding_platform_disable_cli_options = 1 << 2, + // Do not initialize ICU. + node_embedding_platform_no_icu = 1 << 3, + // Do not modify stdio file descriptor or TTY state. + node_embedding_platform_no_stdio_initialization = 1 << 4, + // Do not register Node.js-specific signal handlers + // and reset other signal handlers to default state. + node_embedding_platform_no_default_signal_handling = 1 << 5, + // Do not initialize OpenSSL config. + node_embedding_platform_no_init_openssl = 1 << 8, + // Do not initialize Node.js debugging based on environment variables. + node_embedding_platform_no_parse_global_debug_variables = 1 << 9, + // Do not adjust OS resource limits for this process. + node_embedding_platform_no_adjust_resource_limits = 1 << 10, + // Do not map code segments into large pages for this process. + node_embedding_platform_no_use_large_pages = 1 << 11, + // Skip printing output for --help, --version, --v8-options. + node_embedding_platform_no_print_help_or_version_output = 1 << 12, + // Initialize the process for predictable snapshot generation. + node_embedding_platform_generate_predictable_snapshot = 1 << 14, +} node_embedding_platform_flags; + +typedef enum { + node_embedding_runtime_no_flags = 0, + // Use the default behavior for Node.js instances. + node_embedding_runtime_default_flags = 1 << 0, + // Controls whether this Environment is allowed to affect per-process state + // (e.g. cwd, process title, uid, etc.). + // This is set when using node_embedding_runtime_default_flags. + node_embedding_runtime_owns_process_state = 1 << 1, + // Set if this Environment instance is associated with the global inspector + // handling code (i.e. listening on SIGUSR1). + // This is set when using node_embedding_runtime_default_flags. + node_embedding_runtime_owns_inspector = 1 << 2, + // Set if Node.js should not run its own esm loader. This is needed by some + // embedders, because it's possible for the Node.js esm loader to conflict + // with another one in an embedder environment, e.g. Blink's in Chromium. + node_embedding_runtime_no_register_esm_loader = 1 << 3, + // Set this flag to make Node.js track "raw" file descriptors, i.e. managed + // by fs.open() and fs.close(), and close them during + // node_embedding_delete_runtime(). + node_embedding_runtime_track_unmanaged_fds = 1 << 4, + // Set this flag to force hiding console windows when spawning child + // processes. This is usually used when embedding Node.js in GUI programs on + // Windows. + node_embedding_runtime_hide_console_windows = 1 << 5, + // Set this flag to disable loading native addons via `process.dlopen`. + // This environment flag is especially important for worker threads + // so that a worker thread can't load a native addon even if `execArgv` + // is overwritten and `--no-addons` is not specified but was specified + // for this Environment instance. + node_embedding_runtime_no_native_addons = 1 << 6, + // Set this flag to disable searching modules from global paths like + // $HOME/.node_modules and $NODE_PATH. This is used by standalone apps that + // do not expect to have their behaviors changed because of globally + // installed modules. + node_embedding_runtime_no_global_search_paths = 1 << 7, + // Do not export browser globals like setTimeout, console, etc. + node_embedding_runtime_no_browser_globals = 1 << 8, + // Controls whether or not the Environment should call V8Inspector::create(). + // This control is needed by embedders who may not want to initialize the V8 + // inspector in situations where one has already been created, + // e.g. Blink's in Chromium. + node_embedding_runtime_no_create_inspector = 1 << 9, + // Controls whether or not the InspectorAgent for this Environment should + // call StartDebugSignalHandler. This control is needed by embedders who may + // not want to allow other processes to start the V8 inspector. + node_embedding_runtime_no_start_debug_signal_handler = 1 << 10, + // Controls whether the InspectorAgent created for this Environment waits for + // Inspector frontend events during the Environment creation. It's used to + // call node::Stop(env) on a Worker thread that is waiting for the events. + node_embedding_runtime_no_wait_for_inspector_frontend = 1 << 11 +} node_embedding_runtime_flags; + +typedef enum { + node_embedding_snapshot_no_flags = 0, + // Whether code cache should be generated as part of the snapshot. + // Code cache reduces the time spent on compiling functions included + // in the snapshot at the expense of a bigger snapshot size and + // potentially breaking portability of the snapshot. + node_embedding_snapshot_no_code_cache = 1 << 0, +} node_embedding_snapshot_flags; + +typedef enum { + // Run the event loop once and wait if there are no items. + // It matches the UV_RUN_ONCE behavior. + node_embedding_event_loop_run_once = 1, + + // Run the event loop once and do not wait if there are no items. + // It matches the UV_RUN_NOWAIT behavior. + node_embedding_event_loop_run_nowait = 2, +} node_embedding_event_loop_run_mode; + +//============================================================================== +// Callbacks +//============================================================================== + +typedef napi_status(NAPI_CDECL* node_embedding_error_handler)( + void* handler_data, + const char* messages[], + size_t messages_size, + int32_t exit_code, + napi_status status); + +typedef void(NAPI_CDECL* node_embedding_get_args_callback)(void* cb_data, + int32_t argc, + const char* argv[]); + +typedef void(NAPI_CDECL* node_embedding_preload_callback)(void* cb_data, + napi_env env, + napi_value process, + napi_value require); + +typedef void(NAPI_CDECL* node_embedding_store_blob_callback)( + void* cb_data, const uint8_t* blob, size_t size); + +typedef bool(NAPI_CDECL* node_embedding_event_loop_predicate)( + void* predicate_data, bool has_work); + +//============================================================================== +// Functions +//============================================================================== + +//------------------------------------------------------------------------------ +// Error handling functions +//------------------------------------------------------------------------------ + +// Sets the global error handing for the Node.js embedding API. +NAPI_EXTERN napi_status NAPI_CDECL node_embedding_on_error( + node_embedding_error_handler error_handler, void* error_handler_data); + +//------------------------------------------------------------------------------ +// Node.js global platform functions. +//------------------------------------------------------------------------------ + +// Creates a new Node.js platform instance. +NAPI_EXTERN napi_status NAPI_CDECL node_embedding_create_platform( + int32_t api_version, node_embedding_platform* result); + +// Deletes the Node.js platform instance. +NAPI_EXTERN napi_status NAPI_CDECL +node_embedding_delete_platform(node_embedding_platform platform); + +// Checks if the Node.js platform is initialized. +NAPI_EXTERN napi_status NAPI_CDECL node_embedding_platform_is_initialized( + node_embedding_platform platform, bool* result); + +// Sets the flags for the Node.js platform initialization. +NAPI_EXTERN napi_status NAPI_CDECL node_embedding_platform_set_flags( + node_embedding_platform platform, node_embedding_platform_flags flags); + +// Sets the CLI arguments for the Node.js platform initialization. +NAPI_EXTERN napi_status NAPI_CDECL node_embedding_platform_set_args( + node_embedding_platform platform, int32_t argc, char* argv[]); + +// Initializes the Node.js platform. +NAPI_EXTERN napi_status NAPI_CDECL node_embedding_platform_initialize( + node_embedding_platform platform, bool* early_return); + +// Gets the parsed list of non-Node.js arguments. +NAPI_EXTERN napi_status NAPI_CDECL +node_embedding_platform_get_args(node_embedding_platform platform, + node_embedding_get_args_callback get_args_cb, + void* get_args_cb_data); + +// Gets the parsed list of Node.js arguments. +NAPI_EXTERN napi_status NAPI_CDECL node_embedding_platform_get_exec_args( + node_embedding_platform platform, + node_embedding_get_args_callback get_args_cb, + void* get_args_cb_data); + +//------------------------------------------------------------------------------ +// Node.js runtime functions. +//------------------------------------------------------------------------------ + +// Creates a new Node.js runtime instance. +NAPI_EXTERN napi_status NAPI_CDECL node_embedding_create_runtime( + node_embedding_platform platform, node_embedding_runtime* result); + +// Deletes the Node.js runtime instance. +NAPI_EXTERN napi_status NAPI_CDECL +node_embedding_delete_runtime(node_embedding_runtime runtime); + +// Checks if the Node.js runtime is initialized. +NAPI_EXTERN napi_status NAPI_CDECL node_embedding_runtime_is_initialized( + node_embedding_runtime runtime, bool* result); + +// Sets the flags for the Node.js runtime initialization. +NAPI_EXTERN napi_status NAPI_CDECL node_embedding_runtime_set_flags( + node_embedding_runtime runtime, node_embedding_runtime_flags flags); + +// Sets the non-Node.js CLI arguments for the Node.js runtime initialization. +NAPI_EXTERN napi_status NAPI_CDECL node_embedding_runtime_set_args( + node_embedding_runtime runtime, int32_t argc, const char* argv[]); + +// Sets the Node.js CLI arguments for the Node.js runtime initialization. +NAPI_EXTERN napi_status NAPI_CDECL node_embedding_runtime_set_exec_args( + node_embedding_runtime runtime, int32_t argc, const char* argv[]); + +// Sets the preload callback for the Node.js runtime initialization. +NAPI_EXTERN napi_status NAPI_CDECL +node_embedding_runtime_on_preload(node_embedding_runtime runtime, + node_embedding_preload_callback preload_cb, + void* preload_cb_data); + +// Sets the snapshot for the Node.js runtime initialization. +NAPI_EXTERN napi_status NAPI_CDECL node_embedding_runtime_use_snapshot( + node_embedding_runtime runtime, const uint8_t* snapshot, size_t size); + +// Sets the snapshot creation parameters for the Node.js runtime initialization. +NAPI_EXTERN napi_status NAPI_CDECL node_embedding_runtime_on_create_snapshot( + node_embedding_runtime runtime, + node_embedding_store_blob_callback store_blob_cb, + void* store_blob_cb_data, + node_embedding_snapshot_flags snapshot_flags); + +// Initializes the Node.js runtime. +NAPI_EXTERN napi_status NAPI_CDECL node_embedding_runtime_initialize( + node_embedding_runtime runtime, const char* main_script); + +//------------------------------------------------------------------------------ +// Node.js runtime functions for the event loop. +//------------------------------------------------------------------------------ + +// Runs the Node.js runtime event loop. +// It does not block the calling thread. +NAPI_EXTERN napi_status NAPI_CDECL +node_embedding_runtime_run_event_loop(node_embedding_runtime runtime); + +// Runs the Node.js runtime event loop until the predicate returns false. +// It may block the calling thread depending on the is_thread_blocking +// parameter. +NAPI_EXTERN napi_status NAPI_CDECL node_embedding_runtime_run_event_loop_while( + node_embedding_runtime runtime, + node_embedding_event_loop_predicate predicate, + void* predicate_data, + node_embedding_event_loop_run_mode run_mode, + bool* has_more_work); + +// Runs the Node.js runtime event loop until the promise is resolved. +// It may block the calling thread. +NAPI_EXTERN napi_status NAPI_CDECL +node_embedding_runtime_await_promise(node_embedding_runtime runtime, + napi_value promise, + napi_value* result, + bool* has_more_work); + +//------------------------------------------------------------------------------ +// Node.js runtime functions for the Node-API interop. +//------------------------------------------------------------------------------ + +// Sets the Node-API version for the Node.js runtime initialization. +NAPI_EXTERN napi_status NAPI_CDECL node_embedding_runtime_set_node_api_version( + node_embedding_runtime runtime, int32_t node_api_version); + +// Gets the Node-API environment associated with the initialized Node.js +// runtime. +NAPI_EXTERN napi_status NAPI_CDECL node_embedding_runtime_get_node_api_env( + node_embedding_runtime runtime, napi_env* env); + +// Opens a new Node-API scope for the current thread. +NAPI_EXTERN napi_status NAPI_CDECL +node_embedding_runtime_open_scope(node_embedding_runtime runtime); + +// Closes the current Node-API scope for the current thread. +NAPI_EXTERN napi_status NAPI_CDECL +node_embedding_runtime_close_scope(node_embedding_runtime runtime); + +EXTERN_C_END + +#ifdef __cplusplus + +//------------------------------------------------------------------------------ +// Convenience union operator for the Node.js flags. +//------------------------------------------------------------------------------ + +inline constexpr node_embedding_platform_flags operator|( + node_embedding_platform_flags lhs, node_embedding_platform_flags rhs) { + return static_cast(static_cast(lhs) | + static_cast(rhs)); +} + +inline constexpr node_embedding_runtime_flags operator|( + node_embedding_runtime_flags lhs, node_embedding_runtime_flags rhs) { + return static_cast(static_cast(lhs) | + static_cast(rhs)); +} + +inline constexpr node_embedding_snapshot_flags operator|( + node_embedding_snapshot_flags lhs, node_embedding_snapshot_flags rhs) { + return static_cast(static_cast(lhs) | + static_cast(rhs)); +} + +#endif + +#endif // SRC_NODE_EMBEDDING_API_H_ + +// TODO: (vmoroz) Remove the main_script parameter. +// TODO: (vmoroz) Add startup callback with process and require parameters. +// TODO: (vmoroz) Add ABI-safe way to access internal module functionality. +// TODO: (vmoroz) Allow setting the global inspector for a specific environment. +// TODO: (vmoroz) Start workers from C++. +// TODO: (vmoroz) Worker to inherit parent inspector. +// TODO: (vmoroz) Cancel pending tasks on delete env. +// TODO: (vmoroz) Can we init plat again if it returns early? +// TODO: (vmoroz) Add simpler threading model - without open/close scope. +// TODO: (vmoroz) Simplify API use for simple default cases. +// TODO: (vmoroz) Add a way to add embedded modules. +// TODO: (vmoroz) Check how to pass the V8 thread pool size. + +// TODO: (vmoroz) Make the args story simpler or clear named. +// TODO: (vmoroz) Consider to have one function to retrieve the both arg types. +// TODO: (vmoroz) Consider to have one function to set the both arg types. diff --git a/test/README.md b/test/README.md index 9252f5c9496ac6..7ba5294ca92ece 100644 --- a/test/README.md +++ b/test/README.md @@ -23,7 +23,7 @@ For the tests to run on Windows, be sure to clone Node.js source code with the | `code-cache` | No | Tests for a Node.js binary compiled with V8 code cache. | | `common` | _N/A_ | Common modules shared among many tests.[^1] | | `doctool` | Yes | Tests for the documentation generator. | -| `embdedding` | Yes | Test Node.js embedding API | +| `embdedding` | Yes | Test Node.js embedding API. | | `es-module` | Yes | Test ESM module loading. | | `fixtures` | _N/A_ | Test fixtures used in various tests throughout the test suite. | | `internet` | No | Tests that make real outbound network connections.[^2] | diff --git a/test/embedding/embedtest_concurrent_node_api.cc b/test/embedding/embedtest_concurrent_node_api.cc index bb13d58b881e77..f1394b625f1a27 100644 --- a/test/embedding/embedtest_concurrent_node_api.cc +++ b/test/embedding/embedtest_concurrent_node_api.cc @@ -7,34 +7,42 @@ static const char* main_script = "globalThis.require = require('module').createRequire(process.execPath);\n" "require('vm').runInThisContext(process.argv[1]);"; -// We can use multiple environments at the same time on their own threads. +// Tests that multiple runtimes can be run at the same time in their own +// threads. The test creates 12 threads and 12 runtimes. Each runtime runs in it +// own thread. extern "C" int32_t test_main_concurrent_node_api(int32_t argc, char* argv[]) { + node_embedding_platform platform; + CHECK(node_embedding_create_platform(1, &platform)); + CHECK(node_embedding_platform_set_args(platform, argc, argv)); + bool early_return = false; + CHECK(node_embedding_platform_initialize(platform, &early_return)); + if (early_return) { + return 0; + } + std::atomic global_count{0}; std::atomic global_exit_code{0}; - CHECK(node_api_initialize_platform(argc, - argv, - node_api_platform_no_flags, - nullptr, - nullptr, - nullptr, - nullptr)); const size_t thread_count = 12; std::vector threads; threads.reserve(thread_count); for (size_t i = 0; i < thread_count; i++) { - threads.emplace_back([&global_count, &global_exit_code] { + threads.emplace_back([platform, &global_count, &global_exit_code] { int32_t exit_code = [&]() { - node_api_env_options options; - CHECK(node_api_create_env_options(&options)); - CHECK(node_api_env_options_set_flags( - options, - node_api_env_default_flags | node_api_env_no_create_inspector)); + node_embedding_runtime runtime; + CHECK(node_embedding_create_runtime(platform, &runtime)); + // Inspector can be associated with only one runtime in the process. + CHECK(node_embedding_runtime_set_flags( + runtime, + node_embedding_runtime_default_flags | + node_embedding_runtime_no_create_inspector)); + CHECK( + node_embedding_runtime_set_node_api_version(runtime, NAPI_VERSION)); + CHECK(node_embedding_runtime_initialize(runtime, main_script)); napi_env env; - CHECK(node_api_create_env( - options, nullptr, nullptr, main_script, NAPI_VERSION, &env)); + CHECK(node_embedding_runtime_get_node_api_env(runtime, &env)); - CHECK(node_api_open_env_scope(env)); + CHECK(node_embedding_runtime_open_scope(runtime)); napi_value global, my_count; CHECK(napi_get_global(env, &global)); @@ -43,8 +51,8 @@ extern "C" int32_t test_main_concurrent_node_api(int32_t argc, char* argv[]) { CHECK(napi_get_value_int32(env, my_count, &count)); global_count.fetch_add(count); - CHECK(node_api_close_env_scope(env)); - CHECK(node_api_delete_env(env, nullptr)); + CHECK(node_embedding_runtime_close_scope(runtime)); + CHECK(node_embedding_delete_runtime(runtime)); return 0; }(); if (exit_code != 0) { @@ -59,39 +67,44 @@ extern "C" int32_t test_main_concurrent_node_api(int32_t argc, char* argv[]) { CHECK_EXIT_CODE(global_exit_code.load()); - CHECK(node_api_dispose_platform()); + CHECK(node_embedding_delete_platform(platform)); fprintf(stdout, "%d\n", global_count.load()); return 0; } -// We can use multiple environments on the same thread. -// For each use we must open and close the environment scope. +// Tests that multiple runtimes can run in the same thread. +// The runtime scope must be opened and closed for each use. +// There are 12 runtimes that share the same main thread. extern "C" int32_t test_main_multi_env_node_api(int32_t argc, char* argv[]) { - CHECK(node_api_initialize_platform(argc, - argv, - node_api_platform_no_flags, - nullptr, - nullptr, - nullptr, - nullptr)); - - const size_t env_count = 12; - std::vector envs; - envs.reserve(env_count); - for (size_t i = 0; i < env_count; i++) { - node_api_env_options options; - CHECK(node_api_create_env_options(&options)); - CHECK(node_api_env_options_set_flags( - options, - node_api_env_default_flags | node_api_env_no_create_inspector)); - napi_env env; - CHECK(node_api_create_env( - options, nullptr, nullptr, main_script, NAPI_VERSION, &env)); - envs.push_back(env); + node_embedding_platform platform; + CHECK(node_embedding_create_platform(1, &platform)); + CHECK(node_embedding_platform_set_args(platform, argc, argv)); + bool early_return = false; + CHECK(node_embedding_platform_initialize(platform, &early_return)); + if (early_return) { + return 0; + } - CHECK(node_api_open_env_scope(env)); + const size_t runtime_count = 12; + std::vector runtimes; + runtimes.reserve(runtime_count); + for (size_t i = 0; i < runtime_count; i++) { + node_embedding_runtime runtime; + CHECK(node_embedding_create_runtime(platform, &runtime)); + // Inspector can be associated with only one runtime in the process. + CHECK(node_embedding_runtime_set_flags( + runtime, + node_embedding_runtime_default_flags | + node_embedding_runtime_no_create_inspector)); + CHECK(node_embedding_runtime_set_node_api_version(runtime, NAPI_VERSION)); + CHECK(node_embedding_runtime_initialize(runtime, main_script)); + runtimes.push_back(runtime); + + napi_env env; + CHECK(node_embedding_runtime_get_node_api_env(runtime, &env)); + CHECK(node_embedding_runtime_open_scope(runtime)); napi_value undefined, global, func; CHECK(napi_get_undefined(env, &undefined)); @@ -103,20 +116,22 @@ extern "C" int32_t test_main_multi_env_node_api(int32_t argc, char* argv[]) { CHECK_TRUE(func_type == napi_function); CHECK(napi_call_function(env, undefined, func, 0, nullptr, nullptr)); - CHECK(node_api_close_env_scope(env)); + CHECK(node_embedding_runtime_close_scope(runtime)); } bool more_work = false; do { more_work = false; - for (napi_env env : envs) { - CHECK(node_api_open_env_scope(env)); + for (node_embedding_runtime runtime : runtimes) { + napi_env env; + CHECK(node_embedding_runtime_get_node_api_env(runtime, &env)); + CHECK(node_embedding_runtime_open_scope(runtime)); bool has_more_work = false; bool had_run_once = false; - CHECK(node_api_run_env_while( - env, - [](void* predicate_data) { + CHECK(node_embedding_runtime_run_event_loop_while( + runtime, + [](void* predicate_data, bool has_work) { bool* had_run_once = static_cast(predicate_data); if (*had_run_once) { return false; @@ -125,17 +140,19 @@ extern "C" int32_t test_main_multi_env_node_api(int32_t argc, char* argv[]) { return true; }, &had_run_once, + node_embedding_event_loop_run_nowait, &has_more_work)); more_work |= has_more_work; - CHECK(node_api_close_env_scope(env)); + CHECK(node_embedding_runtime_close_scope(runtime)); } } while (more_work); int32_t global_count = 0; - for (size_t i = 0; i < env_count; i++) { - napi_env env = envs[i]; - CHECK(node_api_open_env_scope(env)); + for (node_embedding_runtime runtime : runtimes) { + napi_env env; + CHECK(node_embedding_runtime_get_node_api_env(runtime, &env)); + CHECK(node_embedding_runtime_open_scope(runtime)); napi_value global, my_count; CHECK(napi_get_global(env, &global)); @@ -149,34 +166,37 @@ extern "C" int32_t test_main_multi_env_node_api(int32_t argc, char* argv[]) { global_count += count; - CHECK(node_api_close_env_scope(env)); - CHECK(node_api_delete_env(env, nullptr)); + CHECK(node_embedding_runtime_close_scope(runtime)); + CHECK(node_embedding_delete_runtime(runtime)); } - CHECK(node_api_dispose_platform()); + CHECK(node_embedding_delete_platform(platform)); fprintf(stdout, "%d\n", global_count); return 0; } -// We can use the environment from different threads as long as only one thread -// at a time is using it. +// Tests that a runtime can be invoked from different threads as long as only +// one thread uses it at a time. extern "C" int32_t test_main_multi_thread_node_api(int32_t argc, char* argv[]) { - CHECK(node_api_initialize_platform(argc, - argv, - node_api_platform_no_flags, - nullptr, - nullptr, - nullptr, - nullptr)); - - node_api_env_options options; - CHECK(node_api_create_env_options(&options)); + node_embedding_platform platform; + CHECK(node_embedding_create_platform(NODE_EMBEDDING_VERSION, &platform)); + CHECK(node_embedding_platform_set_args(platform, argc, argv)); + bool early_return = false; + CHECK(node_embedding_platform_initialize(platform, &early_return)); + if (early_return) { + return 0; + } + + node_embedding_runtime runtime; + CHECK(node_embedding_create_runtime(platform, &runtime)); + CHECK(node_embedding_runtime_set_node_api_version(runtime, NAPI_VERSION)); + CHECK(node_embedding_runtime_initialize(runtime, main_script)); napi_env env; - CHECK(node_api_create_env( - options, nullptr, nullptr, main_script, NAPI_VERSION, &env)); + CHECK(node_embedding_runtime_get_node_api_env(runtime, &env)); + // Use mutex to synchronize access to the runtime. std::mutex mutex; std::atomic result_count{0}; std::atomic result_exit_code{0}; @@ -184,10 +204,10 @@ extern "C" int32_t test_main_multi_thread_node_api(int32_t argc, char* argv[]) { std::vector threads; threads.reserve(thread_count); for (size_t i = 0; i < thread_count; i++) { - threads.emplace_back([env, &result_count, &result_exit_code, &mutex] { + threads.emplace_back([runtime, env, &result_count, &result_exit_code, &mutex] { int32_t exit_code = [&]() { std::scoped_lock lock(mutex); - CHECK(node_api_open_env_scope(env)); + CHECK(node_embedding_runtime_open_scope(runtime)); napi_value undefined, global, func, my_count; CHECK(napi_get_undefined(env, &undefined)); @@ -207,7 +227,7 @@ extern "C" int32_t test_main_multi_thread_node_api(int32_t argc, char* argv[]) { CHECK(napi_get_value_int32(env, my_count, &count)); result_count.store(count); - CHECK(node_api_close_env_scope(env)); + CHECK(node_embedding_runtime_close_scope(runtime)); return 0; }(); if (exit_code != 0) { @@ -222,8 +242,8 @@ extern "C" int32_t test_main_multi_thread_node_api(int32_t argc, char* argv[]) { CHECK_EXIT_CODE(result_exit_code.load()); - CHECK(node_api_delete_env(env, nullptr)); - CHECK(node_api_dispose_platform()); + CHECK(node_embedding_delete_runtime(runtime)); + CHECK(node_embedding_delete_platform(platform)); fprintf(stdout, "%d\n", result_count.load()); diff --git a/test/embedding/embedtest_main.cc b/test/embedding/embedtest_main.cc index f2aa1305854b85..388ff6f6d03abe 100644 --- a/test/embedding/embedtest_main.cc +++ b/test/embedding/embedtest_main.cc @@ -39,10 +39,10 @@ NODE_MAIN(int32_t argc, node::argv_type raw_argv[]) { return CallWithoutArg1(test_main_multi_env_node_api, argc, argv); } else if (strcmp(arg1, "multi-thread-node-api") == 0) { return CallWithoutArg1(test_main_multi_thread_node_api, argc, argv); - } else if (strcmp(arg1, "snapshot-node-api") == 0) { - return CallWithoutArg1(test_main_snapshot_node_api, argc, argv); } else if (strcmp(arg1, "preload-node-api") == 0) { return CallWithoutArg1(test_main_preload_node_api, argc, argv); + } else if (strcmp(arg1, "snapshot-node-api") == 0) { + return CallWithoutArg1(test_main_snapshot_node_api, argc, argv); } } return test_main_cpp_api(argc, argv); diff --git a/test/embedding/embedtest_modules_node_api.cc b/test/embedding/embedtest_modules_node_api.cc index d1d03361444981..96700bd14ed4c4 100644 --- a/test/embedding/embedtest_modules_node_api.cc +++ b/test/embedding/embedtest_modules_node_api.cc @@ -1,7 +1,7 @@ #include "embedtest_node_api.h" -#include -#include +#include +#include extern "C" int32_t test_main_modules_node_api(int32_t argc, char* argv[]) { if (argc < 3) { @@ -9,15 +9,25 @@ extern "C" int32_t test_main_modules_node_api(int32_t argc, char* argv[]) { return 2; } - CHECK(node_api_initialize_platform( - argc, argv, node_api_platform_no_flags, NULL, NULL, NULL, NULL)); + CHECK(node_embedding_on_error(HandleTestError, argv[0])); - node_api_env_options options; - CHECK(node_api_create_env_options(&options)); + node_embedding_platform platform; + CHECK(node_embedding_create_platform(NODE_EMBEDDING_VERSION, &platform)); + CHECK(node_embedding_platform_set_args(platform, argc, argv)); + bool early_return = false; + CHECK(node_embedding_platform_initialize(platform, &early_return)); + if (early_return) { + return 0; + } + + node_embedding_runtime runtime; + CHECK(node_embedding_create_runtime(platform, &runtime)); + CHECK(node_embedding_runtime_set_node_api_version(runtime, NAPI_VERSION)); + CHECK(node_embedding_runtime_initialize(runtime, nullptr)); napi_env env; - CHECK(node_api_create_env(options, NULL, NULL, NULL, NAPI_VERSION, &env)); + CHECK(node_embedding_runtime_get_node_api_env(runtime, &env)); - CHECK(node_api_open_env_scope(env)); + CHECK(node_embedding_runtime_open_scope(runtime)); napi_value global, import_name, require_name, import, require, cjs, es6, value; @@ -37,7 +47,8 @@ extern "C" int32_t test_main_modules_node_api(int32_t argc, char* argv[]) { size_t bufferlen; CHECK(napi_call_function(env, global, import, 1, &es6, &es6_promise)); - CHECK(node_api_await_promise(env, es6_promise, &es6_module, nullptr)); + CHECK(node_embedding_runtime_await_promise( + runtime, es6_promise, &es6_module, nullptr)); CHECK(napi_get_property(env, es6_module, value, &es6_result)); CHECK(napi_get_value_string_utf8( @@ -54,8 +65,8 @@ extern "C" int32_t test_main_modules_node_api(int32_t argc, char* argv[]) { FAIL("Unexpected value: %s\n", buffer); } - CHECK(node_api_close_env_scope(env)); - CHECK(node_api_delete_env(env, NULL)); - CHECK(node_api_dispose_platform()); + CHECK(node_embedding_runtime_close_scope(runtime)); + CHECK(node_embedding_delete_runtime(runtime)); + CHECK(node_embedding_delete_platform(platform)); return 0; } diff --git a/test/embedding/embedtest_node_api.cc b/test/embedding/embedtest_node_api.cc index 42c04359d5c423..01e7cdadeddc2f 100644 --- a/test/embedding/embedtest_node_api.cc +++ b/test/embedding/embedtest_node_api.cc @@ -4,44 +4,30 @@ #include #include -// Note: This file is being referred to from doc/api/embedding.md, and excerpts -// from it are included in the documentation. Try to keep these in sync. - -static int32_t RunNodeInstance(); - static const char* main_script = "globalThis.require = require('module').createRequire(process.execPath);\n" "globalThis.embedVars = { nön_ascıı: '🏳️‍🌈' };\n" "require('vm').runInThisContext(process.argv[1]);"; -static const char* exe_name; - -static void NAPI_CDECL get_errors(void* data, - const char* errors[], - size_t count) { - for (size_t i = 0; i < count && i < 30; ++i) { - fprintf(stderr, "%s: %s\n", exe_name, errors[i]); - } -} +static int32_t RunNodeInstance(node_embedding_platform platform); extern "C" int32_t test_main_node_api(int32_t argc, char* argv[]) { - exe_name = argv[0]; + CHECK(node_embedding_on_error(HandleTestError, argv[0])); + + node_embedding_platform platform; + CHECK(node_embedding_create_platform(NODE_EMBEDDING_VERSION, &platform)); + CHECK(node_embedding_platform_set_args(platform, argc, argv)); + CHECK(node_embedding_platform_set_flags( + platform, node_embedding_platform_disable_node_options_env)); bool early_return = false; - int32_t exit_code = 0; - node_api_initialize_platform(argc, - argv, - node_api_platform_disable_node_options_env, - get_errors, - NULL, - &early_return, - &exit_code); + CHECK(node_embedding_platform_initialize(platform, &early_return)); if (early_return) { - return exit_code; + return 0; } - CHECK_EXIT_CODE(RunNodeInstance()); + CHECK_EXIT_CODE(RunNodeInstance(platform)); - CHECK(node_api_dispose_platform()); + CHECK(node_embedding_delete_platform(platform)); return 0; } @@ -50,8 +36,6 @@ int32_t callMe(napi_env env) { napi_value cb; napi_value key; - CHECK(node_api_open_env_scope(env)); - CHECK(napi_get_global(env, &global)); CHECK(napi_create_string_utf8(env, "callMe", NAPI_AUTO_LENGTH, &key)); CHECK(napi_get_property(env, global, key, &cb)); @@ -79,7 +63,6 @@ int32_t callMe(napi_env env) { FAIL("Invalid callMe value\n"); } - CHECK(node_api_close_env_scope(env)); return 0; } @@ -94,13 +77,11 @@ napi_value c_cb(napi_env env, napi_callback_info info) { return NULL; } -int32_t waitMe(napi_env env) { +int32_t waitMe(napi_env env, node_embedding_runtime runtime) { napi_value global; napi_value cb; napi_value key; - CHECK(node_api_open_env_scope(env)); - CHECK(napi_get_global(env, &global)); CHECK(napi_create_string_utf8(env, "waitMe", NAPI_AUTO_LENGTH, &key)); CHECK(napi_get_property(env, global, key, &cb)); @@ -124,7 +105,7 @@ int32_t waitMe(napi_env env) { FAIL("Anachronism detected: %s\n", callback_buf); } - CHECK(node_api_run_env(env)); + CHECK(node_embedding_runtime_run_event_loop(runtime)); if (strcmp(callback_buf, "waited you") != 0) { FAIL("Invalid value received: %s\n", callback_buf); @@ -134,17 +115,14 @@ int32_t waitMe(napi_env env) { FAIL("Invalid waitMe value\n"); } - CHECK(node_api_close_env_scope(env)); return 0; } -int32_t waitMeWithCheese(napi_env env) { +int32_t waitMeWithCheese(napi_env env, node_embedding_runtime runtime) { napi_value global; napi_value cb; napi_value key; - CHECK(node_api_open_env_scope(env)); - CHECK(napi_get_global(env, &global)); CHECK(napi_create_string_utf8(env, "waitPromise", NAPI_AUTO_LENGTH, &key)); CHECK(napi_get_property(env, global, key, &cb)); @@ -176,7 +154,8 @@ int32_t waitMeWithCheese(napi_env env) { FAIL("Result is not a Promise\n"); } - napi_status r = node_api_await_promise(env, promise, &result, nullptr); + napi_status r = node_embedding_runtime_await_promise( + runtime, promise, &result, nullptr); if (r != napi_ok && r != napi_pending_exception) { FAIL("Failed awaiting promise: %d\n", r); } @@ -197,23 +176,24 @@ int32_t waitMeWithCheese(napi_env env) { FAIL("Invalid waitPromise value\n"); } - CHECK(node_api_close_env_scope(env)); return 0; } -int32_t RunNodeInstance() { - node_api_env_options options; - CHECK(node_api_create_env_options(&options)); +int32_t RunNodeInstance(node_embedding_platform platform) { + node_embedding_runtime runtime; + CHECK(node_embedding_create_runtime(platform, &runtime)); + CHECK(node_embedding_runtime_set_node_api_version(runtime, NAPI_VERSION)); + CHECK(node_embedding_runtime_initialize(runtime, main_script)); napi_env env; - CHECK(node_api_create_env( - options, NULL, NULL, main_script, NAPI_VERSION, &env)); + CHECK(node_embedding_runtime_get_node_api_env(runtime, &env)); + CHECK(node_embedding_runtime_open_scope(runtime)); CHECK_EXIT_CODE(callMe(env)); - CHECK_EXIT_CODE(waitMe(env)); - CHECK_EXIT_CODE(waitMeWithCheese(env)); + CHECK_EXIT_CODE(waitMe(env, runtime)); + CHECK_EXIT_CODE(waitMeWithCheese(env, runtime)); + CHECK(node_embedding_runtime_close_scope(runtime)); - int32_t exit_code; - CHECK(node_api_delete_env(env, &exit_code)); + CHECK(node_embedding_delete_runtime(runtime)); - return exit_code; + return 0; } diff --git a/test/embedding/embedtest_node_api.h b/test/embedding/embedtest_node_api.h index 04e61dd0a144a6..acf9a87f1c23a0 100644 --- a/test/embedding/embedtest_node_api.h +++ b/test/embedding/embedtest_node_api.h @@ -2,26 +2,35 @@ #define TEST_EMBEDDING_EMBEDTEST_NODE_API_H_ #define NAPI_EXPERIMENTAL -#include +#include #ifdef __cplusplus #include #include -extern "C" inline void NAPI_CDECL GetMessageVector(void* data, - const char* messages[], - size_t size) { - static_cast*>(data)->assign(messages, - messages + size); -} - extern "C" inline void NAPI_CDECL GetArgsVector(void* data, int32_t argc, const char* argv[]) { static_cast*>(data)->assign(argv, argv + argc); } +extern "C" inline napi_status NAPI_CDECL HandleTestError(void* handler_data, + const char* messages[], + size_t messages_size, + int32_t exit_code, + napi_status status) { + auto exe_name = static_cast(handler_data); + if (exit_code != 0) { + for (size_t i = 0; i < messages_size; ++i) + fprintf(stderr, "%s: %s\n", exe_name, messages[i]); + exit(exit_code); + } else { + for (size_t i = 0; i < messages_size; ++i) printf("%s\n", messages[i]); + } + return status; +} + #endif #define CHECK(expr) \ diff --git a/test/embedding/embedtest_preload_node_api.cc b/test/embedding/embedtest_preload_node_api.cc index 18694b663f267d..c1871685fff879 100644 --- a/test/embedding/embedtest_preload_node_api.cc +++ b/test/embedding/embedtest_preload_node_api.cc @@ -7,37 +7,37 @@ static const char* main_script = "globalThis.require = require('module').createRequire(process.execPath);\n" "require('vm').runInThisContext(process.argv[1]);"; -// Test the preload callback being called. +// Tests that the same preload callback is called from the main thread and from +// the worker thread. extern "C" int32_t test_main_preload_node_api(int32_t argc, char* argv[]) { - CHECK(node_api_initialize_platform(argc, - argv, - node_api_platform_no_flags, - nullptr, - nullptr, - nullptr, - nullptr)); + node_embedding_platform platform; + CHECK(node_embedding_create_platform(NODE_EMBEDDING_VERSION, &platform)); + CHECK(node_embedding_platform_set_args(platform, argc, argv)); + bool early_return = false; + CHECK(node_embedding_platform_initialize(platform, &early_return)); + if (early_return) { + return 0; + } - node_api_env_options options; - CHECK(node_api_create_env_options(&options)); - CHECK(node_api_env_options_set_preload_callback( - options, - [](napi_env env, + node_embedding_runtime runtime; + CHECK(node_embedding_create_runtime(platform, &runtime)); + CHECK(node_embedding_runtime_set_node_api_version(runtime, NAPI_VERSION)); + CHECK(node_embedding_runtime_on_preload( + runtime, + [](void* /*cb_data*/, + napi_env env, napi_value /*process*/, - napi_value /*require*/, - void* /*cb_data*/) { + napi_value /*require*/ + ) { napi_value global, value; napi_get_global(env, &global); napi_create_int32(env, 42, &value); napi_set_named_property(env, global, "preloadValue", value); }, nullptr)); - napi_env env; - CHECK(node_api_create_env( - options, nullptr, nullptr, main_script, NAPI_VERSION, &env)); - - CHECK(node_api_delete_env(env, nullptr)); - - CHECK(node_api_dispose_platform()); + CHECK(node_embedding_runtime_initialize(runtime, main_script)); + CHECK(node_embedding_delete_runtime(runtime)); + CHECK(node_embedding_delete_platform(platform)); return 0; } diff --git a/test/embedding/embedtest_snapshot_node_api.cc b/test/embedding/embedtest_snapshot_node_api.cc index bde72af09443b5..0bf7d99d65788a 100644 --- a/test/embedding/embedtest_snapshot_node_api.cc +++ b/test/embedding/embedtest_snapshot_node_api.cc @@ -1,78 +1,65 @@ #include "embedtest_node_api.h" -#include -#include -#include - +#include +#include +#include +#include +#include #include class CStringArray { + static constexpr size_t kInplaceBufferSize = 32; + public: explicit CStringArray(const std::vector& strings) noexcept : size_(strings.size()) { - if (size_ < inplace_buffer_.size()) { - cstrings_ = inplace_buffer_.data(); + if (size_ <= inplace_buffer_.size()) { + c_strs_ = inplace_buffer_.data(); } else { allocated_buffer_ = std::make_unique(size_); - cstrings_ = allocated_buffer_.get(); + c_strs_ = allocated_buffer_.get(); } for (size_t i = 0; i < size_; ++i) { - cstrings_[i] = strings[i].c_str(); + c_strs_[i] = strings[i].c_str(); } } - const char** cstrings() const { return cstrings_; } + CStringArray(const CStringArray&) = delete; + CStringArray& operator=(const CStringArray&) = delete; + + const char** c_strs() const { return c_strs_; } size_t size() const { return size_; } private: - const char** cstrings_; - size_t size_; - std::array inplace_buffer_; + const char** c_strs_{}; + size_t size_{}; + std::array inplace_buffer_; std::unique_ptr allocated_buffer_; }; -static int32_t RunNodeInstance(); +static int32_t RunNodeInstance(node_embedding_platform platform); extern "C" int32_t test_main_snapshot_node_api(int32_t argc, char* argv[]) { - std::vector args(argv, argv + argc); + CHECK(node_embedding_on_error(HandleTestError, argv[0])); + + node_embedding_platform platform; + CHECK(node_embedding_create_platform(NODE_EMBEDDING_VERSION, &platform)); + CHECK(node_embedding_platform_set_args(platform, argc, argv)); + CHECK(node_embedding_platform_set_flags( + platform, node_embedding_platform_disable_node_options_env)); bool early_return = false; - int32_t exit_code = 0; - - std::vector errors; - node_api_initialize_platform(argc, - argv, - node_api_platform_disable_node_options_env, - GetMessageVector, - &errors, - &early_return, - &exit_code); + CHECK(node_embedding_platform_initialize(platform, &early_return)); if (early_return) { - if (exit_code != 0) { - for (const std::string& err : errors) - fprintf(stderr, "%s: %s\n", args[0].c_str(), err.c_str()); - } else { - for (const std::string& err : errors) printf("%s\n", err.c_str()); - } - return exit_code; + return 0; } - CHECK_EXIT_CODE(RunNodeInstance()); + CHECK_EXIT_CODE(RunNodeInstance(platform)); - CHECK(node_api_dispose_platform()); + CHECK(node_embedding_delete_platform(platform)); return 0; } -static const char* exe_name; - -static void NAPI_CDECL get_errors(void* data, - const char* errors[], - size_t count) { - for (size_t i = 0; i < count && i < 30; ++i) { - fprintf(stderr, "%s: %s\n", exe_name, errors[i]); - } -} - -int32_t RunNodeInstance() { +int32_t RunNodeInstance(node_embedding_platform platform) { // Format of the arguments of this binary: // Building snapshot: // embedtest js_code_to_eval arg1 arg2... @@ -87,18 +74,13 @@ int32_t RunNodeInstance() { // No snapshot: // embedtest arg1 arg2... - int32_t exit_code = 0; - - node_api_env_options options; - CHECK(node_api_create_env_options(&options)); - std::vector args, exec_args; - CHECK(node_api_env_options_get_args(options, GetArgsVector, &args)); - CHECK(node_api_env_options_get_exec_args(options, GetArgsVector, &exec_args)); + std::vector args; + CHECK(node_embedding_platform_get_args(platform, GetArgsVector, &args)); - exe_name = args[0].c_str(); std::vector filtered_args; bool is_building_snapshot = false; - node_api_snapshot_flags snapshot_flags = node_api_snapshot_no_flags; + node_embedding_snapshot_flags snapshot_flags = + node_embedding_snapshot_no_flags; std::string snapshot_blob_path; for (size_t i = 0; i < args.size(); ++i) { const std::string& arg = args[i]; @@ -108,8 +90,7 @@ int32_t RunNodeInstance() { // This parameter is not implemented by the Node-API, and we must not // include it in the filtered_args. } else if (arg == "--without-code-cache") { - snapshot_flags = static_cast( - snapshot_flags | node_api_snapshot_no_code_cache); + snapshot_flags = snapshot_flags | node_embedding_snapshot_no_code_cache; } else if (arg == "--embedder-snapshot-blob") { assert(i + 1 < args.size()); snapshot_blob_path = args[i + 1]; @@ -119,27 +100,27 @@ int32_t RunNodeInstance() { } } + node_embedding_runtime runtime; + CHECK(node_embedding_create_runtime(platform, &runtime)); + bool use_snapshot = false; if (!snapshot_blob_path.empty() && !is_building_snapshot) { use_snapshot = true; FILE* fp = fopen(snapshot_blob_path.c_str(), "rb"); assert(fp != nullptr); // Node-API only supports loading snapshots from blobs. - uv_fs_t req = uv_fs_t(); - int32_t statret = - uv_fs_stat(nullptr, &req, snapshot_blob_path.c_str(), nullptr); - assert(statret == 0); - size_t filesize = req.statbuf.st_size; - uv_fs_req_cleanup(&req); - - std::vector vec(filesize); + fseek(fp, 0, SEEK_END); + size_t filesize = static_cast(ftell(fp)); + fseek(fp, 0, SEEK_SET); + + std::vector vec(filesize); size_t read = fread(vec.data(), filesize, 1, fp); assert(read == 1); assert(snapshot); int32_t ret = fclose(fp); assert(ret == 0); - CHECK(node_api_env_options_use_snapshot(options, vec.data(), vec.size())); + CHECK(node_embedding_runtime_use_snapshot(runtime, vec.data(), vec.size())); } if (is_building_snapshot) { @@ -152,12 +133,12 @@ int32_t RunNodeInstance() { } CStringArray filtered_args_arr(filtered_args); - CHECK(node_api_env_options_set_args( - options, filtered_args_arr.size(), filtered_args_arr.cstrings())); + CHECK(node_embedding_runtime_set_args( + runtime, filtered_args_arr.size(), filtered_args_arr.c_strs())); if (!snapshot_blob_path.empty() && is_building_snapshot) { - CHECK(node_api_env_options_create_snapshot( - options, + CHECK(node_embedding_runtime_on_create_snapshot( + runtime, [](void* cb_data, const uint8_t* blob, size_t size) { const char* snapshot_blob_path = static_cast(cb_data); FILE* fp = fopen(snapshot_blob_path, "wb"); @@ -173,39 +154,29 @@ int32_t RunNodeInstance() { snapshot_flags)); } - napi_env env; if (use_snapshot) { - CHECK(node_api_create_env( - options, get_errors, nullptr, nullptr, NAPI_VERSION, &env)); + CHECK(node_embedding_runtime_initialize(runtime, nullptr)); } else if (is_building_snapshot) { // Environment created for snapshotting must set process.argv[1] to // the name of the main script, which was inserted above. - CHECK(node_api_create_env( - options, - get_errors, - nullptr, + CHECK(node_embedding_runtime_initialize( + runtime, "const assert = require('assert');" "assert(require('v8').startupSnapshot.isBuildingSnapshot());" "globalThis.embedVars = { nön_ascıı: '🏳️‍🌈' };" "globalThis.require = require;" - "require('vm').runInThisContext(process.argv[2]);", - NAPI_VERSION, - &env)); + "require('vm').runInThisContext(process.argv[2]);")); } else { - CHECK(node_api_create_env( - options, - get_errors, - nullptr, + CHECK(node_embedding_runtime_initialize( + runtime, "const publicRequire = require('module').createRequire(process.cwd() + " "'/');" "globalThis.require = publicRequire;" "globalThis.embedVars = { nön_ascıı: '🏳️‍🌈' };" - "require('vm').runInThisContext(process.argv[1]);", - NAPI_VERSION, - &env)); + "require('vm').runInThisContext(process.argv[1]);")); } - CHECK(node_api_delete_env(env, &exit_code)); + CHECK(node_embedding_delete_runtime(runtime)); - return exit_code; + return 0; } From 593c3739fef3b7d5398e8d37f1bd816adf1cde11 Mon Sep 17 00:00:00 2001 From: Vladimir Morozov Date: Tue, 10 Sep 2024 07:09:16 -0700 Subject: [PATCH 11/22] add support for linked modules --- doc/api/embedding.md | 98 ++++++++------ src/node_api.cc | 10 +- src/node_embedding_api.cc | 115 ++++++++++++++++- src/node_embedding_api.h | 14 +- .../embedtest_concurrent_node_api.cc | 4 - test/embedding/embedtest_main.cc | 4 + test/embedding/embedtest_modules_node_api.cc | 120 ++++++++++++++++++ test/embedding/embedtest_node_api.cc | 14 +- test/embedding/embedtest_node_api.h | 4 + test/embedding/embedtest_preload_node_api.cc | 4 - test/embedding/embedtest_snapshot_node_api.cc | 8 +- test/embedding/test-embedding.js | 46 ++++++- test/embedding/use-linked-modules.js | 28 ++++ 13 files changed, 405 insertions(+), 64 deletions(-) create mode 100644 test/embedding/use-linked-modules.js diff --git a/doc/api/embedding.md b/doc/api/embedding.md index cda3481923add3..3828636bbe79c6 100644 --- a/doc/api/embedding.md +++ b/doc/api/embedding.md @@ -214,7 +214,6 @@ The C embedder API has the four major API function groups: set of functions. The embedding APIs provide access to functions that retrieve or create `napi_env` instances related to a runtime instance. - ## API reference The C embedder API is split up by the four major groups described above. @@ -234,7 +233,6 @@ added: REPLACEME This is an opaque pointer that represents a Node.js platform instance. Node.js allows only a single platform instance per process. - ##### `node_embedding_platform_flags` + +> Stability: 1 - Experimental + +```c +typedef napi_value(NAPI_CDECL* node_embedding_initialize_module_callback)( + void* cb_data, + napi_env env, + const char* module_name, + napi_value exports); +``` + +Function pointer type for initializing linked native module that can be defined +in the embedder executable. + +The callback parameters: + +- `[in] cb_data`: The user data associated with this callback. +- `[in] env`: Node API environment. +- `[in] module_name`: Name of the module. +- `[in] exports`: The `exports` module object. + +All module exports must be added as properties to the `exports` object. +As an alternative a new object or another value can be created in the callback +and returned. #### Functions @@ -800,7 +812,6 @@ If it is planned to create more than one runtime instance or a non-default platform configuration is required, then it is recommended to create the Node.js platform instance explicitly. - ##### `node_embedding_delete_runtime` + +> Stability: 1 - Experimental + +Adds a linked module for the Node.js runtime instance. + +```c +napi_status NAPI_CDECL +node_embedding_runtime_add_module( + node_embedding_runtime runtime, + const char* module_name, + node_embedding_initialize_module_callback init_module_cb, + void* init_module_cb_data, + int32_t module_node_api_version); +``` + +- `[in] runtime`: The Node.js runtime instance. +- `[in] module_name`: The name of the module. +- `[in] init_module_cb`: Module initialization callback. It is called for the + main and worker threads. The caller must take care about the thread safety. +- `[in] init_module_cb_data`: The user data for the init_module_cb. +- `[in] module_node_api_version`: The Node API version used by the module. + +Returns `napi_ok` if there were no issues. + +The registered module can be accessed in JavaScript as +`process._linkedBinding(module_name)` in the main JS and in the related +worker threads. ##### `node_embedding_runtime_initialize` @@ -1022,7 +1058,6 @@ with a V8 `Isolate` and V8 `Context`. After the initialization is completed the Node.js runtime settings cannot be changed anymore. - ### Event loop APIs #### Data types @@ -1052,7 +1087,6 @@ beahvior. - `node_embedding_event_loop_run_nowait` - Run the event loop once and do not wait if there are no items. It matches the `UV_RUN_NOWAIT` behavior. - #### Callback types ##### `node_embedding_event_loop_predicate` @@ -1079,7 +1113,6 @@ The callback parameters: Returns `true` if the runtime loop must continue to run. - #### Functions ##### `node_embedding_runtime_run_event_loop` @@ -1103,7 +1136,6 @@ Returns `napi_ok` if there were no issues. The function exits when there are no more tasks to process in the loop. - ##### `node_embedding_runtime_run_event_loop_while` + +> Stability: 1 - Experimental + +Runs Node.js main function as if it is invoked from Node.js CLI without any +embedder customizations. + +```c +int32_t NAPI_CDECL +node_embedding_run_nodejs_main(int32_t argc, + char* argv[]); +``` + +- `[in] argc`: Number of items in the `argv` array. +- `[in] argv`: CLI arguments as an array of zero terminating strings. + +Returns 0 if there were no issues. + ##### `node_embedding_on_error` + +> Stability: 1 - Experimental + +The exit code returned from the C Node.js embedding APIs. + +```c +typedef enum { + node_embedding_exit_code_ok = 0, + node_embedding_exit_code_generic_user_error = 1, + node_embedding_exit_code_internal_js_parse_error = 3, + node_embedding_exit_code_internal_js_evaluation_failure = 4, + node_embedding_exit_code_v8_fatal_error = 5, + node_embedding_exit_code_invalid_fatal_exception_monkey_patching = 6, + node_embedding_exit_code_exception_in_fatal_exception_handler = 7, + node_embedding_exit_code_invalid_command_line_argument = 9, + node_embedding_exit_code_bootstrap_failure = 10, + node_embedding_exit_code_invalid_command_line_argument2 = 12, + node_embedding_exit_code_unsettled_top_level_await = 13, + node_embedding_exit_code_startup_snapshot_failure = 14, + node_embedding_exit_code_abort = 134, +} node_embedding_exit_code; +``` + +These values match to the C++ `node::ExitCode` enum that are used as Node.js +process exit codes. + +- `node_embedding_exit_code_ok` - No issues. +- `node_embedding_exit_code_generic_user_error` - It was originally intended for + uncaught JS exceptions from the user land but we actually use this for all + kinds of generic errors. +- `node_embedding_exit_code_internal_js_parse_error` - It is unused because we + pre-compile all builtins during snapshot building, when we exit with 1 if + there's any error. +- `node_embedding_exit_code_internal_js_evaluation_failure` - It is actually + unused. We exit with 1 in this case. +- `node_embedding_exit_code_v8_fatal_error` - It is actually unused. We exit + with 133 (128+`SIGTRAP`) or 134 (128+`SIGABRT`) in this case. +- `node_embedding_exit_code_invalid_fatal_exception_monkey_patching` +- `node_embedding_exit_code_exception_in_fatal_exception_handler` +- `node_embedding_exit_code_invalid_command_line_argument` +- `node_embedding_exit_code_bootstrap_failure` +- `node_embedding_exit_code_invalid_command_line_argument2` - This was intended + for invalid inspector arguments but is actually now just a duplicate of + `node_embedding_exit_code_invalid_command_line_argument`. +- `node_embedding_exit_code_unsettled_top_level_await` - +- `node_embedding_exit_code_startup_snapshot_failure` - +- `node_embedding_exit_code_abort` - If the process exits from unhandled signals + e.g. `SIGABRT`, `SIGTRAP`, typically the exit codes are 128 + signal number. + We also exit with certain error codes directly for legacy reasons. Here we + define those that are used to normalize the exit code on Windows. + ##### `node_embedding_platform_flags` + +> Stability: 1 - Experimental + +The state of the completed `Promise`. + +```c +typedef enum { + node_embedding_promise_state_pending = 0, + node_embedding_promise_state_fulfilled = 1, + node_embedding_promise_state_rejected = 2, +} node_embedding_promise_state; +``` + +These values match to `v8::Promise::PromiseState` enum and indicate the internal +state of a `Promise` object. + +- `node_embedding_promise_state_pending` - The Promise is still awaiting to + be completed. +- `node_embedding_promise_state_fulfilled` - The Promise was successfully + fulfilled. +- `node_embedding_promise_state_rejected` - The Promise was rejected due an + error. + #### Callback types ##### `node_embedding_event_loop_predicate` @@ -1148,13 +1230,13 @@ added: REPLACEME Runs Node.js runtime instance event loop. ```c -napi_status NAPI_CDECL +node_embedding_exit_code NAPI_CDECL node_embedding_runtime_run_event_loop(node_embedding_runtime runtime); ``` - `[in] runtime`: The Node.js runtime instance. -Returns `napi_ok` if there were no issues. +Returns `node_embedding_exit_code_ok` if there were no issues. The function exits when there are no more tasks to process in the loop. @@ -1170,7 +1252,7 @@ Runs Node.js runtime instance event loop while there tasks to process and the provided predicate returns true. ```c -napi_status NAPI_CDECL +node_embedding_exit_code NAPI_CDECL node_embedding_runtime_run_event_loop_while( node_embedding_runtime runtime, node_embedding_runtime_event_loop_predicate predicate, @@ -1192,7 +1274,7 @@ node_embedding_runtime_run_event_loop_while( - `[out] has_more_work`: `true` if the runtime event loop has more tasks after returning from the function. -Returns `napi_ok` if there were no issues. +Returns `node_embedding_exit_code_ok` if there were no issues. ##### `node_embedding_runtime_await_promise` @@ -1207,20 +1289,23 @@ with a success of a failure. It blocks the thread if there are to tasks in the loop and the promise is not completed. ```c -napi_status NAPI_CDECL +node_embedding_exit_code NAPI_CDECL node_embedding_runtime_await_promise(node_embedding_runtime runtime, napi_value promise, + node_embedding_promise_state* state, napi_value* result, bool* has_more_work); ``` - `[in] runtime`: The Node.js runtime instance. - `[in] promise`: The promise to complete. -- `[out] result`: Result of the `promise` completion. +- `[out] state`: The state of the `promise` upon the return from the function. +- `[out] result`: Result of the `promise` completion. It is either fulfilled or + rejected value depending on `state`. - `[out] has_more_work`: `true` if the runtime event loop has more tasks after returning from the function. -Returns `napi_ok` if there were no issues. +Returns `node_embedding_exit_code_ok` if there were no issues. ### JavaScript/Native interop APIs @@ -1237,7 +1322,7 @@ added: REPLACEME Sets the Node-API version for the Node.js runtime instance. ```c -napi_status NAPI_CDECL +node_embedding_exit_code NAPI_CDECL node_runtime_set_node_api_version(node_embedding_runtime runtime, int32_t node_api_version); ``` @@ -1245,7 +1330,7 @@ node_runtime_set_node_api_version(node_embedding_runtime runtime, - `[in] runtime`: The Node.js runtime instance. - `[in] node_api_version`: The version of the Node-API. -Returns `napi_ok` if there were no issues. +Returns `node_embedding_exit_code_ok` if there were no issues. By default it is using the Node-API version 8. @@ -1260,15 +1345,15 @@ added: REPLACEME Gets `napi_env` associated with the Node.js runtime instance. ```c -napi_status NAPI_CDECL +node_embedding_exit_code NAPI_CDECL node_embedding_runtime_get_node_api_env(node_runtime embedding_runtime, - napi_env* env); + napi_env* env); ``` - `[in] runtime`: The Node.js runtime instance. -- `[out] env`: An instance of `napi_env`. +- `[out] env`: An instance of Node API environment. -Returns `napi_ok` if there were no issues. +Returns `node_embedding_exit_code_ok` if there were no issues. ##### `node_embedding_runtime_open_scope` @@ -1281,13 +1366,13 @@ added: REPLACEME Opens V8 Isolate and Context scope associated with the Node.js runtime instance. ```c -napi_status NAPI_CDECL +node_embedding_exit_code NAPI_CDECL node_embedding_runtime_open_scope(node_embedding_runtime runtime); ``` - `[in] runtime`: The Node.js runtime instance. -Returns `napi_ok` if there were no issues. +Returns `node_embedding_exit_code_ok` if there were no issues. Any Node-API function call requires the runtime scope to be opened for the current thread. @@ -1309,13 +1394,13 @@ Closes V8 Isolate and Context scope associated with the Node.js runtime instance. ```c -napi_status NAPI_CDECL +node_embedding_exit_code NAPI_CDECL node_embedding_runtime_close_scope(node_embedding_runtime runtime); ``` - `[in] runtime`: The Node.js embedding_runtime instance. -Returns `napi_ok` if there were no issues. +Returns `node_embedding_exit_code_ok` if there were no issues. Any Node-API function call requires the runtime scope to be opened for the current thread. Each opened runtime scoped must be closed in the end. diff --git a/src/node_embedding_api.cc b/src/node_embedding_api.cc index 8a3827dd537ebf..2d41cdc82e0dc9 100644 --- a/src/node_embedding_api.cc +++ b/src/node_embedding_api.cc @@ -18,8 +18,7 @@ "Argument must not be null: " #platform, \ __FILE__, \ __LINE__, \ - 1, \ - napi_invalid_arg) \ + node_embedding_exit_code_generic_user_error) \ : reinterpret_cast(platform) #define EMBEDDED_RUNTIME(runtime) \ @@ -27,8 +26,7 @@ "Argument must not be null: " #runtime, \ __FILE__, \ __LINE__, \ - 1, \ - napi_invalid_arg) \ + node_embedding_exit_code_generic_user_error) \ : reinterpret_cast(runtime) #define ARG_NOT_NULL(arg) \ @@ -38,8 +36,7 @@ "Argument must not be null: " #arg, \ __FILE__, \ __LINE__, \ - 1, \ - napi_invalid_arg); \ + node_embedding_exit_code_generic_user_error); \ } \ } while (false) @@ -50,8 +47,7 @@ "Expression returned false: " #expr, \ __FILE__, \ __LINE__, \ - 1, \ - napi_generic_failure); \ + node_embedding_exit_code_generic_user_error); \ } \ } while (false) @@ -114,30 +110,29 @@ class CStringArray { class EmbeddedErrorHandling { public: - static napi_status SetErrorHandler(node_embedding_error_handler error_handler, - void* error_handler_data); + static node_embedding_exit_code SetErrorHandler( + node_embedding_error_handler error_handler, void* error_handler_data); - static napi_status HandleError(const std::string& message, - int32_t exit_code, - napi_status status); + static node_embedding_exit_code HandleError( + const std::string& message, node_embedding_exit_code exit_code); - static napi_status HandleError(const std::vector& messages, - int32_t exit_code, - napi_status status); + static node_embedding_exit_code HandleError( + const std::vector& messages, + node_embedding_exit_code exit_code); - static napi_status HandleError(const char* message, - const char* filename, - int32_t line, - int32_t exit_code, - napi_status status); + static node_embedding_exit_code HandleError( + const char* message, + const char* filename, + int32_t line, + node_embedding_exit_code exit_code); static std::string FormatString(const char* format, ...); - static napi_status DefaultErrorHandler(void* handler_data, - const char* messages[], - size_t messages_size, - int32_t exit_code, - napi_status status); + static node_embedding_exit_code DefaultErrorHandler( + void* handler_data, + const char* messages[], + size_t messages_size, + node_embedding_exit_code exit_code); static node_embedding_error_handler error_handler() { return error_handler_ ? error_handler_ : DefaultErrorHandler; @@ -159,23 +154,23 @@ class EmbeddedPlatform { EmbeddedPlatform(const EmbeddedPlatform&) = delete; EmbeddedPlatform& operator=(const EmbeddedPlatform&) = delete; - napi_status DeleteMe(); + node_embedding_exit_code DeleteMe(); - napi_status IsInitialized(bool* result); + node_embedding_exit_code IsInitialized(bool* result); - napi_status SetFlags(node_embedding_platform_flags flags); + node_embedding_exit_code SetFlags(node_embedding_platform_flags flags); - napi_status SetArgs(int32_t argc, char* argv[]); + node_embedding_exit_code SetArgs(int32_t argc, char* argv[]); - napi_status Initialize(bool* early_return); + node_embedding_exit_code Initialize(bool* early_return); - napi_status GetArgs(node_embedding_get_args_callback get_args, - void* get_args_data); + node_embedding_exit_code GetArgs(node_embedding_get_args_callback get_args, + void* get_args_data); - napi_status GetExecArgs(node_embedding_get_args_callback get_args, - void* get_args_data); + node_embedding_exit_code GetExecArgs( + node_embedding_get_args_callback get_args, void* get_args_data); - napi_status CreateRuntime(node_embedding_runtime* result); + node_embedding_exit_code CreateRuntime(node_embedding_runtime* result); node::InitializationResult* init_result() { return init_result_.get(); } @@ -228,52 +223,55 @@ class EmbeddedRuntime { public: explicit EmbeddedRuntime(EmbeddedPlatform* platform); - napi_status DeleteMe(); + node_embedding_exit_code DeleteMe(); - napi_status IsInitialized(bool* result); + node_embedding_exit_code IsInitialized(bool* result); - napi_status SetFlags(node_embedding_runtime_flags flags); + node_embedding_exit_code SetFlags(node_embedding_runtime_flags flags); - napi_status SetArgs(int32_t argc, const char* argv[]); + node_embedding_exit_code SetArgs(int32_t argc, const char* argv[]); - napi_status SetExecArgs(int32_t argc, const char* argv[]); + node_embedding_exit_code SetExecArgs(int32_t argc, const char* argv[]); - napi_status SetPreloadCallback(node_embedding_preload_callback preload_cb, - void* preload_cb_data); + node_embedding_exit_code SetPreloadCallback( + node_embedding_preload_callback preload_cb, void* preload_cb_data); - napi_status SetSnapshotBlob(const uint8_t* snapshot, size_t size); + node_embedding_exit_code SetSnapshotBlob(const uint8_t* snapshot, + size_t size); - napi_status OnCreateSnapshotBlob( + node_embedding_exit_code OnCreateSnapshotBlob( node_embedding_store_blob_callback store_blob_cb, void* store_blob_cb_data, node_embedding_snapshot_flags snapshot_flags); - napi_status AddModule( + node_embedding_exit_code AddModule( const char* module_name, node_embedding_initialize_module_callback init_module_cb, void* init_module_cb_data, int32_t module_node_api_version); - napi_status Initialize(const char* main_script); + node_embedding_exit_code Initialize(const char* main_script); - napi_status RunEventLoop(); + node_embedding_exit_code RunEventLoop(); - napi_status RunEventLoopWhile(node_embedding_event_loop_predicate predicate, - void* predicate_data, - node_embedding_event_loop_run_mode run_mode, - bool* has_more_work); + node_embedding_exit_code RunEventLoopWhile( + node_embedding_event_loop_predicate predicate, + void* predicate_data, + node_embedding_event_loop_run_mode run_mode, + bool* has_more_work); - napi_status AwaitPromise(napi_value promise, - napi_value* result, - bool* has_more_work); + node_embedding_exit_code AwaitPromise(napi_value promise, + node_embedding_promise_state* state, + napi_value* result, + bool* has_more_work); - napi_status SetNodeApiVersion(int32_t node_api_version); + node_embedding_exit_code SetNodeApiVersion(int32_t node_api_version); - napi_status GetNodeApiEnv(napi_env* env); + node_embedding_exit_code GetNodeApiEnv(napi_env* env); - napi_status OpenScope(); + node_embedding_exit_code OpenScope(); - napi_status CloseScope(); + node_embedding_exit_code CloseScope(); bool IsScopeOpened() const; @@ -339,62 +337,54 @@ class EmbeddedRuntime { // EmbeddedErrorHandling implementation. //----------------------------------------------------------------------------- -napi_status EmbeddedErrorHandling::SetErrorHandler( +node_embedding_exit_code EmbeddedErrorHandling::SetErrorHandler( node_embedding_error_handler error_handler, void* error_handler_data) { error_handler_ = error_handler; error_handler_data_ = error_handler_data; - return napi_ok; + return node_embedding_exit_code_ok; } -napi_status EmbeddedErrorHandling::HandleError(const std::string& message, - int32_t exit_code, - napi_status status) { +node_embedding_exit_code EmbeddedErrorHandling::HandleError( + const std::string& message, node_embedding_exit_code exit_code) { const char* message_c_str = message.c_str(); - return error_handler()( - error_handler_data_, &message_c_str, 1, exit_code, status); + return error_handler()(error_handler_data_, &message_c_str, 1, exit_code); } -napi_status EmbeddedErrorHandling::HandleError( +node_embedding_exit_code EmbeddedErrorHandling::HandleError( const std::vector& messages, - int32_t exit_code, - napi_status status) { + node_embedding_exit_code exit_code) { CStringArray message_arr(messages); - return error_handler()(error_handler_data_, - message_arr.c_strs(), - message_arr.size(), - exit_code, - status); -} - -napi_status EmbeddedErrorHandling::HandleError(const char* message, - const char* filename, - int32_t line, - int32_t exit_code, - napi_status status) { + return error_handler()( + error_handler_data_, message_arr.c_strs(), message_arr.size(), exit_code); +} + +node_embedding_exit_code EmbeddedErrorHandling::HandleError( + const char* message, + const char* filename, + int32_t line, + node_embedding_exit_code exit_code) { return HandleError( - FormatString("Error: %s at %s:%d", message, filename, line), - exit_code, - status); + FormatString("Error: %s at %s:%d", message, filename, line), exit_code); } -napi_status EmbeddedErrorHandling::DefaultErrorHandler(void* /*handler_data*/, - const char* messages[], - size_t messages_size, - int32_t exit_code, - napi_status status) { +node_embedding_exit_code EmbeddedErrorHandling::DefaultErrorHandler( + void* /*handler_data*/, + const char* messages[], + size_t messages_size, + node_embedding_exit_code exit_code) { if (exit_code != 0) { for (size_t i = 0; i < messages_size; ++i) { fprintf(stderr, "%s\n", messages[i]); } fflush(stderr); - exit(exit_code); + node::Exit(static_cast(exit_code)); } else { for (size_t i = 0; i < messages_size; ++i) { fprintf(stdout, "%s\n", messages[i]); } fflush(stdout); } - return status; + return exit_code; } std::string EmbeddedErrorHandling::FormatString(const char* format, ...) { @@ -413,7 +403,7 @@ std::string EmbeddedErrorHandling::FormatString(const char* format, ...) { // EmbeddedPlatform implementation. //----------------------------------------------------------------------------- -napi_status EmbeddedPlatform::DeleteMe() { +node_embedding_exit_code EmbeddedPlatform::DeleteMe() { if (v8_is_initialized_ && !v8_is_uninitialized_) { v8::V8::Dispose(); v8::V8::DisposePlatform(); @@ -422,36 +412,37 @@ napi_status EmbeddedPlatform::DeleteMe() { } delete this; - return napi_ok; + return node_embedding_exit_code_ok; } -napi_status EmbeddedPlatform::IsInitialized(bool* result) { +node_embedding_exit_code EmbeddedPlatform::IsInitialized(bool* result) { ARG_NOT_NULL(result); *result = is_initialized_; - return napi_ok; + return node_embedding_exit_code_ok; } -napi_status EmbeddedPlatform::SetFlags(node_embedding_platform_flags flags) { +node_embedding_exit_code EmbeddedPlatform::SetFlags( + node_embedding_platform_flags flags) { ASSERT(!is_initialized_); flags_ = flags; optional_bits_.flags = true; - return napi_ok; + return node_embedding_exit_code_ok; } -napi_status EmbeddedPlatform::SetArgs(int32_t argc, char* argv[]) { +node_embedding_exit_code EmbeddedPlatform::SetArgs(int32_t argc, char* argv[]) { ARG_NOT_NULL(argv); ASSERT(!is_initialized_); args_.assign(argv, argv + argc); optional_bits_.args = true; - return napi_ok; + return node_embedding_exit_code_ok; } -napi_status EmbeddedPlatform::Initialize(bool* early_return) { +node_embedding_exit_code EmbeddedPlatform::Initialize(bool* early_return) { ASSERT(!is_initialized_); is_initialized_ = true; - // TODO: (vmoroz) default initialize args_. + // TODO(vmoroz): default initialize args_. if (!optional_bits_.flags) { flags_ = node_embedding_platform_no_flags; @@ -461,9 +452,9 @@ napi_status EmbeddedPlatform::Initialize(bool* early_return) { args_, GetProcessInitializationFlags(flags_)); if (init_result_->exit_code() != 0 || !init_result_->errors().empty()) { - return EmbeddedErrorHandling::HandleError(init_result_->errors(), - init_result_->exit_code(), - napi_generic_failure); + return EmbeddedErrorHandling::HandleError( + init_result_->errors(), + static_cast(init_result_->exit_code())); } if (early_return != nullptr) { @@ -473,7 +464,7 @@ napi_status EmbeddedPlatform::Initialize(bool* early_return) { } if (init_result_->early_return()) { - return init_result_->exit_code() == 0 ? napi_ok : napi_generic_failure; + return static_cast(init_result_->exit_code()); } int32_t thread_pool_size = @@ -484,10 +475,10 @@ napi_status EmbeddedPlatform::Initialize(bool* early_return) { v8_is_initialized_ = true; - return napi_ok; + return node_embedding_exit_code_ok; } -napi_status EmbeddedPlatform::GetArgs( +node_embedding_exit_code EmbeddedPlatform::GetArgs( node_embedding_get_args_callback get_args_cb, void* get_args_cb_data) { ARG_NOT_NULL(get_args_cb); ASSERT(is_initialized_); @@ -495,10 +486,10 @@ napi_status EmbeddedPlatform::GetArgs( v8impl::CStringArray args(init_result_->args()); get_args_cb(get_args_cb_data, args.argc(), args.argv()); - return napi_ok; + return node_embedding_exit_code_ok; } -napi_status EmbeddedPlatform::GetExecArgs( +node_embedding_exit_code EmbeddedPlatform::GetExecArgs( node_embedding_get_args_callback get_args_cb, void* get_args_cb_data) { ARG_NOT_NULL(get_args_cb); ASSERT(is_initialized_); @@ -506,10 +497,11 @@ napi_status EmbeddedPlatform::GetExecArgs( v8impl::CStringArray args(init_result_->exec_args()); get_args_cb(get_args_cb_data, args.argc(), args.argv()); - return napi_ok; + return node_embedding_exit_code_ok; } -napi_status EmbeddedPlatform::CreateRuntime(node_embedding_runtime* result) { +node_embedding_exit_code EmbeddedPlatform::CreateRuntime( + node_embedding_runtime* result) { ARG_NOT_NULL(result); ASSERT(is_initialized_); ASSERT(v8_is_initialized_); @@ -519,7 +511,7 @@ napi_status EmbeddedPlatform::CreateRuntime(node_embedding_runtime* result) { *result = reinterpret_cast(runtime.release()); - return napi_ok; + return node_embedding_exit_code_ok; } node::ProcessInitializationFlags::Flags @@ -574,16 +566,17 @@ EmbeddedPlatform::GetProcessInitializationFlags( EmbeddedRuntime::EmbeddedRuntime(EmbeddedPlatform* platform) : platform_(platform) {} -napi_status EmbeddedRuntime::DeleteMe() { +node_embedding_exit_code EmbeddedRuntime::DeleteMe() { ASSERT(!IsScopeOpened()); { v8impl::IsolateLocker isolate_locker(env_setup_.get()); - int32_t ret = node::SpinEventLoop(env_setup_->env()).FromMaybe(1); - if (ret != 0) { + node_embedding_exit_code exit_code = static_cast( + node::SpinEventLoop(env_setup_->env()).FromMaybe(1)); + if (exit_code != node_embedding_exit_code_ok) { return EmbeddedErrorHandling::HandleError( - "Failed while closing the runtime", ret, napi_generic_failure); + "Failed while closing the runtime", exit_code); } } @@ -593,45 +586,48 @@ napi_status EmbeddedRuntime::DeleteMe() { if (create_snapshot_) { node::EmbedderSnapshotData::Pointer snapshot = env_setup->CreateSnapshot(); ASSERT(snapshot); - // TODO: (vmoroz) handle error conditions. + // TODO(vmoroz): handle error conditions. create_snapshot_(snapshot.get()); } node::Stop(env_setup->env()); - return napi_ok; + return node_embedding_exit_code_ok; } -napi_status EmbeddedRuntime::IsInitialized(bool* result) { +node_embedding_exit_code EmbeddedRuntime::IsInitialized(bool* result) { ARG_NOT_NULL(result); *result = is_initialized_; - return napi_ok; + return node_embedding_exit_code_ok; } -napi_status EmbeddedRuntime::SetFlags(node_embedding_runtime_flags flags) { +node_embedding_exit_code EmbeddedRuntime::SetFlags( + node_embedding_runtime_flags flags) { ASSERT(!is_initialized_); flags_ = flags; optional_bits_.flags = true; - return napi_ok; + return node_embedding_exit_code_ok; } -napi_status EmbeddedRuntime::SetArgs(int32_t argc, const char* argv[]) { +node_embedding_exit_code EmbeddedRuntime::SetArgs(int32_t argc, + const char* argv[]) { ARG_NOT_NULL(argv); ASSERT(!is_initialized_); args_.assign(argv, argv + argc); optional_bits_.args = true; - return napi_ok; + return node_embedding_exit_code_ok; } -napi_status EmbeddedRuntime::SetExecArgs(int32_t argc, const char* argv[]) { +node_embedding_exit_code EmbeddedRuntime::SetExecArgs(int32_t argc, + const char* argv[]) { ARG_NOT_NULL(argv); ASSERT(!is_initialized_); exec_args_.assign(argv, argv + argc); optional_bits_.exec_args = true; - return napi_ok; + return node_embedding_exit_code_ok; } -napi_status EmbeddedRuntime::SetPreloadCallback( +node_embedding_exit_code EmbeddedRuntime::SetPreloadCallback( node_embedding_preload_callback preload_cb, void* preload_cb_data) { ASSERT(!is_initialized_); @@ -653,20 +649,20 @@ napi_status EmbeddedRuntime::SetPreloadCallback( preload_cb_ = {}; } - return napi_ok; + return node_embedding_exit_code_ok; } -napi_status EmbeddedRuntime::SetSnapshotBlob(const uint8_t* snapshot, - size_t size) { +node_embedding_exit_code EmbeddedRuntime::SetSnapshotBlob( + const uint8_t* snapshot, size_t size) { ARG_NOT_NULL(snapshot); ASSERT(!is_initialized_); snapshot_ = node::EmbedderSnapshotData::FromBlob( std::string_view(reinterpret_cast(snapshot), size)); - return napi_ok; + return node_embedding_exit_code_ok; } -napi_status EmbeddedRuntime::OnCreateSnapshotBlob( +node_embedding_exit_code EmbeddedRuntime::OnCreateSnapshotBlob( node_embedding_store_blob_callback store_blob_cb, void* store_blob_cb_data, node_embedding_snapshot_flags snapshot_flags) { @@ -687,10 +683,10 @@ napi_status EmbeddedRuntime::OnCreateSnapshotBlob( static_cast(node::SnapshotFlags::kWithoutCodeCache)); } - return napi_ok; + return node_embedding_exit_code_ok; } -napi_status EmbeddedRuntime::AddModule( +node_embedding_exit_code EmbeddedRuntime::AddModule( const char* module_name, node_embedding_initialize_module_callback init_module_cb, void* init_module_cb_data, @@ -708,14 +704,13 @@ napi_status EmbeddedRuntime::AddModule( return EmbeddedErrorHandling::HandleError( EmbeddedErrorHandling::FormatString( "Module with name '%s' is already added.", module_name), - 1, - napi_invalid_arg); + node_embedding_exit_code_generic_user_error); } - return napi_ok; + return node_embedding_exit_code_ok; } -napi_status EmbeddedRuntime::Initialize(const char* main_script) { +node_embedding_exit_code EmbeddedRuntime::Initialize(const char* main_script) { ASSERT(!is_initialized_); is_initialized_ = true; @@ -745,7 +740,8 @@ napi_status EmbeddedRuntime::Initialize(const char* main_script) { } if (env_setup_ == nullptr || !errors.empty()) { - return EmbeddedErrorHandling::HandleError(errors, 1, napi_generic_failure); + return EmbeddedErrorHandling::HandleError( + errors, node_embedding_exit_code_generic_user_error); } v8impl::IsolateLocker isolate_locker(env_setup_.get()); @@ -764,20 +760,20 @@ napi_status EmbeddedRuntime::Initialize(const char* main_script) { : node::LoadEnvironment( node_env, std::string_view(main_script), preload_cb_); - if (ret.IsEmpty()) return napi_pending_exception; + if (ret.IsEmpty()) return node_embedding_exit_code_generic_user_error; - return napi_ok; + return node_embedding_exit_code_ok; } -napi_status EmbeddedRuntime::RunEventLoop() { +node_embedding_exit_code EmbeddedRuntime::RunEventLoop() { if (node::SpinEventLoopWithoutCleanup(env_setup_->env()).IsNothing()) { - return napi_closing; + return node_embedding_exit_code_generic_user_error; } - return napi_ok; + return node_embedding_exit_code_ok; } -napi_status EmbeddedRuntime::RunEventLoopWhile( +node_embedding_exit_code EmbeddedRuntime::RunEventLoopWhile( node_embedding_event_loop_predicate predicate, void* predicate_data, node_embedding_event_loop_run_mode run_mode, @@ -793,7 +789,7 @@ napi_status EmbeddedRuntime::RunEventLoopWhile( return predicate(predicate_data, has_work); }) .IsNothing()) { - return napi_closing; + return node_embedding_exit_code_generic_user_error; } } @@ -801,84 +797,91 @@ napi_status EmbeddedRuntime::RunEventLoopWhile( *has_more_work = uv_loop_alive(env_setup_->env()->event_loop()); } - return napi_ok; + return node_embedding_exit_code_ok; } -napi_status EmbeddedRuntime::AwaitPromise(napi_value promise, - napi_value* result, - bool* has_more_work) { - NAPI_PREAMBLE(node_api_env_); - CHECK_ARG(node_api_env_, result); - - v8::EscapableHandleScope scope(node_api_env_->isolate); - - v8::Local promise_value = v8impl::V8LocalValueFromJsValue(promise); - if (promise_value.IsEmpty() || !promise_value->IsPromise()) - return napi_invalid_arg; - v8::Local promise_object = promise_value.As(); - - v8::Local rejected = - v8::Boolean::New(node_api_env_->isolate, false); - v8::Local err_handler = - v8::Function::New( - node_api_env_->context(), - [](const v8::FunctionCallbackInfo& info) { return; }, - rejected) - .ToLocalChecked(); - - if (promise_object->Catch(node_api_env_->context(), err_handler).IsEmpty()) - return napi_pending_exception; - - if (node::SpinEventLoopWithoutCleanup( - env_setup_->env(), - UV_RUN_ONCE, - [&promise_object](bool /*has_work*/) { - return promise_object->State() == - v8::Promise::PromiseState::kPending; - }) - .IsNothing()) - return napi_closing; +node_embedding_exit_code EmbeddedRuntime::AwaitPromise( + napi_value promise, + node_embedding_promise_state* state, + napi_value* result, + bool* has_more_work) { + napi_status status = [&]() { + NAPI_PREAMBLE(node_api_env_); + CHECK_ARG(node_api_env_, state); + CHECK_ARG(node_api_env_, result); + + v8::EscapableHandleScope scope(node_api_env_->isolate); + + v8::Local promise_value = + v8impl::V8LocalValueFromJsValue(promise); + if (promise_value.IsEmpty() || !promise_value->IsPromise()) + return napi_invalid_arg; + v8::Local promise_object = promise_value.As(); + + v8::Local rejected = + v8::Boolean::New(node_api_env_->isolate, false); + v8::Local err_handler = + v8::Function::New( + node_api_env_->context(), + [](const v8::FunctionCallbackInfo& info) { return; }, + rejected) + .ToLocalChecked(); + + if (promise_object->Catch(node_api_env_->context(), err_handler).IsEmpty()) + return napi_pending_exception; - *result = - v8impl::JsValueFromV8LocalValue(scope.Escape(promise_object->Result())); + if (node::SpinEventLoopWithoutCleanup( + env_setup_->env(), + UV_RUN_ONCE, + [&promise_object](bool /*has_work*/) { + return promise_object->State() == + v8::Promise::PromiseState::kPending; + }) + .IsNothing()) + return napi_closing; - if (has_more_work != nullptr) { - *has_more_work = uv_loop_alive(env_setup_->env()->event_loop()); - } + *state = static_cast(promise_object->State()); + *result = + v8impl::JsValueFromV8LocalValue(scope.Escape(promise_object->Result())); - if (promise_object->State() == v8::Promise::PromiseState::kRejected) - return napi_pending_exception; + if (has_more_work != nullptr) { + *has_more_work = uv_loop_alive(env_setup_->env()->event_loop()); + } - return napi_ok; + return napi_ok; + }(); + return (status == napi_ok) ? node_embedding_exit_code_ok + : node_embedding_exit_code_generic_user_error; } -napi_status EmbeddedRuntime::SetNodeApiVersion(int32_t node_api_version) { +node_embedding_exit_code EmbeddedRuntime::SetNodeApiVersion( + int32_t node_api_version) { ASSERT(!is_initialized_); node_api_version_ = node_api_version; - return napi_ok; + return node_embedding_exit_code_ok; } -napi_status EmbeddedRuntime::GetNodeApiEnv(napi_env* env) { +node_embedding_exit_code EmbeddedRuntime::GetNodeApiEnv(napi_env* env) { ARG_NOT_NULL(env); *env = node_api_env_; - return napi_ok; + return node_embedding_exit_code_ok; } -napi_status EmbeddedRuntime::OpenScope() { +node_embedding_exit_code EmbeddedRuntime::OpenScope() { if (isolate_locker_.has_value()) { ASSERT(isolate_locker_->IsLocked()); isolate_locker_->IncrementLockCount(); } else { isolate_locker_.emplace(env_setup_.get()); } - return napi_ok; + return node_embedding_exit_code_ok; } -napi_status EmbeddedRuntime::CloseScope() { +node_embedding_exit_code EmbeddedRuntime::CloseScope() { ASSERT(isolate_locker_.has_value()); ASSERT(isolate_locker_->IsLocked()); if (isolate_locker_->DecrementLockCount()) isolate_locker_.reset(); - return napi_ok; + return node_embedding_exit_code_ok; } bool EmbeddedRuntime::IsScopeOpened() const { @@ -999,94 +1002,95 @@ void EmbeddedRuntime::RegisterModules() { } // end of anonymous namespace } // end of namespace v8impl -int32_t NAPI_CDECL node_embedding_run_nodejs_main(int32_t argc, char* argv[]) { - return node::Start(argc, argv); +node_embedding_exit_code NAPI_CDECL +node_embedding_run_nodejs_main(int32_t argc, char* argv[]) { + return static_cast(node::Start(argc, argv)); } -napi_status NAPI_CDECL node_embedding_on_error( +node_embedding_exit_code NAPI_CDECL node_embedding_on_error( node_embedding_error_handler error_handler, void* error_handler_data) { return v8impl::EmbeddedErrorHandling::SetErrorHandler(error_handler, error_handler_data); } -napi_status NAPI_CDECL node_embedding_create_platform( +node_embedding_exit_code NAPI_CDECL node_embedding_create_platform( int32_t api_version, node_embedding_platform* result) { ARG_NOT_NULL(result); *result = reinterpret_cast( new v8impl::EmbeddedPlatform(api_version)); - return napi_ok; + return node_embedding_exit_code_ok; } -napi_status NAPI_CDECL +node_embedding_exit_code NAPI_CDECL node_embedding_delete_platform(node_embedding_platform platform) { return EMBEDDED_PLATFORM(platform)->DeleteMe(); } -napi_status NAPI_CDECL node_embedding_platform_is_initialized( +node_embedding_exit_code NAPI_CDECL node_embedding_platform_is_initialized( node_embedding_platform platform, bool* result) { return EMBEDDED_PLATFORM(platform)->IsInitialized(result); } -napi_status NAPI_CDECL node_embedding_platform_set_flags( +node_embedding_exit_code NAPI_CDECL node_embedding_platform_set_flags( node_embedding_platform platform, node_embedding_platform_flags flags) { return EMBEDDED_PLATFORM(platform)->SetFlags(flags); } -napi_status NAPI_CDECL node_embedding_platform_set_args( +node_embedding_exit_code NAPI_CDECL node_embedding_platform_set_args( node_embedding_platform platform, int32_t argc, char* argv[]) { return EMBEDDED_PLATFORM(platform)->SetArgs(argc, argv); } -napi_status NAPI_CDECL node_embedding_platform_initialize( +node_embedding_exit_code NAPI_CDECL node_embedding_platform_initialize( node_embedding_platform platform, bool* early_return) { return EMBEDDED_PLATFORM(platform)->Initialize(early_return); } -napi_status NAPI_CDECL +node_embedding_exit_code NAPI_CDECL node_embedding_platform_get_args(node_embedding_platform platform, node_embedding_get_args_callback get_args, void* get_args_data) { return EMBEDDED_PLATFORM(platform)->GetArgs(get_args, get_args_data); } -napi_status NAPI_CDECL +node_embedding_exit_code NAPI_CDECL node_embedding_platform_get_exec_args(node_embedding_platform platform, node_embedding_get_args_callback get_args, void* get_args_data) { return EMBEDDED_PLATFORM(platform)->GetExecArgs(get_args, get_args_data); } -napi_status NAPI_CDECL node_embedding_create_runtime( +node_embedding_exit_code NAPI_CDECL node_embedding_create_runtime( node_embedding_platform platform, node_embedding_runtime* result) { return EMBEDDED_PLATFORM(platform)->CreateRuntime(result); } -napi_status NAPI_CDECL +node_embedding_exit_code NAPI_CDECL node_embedding_delete_runtime(node_embedding_runtime runtime) { return EMBEDDED_RUNTIME(runtime)->DeleteMe(); } -napi_status NAPI_CDECL node_embedding_runtime_is_initialized( +node_embedding_exit_code NAPI_CDECL node_embedding_runtime_is_initialized( node_embedding_runtime runtime, bool* result) { return EMBEDDED_RUNTIME(runtime)->IsInitialized(result); } -napi_status NAPI_CDECL node_embedding_runtime_set_flags( +node_embedding_exit_code NAPI_CDECL node_embedding_runtime_set_flags( node_embedding_runtime runtime, node_embedding_runtime_flags flags) { return EMBEDDED_RUNTIME(runtime)->SetFlags(flags); } -napi_status NAPI_CDECL node_embedding_runtime_set_args( +node_embedding_exit_code NAPI_CDECL node_embedding_runtime_set_args( node_embedding_runtime runtime, int32_t argc, const char* argv[]) { return EMBEDDED_RUNTIME(runtime)->SetArgs(argc, argv); } -napi_status NAPI_CDECL node_embedding_runtime_set_exec_args( +node_embedding_exit_code NAPI_CDECL node_embedding_runtime_set_exec_args( node_embedding_runtime runtime, int32_t argc, const char* argv[]) { return EMBEDDED_RUNTIME(runtime)->SetExecArgs(argc, argv); } -napi_status NAPI_CDECL +node_embedding_exit_code NAPI_CDECL node_embedding_runtime_on_preload(node_embedding_runtime runtime, node_embedding_preload_callback preload_cb, void* preload_cb_data) { @@ -1094,12 +1098,12 @@ node_embedding_runtime_on_preload(node_embedding_runtime runtime, preload_cb_data); } -napi_status NAPI_CDECL node_embedding_runtime_use_snapshot( +node_embedding_exit_code NAPI_CDECL node_embedding_runtime_use_snapshot( node_embedding_runtime runtime, const uint8_t* snapshot, size_t size) { return EMBEDDED_RUNTIME(runtime)->SetSnapshotBlob(snapshot, size); } -napi_status NAPI_CDECL node_embedding_runtime_on_create_snapshot( +node_embedding_exit_code NAPI_CDECL node_embedding_runtime_on_create_snapshot( node_embedding_runtime runtime, node_embedding_store_blob_callback store_blob_cb, void* store_blob_cb_data, @@ -1108,7 +1112,7 @@ napi_status NAPI_CDECL node_embedding_runtime_on_create_snapshot( store_blob_cb, store_blob_cb_data, snapshot_flags); } -NAPI_EXTERN napi_status NAPI_CDECL node_embedding_runtime_add_module( +node_embedding_exit_code NAPI_CDECL node_embedding_runtime_add_module( node_embedding_runtime runtime, const char* module_name, node_embedding_initialize_module_callback init_module_cb, @@ -1120,17 +1124,17 @@ NAPI_EXTERN napi_status NAPI_CDECL node_embedding_runtime_add_module( module_node_api_version); } -napi_status NAPI_CDECL node_embedding_runtime_initialize( +node_embedding_exit_code NAPI_CDECL node_embedding_runtime_initialize( node_embedding_runtime runtime, const char* main_script) { return EMBEDDED_RUNTIME(runtime)->Initialize(main_script); } -napi_status NAPI_CDECL +node_embedding_exit_code NAPI_CDECL node_embedding_runtime_run_event_loop(node_embedding_runtime runtime) { return EMBEDDED_RUNTIME(runtime)->RunEventLoop(); } -napi_status NAPI_CDECL node_embedding_runtime_run_event_loop_while( +node_embedding_exit_code NAPI_CDECL node_embedding_runtime_run_event_loop_while( node_embedding_runtime runtime, node_embedding_event_loop_predicate predicate, void* predicate_data, @@ -1140,31 +1144,32 @@ napi_status NAPI_CDECL node_embedding_runtime_run_event_loop_while( predicate, predicate_data, run_mode, has_more_work); } -napi_status NAPI_CDECL +node_embedding_exit_code NAPI_CDECL node_embedding_runtime_await_promise(node_embedding_runtime runtime, napi_value promise, + node_embedding_promise_state* state, napi_value* result, bool* has_more_work) { return EMBEDDED_RUNTIME(runtime)->AwaitPromise( - promise, result, has_more_work); + promise, state, result, has_more_work); } -napi_status NAPI_CDECL node_embedding_runtime_set_node_api_version( +node_embedding_exit_code NAPI_CDECL node_embedding_runtime_set_node_api_version( node_embedding_runtime runtime, int32_t node_api_version) { return EMBEDDED_RUNTIME(runtime)->SetNodeApiVersion(node_api_version); } -napi_status NAPI_CDECL node_embedding_runtime_get_node_api_env( +node_embedding_exit_code NAPI_CDECL node_embedding_runtime_get_node_api_env( node_embedding_runtime runtime, napi_env* env) { return EMBEDDED_RUNTIME(runtime)->GetNodeApiEnv(env); } -napi_status NAPI_CDECL +node_embedding_exit_code NAPI_CDECL node_embedding_runtime_open_scope(node_embedding_runtime runtime) { return EMBEDDED_RUNTIME(runtime)->OpenScope(); } -napi_status NAPI_CDECL +node_embedding_exit_code NAPI_CDECL node_embedding_runtime_close_scope(node_embedding_runtime runtime) { return EMBEDDED_RUNTIME(runtime)->CloseScope(); } diff --git a/src/node_embedding_api.h b/src/node_embedding_api.h index 544755ce78dda8..b6e880581af9d5 100644 --- a/src/node_embedding_api.h +++ b/src/node_embedding_api.h @@ -29,6 +29,38 @@ EXTERN_C_START typedef struct node_embedding_platform__* node_embedding_platform; typedef struct node_embedding_runtime__* node_embedding_runtime; +typedef enum { + node_embedding_exit_code_ok = 0, + // 1 was intended for uncaught JS exceptions from the user land but we + // actually use this for all kinds of generic errors. + node_embedding_exit_code_generic_user_error = 1, + // 2 is unused + // 3 is actually unused because we pre-compile all builtins during + // snapshot building, when we exit with 1 if there's any error. + node_embedding_exit_code_internal_js_parse_error = 3, + // 4 is actually unused. We exit with 1 in this case. + node_embedding_exit_code_internal_js_evaluation_failure = 4, + // 5 is actually unused. We exit with 133 (128+SIGTRAP) or 134 + // (128+SIGABRT) in this case. + node_embedding_exit_code_v8_fatal_error = 5, + node_embedding_exit_code_invalid_fatal_exception_monkey_patching = 6, + node_embedding_exit_code_exception_in_fatal_exception_handler = 7, + // 8 is unused + node_embedding_exit_code_invalid_command_line_argument = 9, + node_embedding_exit_code_bootstrap_failure = 10, + // 11 is unused + // This was intended for invalid inspector arguments but is actually now + // just a duplicate of node_embedding_exit_code_invalid_command_line_argument + node_embedding_exit_code_invalid_command_line_argument2 = 12, + node_embedding_exit_code_unsettled_top_level_await = 13, + node_embedding_exit_code_startup_snapshot_failure = 14, + // If the process exits from unhandled signals e.g. SIGABRT, SIGTRAP, + // typically the exit codes are 128 + signal number. We also exit with + // certain error codes directly for legacy reasons. Here we define those + // that are used to normalize the exit code on Windows. + node_embedding_exit_code_abort = 134, +} node_embedding_exit_code; + typedef enum { node_embedding_platform_no_flags = 0, // Enable stdio inheritance, which is disabled by default. @@ -131,16 +163,21 @@ typedef enum { node_embedding_event_loop_run_nowait = 2, } node_embedding_event_loop_run_mode; +typedef enum { + node_embedding_promise_state_pending = 0, + node_embedding_promise_state_fulfilled = 1, + node_embedding_promise_state_rejected = 2, +} node_embedding_promise_state; + //============================================================================== // Callbacks //============================================================================== -typedef napi_status(NAPI_CDECL* node_embedding_error_handler)( +typedef node_embedding_exit_code(NAPI_CDECL* node_embedding_error_handler)( void* handler_data, const char* messages[], size_t messages_size, - int32_t exit_code, - napi_status status); + node_embedding_exit_code exit_code); typedef void(NAPI_CDECL* node_embedding_get_args_callback)(void* cb_data, int32_t argc, @@ -170,15 +207,15 @@ typedef bool(NAPI_CDECL* node_embedding_event_loop_predicate)( // Runs Node.js main function as if it is invoked from Node.js CLI without any // embedder customizations. -NAPI_EXTERN int32_t NAPI_CDECL node_embedding_run_nodejs_main(int32_t argc, - char* argv[]); +NAPI_EXTERN node_embedding_exit_code NAPI_CDECL +node_embedding_run_nodejs_main(int32_t argc, char* argv[]); //------------------------------------------------------------------------------ // Error handling functions. //------------------------------------------------------------------------------ // Sets the global error handing for the Node.js embedding API. -NAPI_EXTERN napi_status NAPI_CDECL node_embedding_on_error( +NAPI_EXTERN node_embedding_exit_code NAPI_CDECL node_embedding_on_error( node_embedding_error_handler error_handler, void* error_handler_data); //------------------------------------------------------------------------------ @@ -186,37 +223,43 @@ NAPI_EXTERN napi_status NAPI_CDECL node_embedding_on_error( //------------------------------------------------------------------------------ // Creates a new Node.js platform instance. -NAPI_EXTERN napi_status NAPI_CDECL node_embedding_create_platform( +NAPI_EXTERN node_embedding_exit_code NAPI_CDECL node_embedding_create_platform( int32_t api_version, node_embedding_platform* result); // Deletes the Node.js platform instance. -NAPI_EXTERN napi_status NAPI_CDECL +NAPI_EXTERN node_embedding_exit_code NAPI_CDECL node_embedding_delete_platform(node_embedding_platform platform); // Checks if the Node.js platform is initialized. -NAPI_EXTERN napi_status NAPI_CDECL node_embedding_platform_is_initialized( - node_embedding_platform platform, bool* result); +NAPI_EXTERN node_embedding_exit_code NAPI_CDECL +node_embedding_platform_is_initialized(node_embedding_platform platform, + bool* result); // Sets the flags for the Node.js platform initialization. -NAPI_EXTERN napi_status NAPI_CDECL node_embedding_platform_set_flags( - node_embedding_platform platform, node_embedding_platform_flags flags); +NAPI_EXTERN node_embedding_exit_code NAPI_CDECL +node_embedding_platform_set_flags(node_embedding_platform platform, + node_embedding_platform_flags flags); // Sets the CLI arguments for the Node.js platform initialization. -NAPI_EXTERN napi_status NAPI_CDECL node_embedding_platform_set_args( - node_embedding_platform platform, int32_t argc, char* argv[]); +NAPI_EXTERN node_embedding_exit_code NAPI_CDECL +node_embedding_platform_set_args(node_embedding_platform platform, + int32_t argc, + char* argv[]); // Initializes the Node.js platform. -NAPI_EXTERN napi_status NAPI_CDECL node_embedding_platform_initialize( - node_embedding_platform platform, bool* early_return); +NAPI_EXTERN node_embedding_exit_code NAPI_CDECL +node_embedding_platform_initialize(node_embedding_platform platform, + bool* early_return); // Gets the parsed list of non-Node.js arguments. -NAPI_EXTERN napi_status NAPI_CDECL +NAPI_EXTERN node_embedding_exit_code NAPI_CDECL node_embedding_platform_get_args(node_embedding_platform platform, node_embedding_get_args_callback get_args_cb, void* get_args_cb_data); // Gets the parsed list of Node.js arguments. -NAPI_EXTERN napi_status NAPI_CDECL node_embedding_platform_get_exec_args( +NAPI_EXTERN node_embedding_exit_code NAPI_CDECL +node_embedding_platform_get_exec_args( node_embedding_platform platform, node_embedding_get_args_callback get_args_cb, void* get_args_cb_data); @@ -226,41 +269,48 @@ NAPI_EXTERN napi_status NAPI_CDECL node_embedding_platform_get_exec_args( //------------------------------------------------------------------------------ // Creates a new Node.js runtime instance. -NAPI_EXTERN napi_status NAPI_CDECL node_embedding_create_runtime( +NAPI_EXTERN node_embedding_exit_code NAPI_CDECL node_embedding_create_runtime( node_embedding_platform platform, node_embedding_runtime* result); // Deletes the Node.js runtime instance. -NAPI_EXTERN napi_status NAPI_CDECL +NAPI_EXTERN node_embedding_exit_code NAPI_CDECL node_embedding_delete_runtime(node_embedding_runtime runtime); // Checks if the Node.js runtime is initialized. -NAPI_EXTERN napi_status NAPI_CDECL node_embedding_runtime_is_initialized( - node_embedding_runtime runtime, bool* result); +NAPI_EXTERN node_embedding_exit_code NAPI_CDECL +node_embedding_runtime_is_initialized(node_embedding_runtime runtime, + bool* result); // Sets the flags for the Node.js runtime initialization. -NAPI_EXTERN napi_status NAPI_CDECL node_embedding_runtime_set_flags( - node_embedding_runtime runtime, node_embedding_runtime_flags flags); +NAPI_EXTERN node_embedding_exit_code NAPI_CDECL +node_embedding_runtime_set_flags(node_embedding_runtime runtime, + node_embedding_runtime_flags flags); // Sets the non-Node.js CLI arguments for the Node.js runtime initialization. -NAPI_EXTERN napi_status NAPI_CDECL node_embedding_runtime_set_args( +NAPI_EXTERN node_embedding_exit_code NAPI_CDECL node_embedding_runtime_set_args( node_embedding_runtime runtime, int32_t argc, const char* argv[]); // Sets the Node.js CLI arguments for the Node.js runtime initialization. -NAPI_EXTERN napi_status NAPI_CDECL node_embedding_runtime_set_exec_args( - node_embedding_runtime runtime, int32_t argc, const char* argv[]); +NAPI_EXTERN node_embedding_exit_code NAPI_CDECL +node_embedding_runtime_set_exec_args(node_embedding_runtime runtime, + int32_t argc, + const char* argv[]); // Sets the preload callback for the Node.js runtime initialization. -NAPI_EXTERN napi_status NAPI_CDECL +NAPI_EXTERN node_embedding_exit_code NAPI_CDECL node_embedding_runtime_on_preload(node_embedding_runtime runtime, node_embedding_preload_callback preload_cb, void* preload_cb_data); // Sets the snapshot for the Node.js runtime initialization. -NAPI_EXTERN napi_status NAPI_CDECL node_embedding_runtime_use_snapshot( - node_embedding_runtime runtime, const uint8_t* snapshot, size_t size); +NAPI_EXTERN node_embedding_exit_code NAPI_CDECL +node_embedding_runtime_use_snapshot(node_embedding_runtime runtime, + const uint8_t* snapshot, + size_t size); // Sets the snapshot creation parameters for the Node.js runtime initialization. -NAPI_EXTERN napi_status NAPI_CDECL node_embedding_runtime_on_create_snapshot( +NAPI_EXTERN node_embedding_exit_code NAPI_CDECL +node_embedding_runtime_on_create_snapshot( node_embedding_runtime runtime, node_embedding_store_blob_callback store_blob_cb, void* store_blob_cb_data, @@ -269,7 +319,8 @@ NAPI_EXTERN napi_status NAPI_CDECL node_embedding_runtime_on_create_snapshot( // Adds a new module to the Node.js runtime. // It is accessed as process._linkedBinding(module_name) in the main JS and in // the related worker threads. -NAPI_EXTERN napi_status NAPI_CDECL node_embedding_runtime_add_module( +NAPI_EXTERN node_embedding_exit_code NAPI_CDECL +node_embedding_runtime_add_module( node_embedding_runtime runtime, const char* module_name, node_embedding_initialize_module_callback init_module_cb, @@ -277,8 +328,9 @@ NAPI_EXTERN napi_status NAPI_CDECL node_embedding_runtime_add_module( int32_t module_node_api_version); // Initializes the Node.js runtime. -NAPI_EXTERN napi_status NAPI_CDECL node_embedding_runtime_initialize( - node_embedding_runtime runtime, const char* main_script); +NAPI_EXTERN node_embedding_exit_code NAPI_CDECL +node_embedding_runtime_initialize(node_embedding_runtime runtime, + const char* main_script); //------------------------------------------------------------------------------ // Node.js runtime functions for the event loop. @@ -286,13 +338,14 @@ NAPI_EXTERN napi_status NAPI_CDECL node_embedding_runtime_initialize( // Runs the Node.js runtime event loop. // It does not block the calling thread. -NAPI_EXTERN napi_status NAPI_CDECL +NAPI_EXTERN node_embedding_exit_code NAPI_CDECL node_embedding_runtime_run_event_loop(node_embedding_runtime runtime); // Runs the Node.js runtime event loop until the predicate returns false. // It may block the calling thread depending on the is_thread_blocking // parameter. -NAPI_EXTERN napi_status NAPI_CDECL node_embedding_runtime_run_event_loop_while( +NAPI_EXTERN node_embedding_exit_code NAPI_CDECL +node_embedding_runtime_run_event_loop_while( node_embedding_runtime runtime, node_embedding_event_loop_predicate predicate, void* predicate_data, @@ -301,9 +354,10 @@ NAPI_EXTERN napi_status NAPI_CDECL node_embedding_runtime_run_event_loop_while( // Runs the Node.js runtime event loop until the promise is resolved. // It may block the calling thread. -NAPI_EXTERN napi_status NAPI_CDECL +NAPI_EXTERN node_embedding_exit_code NAPI_CDECL node_embedding_runtime_await_promise(node_embedding_runtime runtime, napi_value promise, + node_embedding_promise_state* state, napi_value* result, bool* has_more_work); @@ -312,20 +366,22 @@ node_embedding_runtime_await_promise(node_embedding_runtime runtime, //------------------------------------------------------------------------------ // Sets the Node-API version for the Node.js runtime initialization. -NAPI_EXTERN napi_status NAPI_CDECL node_embedding_runtime_set_node_api_version( - node_embedding_runtime runtime, int32_t node_api_version); +NAPI_EXTERN node_embedding_exit_code NAPI_CDECL +node_embedding_runtime_set_node_api_version(node_embedding_runtime runtime, + int32_t node_api_version); // Gets the Node-API environment associated with the initialized Node.js // runtime. -NAPI_EXTERN napi_status NAPI_CDECL node_embedding_runtime_get_node_api_env( - node_embedding_runtime runtime, napi_env* env); +NAPI_EXTERN node_embedding_exit_code NAPI_CDECL +node_embedding_runtime_get_node_api_env(node_embedding_runtime runtime, + napi_env* env); // Opens a new Node-API scope for the current thread. -NAPI_EXTERN napi_status NAPI_CDECL +NAPI_EXTERN node_embedding_exit_code NAPI_CDECL node_embedding_runtime_open_scope(node_embedding_runtime runtime); // Closes the current Node-API scope for the current thread. -NAPI_EXTERN napi_status NAPI_CDECL +NAPI_EXTERN node_embedding_exit_code NAPI_CDECL node_embedding_runtime_close_scope(node_embedding_runtime runtime); EXTERN_C_END @@ -358,25 +414,24 @@ inline constexpr node_embedding_snapshot_flags operator|( #endif // SRC_NODE_EMBEDDING_API_H_ -// TODO: (vmoroz) Add exit code enum. Replace napi_status with the exit code. -// TODO: (vmoroz) Remove the main_script parameter from the initialize function. -// TODO: (vmoroz) Add startup callback with process and require parameters. -// TODO: (vmoroz) Generate the main script based on the runtime settings. -// TODO: (vmoroz) Set the global inspector for a specific environment. -// TODO: (vmoroz) Start workers from C++. -// TODO: (vmoroz) Worker to inherit parent inspector. -// TODO: (vmoroz) Cancel pending tasks on delete env. -// TODO: (vmoroz) The runtime delete must avoid pumping tasks. -// TODO: (vmoroz) Can we initialize platform again if it returns early? -// TODO: (vmoroz) Add simpler threading model - without open/close scope. -// TODO: (vmoroz) Simplify API use for simple default cases. -// TODO: (vmoroz) Test passing the V8 thread pool size. -// TODO: (vmoroz) Make the args story simpler or clear named. -// TODO: (vmoroz) Consider to have one function to retrieve the both arg types. -// TODO: (vmoroz) Consider to have one function to set the both arg types. -// TODO: (vmoroz) Single runtime by default vs multiple runtimes on demand. -// TODO: (vmoroz) Add a way to terminate the runtime. -// TODO: (vmoroz) Allow to provide custom thread pool from the app. -// TODO: (vmoroz) Follow the UV example that integrates UV loop with QT loop. -// TODO: (vmoroz) Consider adding a v-table for the API functions to simplify +// TODO(vmoroz): Remove the main_script parameter from the initialize function. +// TODO(vmoroz): Add startup callback with process and require parameters. +// TODO(vmoroz): Generate the main script based on the runtime settings. +// TODO(vmoroz): Set the global inspector for a specific environment. +// TODO(vmoroz): Start workers from C++. +// TODO(vmoroz): Worker to inherit parent inspector. +// TODO(vmoroz): Cancel pending tasks on delete env. +// TODO(vmoroz): The runtime delete must avoid pumping tasks. +// TODO(vmoroz): Can we initialize platform again if it returns early? +// TODO(vmoroz): Add simpler threading model - without open/close scope. +// TODO(vmoroz): Simplify API use for simple default cases. +// TODO(vmoroz): Test passing the V8 thread pool size. +// TODO(vmoroz): Make the args story simpler or clear named. +// TODO(vmoroz): Consider to have one function to retrieve the both arg types. +// TODO(vmoroz): Consider to have one function to set the both arg types. +// TODO(vmoroz): Single runtime by default vs multiple runtimes on demand. +// TODO(vmoroz): Add a way to terminate the runtime. +// TODO(vmoroz): Allow to provide custom thread pool from the app. +// TODO(vmoroz): Follow the UV example that integrates UV loop with QT loop. +// TODO(vmoroz): Consider adding a v-table for the API functions to simplify // binding with other languages. diff --git a/test/embedding/embedtest_modules_node_api.cc b/test/embedding/embedtest_modules_node_api.cc index fa7ead2e4f96ca..4154de42b1df10 100644 --- a/test/embedding/embedtest_modules_node_api.cc +++ b/test/embedding/embedtest_modules_node_api.cc @@ -167,8 +167,9 @@ extern "C" int32_t test_main_modules_node_api(int32_t argc, char* argv[]) { size_t bufferlen; CHECK(napi_call_function(env, global, import, 1, &es6, &es6_promise)); + node_embedding_promise_state es6_promise_state; CHECK(node_embedding_runtime_await_promise( - runtime, es6_promise, &es6_module, nullptr)); + runtime, es6_promise, &es6_promise_state, &es6_module, nullptr)); CHECK(napi_get_property(env, es6_module, value, &es6_result)); CHECK(napi_get_value_string_utf8( diff --git a/test/embedding/embedtest_node_api.cc b/test/embedding/embedtest_node_api.cc index 462984332dac16..d16ece19bd9ea8 100644 --- a/test/embedding/embedtest_node_api.cc +++ b/test/embedding/embedtest_node_api.cc @@ -33,13 +33,15 @@ extern "C" int32_t test_main_node_api(int32_t argc, char* argv[]) { napi_status AddUtf8String(std::string& str, napi_env env, napi_value value) { size_t str_size = 0; - napi_status status = napi_get_value_string_utf8(env, value, nullptr, 0, &str_size); + napi_status status = + napi_get_value_string_utf8(env, value, nullptr, 0, &str_size); if (status != napi_ok) { return status; } size_t offset = str.size(); str.resize(offset + str_size); - status = napi_get_value_string_utf8(env, value, &str[0] + offset, str_size + 1, &str_size); + status = napi_get_value_string_utf8( + env, value, &str[0] + offset, str_size + 1, &str_size); return status; } @@ -166,14 +168,12 @@ int32_t waitMeWithCheese(napi_env env, node_embedding_runtime runtime) { FAIL("Result is not a Promise\n"); } - napi_status r = node_embedding_runtime_await_promise( - runtime, promise, &result, nullptr); - if (r != napi_ok && r != napi_pending_exception) { - FAIL("Failed awaiting promise: %d\n", r); - } + node_embedding_promise_state promise_state; + CHECK(node_embedding_runtime_await_promise( + runtime, promise, &promise_state, &result, nullptr)); const char* expected; - if (r == napi_ok) + if (promise_state == node_embedding_promise_state_fulfilled) expected = "waited with cheese"; else expected = "waited without cheese"; diff --git a/test/embedding/embedtest_node_api.h b/test/embedding/embedtest_node_api.h index ad1612b7385311..6b12e5b6586fe7 100644 --- a/test/embedding/embedtest_node_api.h +++ b/test/embedding/embedtest_node_api.h @@ -15,20 +15,20 @@ extern "C" inline void NAPI_CDECL GetArgsVector(void* data, static_cast*>(data)->assign(argv, argv + argc); } -extern "C" inline napi_status NAPI_CDECL HandleTestError(void* handler_data, - const char* messages[], - size_t messages_size, - int32_t exit_code, - napi_status status) { +extern "C" inline node_embedding_exit_code NAPI_CDECL +HandleTestError(void* handler_data, + const char* messages[], + size_t messages_size, + node_embedding_exit_code exit_code) { auto exe_name = static_cast(handler_data); if (exit_code != 0) { for (size_t i = 0; i < messages_size; ++i) fprintf(stderr, "%s: %s\n", exe_name, messages[i]); - exit(exit_code); + exit(static_cast(exit_code)); } else { for (size_t i = 0; i < messages_size; ++i) printf("%s\n", messages[i]); } - return status; + return exit_code; } #endif @@ -39,7 +39,7 @@ napi_status AddUtf8String(std::string& str, napi_env env, napi_value value); #define CHECK(expr) \ do { \ - if ((expr) != napi_ok) { \ + if ((expr) != node_embedding_exit_code_ok) { \ fprintf(stderr, "Failed: %s\n", #expr); \ fprintf(stderr, "File: %s\n", __FILE__); \ fprintf(stderr, "Line: %d\n", __LINE__); \ @@ -73,7 +73,7 @@ napi_status AddUtf8String(std::string& str, napi_env env, napi_value value); #endif // TEST_EMBEDDING_EMBEDTEST_NODE_API_H_ -// TODO: (vmoroz) Enable the test_main_modules_node_api test. -// TODO: (vmoroz) Test failure in Preload callback. -// TODO: (vmoroz) Test failure in linked modules. -// TODO: (vmoroz) Make sure that delete call matches the create call. +// TODO(vmoroz): Enable the test_main_modules_node_api test. +// TODO(vmoroz): Test failure in Preload callback. +// TODO(vmoroz): Test failure in linked modules. +// TODO(vmoroz): Make sure that delete call matches the create call. From cbbe06aa2ce5374bf5ef24f64d3b588f6d8145eb Mon Sep 17 00:00:00 2001 From: Vladimir Morozov Date: Thu, 12 Sep 2024 06:38:01 -0700 Subject: [PATCH 14/22] simplify the Node-API code invocation --- doc/api/embedding.md | 77 ++--- src/node_embedding_api.cc | 61 ++-- src/node_embedding_api.h | 20 +- .../embedtest_concurrent_node_api.cc | 127 ++++---- test/embedding/embedtest_modules_node_api.cc | 87 +++--- test/embedding/embedtest_node_api.cc | 288 ++++++++++-------- test/embedding/embedtest_node_api.h | 52 ++++ test/embedding/test-embedding.js | 24 +- 8 files changed, 383 insertions(+), 353 deletions(-) diff --git a/doc/api/embedding.md b/doc/api/embedding.md index c2af6840d4ea3e..7107ee8d3ad5ca 100644 --- a/doc/api/embedding.md +++ b/doc/api/embedding.md @@ -1309,9 +1309,9 @@ Returns `node_embedding_exit_code_ok` if there were no issues. ### JavaScript/Native interop APIs -#### Functions +#### Callback types -##### `node_embedding_runtime_set_node_api_version` +##### `node_embedding_node_api_callback` - -> Stability: 1 - Experimental - -Gets `napi_env` associated with the Node.js runtime instance. +Function pointer type for callback that invokes Node-API code. -```c -node_embedding_exit_code NAPI_CDECL -node_embedding_runtime_get_node_api_env(node_runtime embedding_runtime, - napi_env* env); -``` +The callback parameters: -- `[in] runtime`: The Node.js runtime instance. -- `[out] env`: An instance of Node API environment. +- `[in] cb_data`: The user data associated with this callback. +- `[in] env`: Node-API environment. -Returns `node_embedding_exit_code_ok` if there were no issues. +#### Functions -##### `node_embedding_runtime_open_scope` +##### `node_embedding_runtime_set_node_api_version` - -> Stability: 1 - Experimental - -Gets the parsed list of Node.js arguments. - -```c -node_embedding_exit_code NAPI_CDECL -node_embedding_platform_get_exec_args( +node_embedding_platform_get_parsed_args( node_embedding_platform platform, node_embedding_get_args_callback get_args_cb, - void* get_args_cb_data); + void* get_args_cb_data, + node_embedding_get_args_callback get_exec_args_cb, + void* get_exec_args_cb_data); ``` - `[in] platform`: The Node.js platform instance. -- `[in] get_args_cb`: The callback to receive Node.js arguments. +- `[in] get_args_cb`: The callback to receive non-Node.js arguments. - `[in] get_args_cb_data`: Optional. The callback data that will be passed to the `get_args_cb` callback. It can be deleted right after the function call. +- `[in] get_exec_args_cb`: The callback to receive Node.js arguments. +- `[in] get_exec_args_cb_data`: Optional. The callback data that will be passed + to the `get_exec_args_cb` callback. It can be deleted right after the function + call. Returns `node_embedding_exit_code_ok` if there were no issues. @@ -968,29 +950,16 @@ Sets the non-Node.js arguments for the Node.js runtime instance. node_embedding_exit_code NAPI_CDECL node_embedding_runtime_set_args(node_embedding_runtime runtime, int32_t argc, - const char* argv[]); + const char* argv[], + int32_t exec_argc, + const char* exec_argv[]); ``` - `[in] runtime`: The Node.js runtime instance to configure. - `[in] argc`: Number of items in the `argv` array. - `[in] argv`: non-Node.js arguments as an array of zero terminating strings. - -Returns `node_embedding_exit_code_ok` if there were no issues. - -##### `node_embedding_runtime_set_exec_args` - -Sets the Node.js arguments for the Node.js runtime instance. - -```c -node_embedding_exit_code NAPI_CDECL -node_embedding_runtime_set_exec_args(node_embedding_runtime runtime, - int32_t argc, - const char* argv[]); -``` - -- `[in] runtime`: The Node.js runtime instance to configure. -- `[in] argc`: Number of items in the `argv` array. -- `[in] argv`: Node.js arguments as an array of zero terminating strings. +- `[in] exec_argc`: Number of items in the `exec_argv` array. +- `[in] exec_argv`: Node.js arguments as an array of zero terminating strings. Returns `node_embedding_exit_code_ok` if there were no issues. @@ -1021,7 +990,7 @@ node_embedding_runtime_on_preload( Returns `node_embedding_exit_code_ok` if there were no issues. -##### `node_embedding_runtime_use_snapshot` +##### `node_embedding_runtime_add_module` - -> Stability: 1 - Experimental - -Flags are used to create a Node.js runtime JavaScript snapshot. - -```c -typedef enum { - node_embedding_snapshot_no_flags = 0, - node_embedding_snapshot_no_code_cache = 1 << 0, -} node_embedding_snapshot_flags; -``` - -These flags match to the C++ `node::SnapshotFlags` and control the -Node.js runtime snapshot creation. - -- `node_embedding_snapshot_no_flags` - No flags set. -- `node_embedding_snapshot_no_code_cache` - Whether code cache should be - generated as part of the snapshot. Code cache reduces the time spent on - compiling functions included in the snapshot at the expense of a bigger - snapshot size and potentially breaking portability of the snapshot. - #### Callback types ##### `node_embedding_runtime_preload_callback` @@ -763,6 +737,7 @@ added: REPLACEME ```c typedef void(NAPI_CDECL* node_embedding_runtime_preload_callback)( + node_embedding_runtime runtime, void* cb_data, napi_env env, napi_value process, @@ -774,35 +749,12 @@ runtime initially loads the JavaScript code. The callback parameters: +- `[in] runtime`: The runtime owning the callback. - `[in] cb_data`: The user data associated with this callback. - `[in] env`: Node-API environmentStart of the blob memory span. - `[in] process`: The Node.js `process` object. - `[in] require`: The internal `require` function. -##### `node_embedding_store_blob_callback` - - - -> Stability: 1 - Experimental - -```c -typedef void(NAPI_CDECL* node_embedding_store_blob_callback)( - void* cb_data, - const uint8_t* blob, - size_t size); -``` - -Function pointer type for user-provided native function that is called when the -runtime needs to store the snapshot blob. - -The callback parameters: - -- `[in] cb_data`: The user data associated with this callback. -- `[in] blob`: Start of the blob memory span. -- `[in] size`: Size of the blob memory span. - ##### `node_embedding_initialize_module_callback` - -> Stability: 1 - Experimental - -Sets a callback to store created snapshot when Node.js runtime instance -finished execution. - -```c -node_embedding_exit_code NAPI_CDECL -node_embedding_runtime_on_create_snapshot( - node_embedding_runtime runtime, - node_embedding_store_blob_callback store_blob_cb, - void* store_blob_cb_data, - node_embedding_snapshot_flags snapshot_flags); -``` - -- `[in] runtime`: The Node.js runtime instance. -- `[in] store_blob_cb`: The store blob callback to be called before Node.js - runtime instance is deleted. -- `[in] store_blob_cb_data`: Optional. The store blob callback data that will be - passed to the `store_blob_cb` callback. It can be removed after the - `node_embedding_delete_runtime` call. -- `[in] snapshot_flags`: The flags controlling the snapshot creation. - -Returns `node_embedding_exit_code_ok` if there were no issues. - -##### `node_embedding_runtime_initialize_from_script` +##### `node_embedding_runtime_initialize` - -> Stability: 1 - Experimental - -Initializes the Node.js runtime instance. - -```c -node_embedding_exit_code NAPI_CDECL -node_embedding_runtime_initialize_from_snapshot(node_embedding_runtime runtime, - const uint8_t* snapshot, - size_t size); -``` - -- `[in] runtime`: The Node.js runtime instance to initialize. -- `[in] snapshot`: Start of the snapshot memory span. -- `[in] size`: Size of the snapshot memory span. - -Returns `node_embedding_exit_code_ok` if there were no issues. - -The Node.js runtime initialization creates new Node.js environment associated -with a V8 `Isolate` and V8 `Context`. - -After the initialization is completed the Node.js runtime settings cannot be -changed anymore. - ### Event loop APIs #### Data types @@ -1125,50 +1020,25 @@ The event loop run mode. ```c typedef enum { + node_embedding_event_loop_run_default = 0, node_embedding_event_loop_run_once = 1, node_embedding_event_loop_run_nowait = 2, } node_embedding_event_loop_run_mode; ``` These values match to UV library `uv_run_mode` enum and control the event loop -beahvior. +behavior. +- `node_embedding_event_loop_run_default` - RRun the event loop until it is + completed. It matches the `UV_RUN_DEFAULT` behavior. - `node_embedding_event_loop_run_once` - Run the event loop once and wait if there are no items. It matches the `UV_RUN_ONCE` behavior. - `node_embedding_event_loop_run_nowait` - Run the event loop once and do not wait if there are no items. It matches the `UV_RUN_NOWAIT` behavior. -##### `node_embedding_promise_state` - - - -> Stability: 1 - Experimental - -The state of the completed `Promise`. - -```c -typedef enum { - node_embedding_promise_state_pending = 0, - node_embedding_promise_state_fulfilled = 1, - node_embedding_promise_state_rejected = 2, -} node_embedding_promise_state; -``` - -These values match to `v8::Promise::PromiseState` enum and indicate the internal -state of a `Promise` object. - -- `node_embedding_promise_state_pending` - The Promise is still awaiting to - be completed. -- `node_embedding_promise_state_fulfilled` - The Promise was successfully - fulfilled. -- `node_embedding_promise_state_rejected` - The Promise was rejected due an - error. - #### Callback types -##### `node_embedding_event_loop_predicate` +##### `node_embedding_event_loop_handler` - -> Stability: 1 - Experimental - -Sets the CLI args for the Node.js platform instance. - -```c -node_embedding_exit_code NAPI_CDECL -node_embedding_platform_set_args(node_embedding_platform platform, - int32_t argc, - char* argv[]); -``` - -- `[in] platform`: The Node.js platform instance to configure. -- `[in] argc`: Number of items in the `argv` array. -- `[in] argv`: CLI arguments as an array of zero terminating strings. - -Returns `node_embedding_exit_code_ok` if there were no issues. +If the platform was initialized before the deletion, then the method +uninitializes the platform before deletion. -##### `node_embedding_platform_initialize` +##### `node_embedding_platform_set_flags` + +> Stability: 1 - Experimental + +```c +typedef node_embedding_exit_code( + NAPI_CDECL* node_embedding_configure_runtime_callback)( + void* cb_data, + node_embedding_platform platform, + node_embedding_runtime runtime); +``` + +Function pointer type for user-provided native function that provides additional + platform configuration.. + +The callback parameters: + +- `[in] cb_data`: The user data associated with this callback. +- `[in] platform`: The platform for the for the runtime. +- `[in] runtime`: The runtime instance being configured. + ##### `node_embedding_runtime_preload_callback` + +> Stability: 1 - Experimental + +```c +typedef napi_value(NAPI_CDECL* node_embedding_start_execution_callback)( + void* cb_data, + node_embedding_runtime runtime, + napi_env env, + napi_value process, + napi_value require, + napi_value run_cjs); +``` + +Function pointer type for user-provided native function that is called when the +runtime starts the execution. + +The callback parameters: + - `[in] cb_data`: The user data associated with this callback. +- `[in] runtime`: The runtime owning the callback. - `[in] env`: Node-API environmentStart of the blob memory span. - `[in] process`: The Node.js `process` object. - `[in] require`: The internal `require` function. +- `[in] run_cjs`: The internal function that runs Common JS modules. ##### `node_embedding_initialize_module_callback` @@ -765,8 +805,8 @@ added: REPLACEME ```c typedef napi_value(NAPI_CDECL* node_embedding_initialize_module_callback)( - node_embedding_runtime runtime, void* cb_data, + node_embedding_runtime runtime, napi_env env, const char* module_name, napi_value exports); @@ -777,8 +817,8 @@ in the embedder executable. The callback parameters: -- `[in] runtime`: The runtime owning the callback. - `[in] cb_data`: The user data associated with this callback. +- `[in] runtime`: The runtime owning the callback. - `[in] env`: Node API environment. - `[in] module_name`: Name of the module. - `[in] exports`: The `exports` module object. @@ -789,7 +829,7 @@ and returned. #### Functions -##### `node_embedding_create_runtime` +##### `node_embedding_run_runtime` + +> Stability: 1 - Experimental + +Opens scope to run Node-API code. + +```c +node_embedding_exit_code NAPI_CDECL +node_embedding_close_node_api_scope( + node_embedding_runtime runtime); ``` - `[in] runtime`: The Node.js embedding_runtime instance. -- `[in] node_api_cb`: The callback that executes Node-API code. -- `[in] node_api_cb_data`: The data associated with the callback. Returns `node_embedding_exit_code_ok` if there were no issues. -The function invokes the callback that runs Node-API code in the V8 Isolate -scope, V8 Handle scope, and V8 Context scope. Then, it triggers the uncaught -exception handler if there were any unhandled errors. +The function closes the V8 Isolate, V8 handle, and V8 context scopes. +Then, it triggers the uncaught exception handler if there were any +unhandled errors. ## Examples diff --git a/src/js_native_api_v8.h b/src/js_native_api_v8.h index 99bb30cfbe9a9d..9111a4aa49eab3 100644 --- a/src/js_native_api_v8.h +++ b/src/js_native_api_v8.h @@ -85,14 +85,30 @@ struct napi_env__ { // v8 uses a special exception to indicate termination, the // `handle_exception` callback should identify such case using // terminatedOrTerminating() before actually handle the exception - template - inline void CallIntoModule(T&& call, U&& handle_exception = HandleThrow) { - int open_handle_scopes_before = open_handle_scopes; - int open_callback_scopes_before = open_callback_scopes; - napi_clear_last_error(this); + template + inline void CallIntoModule( + Call&& call, JSExceptionHandler&& handle_exception = HandleThrow) { + CallModuleScope scope = OpenCallModuleScope(); call(this); - CHECK_EQ(open_handle_scopes, open_handle_scopes_before); - CHECK_EQ(open_callback_scopes, open_callback_scopes_before); + CloseCallModuleScope(scope, handle_exception); + } + + struct CallModuleScope { + int open_handle_scopes_before; + int open_callback_scopes_before; + }; + + inline CallModuleScope OpenCallModuleScope() { + napi_clear_last_error(this); + return {open_handle_scopes, open_callback_scopes}; + } + + template + inline void CloseCallModuleScope( + const CallModuleScope& scope, + JSExceptionHandler&& handle_exception = HandleThrow) { + CHECK_EQ(open_handle_scopes, scope.open_handle_scopes_before); + CHECK_EQ(open_callback_scopes, scope.open_callback_scopes_before); if (!last_exception.IsEmpty()) { handle_exception(this, last_exception.Get(this->isolate)); last_exception.Reset(); diff --git a/src/node_embedding_api.cc b/src/node_embedding_api.cc index bd2c8bdf7f74de..0f1d8a20b70176 100644 --- a/src/node_embedding_api.cc +++ b/src/node_embedding_api.cc @@ -16,55 +16,40 @@ #define EMBEDDED_PLATFORM(platform) \ ((platform) == nullptr) \ ? v8impl::EmbeddedErrorHandling::HandleError( \ - "Argument must not be null: " #platform, \ - __FILE__, \ - __LINE__, \ - node_embedding_exit_code_generic_user_error) \ + "Argument must not be null: " #platform, __FILE__, __LINE__) \ : reinterpret_cast(platform) #define EMBEDDED_RUNTIME(runtime) \ - (runtime) == nullptr ? v8impl::EmbeddedErrorHandling::HandleError( \ - "Argument must not be null: " #runtime, \ - __FILE__, \ - __LINE__, \ - node_embedding_exit_code_generic_user_error) \ - : reinterpret_cast(runtime) + (runtime) == nullptr \ + ? v8impl::EmbeddedErrorHandling::HandleError( \ + "Argument must not be null: " #runtime, __FILE__, __LINE__) \ + : reinterpret_cast(runtime) #define ARG_IS_NOT_NULL(arg) \ do { \ if ((arg) == nullptr) { \ return v8impl::EmbeddedErrorHandling::HandleError( \ - "Argument must not be null: " #arg, \ - __FILE__, \ - __LINE__, \ - node_embedding_exit_code_generic_user_error); \ + "Argument must not be null: " #arg, __FILE__, __LINE__); \ } \ } while (false) -#define ARG_IS_NULL(arg) \ +#define ASSERT(expr) \ do { \ - if ((arg) != nullptr) { \ + if (!(expr)) { \ return v8impl::EmbeddedErrorHandling::HandleError( \ - "Argument must be null: " #arg, \ - __FILE__, \ - __LINE__, \ - node_embedding_exit_code_generic_user_error); \ + "Expression returned false: " #expr, __FILE__, __LINE__); \ } \ } while (false) -#define ASSERT(expr) \ +#define CHECK_EXIT_CODE(expr) \ do { \ - if (!(expr)) { \ - return v8impl::EmbeddedErrorHandling::HandleError( \ - "Expression returned false: " #expr, \ - __FILE__, \ - __LINE__, \ - node_embedding_exit_code_generic_user_error); \ + node_embedding_exit_code status = (expr); \ + if (status != node_embedding_exit_code_ok) { \ + return status; \ } \ } while (false) namespace node { - // Declare functions implemented in embed_helpers.cc v8::Maybe SpinEventLoopWithoutCleanup(Environment* env, uv_run_mode run_mode); @@ -123,17 +108,21 @@ class EmbeddedErrorHandling { node_embedding_error_handler error_handler, void* error_handler_data); static node_embedding_exit_code HandleError( - const std::string& message, node_embedding_exit_code exit_code); + const std::string& message, + node_embedding_exit_code exit_code = + node_embedding_exit_code_generic_user_error); static node_embedding_exit_code HandleError( const std::vector& messages, - node_embedding_exit_code exit_code); + node_embedding_exit_code exit_code = + node_embedding_exit_code_generic_user_error); static node_embedding_exit_code HandleError( const char* message, const char* filename, int32_t line, - node_embedding_exit_code exit_code); + node_embedding_exit_code exit_code = + node_embedding_exit_code_generic_user_error); static std::string FormatString(const char* format, ...); @@ -157,21 +146,40 @@ void* EmbeddedErrorHandling::error_handler_data_{}; class EmbeddedPlatform { public: - explicit EmbeddedPlatform(int32_t api_version) noexcept - : api_version_(api_version) {} + EmbeddedPlatform(int32_t argc, char* argv[]) noexcept + : args_(argv, argv + argc) {} EmbeddedPlatform(const EmbeddedPlatform&) = delete; EmbeddedPlatform& operator=(const EmbeddedPlatform&) = delete; - node_embedding_exit_code DeleteMe(); + static node_embedding_exit_code SetApiVersion(int32_t embedding_api_version, + int32_t node_api_version); + + static node_embedding_exit_code RunMain( + int32_t argc, + char* argv[], + node_embedding_configure_platform_callback configure_platform_cb, + void* configure_platform_cb_data, + node_embedding_configure_runtime_callback configure_runtime_cb, + void* configure_runtime_cb_data, + node_embedding_node_api_callback node_api_cb, + void* node_api_cb_data); + + static node_embedding_exit_code Create( + int32_t argc, + char* argv[], + node_embedding_configure_platform_callback configure_platform_cb, + void* configure_platform_cb_data, + node_embedding_platform* result); - node_embedding_exit_code IsInitialized(bool* result); + node_embedding_exit_code DeleteMe(); node_embedding_exit_code SetFlags(node_embedding_platform_flags flags); - node_embedding_exit_code SetArgs(int32_t argc, char* argv[]); - - node_embedding_exit_code Initialize(bool* early_return); + node_embedding_exit_code Initialize( + node_embedding_configure_platform_callback configure_platform_cb, + void* configure_platform_cb_data, + bool* early_return); node_embedding_exit_code GetParsedArgs( node_embedding_get_args_callback get_args_cb, @@ -179,18 +187,24 @@ class EmbeddedPlatform { node_embedding_get_args_callback get_exec_args_cb, void* get_exec_args_cb_data); - node_embedding_exit_code CreateRuntime(node_embedding_runtime* result); - node::InitializationResult* init_result() { return init_result_.get(); } node::MultiIsolatePlatform* get_v8_platform() { return v8_platform_.get(); } + static int32_t embedding_api_version() { + return embedding_api_version_ == 0 ? NODE_EMBEDDING_VERSION + : embedding_api_version_; + } + + static int32_t node_api_version() { + return node_api_version_ == 0 ? NAPI_VERSION : node_api_version_; + } + private: static node::ProcessInitializationFlags::Flags GetProcessInitializationFlags( node_embedding_platform_flags flags); private: - int32_t api_version_{1}; bool is_initialized_{false}; bool v8_is_initialized_{false}; bool v8_is_uninitialized_{false}; @@ -198,13 +212,18 @@ class EmbeddedPlatform { std::vector args_; struct { bool flags : 1; - bool args : 1; } optional_bits_{}; std::shared_ptr init_result_; std::unique_ptr v8_platform_; + + static int32_t embedding_api_version_; + static int32_t node_api_version_; }; +int32_t EmbeddedPlatform::embedding_api_version_{}; +int32_t EmbeddedPlatform::node_api_version_{}; + struct IsolateLocker { IsolateLocker(node::CommonEnvironmentSetup* env_setup) : v8_locker_(env_setup->isolate()), @@ -232,9 +251,23 @@ class EmbeddedRuntime { public: explicit EmbeddedRuntime(EmbeddedPlatform* platform); - node_embedding_exit_code DeleteMe(); + EmbeddedRuntime(const EmbeddedRuntime&) = delete; + EmbeddedRuntime& operator=(const EmbeddedRuntime&) = delete; + + static node_embedding_exit_code Run( + node_embedding_platform platform, + node_embedding_configure_runtime_callback configure_runtime_cb, + void* configure_runtime_cb_data, + node_embedding_node_api_callback node_api_cb, + void* node_api_cb_data); - node_embedding_exit_code IsInitialized(bool* result); + static node_embedding_exit_code Create( + node_embedding_platform platform, + node_embedding_configure_runtime_callback configure_runtime_cb, + void* configure_runtime_cb_data, + node_embedding_runtime* result); + + node_embedding_exit_code DeleteMe(); node_embedding_exit_code SetFlags(node_embedding_runtime_flags flags); @@ -243,8 +276,12 @@ class EmbeddedRuntime { int32_t exec_argc, const char* exec_argv[]); - node_embedding_exit_code OnPreloadCallback( - node_embedding_preload_callback preload_cb, void* preload_cb_data); + node_embedding_exit_code OnPreload(node_embedding_preload_callback preload_cb, + void* preload_cb_data); + + node_embedding_exit_code OnStartExecution( + node_embedding_start_execution_callback start_execution_cb, + void* start_execution_cb_data); node_embedding_exit_code AddModule( const char* module_name, @@ -252,9 +289,11 @@ class EmbeddedRuntime { void* init_module_cb_data, int32_t module_node_api_version); - node_embedding_exit_code Initialize(const char* main_script); + node_embedding_exit_code Initialize( + node_embedding_configure_runtime_callback configure_runtime_cb, + void* configure_runtime_cb_data); - node_embedding_exit_code OnEventLoopRunRequest( + node_embedding_exit_code OnWakeUpEventLoop( node_embedding_event_loop_handler event_loop_handler, void* event_loop_handler_data); @@ -263,18 +302,19 @@ class EmbeddedRuntime { node_embedding_exit_code CompleteEventLoop(); - node_embedding_exit_code SetNodeApiVersion(int32_t node_api_version); - - node_embedding_exit_code InvokeNodeApi( + node_embedding_exit_code RunNodeApi( node_embedding_node_api_callback node_api_cb, void* node_api_cb_data); - bool IsScopeOpened() const; + node_embedding_exit_code OpenNodeApiScope(napi_env* env); + node_embedding_exit_code CloseNodeApiScope(); + bool IsNodeApiScopeOpened() const; static napi_env GetOrCreateNodeApiEnv(node::Environment* node_env, - const std::string& module_filename, - int32_t node_api_version); + const std::string& module_filename); private: + static void TriggerFatalException(napi_env env, + v8::Local local_err); static node::EnvironmentFlags::Flags GetEnvironmentFlags( node_embedding_runtime_flags flags); @@ -317,7 +357,7 @@ class EmbeddedRuntime { std::vector args_; std::vector exec_args_; node::EmbedderPreloadCallback preload_cb_{}; - int32_t node_api_version_{0}; + node::StartExecutionCallback start_execution_cb_{}; napi_env node_api_env_{}; struct { @@ -337,6 +377,8 @@ class EmbeddedRuntime { uv_sem_t polling_sem_{}; uv_thread_t polling_thread_{}; bool polling_thread_closed_{false}; + + napi_env__::CallModuleScope module_scope_{}; }; //----------------------------------------------------------------------------- @@ -390,7 +432,7 @@ node_embedding_exit_code EmbeddedErrorHandling::DefaultErrorHandler( } fflush(stdout); } - return exit_code; + return node_embedding_exit_code_ok; } std::string EmbeddedErrorHandling::FormatString(const char* format, ...) { @@ -409,24 +451,83 @@ std::string EmbeddedErrorHandling::FormatString(const char* format, ...) { // EmbeddedPlatform implementation. //----------------------------------------------------------------------------- +/*static*/ node_embedding_exit_code EmbeddedPlatform::SetApiVersion( + int32_t embedding_api_version, int32_t node_api_version) { + embedding_api_version_ = embedding_api_version; + ASSERT(embedding_api_version_ > 0 && + embedding_api_version_ <= NODE_EMBEDDING_VERSION); + + node_api_version_ = node_api_version; + ASSERT(node_api_version_ >= NODE_API_DEFAULT_MODULE_API_VERSION && + (node_api_version_ <= NAPI_VERSION || + node_api_version_ == NAPI_VERSION_EXPERIMENTAL)); + + return node_embedding_exit_code_ok; +} + +node_embedding_exit_code EmbeddedPlatform::RunMain( + int32_t argc, + char* argv[], + node_embedding_configure_platform_callback configure_platform_cb, + void* configure_platform_cb_data, + node_embedding_configure_runtime_callback configure_runtime_cb, + void* configure_runtime_cb_data, + node_embedding_node_api_callback node_api_cb, + void* node_api_cb_data) { + node_embedding_platform platform{}; + CHECK_EXIT_CODE(EmbeddedPlatform::Create(argc, + argv, + configure_platform_cb, + configure_platform_cb_data, + &platform)); + if (platform == nullptr) { + return node_embedding_exit_code_ok; + } + return EmbeddedRuntime::Run(platform, + configure_runtime_cb, + configure_runtime_cb_data, + node_api_cb, + node_api_cb_data); +} + +/*static*/ node_embedding_exit_code EmbeddedPlatform::Create( + int32_t argc, + char* argv[], + node_embedding_configure_platform_callback configure_platform_cb, + void* configure_platform_cb_data, + node_embedding_platform* result) { + ARG_IS_NOT_NULL(result); + + // Hack around with the argv pointer. Used for process.title = "blah". + argv = uv_setup_args(argc, argv); + + auto platform_ptr = std::make_unique(argc, argv); + bool early_return = false; + CHECK_EXIT_CODE(platform_ptr->Initialize( + configure_platform_cb, configure_platform_cb_data, &early_return)); + if (early_return) { + return platform_ptr.release()->DeleteMe(); + } + + // The initialization was successful, the caller is responsible for deleting + // the platform instance. + *result = reinterpret_cast(platform_ptr.release()); + return node_embedding_exit_code_ok; +} + node_embedding_exit_code EmbeddedPlatform::DeleteMe() { + ASSERT(is_initialized_); if (v8_is_initialized_ && !v8_is_uninitialized_) { + v8_is_uninitialized_ = true; v8::V8::Dispose(); v8::V8::DisposePlatform(); node::TearDownOncePerProcess(); - v8_is_uninitialized_ = false; } delete this; return node_embedding_exit_code_ok; } -node_embedding_exit_code EmbeddedPlatform::IsInitialized(bool* result) { - ARG_IS_NOT_NULL(result); - *result = is_initialized_; - return node_embedding_exit_code_ok; -} - node_embedding_exit_code EmbeddedPlatform::SetFlags( node_embedding_platform_flags flags) { ASSERT(!is_initialized_); @@ -435,21 +536,21 @@ node_embedding_exit_code EmbeddedPlatform::SetFlags( return node_embedding_exit_code_ok; } -node_embedding_exit_code EmbeddedPlatform::SetArgs(int32_t argc, char* argv[]) { - ARG_IS_NOT_NULL(argv); +node_embedding_exit_code EmbeddedPlatform::Initialize( + node_embedding_configure_platform_callback configure_platform_cb, + void* configure_platform_cb_data, + bool* early_return) { ASSERT(!is_initialized_); - args_.assign(argv, argv + argc); - optional_bits_.args = true; - return node_embedding_exit_code_ok; -} -node_embedding_exit_code EmbeddedPlatform::Initialize(bool* early_return) { - ASSERT(!is_initialized_); + node_embedding_platform platform = + reinterpret_cast(this); + if (configure_platform_cb != nullptr) { + CHECK_EXIT_CODE( + configure_platform_cb(configure_platform_cb_data, platform)); + } is_initialized_ = true; - // TODO(vmoroz): default initialize args_. - if (!optional_bits_.flags) { flags_ = node_embedding_platform_no_flags; } @@ -458,19 +559,14 @@ node_embedding_exit_code EmbeddedPlatform::Initialize(bool* early_return) { args_, GetProcessInitializationFlags(flags_)); if (init_result_->exit_code() != 0 || !init_result_->errors().empty()) { - return EmbeddedErrorHandling::HandleError( + CHECK_EXIT_CODE(EmbeddedErrorHandling::HandleError( init_result_->errors(), - static_cast(init_result_->exit_code())); - } - - if (early_return != nullptr) { - *early_return = init_result_->early_return(); - } else if (init_result_->early_return()) { - exit(init_result_->exit_code()); + static_cast(init_result_->exit_code()))); } if (init_result_->early_return()) { - return static_cast(init_result_->exit_code()); + *early_return = true; + return node_embedding_exit_code_ok; } int32_t thread_pool_size = @@ -500,19 +596,6 @@ node_embedding_exit_code EmbeddedPlatform::GetParsedArgs( v8impl::CStringArray exec_args(init_result_->exec_args()); get_exec_args_cb(get_exec_args_cb_data, exec_args.argc(), exec_args.argv()); } - return node_embedding_exit_code_ok; -} - -node_embedding_exit_code EmbeddedPlatform::CreateRuntime( - node_embedding_runtime* result) { - ARG_IS_NOT_NULL(result); - ASSERT(is_initialized_); - ASSERT(v8_is_initialized_); - - std::unique_ptr runtime = - std::make_unique(this); - - *result = reinterpret_cast(runtime.release()); return node_embedding_exit_code_ok; } @@ -566,23 +649,59 @@ EmbeddedPlatform::GetProcessInitializationFlags( // EmbeddedRuntime implementation. //----------------------------------------------------------------------------- +/*static*/ node_embedding_exit_code EmbeddedRuntime::Run( + node_embedding_platform platform, + node_embedding_configure_runtime_callback configure_runtime_cb, + void* configure_runtime_cb_data, + node_embedding_node_api_callback node_api_cb, + void* node_api_cb_data) { + node_embedding_runtime runtime{}; + CHECK_EXIT_CODE(node_embedding_create_runtime( + platform, configure_runtime_cb, configure_runtime_cb_data, &runtime)); + if (node_api_cb != nullptr) { + CHECK_EXIT_CODE( + node_embedding_run_node_api(runtime, node_api_cb, node_api_cb_data)); + } + + CHECK_EXIT_CODE(node_embedding_complete_event_loop(runtime)); + CHECK_EXIT_CODE(node_embedding_delete_runtime(runtime)); + return node_embedding_exit_code_ok; +} + +/*static*/ node_embedding_exit_code EmbeddedRuntime::Create( + node_embedding_platform platform, + node_embedding_configure_runtime_callback configure_runtime_cb, + void* configure_runtime_cb_data, + node_embedding_runtime* result) { + ARG_IS_NOT_NULL(platform); + ARG_IS_NOT_NULL(result); + + EmbeddedPlatform* platform_ptr = + reinterpret_cast(platform); + std::unique_ptr runtime_ptr = + std::make_unique(platform_ptr); + + CHECK_EXIT_CODE( + runtime_ptr->Initialize(configure_runtime_cb, configure_runtime_cb_data)); + + *result = reinterpret_cast(runtime_ptr.release()); + + return node_embedding_exit_code_ok; +} + EmbeddedRuntime::EmbeddedRuntime(EmbeddedPlatform* platform) : platform_(platform) {} node_embedding_exit_code EmbeddedRuntime::DeleteMe() { - ASSERT(!IsScopeOpened()); + ASSERT(!IsNodeApiScopeOpened()); std::unique_ptr env_setup = std::move(env_setup_); + if (env_setup != nullptr) { + node::Stop(env_setup->env()); + } - node::Stop(env_setup->env()); - - return node_embedding_exit_code_ok; -} - -node_embedding_exit_code EmbeddedRuntime::IsInitialized(bool* result) { - ARG_IS_NOT_NULL(result); - *result = is_initialized_; + delete this; return node_embedding_exit_code_ok; } @@ -610,7 +729,7 @@ node_embedding_exit_code EmbeddedRuntime::SetArgs(int32_t argc, return node_embedding_exit_code_ok; } -node_embedding_exit_code EmbeddedRuntime::OnPreloadCallback( +node_embedding_exit_code EmbeddedRuntime::OnPreload( node_embedding_preload_callback preload_cb, void* preload_cb_data) { ASSERT(!is_initialized_); @@ -618,18 +737,23 @@ node_embedding_exit_code EmbeddedRuntime::OnPreloadCallback( preload_cb_ = node::EmbedderPreloadCallback( [runtime = reinterpret_cast(this), preload_cb, - preload_cb_data, - node_api_version = node_api_version_](node::Environment* node_env, - v8::Local process, - v8::Local require) { - napi_env env = GetOrCreateNodeApiEnv( - node_env, "", node_api_version); - env->CallIntoModule([&](napi_env env) { - napi_value process_value = v8impl::JsValueFromV8LocalValue(process); - napi_value require_value = v8impl::JsValueFromV8LocalValue(require); - preload_cb( - runtime, preload_cb_data, env, process_value, require_value); - }); + preload_cb_data](node::Environment* node_env, + v8::Local process, + v8::Local require) { + napi_env env = GetOrCreateNodeApiEnv(node_env, ""); + env->CallIntoModule( + [&](napi_env env) { + napi_value process_value = + v8impl::JsValueFromV8LocalValue(process); + napi_value require_value = + v8impl::JsValueFromV8LocalValue(require); + preload_cb(preload_cb_data, + runtime, + env, + process_value, + require_value); + }, + TriggerFatalException); }); } else { preload_cb_ = {}; @@ -638,6 +762,47 @@ node_embedding_exit_code EmbeddedRuntime::OnPreloadCallback( return node_embedding_exit_code_ok; } +node_embedding_exit_code EmbeddedRuntime::OnStartExecution( + node_embedding_start_execution_callback start_execution_cb, + void* start_execution_cb_data) { + ASSERT(!is_initialized_); + + if (start_execution_cb != nullptr) { + start_execution_cb_ = node::StartExecutionCallback( + [this, start_execution_cb, start_execution_cb_data]( + const node::StartExecutionCallbackInfo& info) + -> v8::MaybeLocal { + napi_value result{}; + node_api_env_->CallIntoModule( + [&](napi_env env) { + napi_value process_value = + v8impl::JsValueFromV8LocalValue(info.process_object); + napi_value require_value = + v8impl::JsValueFromV8LocalValue(info.native_require); + napi_value run_cjs_value = + v8impl::JsValueFromV8LocalValue(info.run_cjs); + result = start_execution_cb( + start_execution_cb_data, + reinterpret_cast(this), + env, + process_value, + require_value, + run_cjs_value); + }, + TriggerFatalException); + + if (result == nullptr) + return {}; + else + return v8impl::V8LocalValueFromJsValue(result); + }); + } else { + start_execution_cb_ = {}; + } + + return node_embedding_exit_code_ok; +} + node_embedding_exit_code EmbeddedRuntime::AddModule( const char* module_name, node_embedding_initialize_module_callback init_module_cb, @@ -657,19 +822,27 @@ node_embedding_exit_code EmbeddedRuntime::AddModule( if (!insert_result.second) { return EmbeddedErrorHandling::HandleError( EmbeddedErrorHandling::FormatString( - "Module with name '%s' is already added.", module_name), - node_embedding_exit_code_generic_user_error); + "Module with name '%s' is already added.", module_name)); } return node_embedding_exit_code_ok; } -node_embedding_exit_code EmbeddedRuntime::Initialize(const char* main_script) { +node_embedding_exit_code EmbeddedRuntime::Initialize( + node_embedding_configure_runtime_callback configure_runtime_cb, + void* configure_runtime_cb_data) { ASSERT(!is_initialized_); + if (configure_runtime_cb != nullptr) { + CHECK_EXIT_CODE(configure_runtime_cb( + configure_runtime_cb_data, + reinterpret_cast(platform_), + reinterpret_cast(this))); + } + is_initialized_ = true; - node::MultiIsolatePlatform* platform = platform_->get_v8_platform(); + node::MultiIsolatePlatform* v8_platform = platform_->get_v8_platform(); node::EnvironmentFlags::Flags flags = GetEnvironmentFlags( optional_bits_.flags ? flags_ : node_embedding_runtime_default_flags); @@ -683,26 +856,26 @@ node_embedding_exit_code EmbeddedRuntime::Initialize(const char* main_script) { std::vector errors; env_setup_ = node::CommonEnvironmentSetup::Create( - platform, &errors, args, exec_args, flags); + v8_platform, &errors, args, exec_args, flags); if (env_setup_ == nullptr || !errors.empty()) { - return EmbeddedErrorHandling::HandleError( - errors, node_embedding_exit_code_generic_user_error); + return EmbeddedErrorHandling::HandleError(errors); } v8impl::IsolateLocker isolate_locker(env_setup_.get()); std::string filename = args_.size() > 1 ? args_[1] : ""; - node_api_env_ = - GetOrCreateNodeApiEnv(env_setup_->env(), filename, node_api_version_); + node_api_env_ = GetOrCreateNodeApiEnv(env_setup_->env(), filename); node::Environment* node_env = env_setup_->env(); RegisterModules(); - v8::MaybeLocal ret = node::LoadEnvironment( - node_env, std::string_view(main_script), preload_cb_); - if (ret.IsEmpty()) return node_embedding_exit_code_generic_user_error; + v8::MaybeLocal ret = + node::LoadEnvironment(node_env, start_execution_cb_, preload_cb_); + + if (ret.IsEmpty()) + return EmbeddedErrorHandling::HandleError("Failed to load environment"); InitializeEventLoopPollingThread(); @@ -755,8 +928,8 @@ void EmbeddedRuntime::RunPollingThread(void* data) { if (runtime->polling_thread_closed_) break; runtime->event_loop_handler_( - reinterpret_cast(runtime), - runtime->event_loop_handler_data_); + runtime->event_loop_handler_data_, + reinterpret_cast(runtime)); } } @@ -779,7 +952,7 @@ void EmbeddedRuntime::PollWin32() { event_loop->iocp, byte_count, completion_key, overlapped); } -node_embedding_exit_code EmbeddedRuntime::OnEventLoopRunRequest( +node_embedding_exit_code EmbeddedRuntime::OnWakeUpEventLoop( node_embedding_event_loop_handler event_loop_handler, void* event_loop_handler_data) { ASSERT(!is_initialized_); @@ -829,52 +1002,59 @@ node_embedding_exit_code EmbeddedRuntime::CompleteEventLoop() { return node_embedding_exit_code_ok; } -node_embedding_exit_code EmbeddedRuntime::SetNodeApiVersion( - int32_t node_api_version) { - ASSERT(!is_initialized_); - node_api_version_ = node_api_version; - return node_embedding_exit_code_ok; +/*static*/ void EmbeddedRuntime::TriggerFatalException( + napi_env env, v8::Local local_err) { + node_napi_env__* node_napi_env = static_cast(env); + if (node_napi_env->terminatedOrTerminating()) { + return; + } + // If there was an unhandled exception while calling Node-API, + // report it as a fatal exception. (There is no JavaScript on the + // call stack that can possibly handle it.) + node_napi_env->trigger_fatal_exception(local_err); } -node_embedding_exit_code EmbeddedRuntime::InvokeNodeApi( +node_embedding_exit_code EmbeddedRuntime::RunNodeApi( node_embedding_node_api_callback node_api_cb, void* node_api_cb_data) { ARG_IS_NOT_NULL(node_api_cb); + napi_env env{}; + CHECK_EXIT_CODE(OpenNodeApiScope(&env)); + node_api_cb( + node_api_cb_data, reinterpret_cast(this), env); + CHECK_EXIT_CODE(CloseNodeApiScope()); + return node_embedding_exit_code_ok; +} + +node_embedding_exit_code EmbeddedRuntime::OpenNodeApiScope(napi_env* env) { + ARG_IS_NOT_NULL(env); if (isolate_locker_.has_value()) { ASSERT(isolate_locker_->IsLocked()); isolate_locker_->IncrementLockCount(); } else { isolate_locker_.emplace(env_setup_.get()); + module_scope_ = node_api_env_->OpenCallModuleScope(); } - node_api_env_->CallIntoModule( - [&](napi_env env) { - node_api_cb(reinterpret_cast(this), - node_api_cb_data, - env); - }, - [](napi_env env, v8::Local local_err) { - node_napi_env__* node_napi_env = static_cast(env); - if (node_napi_env->terminatedOrTerminating()) { - return; - } - // If there was an unhandled exception while calling Node-API, - // report it as a fatal exception. (There is no JavaScript on the - // call stack that can possibly handle it.) - node_napi_env->trigger_fatal_exception(local_err); - }); - - if (isolate_locker_->DecrementLockCount()) isolate_locker_.reset(); + *env = node_api_env_; + + return node_embedding_exit_code_ok; +} + +node_embedding_exit_code EmbeddedRuntime::CloseNodeApiScope() { + ASSERT(IsNodeApiScopeOpened()); + if (isolate_locker_->DecrementLockCount()) { + node_api_env_->CloseCallModuleScope(module_scope_, TriggerFatalException); + isolate_locker_.reset(); + } return node_embedding_exit_code_ok; } -bool EmbeddedRuntime::IsScopeOpened() const { +bool EmbeddedRuntime::IsNodeApiScopeOpened() const { return isolate_locker_.has_value(); } napi_env EmbeddedRuntime::GetOrCreateNodeApiEnv( - node::Environment* node_env, - const std::string& module_filename, - int32_t node_api_version) { + node::Environment* node_env, const std::string& module_filename) { SharedData& shared_data = SharedData::Get(); { @@ -884,7 +1064,9 @@ napi_env EmbeddedRuntime::GetOrCreateNodeApiEnv( } // Avoid creating the environment under the lock. - napi_env env = NewEnv(node_env->context(), module_filename, node_api_version); + napi_env env = NewEnv(node_env->context(), + module_filename, + EmbeddedPlatform::node_api_version()); std::scoped_lock lock(shared_data.mutex); auto insert_result = @@ -965,12 +1147,12 @@ void EmbeddedRuntime::RegisterModules() { napi_value node_api_exports = nullptr; env->CallIntoModule([&](napi_env env) { - node_api_exports = module_info->init_module_cb( - module_info->runtime, - module_info->init_module_cb_data, - env, - module_info->module_name.c_str(), - v8impl::JsValueFromV8LocalValue(exports)); + node_api_exports = + module_info->init_module_cb(module_info->init_module_cb_data, + module_info->runtime, + env, + module_info->module_name.c_str(), + v8impl::JsValueFromV8LocalValue(exports)); }); // If register function returned a non-null exports object different from @@ -986,23 +1168,45 @@ void EmbeddedRuntime::RegisterModules() { } // end of anonymous namespace } // end of namespace v8impl -node_embedding_exit_code NAPI_CDECL -node_embedding_run_nodejs_main(int32_t argc, char* argv[]) { - return static_cast(node::Start(argc, argv)); -} - node_embedding_exit_code NAPI_CDECL node_embedding_on_error( node_embedding_error_handler error_handler, void* error_handler_data) { return v8impl::EmbeddedErrorHandling::SetErrorHandler(error_handler, error_handler_data); } +NAPI_EXTERN node_embedding_exit_code NAPI_CDECL node_embedding_set_api_version( + int32_t embedding_api_version, int32_t node_api_version) { + return v8impl::EmbeddedPlatform::SetApiVersion(embedding_api_version, + node_api_version); +} + +node_embedding_exit_code NAPI_CDECL node_embedding_run_main( + int32_t argc, + char* argv[], + node_embedding_configure_platform_callback configure_platform_cb, + void* configure_platform_cb_data, + node_embedding_configure_runtime_callback configure_runtime_cb, + void* configure_runtime_cb_data, + node_embedding_node_api_callback node_api_cb, + void* node_api_cb_data) { + return v8impl::EmbeddedPlatform::RunMain(argc, + argv, + configure_platform_cb, + configure_platform_cb_data, + configure_runtime_cb, + configure_runtime_cb_data, + node_api_cb, + node_api_cb_data); +} + node_embedding_exit_code NAPI_CDECL node_embedding_create_platform( - int32_t api_version, node_embedding_platform* result) { - ARG_IS_NOT_NULL(result); - *result = reinterpret_cast( - new v8impl::EmbeddedPlatform(api_version)); - return node_embedding_exit_code_ok; + int32_t argc, + char* argv[], + node_embedding_configure_platform_callback configure_platform_cb, + void* configure_platform_cb_data, + node_embedding_platform* result) { + return v8impl::EmbeddedPlatform::Create( + argc, argv, configure_platform_cb, configure_platform_cb_data, result); } node_embedding_exit_code NAPI_CDECL @@ -1010,26 +1214,11 @@ node_embedding_delete_platform(node_embedding_platform platform) { return EMBEDDED_PLATFORM(platform)->DeleteMe(); } -node_embedding_exit_code NAPI_CDECL node_embedding_platform_is_initialized( - node_embedding_platform platform, bool* result) { - return EMBEDDED_PLATFORM(platform)->IsInitialized(result); -} - node_embedding_exit_code NAPI_CDECL node_embedding_platform_set_flags( node_embedding_platform platform, node_embedding_platform_flags flags) { return EMBEDDED_PLATFORM(platform)->SetFlags(flags); } -node_embedding_exit_code NAPI_CDECL node_embedding_platform_set_args( - node_embedding_platform platform, int32_t argc, char* argv[]) { - return EMBEDDED_PLATFORM(platform)->SetArgs(argc, argv); -} - -node_embedding_exit_code NAPI_CDECL node_embedding_platform_initialize( - node_embedding_platform platform, bool* early_return) { - return EMBEDDED_PLATFORM(platform)->Initialize(early_return); -} - node_embedding_exit_code NAPI_CDECL node_embedding_platform_get_parsed_args( node_embedding_platform platform, node_embedding_get_args_callback get_args_cb, @@ -1040,9 +1229,26 @@ node_embedding_exit_code NAPI_CDECL node_embedding_platform_get_parsed_args( get_args_cb, get_args_cb_data, get_exec_args_cb, get_exec_args_cb_data); } +node_embedding_exit_code NAPI_CDECL node_embedding_run_runtime( + node_embedding_platform platform, + node_embedding_configure_runtime_callback configure_runtime_cb, + void* configure_runtime_cb_data, + node_embedding_node_api_callback node_api_cb, + void* node_api_cb_data) { + return v8impl::EmbeddedRuntime::Run(platform, + configure_runtime_cb, + configure_runtime_cb_data, + node_api_cb, + node_api_cb_data); +} + node_embedding_exit_code NAPI_CDECL node_embedding_create_runtime( - node_embedding_platform platform, node_embedding_runtime* result) { - return EMBEDDED_PLATFORM(platform)->CreateRuntime(result); + node_embedding_platform platform, + node_embedding_configure_runtime_callback configure_runtime_cb, + void* configure_runtime_cb_data, + node_embedding_runtime* result) { + return v8impl::EmbeddedRuntime::Create( + platform, configure_runtime_cb, configure_runtime_cb_data, result); } node_embedding_exit_code NAPI_CDECL @@ -1050,11 +1256,6 @@ node_embedding_delete_runtime(node_embedding_runtime runtime) { return EMBEDDED_RUNTIME(runtime)->DeleteMe(); } -node_embedding_exit_code NAPI_CDECL node_embedding_runtime_is_initialized( - node_embedding_runtime runtime, bool* result) { - return EMBEDDED_RUNTIME(runtime)->IsInitialized(result); -} - node_embedding_exit_code NAPI_CDECL node_embedding_runtime_set_flags( node_embedding_runtime runtime, node_embedding_runtime_flags flags) { return EMBEDDED_RUNTIME(runtime)->SetFlags(flags); @@ -1073,8 +1274,15 @@ node_embedding_exit_code NAPI_CDECL node_embedding_runtime_on_preload(node_embedding_runtime runtime, node_embedding_preload_callback preload_cb, void* preload_cb_data) { - return EMBEDDED_RUNTIME(runtime)->OnPreloadCallback(preload_cb, - preload_cb_data); + return EMBEDDED_RUNTIME(runtime)->OnPreload(preload_cb, preload_cb_data); +} + +node_embedding_exit_code NAPI_CDECL node_embedding_runtime_on_start_execution( + node_embedding_runtime runtime, + node_embedding_start_execution_callback start_execution_cb, + void* start_execution_cb_data) { + return EMBEDDED_RUNTIME(runtime)->OnStartExecution(start_execution_cb, + start_execution_cb_data); } node_embedding_exit_code NAPI_CDECL node_embedding_runtime_add_module( @@ -1089,41 +1297,39 @@ node_embedding_exit_code NAPI_CDECL node_embedding_runtime_add_module( module_node_api_version); } -node_embedding_exit_code NAPI_CDECL node_embedding_runtime_initialize( - node_embedding_runtime runtime, const char* main_script) { - return EMBEDDED_RUNTIME(runtime)->Initialize(main_script); -} - -node_embedding_exit_code NAPI_CDECL -node_embedding_runtime_on_event_loop_run_request( +node_embedding_exit_code NAPI_CDECL node_embedding_on_wake_up_event_loop( node_embedding_runtime runtime, node_embedding_event_loop_handler event_loop_handler, void* event_loop_handler_data) { - return EMBEDDED_RUNTIME(runtime)->OnEventLoopRunRequest( - event_loop_handler, event_loop_handler_data); + return EMBEDDED_RUNTIME(runtime)->OnWakeUpEventLoop(event_loop_handler, + event_loop_handler_data); } -node_embedding_exit_code NAPI_CDECL node_embedding_runtime_run_event_loop( - node_embedding_runtime runtime, - node_embedding_event_loop_run_mode run_mode, - bool* has_more_work) { +node_embedding_exit_code NAPI_CDECL +node_embedding_run_event_loop(node_embedding_runtime runtime, + node_embedding_event_loop_run_mode run_mode, + bool* has_more_work) { return EMBEDDED_RUNTIME(runtime)->RunEventLoop(run_mode, has_more_work); } node_embedding_exit_code NAPI_CDECL -node_embedding_runtime_complete_event_loop(node_embedding_runtime runtime) { +node_embedding_complete_event_loop(node_embedding_runtime runtime) { return EMBEDDED_RUNTIME(runtime)->CompleteEventLoop(); } -node_embedding_exit_code NAPI_CDECL node_embedding_runtime_set_node_api_version( - node_embedding_runtime runtime, int32_t node_api_version) { - return EMBEDDED_RUNTIME(runtime)->SetNodeApiVersion(node_api_version); +node_embedding_exit_code NAPI_CDECL +node_embedding_run_node_api(node_embedding_runtime runtime, + node_embedding_node_api_callback node_api_cb, + void* node_api_cb_data) { + return EMBEDDED_RUNTIME(runtime)->RunNodeApi(node_api_cb, node_api_cb_data); } -node_embedding_exit_code NAPI_CDECL node_embedding_runtime_invoke_node_api( - node_embedding_runtime runtime, - node_embedding_node_api_callback node_api_cb, - void* node_api_cb_data) { - return EMBEDDED_RUNTIME(runtime)->InvokeNodeApi(node_api_cb, - node_api_cb_data); +node_embedding_exit_code NAPI_CDECL node_embedding_open_node_api_scope( + node_embedding_runtime runtime, napi_env* env) { + return EMBEDDED_RUNTIME(runtime)->OpenNodeApiScope(env); +} + +node_embedding_exit_code NAPI_CDECL +node_embedding_close_node_api_scope(node_embedding_runtime runtime) { + return EMBEDDED_RUNTIME(runtime)->CloseNodeApiScope(); } diff --git a/src/node_embedding_api.h b/src/node_embedding_api.h index 22bf0c0e803d69..19ebbf197d16be 100644 --- a/src/node_embedding_api.h +++ b/src/node_embedding_api.h @@ -166,43 +166,52 @@ typedef node_embedding_exit_code(NAPI_CDECL* node_embedding_error_handler)( size_t messages_size, node_embedding_exit_code exit_code); +typedef node_embedding_exit_code( + NAPI_CDECL* node_embedding_configure_platform_callback)( + void* cb_data, node_embedding_platform platform); + +typedef node_embedding_exit_code( + NAPI_CDECL* node_embedding_configure_runtime_callback)( + void* cb_data, + node_embedding_platform platform, + node_embedding_runtime runtime); + typedef void(NAPI_CDECL* node_embedding_get_args_callback)(void* cb_data, int32_t argc, const char* argv[]); typedef void(NAPI_CDECL* node_embedding_preload_callback)( - node_embedding_runtime runtime, void* cb_data, + node_embedding_runtime runtime, napi_env env, napi_value process, napi_value require); -typedef napi_value(NAPI_CDECL* node_embedding_initialize_module_callback)( +typedef napi_value(NAPI_CDECL* node_embedding_start_execution_callback)( + void* cb_data, node_embedding_runtime runtime, + napi_env env, + napi_value process, + napi_value require, + napi_value run_cjs); + +typedef napi_value(NAPI_CDECL* node_embedding_initialize_module_callback)( void* cb_data, + node_embedding_runtime runtime, napi_env env, const char* module_name, napi_value exports); typedef void(NAPI_CDECL* node_embedding_event_loop_handler)( - node_embedding_runtime runtime, void* handler_data); + void* handler_data, node_embedding_runtime runtime); typedef void(NAPI_CDECL* node_embedding_node_api_callback)( - node_embedding_runtime runtime, void* cb_data, napi_env env); + void* cb_data, node_embedding_runtime runtime, napi_env env); //============================================================================== // Functions //============================================================================== -//------------------------------------------------------------------------------ -// Node.js main function. -//------------------------------------------------------------------------------ - -// Runs Node.js main function as if it is invoked from Node.js CLI without any -// embedder customizations. -NAPI_EXTERN node_embedding_exit_code NAPI_CDECL -node_embedding_run_nodejs_main(int32_t argc, char* argv[]); - //------------------------------------------------------------------------------ // Error handling functions. //------------------------------------------------------------------------------ @@ -215,35 +224,38 @@ NAPI_EXTERN node_embedding_exit_code NAPI_CDECL node_embedding_on_error( // Node.js global platform functions. //------------------------------------------------------------------------------ -// Creates a new Node.js platform instance. +// Sets the API version for the Node.js embedding API and the Node-API. +NAPI_EXTERN node_embedding_exit_code NAPI_CDECL node_embedding_set_api_version( + int32_t embedding_api_version, int32_t node_api_version); + +// Runs Node.js main function as if it is invoked from Node.js CLI. +NAPI_EXTERN node_embedding_exit_code NAPI_CDECL node_embedding_run_main( + int32_t argc, + char* argv[], + node_embedding_configure_platform_callback configure_platform_cb, + void* configure_platform_cb_data, + node_embedding_configure_runtime_callback configure_runtime_cb, + void* configure_runtime_cb_data, + node_embedding_node_api_callback node_api_cb, + void* node_api_cb_data); + +// Creates and configures a new Node.js platform instance. NAPI_EXTERN node_embedding_exit_code NAPI_CDECL node_embedding_create_platform( - int32_t api_version, node_embedding_platform* result); + int32_t argc, + char* argv[], + node_embedding_configure_platform_callback configure_platform_cb, + void* configure_platform_cb_data, + node_embedding_platform* result); // Deletes the Node.js platform instance. NAPI_EXTERN node_embedding_exit_code NAPI_CDECL node_embedding_delete_platform(node_embedding_platform platform); -// Checks if the Node.js platform is initialized. -NAPI_EXTERN node_embedding_exit_code NAPI_CDECL -node_embedding_platform_is_initialized(node_embedding_platform platform, - bool* result); - // Sets the flags for the Node.js platform initialization. NAPI_EXTERN node_embedding_exit_code NAPI_CDECL node_embedding_platform_set_flags(node_embedding_platform platform, node_embedding_platform_flags flags); -// Sets the CLI arguments for the Node.js platform initialization. -NAPI_EXTERN node_embedding_exit_code NAPI_CDECL -node_embedding_platform_set_args(node_embedding_platform platform, - int32_t argc, - char* argv[]); - -// Initializes the Node.js platform. -NAPI_EXTERN node_embedding_exit_code NAPI_CDECL -node_embedding_platform_initialize(node_embedding_platform platform, - bool* early_return); - // Gets the parsed list of non-Node.js and Node.js arguments. NAPI_EXTERN node_embedding_exit_code NAPI_CDECL node_embedding_platform_get_parsed_args( @@ -257,19 +269,25 @@ node_embedding_platform_get_parsed_args( // Node.js runtime functions. //------------------------------------------------------------------------------ +// Runs the Node.js runtime with the provided configuration. +NAPI_EXTERN node_embedding_exit_code NAPI_CDECL node_embedding_run_runtime( + node_embedding_platform platform, + node_embedding_configure_runtime_callback configure_runtime_cb, + void* configure_runtime_cb_data, + node_embedding_node_api_callback node_api_cb, + void* node_api_cb_data); + // Creates a new Node.js runtime instance. NAPI_EXTERN node_embedding_exit_code NAPI_CDECL node_embedding_create_runtime( - node_embedding_platform platform, node_embedding_runtime* result); + node_embedding_platform platform, + node_embedding_configure_runtime_callback configure_runtime_cb, + void* configure_runtime_cb_data, + node_embedding_runtime* result); // Deletes the Node.js runtime instance. NAPI_EXTERN node_embedding_exit_code NAPI_CDECL node_embedding_delete_runtime(node_embedding_runtime runtime); -// Checks if the Node.js runtime is initialized. -NAPI_EXTERN node_embedding_exit_code NAPI_CDECL -node_embedding_runtime_is_initialized(node_embedding_runtime runtime, - bool* result); - // Sets the flags for the Node.js runtime initialization. NAPI_EXTERN node_embedding_exit_code NAPI_CDECL node_embedding_runtime_set_flags(node_embedding_runtime runtime, @@ -290,6 +308,13 @@ node_embedding_runtime_on_preload(node_embedding_runtime runtime, node_embedding_preload_callback preload_cb, void* preload_cb_data); +// Sets the start execution callback for the Node.js runtime initialization. +NAPI_EXTERN node_embedding_exit_code NAPI_CDECL +node_embedding_runtime_on_start_execution( + node_embedding_runtime runtime, + node_embedding_start_execution_callback start_execution_cb, + void* start_execution_cb_data); + // Adds a new module to the Node.js runtime. // It is accessed as process._linkedBinding(module_name) in the main JS and in // the related worker threads. @@ -301,11 +326,6 @@ node_embedding_runtime_add_module( void* init_module_cb_data, int32_t module_node_api_version); -// Initializes the Node.js runtime. -NAPI_EXTERN node_embedding_exit_code NAPI_CDECL -node_embedding_runtime_initialize(node_embedding_runtime runtime, - const char* main_script); - //------------------------------------------------------------------------------ // Node.js runtime functions for the event loop. //------------------------------------------------------------------------------ @@ -315,38 +335,40 @@ node_embedding_runtime_initialize(node_embedding_runtime runtime, // `node_embedding_runtime_complete_event_loop` function call. This function // helps to integrate the Node.js runtime event loop with the host UI loop. NAPI_EXTERN node_embedding_exit_code NAPI_CDECL -node_embedding_runtime_on_event_loop_run_request( +node_embedding_on_wake_up_event_loop( node_embedding_runtime runtime, node_embedding_event_loop_handler event_loop_handler, void* event_loop_handler_data); // Runs the Node.js runtime event loop. NAPI_EXTERN node_embedding_exit_code NAPI_CDECL -node_embedding_runtime_run_event_loop( - node_embedding_runtime runtime, - node_embedding_event_loop_run_mode run_mode, - bool* has_more_work); +node_embedding_run_event_loop(node_embedding_runtime runtime, + node_embedding_event_loop_run_mode run_mode, + bool* has_more_work); // Runs the Node.js runtime event loop in node_embedding_event_loop_run_default // mode and finishes it with emitting the beforeExit and exit process events. NAPI_EXTERN node_embedding_exit_code NAPI_CDECL -node_embedding_runtime_complete_event_loop(node_embedding_runtime runtime); +node_embedding_complete_event_loop(node_embedding_runtime runtime); //------------------------------------------------------------------------------ // Node.js runtime functions for the Node-API interop. //------------------------------------------------------------------------------ -// Sets the Node-API version for the Node.js runtime initialization. +// Invokes Node-API code. NAPI_EXTERN node_embedding_exit_code NAPI_CDECL -node_embedding_runtime_set_node_api_version(node_embedding_runtime runtime, - int32_t node_api_version); +node_embedding_run_node_api(node_embedding_runtime runtime, + node_embedding_node_api_callback node_api_cb, + void* node_api_cb_data); -// Invokes Node-API code. +// Opens a new Node-API scope. NAPI_EXTERN node_embedding_exit_code NAPI_CDECL -node_embedding_runtime_invoke_node_api( - node_embedding_runtime runtime, - node_embedding_node_api_callback node_api_cb, - void* node_api_cb_data); +node_embedding_open_node_api_scope(node_embedding_runtime runtime, + napi_env* env); + +// Closes the current Node-API scope. +NAPI_EXTERN node_embedding_exit_code NAPI_CDECL +node_embedding_close_node_api_scope(node_embedding_runtime runtime); EXTERN_C_END @@ -371,28 +393,3 @@ inline constexpr node_embedding_runtime_flags operator|( #endif #endif // SRC_NODE_EMBEDDING_API_H_ - -// TODO(vmoroz): Allow running Node.js uv_loop from UI loop. Follow the Electron -// implementation. - Complete implementation for non-Windows. -// TODO(vmoroz): Can we use some kind of waiter concept instead of the -// observer thread? -// TODO(vmoroz): Add startup callback with process and require parameters. -// TODO(vmoroz): Generate the main script based on the runtime settings. -// TODO(vmoroz): Set the global Inspector for he main runtime. -// TODO(vmoroz): Start workers from C++. -// TODO(vmoroz): Worker to inherit parent Inspector. -// TODO(vmoroz): Cancel pending tasks on runtime deletion. -// TODO(vmoroz): Can we initialize platform again if it returns early? -// TODO(vmoroz): Simplify API use for simple default cases. -// TODO(vmoroz): Test passing the V8 thread pool size. -// TODO(vmoroz): Make the args story simpler or clear named. -// TODO(vmoroz): Single runtime by default vs multiple runtimes on demand. -// TODO(vmoroz): Add a way to terminate the runtime. -// TODO(vmoroz): Allow to provide custom thread pool from the app. -// TODO(vmoroz): Consider adding a v-table for the API functions to simplify -// binding with other languages. -// TODO(vmoroz): libuv setup for the platform. -// TODO(vmoroz): Augment the node_embedding_run_nodejs_main with callbacks to -// setup platform, setup runtime, and to run runtime. -// TODO(vmoroz): We must not exit the process on node::Environment errors. -// TODO(vmoroz): Be explicit about the recoverable errors. diff --git a/test/embedding/README.md b/test/embedding/README.md new file mode 100644 index 00000000000000..e95379dc562bbc --- /dev/null +++ b/test/embedding/README.md @@ -0,0 +1,76 @@ +# C embedding API + +This file is an overview for C embedding API. +It is mostly to catch all the work in progress notes. + +## The API overview + +### Error handling API +- `node_embedding_on_error` + +### 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_set_args` + +- `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` + +### API 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. + +### Test TODOs + +- [ ] Add tests based on the environment and platform `cctest`s. +- [ ] Enable the test_main_modules_node_api test. +- [ ] Test failure in Preload callback. +- [ ] Test failure in linked modules. +- [ ] Add a test that handles JS errors. +- [ ] Make sure that the delete calls match the create calls. diff --git a/test/embedding/embedtest_modules_node_api.cc b/test/embedding/embedtest_modules_node_api.cc index 596e22dde8f15f..ece71dfa336101 100644 --- a/test/embedding/embedtest_modules_node_api.cc +++ b/test/embedding/embedtest_modules_node_api.cc @@ -4,8 +4,8 @@ #include #include -static napi_value NAPI_CDECL InitGreeterModule(node_embedding_runtime runtime, - void* cb_data, +static napi_value NAPI_CDECL InitGreeterModule(void* cb_data, + node_embedding_runtime runtime, napi_env env, const char* module_name, napi_value exports) { @@ -20,13 +20,14 @@ static napi_value NAPI_CDECL InitGreeterModule(node_embedding_runtime runtime, NAPI_AUTO_LENGTH, [](napi_env env, napi_callback_info info) -> napi_value { std::string greeting = "Hello, "; - napi_value arg; + napi_value arg{}; size_t arg_count = 1; - napi_get_cb_info(env, info, &arg_count, &arg, nullptr, nullptr); - AddUtf8String(greeting, env, arg); + NODE_API_CALL( + napi_get_cb_info(env, info, &arg_count, &arg, nullptr, nullptr)); + NODE_API_CALL(AddUtf8String(greeting, env, arg)); napi_value result; - napi_create_string_utf8( - env, greeting.c_str(), greeting.size(), &result); + NODE_API_CALL(napi_create_string_utf8( + env, greeting.c_str(), greeting.size(), &result)); return result; }, nullptr, @@ -36,8 +37,8 @@ static napi_value NAPI_CDECL InitGreeterModule(node_embedding_runtime runtime, } static napi_value NAPI_CDECL -InitReplicatorModule(node_embedding_runtime runtime, - void* cb_data, +InitReplicatorModule(void* cb_data, + node_embedding_runtime runtime, napi_env env, const char* module_name, napi_value exports) { @@ -52,10 +53,11 @@ InitReplicatorModule(node_embedding_runtime runtime, NAPI_AUTO_LENGTH, [](napi_env env, napi_callback_info info) -> napi_value { std::string str; - napi_value arg; + napi_value arg{}; size_t arg_count = 1; - napi_get_cb_info(env, info, &arg_count, &arg, nullptr, nullptr); - AddUtf8String(str, env, arg); + NODE_API_CALL( + napi_get_cb_info(env, info, &arg_count, &arg, nullptr, nullptr)); + NODE_API_CALL(AddUtf8String(str, env, arg)); str += " " + str; napi_value result; napi_create_string_utf8(env, str.c_str(), str.size(), &result); @@ -76,56 +78,66 @@ extern "C" int32_t test_main_linked_modules_node_api(int32_t argc, std::atomic greeterModuleInitCallCount{0}; std::atomic replicatorModuleInitCallCount{0}; - CHECK(node_embedding_on_error(HandleTestError, argv[0])); - - node_embedding_platform platform; - CHECK(node_embedding_create_platform(NODE_EMBEDDING_VERSION, &platform)); - CHECK(node_embedding_platform_set_args(platform, argc, argv)); - bool early_return = false; - CHECK(node_embedding_platform_initialize(platform, &early_return)); - if (early_return) { - return 0; - } + node_embedding_on_error(HandleTestError, argv[0]); - node_embedding_runtime runtime; - CHECK(node_embedding_create_runtime(platform, &runtime)); - CHECK(node_embedding_runtime_set_node_api_version(runtime, NAPI_VERSION)); - - CHECK(node_embedding_runtime_on_preload( - runtime, - [](node_embedding_runtime runtime, - void* /*cb_data*/, - napi_env env, - napi_value process, - napi_value /*require*/ - ) { - napi_value global; - napi_get_global(env, &global); - napi_set_named_property(env, global, "process", process); + CHECK_EXIT_CODE(RunMain( + argc, + argv, + nullptr, + [&](node_embedding_platform platform, node_embedding_runtime runtime) { + CHECK_EXIT_CODE(node_embedding_runtime_on_preload( + runtime, + [](void* /*cb_data*/, + node_embedding_runtime runtime, + napi_env env, + napi_value process, + napi_value /*require*/ + ) { + napi_value global; + napi_get_global(env, &global); + napi_set_named_property(env, global, "process", process); + }, + nullptr)); + + CHECK_EXIT_CODE( + node_embedding_runtime_add_module(runtime, + "greeter_module", + &InitGreeterModule, + &greeterModuleInitCallCount, + NAPI_VERSION)); + CHECK_EXIT_CODE( + node_embedding_runtime_add_module(runtime, + "replicator_module", + &InitReplicatorModule, + &replicatorModuleInitCallCount, + NAPI_VERSION)); + + CHECK_EXIT_CODE(node_embedding_runtime_on_start_execution( + runtime, + [](void* cb_data, + node_embedding_runtime runtime, + napi_env env, + napi_value process, + napi_value require, + napi_value run_cjs) -> napi_value { + napi_value script, undefined, result; + NODE_API_CALL(napi_create_string_utf8( + env, main_script, NAPI_AUTO_LENGTH, &script)); + NODE_API_CALL(napi_get_undefined(env, &undefined)); + NODE_API_CALL(napi_call_function( + env, undefined, run_cjs, 1, &script, &result)); + return result; + }, + nullptr)); + return node_embedding_exit_code_ok; }, nullptr)); - CHECK(node_embedding_runtime_add_module(runtime, - "greeter_module", - &InitGreeterModule, - &greeterModuleInitCallCount, - NAPI_VERSION)); - CHECK(node_embedding_runtime_add_module(runtime, - "replicator_module", - &InitReplicatorModule, - &replicatorModuleInitCallCount, - NAPI_VERSION)); - - CHECK(node_embedding_runtime_initialize(runtime, main_script)); - - CHECK(node_embedding_runtime_complete_event_loop(runtime)); - CHECK(node_embedding_delete_runtime(runtime)); - CHECK(node_embedding_delete_platform(platform)); - CHECK_TRUE(greeterModuleInitCallCount == expectedGreeterModuleInitCallCount); CHECK_TRUE(replicatorModuleInitCallCount == expectedReplicatorModuleInitCallCount); - return 0; + + return node_embedding_exit_code_ok; } extern "C" int32_t test_main_modules_node_api(int32_t argc, char* argv[]) { @@ -148,8 +160,7 @@ extern "C" int32_t test_main_modules_node_api(int32_t argc, char* argv[]) { node_embedding_runtime runtime; CHECK(node_embedding_create_runtime(platform, &runtime)); - CHECK(node_embedding_runtime_set_node_api_version(runtime, NAPI_VERSION)); - CHECK(node_embedding_runtime_initialize_from_script(runtime, nullptr)); + CHECK(node_embedding_runtime_initialize_from_script(runtime)); int32_t exit_code = 0; CHECK(InvokeNodeApi(runtime, [&](napi_env env) { napi_value global, import_name, require_name, import, require, cjs, es6, diff --git a/test/embedding/embedtest_node_api.cc b/test/embedding/embedtest_node_api.cc index 3ff5b25a2b3382..464185e8799c61 100644 --- a/test/embedding/embedtest_node_api.cc +++ b/test/embedding/embedtest_node_api.cc @@ -1,34 +1,58 @@ #include "embedtest_node_api.h" -#include -#include -#include +#include +#include +#include +#include const char* main_script = "globalThis.require = require('module').createRequire(process.execPath);\n" "globalThis.embedVars = { nön_ascıı: '🏳️‍🌈' };\n" "require('vm').runInThisContext(process.argv[1]);"; -static int32_t RunNodeInstance(node_embedding_platform platform); +void CallMe(node_embedding_runtime runtime, napi_env env); +void WaitMe(node_embedding_runtime runtime, napi_env env); +void WaitMeWithCheese(node_embedding_runtime runtime, napi_env env); extern "C" int32_t test_main_node_api(int32_t argc, char* argv[]) { - CHECK(node_embedding_on_error(HandleTestError, argv[0])); - - node_embedding_platform platform; - CHECK(node_embedding_create_platform(NODE_EMBEDDING_VERSION, &platform)); - CHECK(node_embedding_platform_set_args(platform, argc, argv)); - CHECK(node_embedding_platform_set_flags( - platform, node_embedding_platform_disable_node_options_env)); - bool early_return = false; - CHECK(node_embedding_platform_initialize(platform, &early_return)); - if (early_return) { - return 0; - } - - CHECK_EXIT_CODE(RunNodeInstance(platform)); - - CHECK(node_embedding_delete_platform(platform)); - return 0; + node_embedding_on_error(HandleTestError, argv[0]); + + CHECK_EXIT_CODE(RunMain( + argc, + argv, + [&](node_embedding_platform platform) { + CHECK_EXIT_CODE(node_embedding_platform_set_flags( + platform, node_embedding_platform_disable_node_options_env)); + return node_embedding_exit_code_ok; + }, + [&](node_embedding_platform platform, node_embedding_runtime runtime) { + CHECK_EXIT_CODE(node_embedding_runtime_on_start_execution( + runtime, + [](void* cb_data, + node_embedding_runtime runtime, + napi_env env, + napi_value process, + napi_value require, + napi_value run_cjs) -> napi_value { + napi_status status{}; + napi_value script, undefined, result; + NODE_API_CALL(napi_create_string_utf8( + env, main_script, NAPI_AUTO_LENGTH, &script)); + NODE_API_CALL(napi_get_undefined(env, &undefined)); + NODE_API_CALL(napi_call_function( + env, undefined, run_cjs, 1, &script, &result)); + return result; + }, + nullptr)); + return node_embedding_exit_code_ok; + }, + [&](node_embedding_runtime runtime, napi_env env) { + CallMe(runtime, env); + WaitMe(runtime, env); + WaitMeWithCheese(runtime, env); + })); + + return node_embedding_exit_code_ok; } napi_status AddUtf8String(std::string& str, napi_env env, napi_value value) { @@ -47,7 +71,7 @@ napi_status AddUtf8String(std::string& str, napi_env env, napi_value value) { void GetAndThrowLastErrorMessage(napi_env env) { const napi_extended_error_info* error_info; - napi_get_last_error_info((env), &error_info); + napi_get_last_error_info(env, &error_info); bool is_pending; const char* err_message = error_info->error_message; napi_is_exception_pending((env), &is_pending); @@ -59,43 +83,64 @@ void GetAndThrowLastErrorMessage(napi_env env) { } } -int32_t callMe(node_embedding_runtime runtime) { - int32_t exit_code = 0; - CHECK(InvokeNodeApi(runtime, [&](napi_env env) { - napi_value global; - napi_value cb; - napi_value key; - - NODE_API_CALL(napi_get_global(env, &global)); - NODE_API_CALL( - napi_create_string_utf8(env, "callMe", NAPI_AUTO_LENGTH, &key)); - NODE_API_CALL(napi_get_property(env, global, key, &cb)); - - napi_valuetype cb_type; - NODE_API_CALL(napi_typeof(env, cb, &cb_type)); - - // Only evaluate callMe if it was registered as a function. - if (cb_type == napi_function) { - napi_value undef; - NODE_API_CALL(napi_get_undefined(env, &undef)); - napi_value arg; - NODE_API_CALL( - napi_create_string_utf8(env, "called", NAPI_AUTO_LENGTH, &arg)); - napi_value result; - NODE_API_CALL(napi_call_function(env, undef, cb, 1, &arg, &result)); - - char buf[32]; - size_t len; - NODE_API_CALL(napi_get_value_string_utf8(env, result, buf, 32, &len)); - if (strcmp(buf, "called you") != 0) { - FAIL_RETURN_VOID("Invalid value received: %s\n", buf); - } - printf("%s", buf); - } else if (cb_type != napi_undefined) { - FAIL_RETURN_VOID("Invalid callMe value\n"); +void ThrowLastErrorMessage(napi_env env, const char* message) { + bool is_pending; + napi_is_exception_pending(env, &is_pending); + /* If an exception is already pending, don't rethrow it */ + if (!is_pending) { + const char* error_message = + message != nullptr ? message : "empty error message"; + napi_throw_error(env, nullptr, error_message); + } +} + +std::string FormatString(const char* format, ...) { + va_list args1; + va_start(args1, format); + va_list args2; + va_copy(args2, args1); // Required for some compilers like GCC. + std::string result(std::vsnprintf(nullptr, 0, format, args1), '\0'); + va_end(args1); + std::vsnprintf(&result[0], result.size() + 1, format, args2); + va_end(args2); + return result; +} + +void CallMe(node_embedding_runtime runtime, napi_env env) { + napi_value global; + napi_value cb; + napi_value key; + + NODE_API_CALL_RETURN_VOID(napi_get_global(env, &global)); + NODE_API_CALL_RETURN_VOID( + napi_create_string_utf8(env, "callMe", NAPI_AUTO_LENGTH, &key)); + NODE_API_CALL_RETURN_VOID(napi_get_property(env, global, key, &cb)); + + napi_valuetype cb_type; + NODE_API_CALL_RETURN_VOID(napi_typeof(env, cb, &cb_type)); + + // Only evaluate callMe if it was registered as a function. + if (cb_type == napi_function) { + napi_value undef; + NODE_API_CALL_RETURN_VOID(napi_get_undefined(env, &undef)); + napi_value arg; + NODE_API_CALL_RETURN_VOID( + napi_create_string_utf8(env, "called", NAPI_AUTO_LENGTH, &arg)); + napi_value result; + NODE_API_CALL_RETURN_VOID( + napi_call_function(env, undef, cb, 1, &arg, &result)); + + char buf[32]; + size_t len; + NODE_API_CALL_RETURN_VOID( + napi_get_value_string_utf8(env, result, buf, 32, &len)); + if (strcmp(buf, "called you") != 0) { + NODE_API_FAIL_RETURN_VOID("Invalid value received: %s\n", buf); } - })); - return exit_code; + printf("%s", buf); + } else if (cb_type != napi_undefined) { + NODE_API_FAIL_RETURN_VOID("Invalid callMe value\n"); + } } char callback_buf[32]; @@ -103,177 +148,154 @@ size_t callback_buf_len; napi_value c_cb(napi_env env, napi_callback_info info) { size_t argc = 1; napi_value arg; - - napi_get_cb_info(env, info, &argc, &arg, NULL, NULL); - napi_get_value_string_utf8(env, arg, callback_buf, 32, &callback_buf_len); - return NULL; -} - -int32_t waitMe(node_embedding_runtime runtime) { - int32_t exit_code = 0; - CHECK(InvokeNodeApi(runtime, [&](napi_env env) { - napi_value global; - napi_value cb; - napi_value key; - - NODE_API_CALL(napi_get_global(env, &global)); - NODE_API_CALL( - napi_create_string_utf8(env, "waitMe", NAPI_AUTO_LENGTH, &key)); - NODE_API_CALL(napi_get_property(env, global, key, &cb)); - - napi_valuetype cb_type; - NODE_API_CALL(napi_typeof(env, cb, &cb_type)); - - // Only evaluate waitMe if it was registered as a function. - if (cb_type == napi_function) { - napi_value undef; - NODE_API_CALL(napi_get_undefined(env, &undef)); - napi_value args[2]; - NODE_API_CALL( - napi_create_string_utf8(env, "waited", NAPI_AUTO_LENGTH, &args[0])); - NODE_API_CALL(napi_create_function( - env, "wait_cb", strlen("wait_cb"), c_cb, NULL, &args[1])); - - napi_value result; - memset(callback_buf, 0, 32); - NODE_API_CALL(napi_call_function(env, undef, cb, 2, args, &result)); - if (strcmp(callback_buf, "waited you") == 0) { - FAIL_RETURN_VOID("Anachronism detected: %s\n", callback_buf); - } - - CHECK_RETURN_VOID(node_embedding_runtime_run_event_loop( - runtime, node_embedding_event_loop_run_default, nullptr)); - - if (strcmp(callback_buf, "waited you") != 0) { - FAIL_RETURN_VOID("Invalid value received: %s\n", callback_buf); - } - printf("%s", callback_buf); - } else if (cb_type != napi_undefined) { - FAIL_RETURN_VOID("Invalid waitMe value\n"); - } - })); - - return exit_code; + NODE_API_CALL(napi_get_cb_info(env, info, &argc, &arg, nullptr, nullptr)); + NODE_API_CALL(napi_get_value_string_utf8( + env, arg, callback_buf, 32, &callback_buf_len)); + return nullptr; } -int32_t waitMeWithCheese(node_embedding_runtime runtime) { - int32_t exit_code = 0; - - CHECK(InvokeNodeApi(runtime, [&](napi_env env) { - napi_value global; - NODE_API_CALL(napi_get_global(env, &global)); - - napi_value wait_promise; - NODE_API_CALL( - napi_get_named_property(env, global, "waitPromise", &wait_promise)); - - napi_valuetype wait_promise_type; - NODE_API_CALL(napi_typeof(env, wait_promise, &wait_promise_type)); - - // Only evaluate waitPromise if it was registered as a function. - if (wait_promise_type == napi_undefined) { - return; - } else if (wait_promise_type != napi_function) { - FAIL_RETURN_VOID("Invalid waitPromise value\n"); +void WaitMe(node_embedding_runtime runtime, napi_env env) { + napi_value global; + napi_value cb; + napi_value key; + + NODE_API_CALL_RETURN_VOID(napi_get_global(env, &global)); + NODE_API_CALL_RETURN_VOID( + napi_create_string_utf8(env, "waitMe", NAPI_AUTO_LENGTH, &key)); + NODE_API_CALL_RETURN_VOID(napi_get_property(env, global, key, &cb)); + + napi_valuetype cb_type; + NODE_API_CALL_RETURN_VOID(napi_typeof(env, cb, &cb_type)); + + // Only evaluate waitMe if it was registered as a function. + if (cb_type == napi_function) { + napi_value undef; + NODE_API_CALL_RETURN_VOID(napi_get_undefined(env, &undef)); + napi_value args[2]; + NODE_API_CALL_RETURN_VOID( + napi_create_string_utf8(env, "waited", NAPI_AUTO_LENGTH, &args[0])); + NODE_API_CALL_RETURN_VOID(napi_create_function( + env, "wait_cb", strlen("wait_cb"), c_cb, NULL, &args[1])); + + napi_value result; + memset(callback_buf, 0, 32); + NODE_API_CALL_RETURN_VOID( + napi_call_function(env, undef, cb, 2, args, &result)); + if (strcmp(callback_buf, "waited you") == 0) { + NODE_API_FAIL_RETURN_VOID("Anachronism detected: %s\n", callback_buf); } - napi_value undefined; - napi_get_undefined(env, &undefined); - napi_value arg; - NODE_API_CALL( - napi_create_string_utf8(env, "waited", NAPI_AUTO_LENGTH, &arg)); + node_embedding_run_event_loop( + runtime, node_embedding_event_loop_run_default, nullptr); - memset(callback_buf, 0, 32); - napi_value promise; - NODE_API_CALL( - napi_call_function(env, undefined, wait_promise, 1, &arg, &promise)); - - if (strcmp(callback_buf, "waited with cheese") == 0) { - FAIL_RETURN_VOID("Anachronism detected: %s\n", callback_buf); + if (strcmp(callback_buf, "waited you") != 0) { + NODE_API_FAIL_RETURN_VOID("Invalid value received: %s\n", callback_buf); } + printf("%s", callback_buf); + } else if (cb_type != napi_undefined) { + NODE_API_FAIL_RETURN_VOID("Invalid waitMe value\n"); + } +} - bool is_promise; - NODE_API_CALL(napi_is_promise(env, promise, &is_promise)); - if (!is_promise) { - FAIL_RETURN_VOID("Result is not a Promise\n"); - } +void WaitMeWithCheese(node_embedding_runtime runtime, napi_env env) { + enum class PromiseState { + kPending, + kFulfilled, + kRejected, + }; + + PromiseState promise_state = PromiseState::kPending; + napi_value global{}, wait_promise{}, undefined{}; + napi_value on_fulfilled{}, on_rejected{}; + napi_value then_args[2] = {}; + const char* expected{}; + + NODE_API_CALL_RETURN_VOID(napi_get_global(env, &global)); + NODE_API_CALL_RETURN_VOID(napi_get_undefined(env, &undefined)); + + NODE_API_CALL_RETURN_VOID( + napi_get_named_property(env, global, "waitPromise", &wait_promise)); + + napi_valuetype wait_promise_type; + NODE_API_CALL_RETURN_VOID(napi_typeof(env, wait_promise, &wait_promise_type)); + + // Only evaluate waitPromise if it was registered as a function. + if (wait_promise_type == napi_undefined) { + return; + } else if (wait_promise_type != napi_function) { + NODE_API_FAIL_RETURN_VOID("Invalid waitPromise value\n"); + } - enum class PromiseState { - kPending, - kFulfilled, - kRejected, - }; - - PromiseState promise_state = PromiseState::kPending; - napi_value on_fulfilled, on_rejected; - - NODE_API_CALL(napi_create_function( - env, - "onFulfilled", - NAPI_AUTO_LENGTH, - [](napi_env env, napi_callback_info info) -> napi_value { - size_t argc = 1; - napi_value result; - void* data; - napi_get_cb_info(env, info, &argc, &result, nullptr, &data); - napi_get_value_string_utf8( - env, result, callback_buf, 32, &callback_buf_len); - *static_cast(data) = PromiseState::kFulfilled; - return nullptr; - }, - &promise_state, - &on_fulfilled)); - NODE_API_CALL(napi_create_function( - env, - "rejected", - NAPI_AUTO_LENGTH, - [](napi_env env, napi_callback_info info) -> napi_value { - size_t argc = 1; - napi_value result; - void* data; - napi_get_cb_info(env, info, &argc, &result, nullptr, &data); - napi_get_value_string_utf8( - env, result, callback_buf, 32, &callback_buf_len); - *static_cast(data) = PromiseState::kRejected; - return nullptr; - }, - &promise_state, - &on_rejected)); - napi_value then; - NODE_API_CALL(napi_get_named_property(env, promise, "then", &then)); - napi_value then_args[2] = {on_fulfilled, on_rejected}; - NODE_API_CALL( - napi_call_function(env, promise, then, 2, then_args, nullptr)); - - while (promise_state == PromiseState::kPending) { - CHECK_RETURN_VOID(node_embedding_runtime_run_event_loop( - runtime, node_embedding_event_loop_run_nowait, nullptr)); - } + napi_value arg; + NODE_API_CALL_RETURN_VOID( + napi_create_string_utf8(env, "waited", NAPI_AUTO_LENGTH, &arg)); - const char* expected = (promise_state == PromiseState::kFulfilled) - ? "waited with cheese" - : "waited without cheese"; + memset(callback_buf, 0, 32); + napi_value promise; + NODE_API_CALL_RETURN_VOID( + napi_call_function(env, undefined, wait_promise, 1, &arg, &promise)); - if (strcmp(callback_buf, expected) != 0) { - FAIL_RETURN_VOID("Invalid value received: %s\n", callback_buf); - } - printf("%s", callback_buf); - })); - return exit_code; -} + if (strcmp(callback_buf, "waited with cheese") == 0) { + NODE_API_FAIL_RETURN_VOID("Anachronism detected: %s\n", callback_buf); + } -int32_t RunNodeInstance(node_embedding_platform platform) { - node_embedding_runtime runtime; - CHECK(node_embedding_create_runtime(platform, &runtime)); - CHECK(node_embedding_runtime_set_node_api_version(runtime, NAPI_VERSION)); - CHECK(node_embedding_runtime_initialize(runtime, main_script)); + bool is_promise; + NODE_API_CALL_RETURN_VOID(napi_is_promise(env, promise, &is_promise)); + if (!is_promise) { + NODE_API_FAIL_RETURN_VOID("Result is not a Promise\n"); + } - CHECK_EXIT_CODE(callMe(runtime)); - CHECK_EXIT_CODE(waitMe(runtime)); - CHECK_EXIT_CODE(waitMeWithCheese(runtime)); + NODE_API_CALL_RETURN_VOID(napi_create_function( + env, + "onFulfilled", + NAPI_AUTO_LENGTH, + [](napi_env env, napi_callback_info info) -> napi_value { + size_t argc = 1; + napi_value result; + void* data; + napi_get_cb_info(env, info, &argc, &result, nullptr, &data); + napi_get_value_string_utf8( + env, result, callback_buf, 32, &callback_buf_len); + *static_cast(data) = PromiseState::kFulfilled; + return nullptr; + }, + &promise_state, + &on_fulfilled)); + NODE_API_CALL_RETURN_VOID(napi_create_function( + env, + "rejected", + NAPI_AUTO_LENGTH, + [](napi_env env, napi_callback_info info) -> napi_value { + size_t argc = 1; + napi_value result; + void* data; + napi_get_cb_info(env, info, &argc, &result, nullptr, &data); + napi_get_value_string_utf8( + env, result, callback_buf, 32, &callback_buf_len); + *static_cast(data) = PromiseState::kRejected; + return nullptr; + }, + &promise_state, + &on_rejected)); + napi_value then; + NODE_API_CALL_RETURN_VOID( + napi_get_named_property(env, promise, "then", &then)); + then_args[0] = on_fulfilled; + then_args[1] = on_rejected; + NODE_API_CALL_RETURN_VOID( + napi_call_function(env, promise, then, 2, then_args, nullptr)); + + while (promise_state == PromiseState::kPending) { + node_embedding_run_event_loop( + runtime, node_embedding_event_loop_run_nowait, nullptr); + } - CHECK(node_embedding_runtime_complete_event_loop(runtime)); - CHECK(node_embedding_delete_runtime(runtime)); + expected = (promise_state == PromiseState::kFulfilled) + ? "waited with cheese" + : "waited without cheese"; - return 0; + if (strcmp(callback_buf, expected) != 0) { + NODE_API_FAIL_RETURN_VOID("Invalid value received: %s\n", callback_buf); + } + printf("%s", callback_buf); } diff --git a/test/embedding/embedtest_node_api.h b/test/embedding/embedtest_node_api.h index ab34610af5a596..4ccf916182dbb2 100644 --- a/test/embedding/embedtest_node_api.h +++ b/test/embedding/embedtest_node_api.h @@ -29,7 +29,7 @@ HandleTestError(void* handler_data, } else { for (size_t i = 0; i < messages_size; ++i) printf("%s\n", messages[i]); } - return exit_code; + return node_embedding_exit_code_ok; } #endif @@ -40,93 +40,203 @@ napi_status AddUtf8String(std::string& str, napi_env env, napi_value value); void GetAndThrowLastErrorMessage(napi_env env); -inline node_embedding_exit_code InvokeNodeApi( - node_embedding_runtime runtime, const std::function& func) { - return node_embedding_runtime_invoke_node_api( +void ThrowLastErrorMessage(napi_env env, const char* message); + +std::string FormatString(const char* format, ...); + +inline node_embedding_exit_code RunMain( + int32_t argc, + char* argv[], + const std::function& + configurePlatform, + const std::function& configureRuntime, + const std::function& runNodeApi) { + return node_embedding_run_main( + argc, + argv, + configurePlatform ? + [](void* cb_data, node_embedding_platform platform) { + auto configurePlatform = static_cast< + std::function*>( + cb_data); + return (*configurePlatform)(platform); + } : nullptr, + const_cast< + std::function*>( + &configurePlatform), + configureRuntime ? + [](void* cb_data, + node_embedding_platform platform, + node_embedding_runtime runtime) { + auto configureRuntime = + static_cast*>(cb_data); + return (*configureRuntime)(platform, runtime); + } : nullptr, + const_cast*>( + &configureRuntime), + runNodeApi ? + [](void* cb_data, node_embedding_runtime runtime, napi_env env) { + auto runNodeApi = + static_cast*>( + cb_data); + (*runNodeApi)(runtime, env); + } : nullptr, + const_cast*>( + &runNodeApi)); +} + +inline node_embedding_exit_code RunRuntime( + node_embedding_platform platform, + const std::function& configureRuntime, + const std::function& runNodeApi) { + return node_embedding_run_runtime( + platform, + configureRuntime ? + [](void* cb_data, + node_embedding_platform platform, + node_embedding_runtime runtime) { + auto configureRuntime = + static_cast*>(cb_data); + return (*configureRuntime)(platform, runtime); + } : nullptr, + const_cast*>( + &configureRuntime), + runNodeApi ? + [](void* cb_data, node_embedding_runtime runtime, napi_env env) { + auto runNodeApi = + static_cast*>( + cb_data); + (*runNodeApi)(runtime, env); + } : nullptr, + const_cast*>( + &runNodeApi)); +} + +inline node_embedding_exit_code CreateRuntime( + node_embedding_platform platform, + const std::function& configureRuntime, + node_embedding_runtime* runtime) { + return node_embedding_create_runtime( + platform, + configureRuntime ? + [](void* cb_data, + node_embedding_platform platform, + node_embedding_runtime runtime) { + auto configureRuntime = + static_cast*>(cb_data); + return (*configureRuntime)(platform, runtime); + } : nullptr, + const_cast*>( + &configureRuntime), + runtime); +} + +inline node_embedding_exit_code RunNodeApi( + node_embedding_runtime runtime, + const std::function& func) { + return node_embedding_run_node_api( runtime, - [](node_embedding_runtime runtime, void* cb_data, napi_env env) { - auto func = static_cast*>(cb_data); - (*func)(env); + [](void* cb_data, node_embedding_runtime runtime, napi_env env) { + auto func = + static_cast*>( + cb_data); + (*func)(runtime, env); }, - const_cast*>(&func)); + const_cast*>( + &func)); } -#define NODE_API_CALL(expr) \ +// +// Error handling macros copied from test/js_native_api/common.h +// + +// Empty value so that macros here are able to return NULL or void +#define NODE_API_RETVAL_NOTHING // Intentionally blank #define + +#define NODE_API_FAIL_BASE(ret_val, ...) \ do { \ - if ((expr) != napi_ok) { \ - GetAndThrowLastErrorMessage(env); \ - exit_code = 1; \ - return; \ - } \ + ThrowLastErrorMessage(env, FormatString(__VA_ARGS__).c_str()); \ + return ret_val; \ } while (0) -#define CHECK(expr) \ +// Returns NULL on failed assertion. +// This is meant to be used inside napi_callback methods. +#define NODE_API_FAIL(...) NODE_API_FAIL_BASE(NULL, __VA_ARGS__) + +// Returns empty on failed assertion. +// This is meant to be used inside functions with void return type. +#define NODE_API_FAIL_RETURN_VOID(...) \ + NODE_API_FAIL_BASE(NODE_API_RETVAL_NOTHING, __VA_ARGS__) + +#define NODE_API_ASSERT_BASE(expr, ret_val) \ do { \ - if ((expr) != node_embedding_exit_code_ok) { \ - fprintf(stderr, "Failed: %s\n", #expr); \ - fprintf(stderr, "File: %s\n", __FILE__); \ - fprintf(stderr, "Line: %d\n", __LINE__); \ - return 1; \ + if (!(expr)) { \ + napi_throw_error(env, NULL, "Failed: (" #expr ")"); \ + return ret_val; \ } \ } while (0) -#define CHECK_RETURN_VOID(expr) \ +// Returns NULL on failed assertion. +// This is meant to be used inside napi_callback methods. +#define NODE_API_ASSERT(expr) NODE_API_ASSERT_BASE(expr, NULL) + +// Returns empty on failed assertion. +// This is meant to be used inside functions with void return type. +#define NODE_API_ASSERT_RETURN_VOID(expr) \ + NODE_API_ASSERT_BASE(expr, NODE_API_RETVAL_NOTHING) + +#define NODE_API_CALL_BASE(expr, ret_val) \ do { \ - if ((expr) != node_embedding_exit_code_ok) { \ - fprintf(stderr, "Failed: %s\n", #expr); \ - fprintf(stderr, "File: %s\n", __FILE__); \ - fprintf(stderr, "Line: %d\n", __LINE__); \ - exit_code = 1; \ - return; \ + if ((expr) != napi_ok) { \ + GetAndThrowLastErrorMessage(env); \ + return ret_val; \ } \ } while (0) -#define CHECK_TRUE(expr) \ +// Returns NULL if the_call doesn't return napi_ok. +#define NODE_API_CALL(expr) NODE_API_CALL_BASE(expr, NULL) + +// Returns empty if the_call doesn't return napi_ok. +#define NODE_API_CALL_RETURN_VOID(expr) \ + NODE_API_CALL_BASE(expr, NODE_API_RETVAL_NOTHING) + +#define CHECK_EXIT_CODE(expr) \ do { \ - if (!(expr)) { \ - fprintf(stderr, "Failed: %s\n", #expr); \ - fprintf(stderr, "File: %s\n", __FILE__); \ - fprintf(stderr, "Line: %d\n", __LINE__); \ - return 1; \ + node_embedding_exit_code exit_code = (expr); \ + if (exit_code != node_embedding_exit_code_ok) { \ + return exit_code; \ } \ } while (0) -#define CHECK_TRUE_RETURN_VOID(expr) \ +#define CHECK_EXIT_CODE_RETURN_VOID(expr) \ do { \ - if (!(expr)) { \ + node_embedding_exit_code exit_code_ = (expr); \ + if (exit_code_ != node_embedding_exit_code_ok) { \ fprintf(stderr, "Failed: %s\n", #expr); \ fprintf(stderr, "File: %s\n", __FILE__); \ fprintf(stderr, "Line: %d\n", __LINE__); \ - exit_code = 1; \ + exit(exit_code_); \ return; \ } \ } while (0) -#define FAIL(...) \ - do { \ - fprintf(stderr, __VA_ARGS__); \ - return 1; \ - } while (0) - -#define FAIL_RETURN_VOID(...) \ - do { \ - fprintf(stderr, __VA_ARGS__); \ - exit_code = 1; \ - return; \ - } while (0) - -#define CHECK_EXIT_CODE(code) \ +#define CHECK_TRUE(expr) \ do { \ - int exit_code = (code); \ - if (exit_code != 0) { \ - return exit_code; \ + if (!(expr)) { \ + fprintf(stderr, "Failed: %s\n", #expr); \ + fprintf(stderr, "File: %s\n", __FILE__); \ + fprintf(stderr, "Line: %d\n", __LINE__); \ + return node_embedding_exit_code_generic_user_error; \ } \ } while (0) #endif // TEST_EMBEDDING_EMBEDTEST_NODE_API_H_ - -// TODO(vmoroz): Enable the test_main_modules_node_api test. -// TODO(vmoroz): Test failure in Preload callback. -// TODO(vmoroz): Test failure in linked modules. -// TODO(vmoroz): Add a test that handles JS errors. -// TODO(vmoroz): Make sure that delete call matches the create call. diff --git a/test/embedding/embedtest_nodejs_main_node_api.cc b/test/embedding/embedtest_nodejs_main_node_api.cc index 7f3495904b5fa0..5c6762e6689202 100644 --- a/test/embedding/embedtest_nodejs_main_node_api.cc +++ b/test/embedding/embedtest_nodejs_main_node_api.cc @@ -4,5 +4,6 @@ // invoked from the libnode shared library as it would be run from the Node.js // CLI. No embedder customizations are available in this case. extern "C" int32_t test_main_nodejs_main_node_api(int32_t argc, char* argv[]) { - return node_embedding_run_nodejs_main(argc, argv); + return node_embedding_run_main( + argc, argv, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr); } diff --git a/test/embedding/embedtest_preload_node_api.cc b/test/embedding/embedtest_preload_node_api.cc index ec083e91d49aea..35ead3f2eb0c67 100644 --- a/test/embedding/embedtest_preload_node_api.cc +++ b/test/embedding/embedtest_preload_node_api.cc @@ -6,36 +6,47 @@ // Tests that the same preload callback is called from the main thread and from // the worker thread. extern "C" int32_t test_main_preload_node_api(int32_t argc, char* argv[]) { - node_embedding_platform platform; - CHECK(node_embedding_create_platform(NODE_EMBEDDING_VERSION, &platform)); - CHECK(node_embedding_platform_set_args(platform, argc, argv)); - bool early_return = false; - CHECK(node_embedding_platform_initialize(platform, &early_return)); - if (early_return) { - return 0; - } + CHECK_EXIT_CODE(RunMain( + argc, + argv, + nullptr, + [&](node_embedding_platform platform, node_embedding_runtime runtime) { + CHECK_EXIT_CODE(node_embedding_runtime_on_preload( + runtime, + [](void* /*cb_data*/, + node_embedding_runtime runtime, + napi_env env, + napi_value /*process*/, + napi_value /*require*/ + ) { + napi_value global, value; + NODE_API_CALL_RETURN_VOID(napi_get_global(env, &global)); + NODE_API_CALL_RETURN_VOID(napi_create_int32(env, 42, &value)); + NODE_API_CALL_RETURN_VOID( + napi_set_named_property(env, global, "preloadValue", value)); + }, + nullptr)); + CHECK_EXIT_CODE(node_embedding_runtime_on_start_execution( + runtime, + [](void* cb_data, + node_embedding_runtime runtime, + napi_env env, + napi_value process, + napi_value require, + napi_value run_cjs) -> napi_value { + napi_value script, undefined, result; + NODE_API_CALL(napi_create_string_utf8( + env, main_script, NAPI_AUTO_LENGTH, &script)); + NODE_API_CALL(napi_get_undefined(env, &undefined)); + NODE_API_CALL(napi_call_function( + env, undefined, run_cjs, 1, &script, &result)); + return result; + }, + nullptr)); - node_embedding_runtime runtime; - CHECK(node_embedding_create_runtime(platform, &runtime)); - CHECK(node_embedding_runtime_set_node_api_version(runtime, NAPI_VERSION)); - CHECK(node_embedding_runtime_on_preload( - runtime, - [](node_embedding_runtime runtime, - void* /*cb_data*/, - napi_env env, - napi_value /*process*/, - napi_value /*require*/ - ) { - napi_value global, value; - napi_get_global(env, &global); - napi_create_int32(env, 42, &value); - napi_set_named_property(env, global, "preloadValue", value); + return node_embedding_exit_code_ok; }, nullptr)); - CHECK(node_embedding_runtime_initialize(runtime, main_script)); - CHECK(node_embedding_runtime_complete_event_loop(runtime)); - CHECK(node_embedding_delete_runtime(runtime)); - CHECK(node_embedding_delete_platform(platform)); - return 0; + return node_embedding_exit_code_ok; } diff --git a/test/embedding/embedtest_threading_node_api.cc b/test/embedding/embedtest_threading_node_api.cc index bc3ac7a10aaf9f..bda39e78315c86 100644 --- a/test/embedding/embedtest_threading_node_api.cc +++ b/test/embedding/embedtest_threading_node_api.cc @@ -11,48 +11,63 @@ extern "C" int32_t test_main_threading_runtime_per_thread_node_api( int32_t argc, char* argv[]) { node_embedding_platform platform; - CHECK(node_embedding_create_platform(1, &platform)); - CHECK(node_embedding_platform_set_args(platform, argc, argv)); - bool early_return = false; - CHECK(node_embedding_platform_initialize(platform, &early_return)); - if (early_return) { - return 0; - } - - std::atomic global_count{0}; - std::atomic global_exit_code{0}; - const size_t thread_count = 12; std::vector threads; threads.reserve(thread_count); + std::atomic global_count{0}; + std::atomic global_exit_code{}; + + CHECK_EXIT_CODE( + node_embedding_create_platform(argc, argv, nullptr, nullptr, &platform)); + if (!platform) { + return node_embedding_exit_code_ok; // early return + } + for (size_t i = 0; i < thread_count; i++) { threads.emplace_back([platform, &global_count, &global_exit_code] { - int32_t exit_code = [&]() { - node_embedding_runtime runtime; - CHECK(node_embedding_create_runtime(platform, &runtime)); - // Inspector can be associated with only one runtime in the process. - CHECK(node_embedding_runtime_set_flags( - runtime, - node_embedding_runtime_default_flags | - node_embedding_runtime_no_create_inspector)); - CHECK( - node_embedding_runtime_set_node_api_version(runtime, NAPI_VERSION)); - CHECK(node_embedding_runtime_initialize(runtime, main_script)); - CHECK(InvokeNodeApi(runtime, [&](napi_env env) { - napi_value global, my_count; - NODE_API_CALL(napi_get_global(env, &global)); - NODE_API_CALL( - napi_get_named_property(env, global, "myCount", &my_count)); - int32_t count; - NODE_API_CALL(napi_get_value_int32(env, my_count, &count)); - global_count.fetch_add(count); - })); - - CHECK(node_embedding_runtime_complete_event_loop(runtime)); - CHECK(node_embedding_delete_runtime(runtime)); - return 0; + node_embedding_exit_code exit_code = [&]() { + CHECK_EXIT_CODE(RunRuntime( + platform, + [&](node_embedding_platform platform, + node_embedding_runtime runtime) { + // Inspector can be associated with only one runtime in the + // process. + CHECK_EXIT_CODE(node_embedding_runtime_set_flags( + runtime, + node_embedding_runtime_default_flags | + node_embedding_runtime_no_create_inspector)); + CHECK_EXIT_CODE(node_embedding_runtime_on_start_execution( + runtime, + [](void* cb_data, + node_embedding_runtime runtime, + napi_env env, + napi_value process, + napi_value require, + napi_value run_cjs) -> napi_value { + napi_value script, undefined, result; + NODE_API_CALL(napi_create_string_utf8( + env, main_script, NAPI_AUTO_LENGTH, &script)); + NODE_API_CALL(napi_get_undefined(env, &undefined)); + NODE_API_CALL(napi_call_function( + env, undefined, run_cjs, 1, &script, &result)); + return result; + }, + nullptr)); + return node_embedding_exit_code_ok; + }, + [&](node_embedding_runtime runtime, napi_env env) { + napi_value global, my_count; + NODE_API_CALL_RETURN_VOID(napi_get_global(env, &global)); + NODE_API_CALL_RETURN_VOID( + napi_get_named_property(env, global, "myCount", &my_count)); + int32_t count; + NODE_API_CALL_RETURN_VOID( + napi_get_value_int32(env, my_count, &count)); + global_count.fetch_add(count); + })); + return node_embedding_exit_code_ok; }(); - if (exit_code != 0) { + if (exit_code != node_embedding_exit_code_ok) { global_exit_code.store(exit_code); } }); @@ -64,11 +79,11 @@ extern "C" int32_t test_main_threading_runtime_per_thread_node_api( CHECK_EXIT_CODE(global_exit_code.load()); - CHECK(node_embedding_delete_platform(platform)); + CHECK_EXIT_CODE(node_embedding_delete_platform(platform)); fprintf(stdout, "%d\n", global_count.load()); - return 0; + return node_embedding_exit_code_ok; } // Tests that multiple runtimes can run in the same thread. @@ -77,82 +92,103 @@ extern "C" int32_t test_main_threading_runtime_per_thread_node_api( extern "C" int32_t test_main_threading_several_runtimes_per_thread_node_api( int32_t argc, char* argv[]) { node_embedding_platform platform; - CHECK(node_embedding_create_platform(1, &platform)); - CHECK(node_embedding_platform_set_args(platform, argc, argv)); - bool early_return = false; - CHECK(node_embedding_platform_initialize(platform, &early_return)); - if (early_return) { - return 0; - } - const size_t runtime_count = 12; std::vector runtimes; runtimes.reserve(runtime_count); + bool more_work = false; + int32_t global_count = 0; + + CHECK_EXIT_CODE( + node_embedding_create_platform(argc, argv, nullptr, nullptr, &platform)); + if (!platform) { + return node_embedding_exit_code_ok; // early return + } + for (size_t i = 0; i < runtime_count; i++) { node_embedding_runtime runtime; - CHECK(node_embedding_create_runtime(platform, &runtime)); - // Inspector can be associated with only one runtime in the process. - CHECK(node_embedding_runtime_set_flags( - runtime, - node_embedding_runtime_default_flags | - node_embedding_runtime_no_create_inspector)); - CHECK(node_embedding_runtime_set_node_api_version(runtime, NAPI_VERSION)); - CHECK(node_embedding_runtime_initialize(runtime, main_script)); + CHECK_EXIT_CODE(CreateRuntime( + platform, + [&](node_embedding_platform platform, node_embedding_runtime runtime) { + // Inspector can be associated with only one runtime in the process. + CHECK_EXIT_CODE(node_embedding_runtime_set_flags( + runtime, + node_embedding_runtime_default_flags | + node_embedding_runtime_no_create_inspector)); + CHECK_EXIT_CODE(node_embedding_runtime_on_start_execution( + runtime, + [](void* cb_data, + node_embedding_runtime runtime, + napi_env env, + napi_value process, + napi_value require, + napi_value run_cjs) -> napi_value { + napi_value script, undefined, result; + NODE_API_CALL(napi_create_string_utf8( + env, main_script, NAPI_AUTO_LENGTH, &script)); + NODE_API_CALL(napi_get_undefined(env, &undefined)); + NODE_API_CALL(napi_call_function( + env, undefined, run_cjs, 1, &script, &result)); + return result; + }, + nullptr)); + + return node_embedding_exit_code_ok; + }, + &runtime)); runtimes.push_back(runtime); - int32_t exit_code = 0; - CHECK(InvokeNodeApi(runtime, [&](napi_env env) { - napi_value undefined, global, func; - NODE_API_CALL(napi_get_undefined(env, &undefined)); - NODE_API_CALL(napi_get_global(env, &global)); - NODE_API_CALL(napi_get_named_property(env, global, "incMyCount", &func)); - - napi_valuetype func_type; - NODE_API_CALL(napi_typeof(env, func, &func_type)); - CHECK_TRUE_RETURN_VOID(func_type == napi_function); - NODE_API_CALL( - napi_call_function(env, undefined, func, 0, nullptr, nullptr)); - })); - CHECK(exit_code); + CHECK_EXIT_CODE( + RunNodeApi(runtime, [&](node_embedding_runtime runtime, napi_env env) { + napi_value undefined, global, func; + NODE_API_CALL_RETURN_VOID(napi_get_undefined(env, &undefined)); + NODE_API_CALL_RETURN_VOID(napi_get_global(env, &global)); + NODE_API_CALL_RETURN_VOID( + napi_get_named_property(env, global, "incMyCount", &func)); + + napi_valuetype func_type; + NODE_API_CALL_RETURN_VOID(napi_typeof(env, func, &func_type)); + NODE_API_ASSERT_RETURN_VOID(func_type == napi_function); + NODE_API_CALL_RETURN_VOID( + napi_call_function(env, undefined, func, 0, nullptr, nullptr)); + })); } - bool more_work = false; do { more_work = false; for (node_embedding_runtime runtime : runtimes) { bool has_more_work = false; - CHECK(node_embedding_runtime_run_event_loop( + CHECK_EXIT_CODE(node_embedding_run_event_loop( runtime, node_embedding_event_loop_run_nowait, &has_more_work)); more_work |= has_more_work; } } while (more_work); - int32_t global_count = 0; for (node_embedding_runtime runtime : runtimes) { - int32_t exit_code = 0; - CHECK(InvokeNodeApi(runtime, [&](napi_env env) { - napi_value global, my_count; - NODE_API_CALL(napi_get_global(env, &global)); - NODE_API_CALL(napi_get_named_property(env, global, "myCount", &my_count)); - - napi_valuetype my_count_type; - NODE_API_CALL(napi_typeof(env, my_count, &my_count_type)); - CHECK_TRUE_RETURN_VOID(my_count_type == napi_number); - int32_t count; - NODE_API_CALL(napi_get_value_int32(env, my_count, &count)); - - global_count += count; - })); - CHECK(exit_code); - CHECK(node_embedding_runtime_complete_event_loop(runtime)); - CHECK(node_embedding_delete_runtime(runtime)); + CHECK_EXIT_CODE( + RunNodeApi(runtime, [&](node_embedding_runtime runtime, napi_env env) { + napi_value global, my_count; + NODE_API_CALL_RETURN_VOID(napi_get_global(env, &global)); + NODE_API_CALL_RETURN_VOID( + napi_get_named_property(env, global, "myCount", &my_count)); + + napi_valuetype my_count_type; + NODE_API_CALL_RETURN_VOID(napi_typeof(env, my_count, &my_count_type)); + NODE_API_ASSERT_RETURN_VOID(my_count_type == napi_number); + int32_t count; + NODE_API_CALL_RETURN_VOID( + napi_get_value_int32(env, my_count, &count)); + + global_count += count; + })); + CHECK_EXIT_CODE(node_embedding_complete_event_loop(runtime)); + CHECK_EXIT_CODE(node_embedding_delete_runtime(runtime)); } - CHECK(node_embedding_delete_platform(platform)); + CHECK_EXIT_CODE(node_embedding_delete_platform(platform)); fprintf(stdout, "%d\n", global_count); - return 0; + return node_embedding_exit_code_ok; } // Tests that a runtime can be invoked from different threads as long as only @@ -160,52 +196,76 @@ extern "C" int32_t test_main_threading_several_runtimes_per_thread_node_api( extern "C" int32_t test_main_threading_runtime_in_several_threads_node_api( int32_t argc, char* argv[]) { node_embedding_platform platform; - CHECK(node_embedding_create_platform(NODE_EMBEDDING_VERSION, &platform)); - CHECK(node_embedding_platform_set_args(platform, argc, argv)); - bool early_return = false; - CHECK(node_embedding_platform_initialize(platform, &early_return)); - if (early_return) { - return 0; - } - - node_embedding_runtime runtime; - CHECK(node_embedding_create_runtime(platform, &runtime)); - CHECK(node_embedding_runtime_set_node_api_version(runtime, NAPI_VERSION)); - CHECK(node_embedding_runtime_initialize(runtime, main_script)); // Use mutex to synchronize access to the runtime. std::mutex mutex; std::atomic result_count{0}; - std::atomic result_exit_code{0}; + std::atomic result_exit_code{ + node_embedding_exit_code_ok}; const size_t thread_count = 5; std::vector threads; threads.reserve(thread_count); + + CHECK_EXIT_CODE( + node_embedding_create_platform(argc, argv, nullptr, nullptr, &platform)); + if (!platform) { + return node_embedding_exit_code_ok; // early return + } + + node_embedding_runtime runtime; + CHECK_EXIT_CODE(CreateRuntime( + platform, + [&](node_embedding_platform platform, node_embedding_runtime runtime) { + CHECK_EXIT_CODE(node_embedding_runtime_on_start_execution( + runtime, + [](void* cb_data, + node_embedding_runtime runtime, + napi_env env, + napi_value process, + napi_value require, + napi_value run_cjs) -> napi_value { + napi_value script, undefined, result; + NODE_API_CALL(napi_create_string_utf8( + env, main_script, NAPI_AUTO_LENGTH, &script)); + NODE_API_CALL(napi_get_undefined(env, &undefined)); + NODE_API_CALL(napi_call_function( + env, undefined, run_cjs, 1, &script, &result)); + return result; + }, + nullptr)); + + return node_embedding_exit_code_ok; + }, + &runtime)); + for (size_t i = 0; i < thread_count; i++) { threads.emplace_back([runtime, &result_count, &result_exit_code, &mutex] { std::scoped_lock lock(mutex); - int32_t exit_code = InvokeNodeApi(runtime, [&](napi_env env) { - napi_value undefined, global, func, my_count; - NODE_API_CALL(napi_get_undefined(env, &undefined)); - NODE_API_CALL(napi_get_global(env, &global)); - NODE_API_CALL( - napi_get_named_property(env, global, "incMyCount", &func)); - - napi_valuetype func_type; - NODE_API_CALL(napi_typeof(env, func, &func_type)); - CHECK_TRUE_RETURN_VOID(func_type == napi_function); - NODE_API_CALL( - napi_call_function(env, undefined, func, 0, nullptr, nullptr)); - - NODE_API_CALL( - napi_get_named_property(env, global, "myCount", &my_count)); - napi_valuetype count_type; - NODE_API_CALL(napi_typeof(env, my_count, &count_type)); - CHECK_TRUE_RETURN_VOID(count_type == napi_number); - int32_t count; - NODE_API_CALL(napi_get_value_int32(env, my_count, &count)); - result_count.store(count); - }); - if (exit_code != 0) { + node_embedding_exit_code exit_code = RunNodeApi( + runtime, [&](node_embedding_runtime runtime, napi_env env) { + napi_value undefined, global, func, my_count; + NODE_API_CALL_RETURN_VOID(napi_get_undefined(env, &undefined)); + NODE_API_CALL_RETURN_VOID(napi_get_global(env, &global)); + NODE_API_CALL_RETURN_VOID( + napi_get_named_property(env, global, "incMyCount", &func)); + + napi_valuetype func_type; + NODE_API_CALL_RETURN_VOID(napi_typeof(env, func, &func_type)); + NODE_API_ASSERT_RETURN_VOID(func_type == napi_function); + NODE_API_CALL_RETURN_VOID( + napi_call_function(env, undefined, func, 0, nullptr, nullptr)); + + NODE_API_CALL_RETURN_VOID( + napi_get_named_property(env, global, "myCount", &my_count)); + napi_valuetype count_type; + NODE_API_CALL_RETURN_VOID(napi_typeof(env, my_count, &count_type)); + NODE_API_ASSERT_RETURN_VOID(count_type == napi_number); + int32_t count; + NODE_API_CALL_RETURN_VOID( + napi_get_value_int32(env, my_count, &count)); + result_count.store(count); + }); + if (exit_code != node_embedding_exit_code_ok) { result_exit_code.store(exit_code); } }); @@ -217,13 +277,13 @@ extern "C" int32_t test_main_threading_runtime_in_several_threads_node_api( CHECK_EXIT_CODE(result_exit_code.load()); - CHECK(node_embedding_runtime_complete_event_loop(runtime)); - CHECK(node_embedding_delete_runtime(runtime)); - CHECK(node_embedding_delete_platform(platform)); + CHECK_EXIT_CODE(node_embedding_complete_event_loop(runtime)); + CHECK_EXIT_CODE(node_embedding_delete_runtime(runtime)); + CHECK_EXIT_CODE(node_embedding_delete_platform(platform)); fprintf(stdout, "%d\n", result_count.load()); - return 0; + return node_embedding_exit_code_ok; } // Tests that a the runtime's event loop can be called from the UI thread @@ -273,79 +333,99 @@ extern "C" int32_t test_main_threading_runtime_in_ui_thread_node_api( } ui_queue; node_embedding_platform platform; - CHECK(node_embedding_create_platform(NODE_EMBEDDING_VERSION, &platform)); - CHECK(node_embedding_platform_set_args(platform, argc, argv)); - bool early_return = false; - CHECK(node_embedding_platform_initialize(platform, &early_return)); - if (early_return) { - return 0; + CHECK_EXIT_CODE( + node_embedding_create_platform(argc, argv, nullptr, nullptr, &platform)); + if (!platform) { + return node_embedding_exit_code_ok; // early return } node_embedding_runtime runtime; - CHECK(node_embedding_create_runtime(platform, &runtime)); - CHECK(node_embedding_runtime_set_node_api_version(runtime, NAPI_VERSION)); - - // The callback will be invoked from the runtime's event loop observer thread. - // It must schedule the work to the UI thread's event loop. - node_embedding_runtime_on_event_loop_run_request( - runtime, - [](node_embedding_runtime runtime, void* data) { - auto ui_queue = static_cast(data); - ui_queue->PostTask([runtime, ui_queue]() { - int32_t exit_code = 0; - CHECK_RETURN_VOID(node_embedding_runtime_run_event_loop( - runtime, node_embedding_event_loop_run_nowait, nullptr)); - - // Check myCount and stop the processing when it reaches 5. - exit_code = InvokeNodeApi(runtime, [&](napi_env env) { - napi_value global, my_count; - NODE_API_CALL(napi_get_global(env, &global)); - NODE_API_CALL( - napi_get_named_property(env, global, "myCount", &my_count)); - napi_valuetype count_type; - NODE_API_CALL(napi_typeof(env, my_count, &count_type)); - CHECK_TRUE_RETURN_VOID(count_type == napi_number); - int32_t count; - NODE_API_CALL(napi_get_value_int32(env, my_count, &count)); - if (count == 5) { - CHECK_RETURN_VOID( - node_embedding_runtime_complete_event_loop(runtime)); - fprintf(stdout, "%d\n", count); - ui_queue->Stop(); - } - }); - CHECK_RETURN_VOID(exit_code); - }); + CHECK_EXIT_CODE(CreateRuntime( + platform, + [&](node_embedding_platform platform, node_embedding_runtime runtime) { + // The callback will be invoked from the runtime's event loop observer + // thread. It must schedule the work to the UI thread's event loop. + CHECK_EXIT_CODE(node_embedding_on_wake_up_event_loop( + runtime, + [](void* data, node_embedding_runtime runtime) { + auto ui_queue = static_cast(data); + ui_queue->PostTask([runtime, ui_queue]() { + CHECK_EXIT_CODE_RETURN_VOID(node_embedding_run_event_loop( + runtime, node_embedding_event_loop_run_nowait, nullptr)); + + // Check myCount and stop the processing when it reaches 5. + CHECK_EXIT_CODE_RETURN_VOID(RunNodeApi( + runtime, [&](node_embedding_runtime runtime, napi_env env) { + napi_value global, my_count; + NODE_API_CALL_RETURN_VOID(napi_get_global(env, &global)); + NODE_API_CALL_RETURN_VOID(napi_get_named_property( + env, global, "myCount", &my_count)); + napi_valuetype count_type; + NODE_API_CALL_RETURN_VOID( + napi_typeof(env, my_count, &count_type)); + NODE_API_ASSERT_RETURN_VOID(count_type == napi_number); + int32_t count; + NODE_API_CALL_RETURN_VOID( + napi_get_value_int32(env, my_count, &count)); + if (count == 5) { + node_embedding_complete_event_loop(runtime); + fprintf(stdout, "%d\n", count); + ui_queue->Stop(); + } + })); + }); + }, + &ui_queue)); + + CHECK_EXIT_CODE(node_embedding_runtime_on_start_execution( + runtime, + [](void* cb_data, + node_embedding_runtime runtime, + napi_env env, + napi_value process, + napi_value require, + napi_value run_cjs) -> napi_value { + napi_value script, undefined, result; + NODE_API_CALL(napi_create_string_utf8( + env, main_script, NAPI_AUTO_LENGTH, &script)); + NODE_API_CALL(napi_get_undefined(env, &undefined)); + NODE_API_CALL(napi_call_function( + env, undefined, run_cjs, 1, &script, &result)); + return result; + }, + nullptr)); + + return node_embedding_exit_code_ok; }, - &ui_queue); + &runtime)); - CHECK(node_embedding_runtime_initialize(runtime, main_script)); - - // The initial task starts the JS code that then will do the timer scheduling. - // The timer supposed to be handled by the runtime's event loop. + // The initial task starts the JS code that then will do the timer + // scheduling. The timer supposed to be handled by the runtime's event loop. ui_queue.PostTask([runtime]() { - int32_t exit_code = InvokeNodeApi(runtime, [&](napi_env env) { - napi_value undefined, global, func; - NODE_API_CALL(napi_get_undefined(env, &undefined)); - NODE_API_CALL(napi_get_global(env, &global)); - NODE_API_CALL(napi_get_named_property(env, global, "incMyCount", &func)); - - napi_valuetype func_type; - NODE_API_CALL(napi_typeof(env, func, &func_type)); - CHECK_TRUE_RETURN_VOID(func_type == napi_function); - NODE_API_CALL( - napi_call_function(env, undefined, func, 0, nullptr, nullptr)); - - CHECK_RETURN_VOID(node_embedding_runtime_run_event_loop( - runtime, node_embedding_event_loop_run_nowait, nullptr)); - }); - CHECK_RETURN_VOID(exit_code); + node_embedding_exit_code exit_code = + RunNodeApi(runtime, [&](node_embedding_runtime runtime, napi_env env) { + napi_value undefined, global, func; + NODE_API_CALL_RETURN_VOID(napi_get_undefined(env, &undefined)); + NODE_API_CALL_RETURN_VOID(napi_get_global(env, &global)); + NODE_API_CALL_RETURN_VOID( + napi_get_named_property(env, global, "incMyCount", &func)); + + napi_valuetype func_type; + NODE_API_CALL_RETURN_VOID(napi_typeof(env, func, &func_type)); + NODE_API_ASSERT_RETURN_VOID(func_type == napi_function); + NODE_API_CALL_RETURN_VOID( + napi_call_function(env, undefined, func, 0, nullptr, nullptr)); + + node_embedding_run_event_loop( + runtime, node_embedding_event_loop_run_nowait, nullptr); + }); + CHECK_EXIT_CODE_RETURN_VOID(exit_code); }); ui_queue.Run(); - CHECK(node_embedding_delete_runtime(runtime)); - CHECK(node_embedding_delete_platform(platform)); + CHECK_EXIT_CODE(node_embedding_delete_runtime(runtime)); + CHECK_EXIT_CODE(node_embedding_delete_platform(platform)); - return 0; + return node_embedding_exit_code_ok; } From d261495af552cf4dc7d80f6116cd22418d7fb858 Mon Sep 17 00:00:00 2001 From: Vladimir Morozov Date: Fri, 20 Sep 2024 17:53:15 -0700 Subject: [PATCH 18/22] add config types --- doc/api/embedding.md | 68 ++++++++++++------- src/node_embedding_api.cc | 53 ++++++++------- src/node_embedding_api.h | 25 ++++--- test/embedding/README.md | 5 -- test/embedding/embedtest_modules_node_api.cc | 11 +-- test/embedding/embedtest_node_api.cc | 9 +-- test/embedding/embedtest_node_api.h | 45 ++++++------ test/embedding/embedtest_preload_node_api.cc | 7 +- .../embedding/embedtest_threading_node_api.cc | 25 ++++--- 9 files changed, 141 insertions(+), 107 deletions(-) diff --git a/doc/api/embedding.md b/doc/api/embedding.md index 0f2b8a42783889..9d63eaaccc29c5 100644 --- a/doc/api/embedding.md +++ b/doc/api/embedding.md @@ -233,6 +233,17 @@ added: REPLACEME This is an opaque pointer that represents a Node.js platform instance. Node.js allows only a single platform instance per process. +##### `node_embedding_platform_config` + + + +> Stability: 1 - Experimental + +This is an opaque pointer that represents a Node.js platform configuration +instance. + ##### `node_embedding_exit_code` + +> Stability: 1 - Experimental + +This is an opaque pointer that represents a Node.js runtime configuration. + ##### `node_embedding_runtime_flags` + +> Stability: 1 - Experimental + +```c +typedef void(NAPI_CDECL* node_embedding_run_task_callback)( + void* cb_data); +``` + +Function pointer type for the `node_embedding_run_task_functor` that runs +a task by the task runner. + +The callback parameters: + +- `[in] cb_data`: The data associated with the callback. + +#### Functor types + +##### `node_embedding_post_task_functor` + + + +> Stability: 1 - Experimental + +```c +typedef struct { + void* data; + node_embedding_post_task_callback invoke; + node_embedding_release_data_callback release; +} node_embedding_post_task_functor; +``` + +Functor posts a task to a task runner. + +The functor fields: + +- `data`: The data associated with the functor. +- `invoke`: The callback with this functor. +- `release`: The callback to delete the `data` associated with this functor. + +##### `node_embedding_run_task_functor` + + + +> Stability: 1 - Experimental + +```c +typedef struct { + void* data; + node_embedding_run_task_callback invoke; + node_embedding_release_data_callback release; +} node_embedding_run_task_functor; +``` + +Functor that runs a task. + +The functor fields: + +- `data`: The data associated with the functor. +- `invoke`: The callback with this functor. +- `release`: The callback to delete the `data` associated with this functor. #### Functions -##### `node_embedding_on_wake_up_event_loop` +##### `node_embedding_runtime_set_task_runner` + +> Stability: 1 - Experimental -Note that if new tasks are added in the `beforeExit` event handler, then after -processing these tasks, the `beforeExit` is raised again, and the loop is -continued until the `beforeExit` stops producing new tasks. Only after that the -`exit` event is emitted. No new tasks can be added in the `exit` event handler -or after that. +Terminates the Node.js runtime instance event loop. + +```c +node_embedding_status NAPI_CDECL +node_embedding_terminate_event_loop( + node_embedding_runtime runtime); +``` + +- `[in] runtime`: The Node.js runtime instance. + +Returns `node_embedding_status_ok` if there were no issues. + +The event loop is stopped and cannot be resumed. +No `beforeExit` or `exit` events are going to be raised. ### JavaScript/Native interop APIs #### Callback types -##### `node_embedding_node_api_callback` +##### `node_embedding_run_node_api_callback` + +> Stability: 1 - Experimental + +```c +typedef struct { + void* data; + node_embedding_run_node_api_callback invoke; +} node_embedding_run_node_api_functor_ref; +``` + +Functor reference that invokes Node-API code. + +The functor reference fields: + +- `data`: The data associated with the functor reference. +- `invoke`: The callback with this functor reference. + #### Functions ##### `node_embedding_run_node_api` @@ -1248,21 +1340,19 @@ added: REPLACEME > Stability: 1 - Experimental -Invokes a callback that runs Node-API code. +Runs Node-API code. ```c -node_embedding_exit_code NAPI_CDECL +node_embedding_status NAPI_CDECL node_embedding_run_node_api( node_embedding_runtime runtime, - node_embedding_node_api_callback node_api_cb, - void* node_api_cb_data); + node_embedding_run_node_api_functor_ref run_node_api); ``` - `[in] runtime`: The Node.js embedding_runtime instance. -- `[in] node_api_cb`: The callback that executes Node-API code. -- `[in] node_api_cb_data`: The data associated with the callback. +- `[in] run_node_api`: The functor reference that invokes Node-API code. -Returns `node_embedding_exit_code_ok` if there were no issues. +Returns `node_embedding_status_ok` if there were no issues. The function invokes the callback that runs Node-API code in the Node-API scope. Then, it triggers the uncaught exception handler if there were any @@ -1279,19 +1369,21 @@ added: REPLACEME Opens scope to run Node-API code. ```c -node_embedding_exit_code NAPI_CDECL +node_embedding_status NAPI_CDECL node_embedding_open_node_api_scope( node_embedding_runtime runtime, + node_embedding_node_api_scope* node_api_scope, napi_env* env); ``` - `[in] runtime`: The Node.js embedding_runtime instance. +- `[out] node_api_scope`: The opened Node-API scope. - `[out] env`: The Node-API environment that can be used in the scope. -Returns `node_embedding_exit_code_ok` if there were no issues. +Returns `node_embedding_status_ok` if there were no issues. The function opens up V8 Isolate, V8 handle, and V8 context scopes where it is -safe to the the Node-API environment. +safe to run the Node-API environment. ##### `node_embedding_close_node_api_scope` @@ -1301,17 +1393,19 @@ added: REPLACEME > Stability: 1 - Experimental -Opens scope to run Node-API code. +Closes the Node-API invocation scope. ```c -node_embedding_exit_code NAPI_CDECL +node_embedding_status NAPI_CDECL node_embedding_close_node_api_scope( - node_embedding_runtime runtime); + node_embedding_runtime runtime, + node_embedding_node_api_scope node_api_scope); ``` - `[in] runtime`: The Node.js embedding_runtime instance. +- `[in] node_api_scope`: The Node-API scope to close. -Returns `node_embedding_exit_code_ok` if there were no issues. +Returns `node_embedding_status_ok` if there were no issues. The function closes the V8 Isolate, V8 handle, and V8 context scopes. Then, it triggers the uncaught exception handler if there were any diff --git a/node.gyp b/node.gyp index 623d55ad64ba7b..7f70bfb68e4d2b 100644 --- a/node.gyp +++ b/node.gyp @@ -1276,13 +1276,15 @@ 'sources': [ 'src/node_snapshot_stub.cc', 'test/embedding/embedtest.cc', + 'test/embedding/embedtest_c_api.cc', + 'test/embedding/embedtest_c_api_common.cc', + 'test/embedding/embedtest_c_api_common.h', + 'test/embedding/embedtest_c_api_env.cc', + 'test/embedding/embedtest_c_api_modules.cc', + 'test/embedding/embedtest_c_api_preload.cc', + 'test/embedding/embedtest_c_api_run_main.cc', + 'test/embedding/embedtest_c_api_threading.cc', 'test/embedding/embedtest_main.cc', - 'test/embedding/embedtest_modules_node_api.cc', - 'test/embedding/embedtest_node_api.cc', - 'test/embedding/embedtest_node_api.h', - 'test/embedding/embedtest_nodejs_main_node_api.cc', - 'test/embedding/embedtest_preload_node_api.cc', - 'test/embedding/embedtest_threading_node_api.cc', ], 'conditions': [ diff --git a/src/js_native_api_v8.h b/src/js_native_api_v8.h index 9111a4aa49eab3..b0f9ee72e6ecee 100644 --- a/src/js_native_api_v8.h +++ b/src/js_native_api_v8.h @@ -88,27 +88,28 @@ struct napi_env__ { template inline void CallIntoModule( Call&& call, JSExceptionHandler&& handle_exception = HandleThrow) { - CallModuleScope scope = OpenCallModuleScope(); + CallModuleScopeData scope_data = OpenCallModuleScope(); + auto onModuleScopeLeave = node::OnScopeLeave( + [&] { CloseCallModuleScope(scope_data, handle_exception); }); call(this); - CloseCallModuleScope(scope, handle_exception); } - struct CallModuleScope { + struct CallModuleScopeData { int open_handle_scopes_before; int open_callback_scopes_before; }; - inline CallModuleScope OpenCallModuleScope() { + inline CallModuleScopeData OpenCallModuleScope() { napi_clear_last_error(this); return {open_handle_scopes, open_callback_scopes}; } template inline void CloseCallModuleScope( - const CallModuleScope& scope, + const CallModuleScopeData& scope_data, JSExceptionHandler&& handle_exception = HandleThrow) { - CHECK_EQ(open_handle_scopes, scope.open_handle_scopes_before); - CHECK_EQ(open_callback_scopes, scope.open_callback_scopes_before); + CHECK_EQ(open_handle_scopes, scope_data.open_handle_scopes_before); + CHECK_EQ(open_callback_scopes, scope_data.open_callback_scopes_before); if (!last_exception.IsEmpty()) { handle_exception(this, last_exception.Get(this->isolate)); last_exception.Reset(); diff --git a/src/node_embedding_api.cc b/src/node_embedding_api.cc index 267419091ec40e..72224771fec4ee 100644 --- a/src/node_embedding_api.cc +++ b/src/node_embedding_api.cc @@ -14,7 +14,7 @@ // or expression and their location in the source code. #define CAST_NOT_NULL_TO(value, type) \ - (value) == nullptr ? v8impl::EmbeddedErrorHandling::HandleError( \ + (value) == nullptr ? node::EmbeddedErrorHandling::HandleError( \ "Argument must not be null: " #value, \ __FILE__, \ __LINE__, \ @@ -22,15 +22,15 @@ : reinterpret_cast(value) #define EMBEDDED_PLATFORM(platform) \ - CAST_NOT_NULL_TO(platform, v8impl::EmbeddedPlatform) + CAST_NOT_NULL_TO(platform, node::EmbeddedPlatform) #define EMBEDDED_RUNTIME(runtime) \ - CAST_NOT_NULL_TO(runtime, v8impl::EmbeddedRuntime) + CAST_NOT_NULL_TO(runtime, node::EmbeddedRuntime) #define CHECK_ARG_NOT_NULL(arg) \ do { \ if ((arg) == nullptr) { \ - return v8impl::EmbeddedErrorHandling::HandleError( \ + return node::EmbeddedErrorHandling::HandleError( \ "Argument must not be null: " #arg, \ __FILE__, \ __LINE__, \ @@ -41,7 +41,7 @@ #define ASSERT_ARG(arg, expr) \ do { \ if (!(expr)) { \ - return v8impl::EmbeddedErrorHandling::HandleError( \ + return node::EmbeddedErrorHandling::HandleError( \ "Arg: " #arg " failed: " #expr, \ __FILE__, \ __LINE__, \ @@ -52,7 +52,7 @@ #define ASSERT(expr) \ do { \ if (!(expr)) { \ - return v8impl::EmbeddedErrorHandling::HandleError( \ + return node::EmbeddedErrorHandling::HandleError( \ "Expression returned false: " #expr, \ __FILE__, \ __LINE__, \ @@ -68,41 +68,21 @@ } \ } while (false) -namespace node { -// Declare functions implemented in embed_helpers.cc -v8::Maybe SpinEventLoopWithoutCleanup(Environment* env, - uv_run_mode run_mode); - -} // end of namespace node - namespace v8impl { napi_env NewEnv(v8::Local context, const std::string& module_filename, int32_t module_api_version); -namespace { - -template -struct FunctorDeleter { - void operator()(T* ptr) { - ptr->release(ptr->data); - delete ptr; - } -}; +} // namespace v8impl -template -using FunctorPtr = std::unique_ptr>; +namespace node { -template -FunctorPtr MakeUniqueFunctor(const T& functor) { - return FunctorPtr(new T(functor)); -} +// Declare functions implemented in embed_helpers.cc +v8::Maybe SpinEventLoopWithoutCleanup(Environment* env, + uv_run_mode run_mode); -template -std::shared_ptr MakeSharedFunctor(const T& functor) { - return std::shared_ptr(new T(functor), FunctorDeleter()); -} +namespace { // A helper class to convert std::vector to an array of C strings. // If the number of strings is less than kInplaceBufferSize, the strings are @@ -143,10 +123,68 @@ class CStringArray { std::unique_ptr allocated_buffer_; }; +// Stack implementation that works only with trivially constructible, +// destructible, and copyable types. It uses the small value optimization where +// several elements are stored in the in-place array. +template +class SmallTrivialStack { + static_assert(std::is_trivially_constructible_v, + "T must be trivially constructible"); + static_assert(std::is_trivially_destructible_v, + "T must be trivially destructible"); + static_assert(std::is_trivially_copyable_v, + "T must be trivially copyable"); + + public: + SmallTrivialStack() noexcept : stack_(inplace_entries_.data()) {} + + void Push(T&& value) { + EnsureCapacity(size_ + 1); + stack_[size_++] = std::move(value); + } + + void Pop() { + CHECK_GT(size_, 0); + --size_; + } + + size_t size() const { return size_; } + + const T& top() const { + CHECK_GT(size_, 0); + return stack_[size_ - 1]; + } + + SmallTrivialStack(const SmallTrivialStack&) = delete; + SmallTrivialStack& operator=(const SmallTrivialStack&) = delete; + + private: + void EnsureCapacity(size_t new_size) { + if (new_size <= capacity_) { + return; + } + + size_t new_capacity = capacity_ + capacity_ / 2; + std::unique_ptr new_allocated_entries = + std::make_unique(new_capacity); + std::memcpy(new_allocated_entries.get(), stack_, size_ * sizeof(T)); + allocated_entries_ = std::move(new_allocated_entries); + stack_ = allocated_entries_.get(); + capacity_ = new_capacity; + } + + private: + T* stack_{}; // Points to either inplace_entries_ or allocated_entries_. + size_t size_{}; // Number of elements in the stack. + size_t capacity_{kInplaceEntryCount}; + std::array inplace_entries_; + std::unique_ptr allocated_entries_; +}; + class EmbeddedErrorHandling { public: static node_embedding_status SetErrorHandler( - node_embedding_error_handler error_handler, void* error_handler_data); + const node_embedding_handle_error_functor& error_handler); static node_embedding_status HandleError(const std::string& message, node_embedding_status status); @@ -161,24 +199,16 @@ class EmbeddedErrorHandling { static std::string FormatString(const char* format, ...); + static StdFunction& ErrorHandler(); + + private: static node_embedding_status DefaultErrorHandler( void* handler_data, const char* messages[], size_t messages_size, node_embedding_status status); - - static node_embedding_error_handler error_handler() { - return error_handler_ ? error_handler_ : DefaultErrorHandler; - } - - private: - static node_embedding_error_handler error_handler_; - static void* error_handler_data_; }; -node_embedding_error_handler EmbeddedErrorHandling::error_handler_{}; -void* EmbeddedErrorHandling::error_handler_data_{}; - class EmbeddedPlatform { public: EmbeddedPlatform(int32_t argc, char* argv[]) noexcept @@ -194,8 +224,7 @@ class EmbeddedPlatform { int32_t argc, char* argv[], const node_embedding_configure_platform_functor_ref& configure_platform, - const node_embedding_configure_runtime_functor_ref& configure_runtime, - const node_embedding_node_api_functor_ref& run_node_api); + const node_embedding_configure_runtime_functor_ref& configure_runtime); static node_embedding_status Create( int32_t argc, @@ -252,29 +281,6 @@ class EmbeddedPlatform { int32_t EmbeddedPlatform::embedding_api_version_{}; int32_t EmbeddedPlatform::node_api_version_{}; -struct IsolateLocker { - IsolateLocker(node::CommonEnvironmentSetup* env_setup) - : v8_locker_(env_setup->isolate()), - isolate_scope_(env_setup->isolate()), - handle_scope_(env_setup->isolate()), - context_scope_(env_setup->context()) {} - - bool IsLocked() const { - return v8::Locker::IsLocked(v8::Isolate::GetCurrent()); - } - - void IncrementLockCount() { ++lock_count_; } - - bool DecrementLockCount() { return --lock_count_ == 0; } - - private: - int32_t lock_count_ = 1; - v8::Locker v8_locker_; - v8::Isolate::Scope isolate_scope_; - v8::HandleScope handle_scope_; - v8::Context::Scope context_scope_; -}; - class EmbeddedRuntime { public: explicit EmbeddedRuntime(EmbeddedPlatform* platform); @@ -284,8 +290,7 @@ class EmbeddedRuntime { static node_embedding_status Run( node_embedding_platform platform, - const node_embedding_configure_runtime_functor_ref& configure_runtime, - const node_embedding_node_api_functor_ref& run_node_api); + const node_embedding_configure_runtime_functor_ref& configure_runtime); static node_embedding_status Create( node_embedding_platform platform, @@ -305,7 +310,8 @@ class EmbeddedRuntime { const node_embedding_preload_functor& run_preload); node_embedding_status OnStartExecution( - node_embedding_start_execution_functor start_execution); + const node_embedding_start_execution_functor& start_execution, + const node_embedding_handle_result_functor& handle_result); node_embedding_status AddModule( const char* module_name, @@ -315,24 +321,30 @@ class EmbeddedRuntime { node_embedding_status Initialize( const node_embedding_configure_runtime_functor_ref& configure_runtime); - node_embedding_status OnWakeUpEventLoop( - const node_embedding_event_loop_functor& run_event_loop); + node_embedding_status SetTaskRunner( + const node_embedding_post_task_functor& post_task); node_embedding_status RunEventLoop( node_embedding_event_loop_run_mode run_mode, bool* has_more_work); node_embedding_status CompleteEventLoop(); + node_embedding_status TerminateEventLoop(); node_embedding_status RunNodeApi( - const node_embedding_node_api_functor_ref& run_node_api); + const node_embedding_run_node_api_functor_ref& run_node_api); - node_embedding_status OpenNodeApiScope(napi_env* env); - node_embedding_status CloseNodeApiScope(); + node_embedding_status OpenNodeApiScope( + node_embedding_node_api_scope* node_api_scope, napi_env* env); + node_embedding_status CloseNodeApiScope( + node_embedding_node_api_scope node_api_scope); bool IsNodeApiScopeOpened() const; static napi_env GetOrCreateNodeApiEnv(node::Environment* node_env, const std::string& module_filename); + size_t OpenV8Scope(); + void CloseV8Scope(size_t nest_level); + private: static void TriggerFatalException(napi_env env, v8::Local local_err); @@ -346,17 +358,18 @@ class EmbeddedRuntime { v8::Local context, void* priv); - void InitializeEventLoopPollingThread(); - void DestroyEventLoopPollingThread(); - void WakeupEventLoopPollingThread(); + uv_loop_t* EventLoop(); + void InitializePollingThread(); + void DestroyPollingThread(); + void WakeupPollingThread(); static void RunPollingThread(void* data); - void PollWin32(); + void PollEvents(); private: struct ModuleInfo { node_embedding_runtime runtime; std::string module_name; - FunctorPtr init_module; + StdFunction init_module; int32_t module_node_api_version; }; @@ -370,14 +383,59 @@ class EmbeddedRuntime { } }; + struct V8ScopeLocker { + explicit V8ScopeLocker(EmbeddedRuntime& runtime) + : runtime_(runtime), nest_level_(runtime_.OpenV8Scope()) {} + + ~V8ScopeLocker() { runtime_.CloseV8Scope(nest_level_); } + + V8ScopeLocker(const V8ScopeLocker&) = delete; + V8ScopeLocker& operator=(const V8ScopeLocker&) = delete; + + private: + EmbeddedRuntime& runtime_; + size_t nest_level_; + }; + + struct V8ScopeData { + V8ScopeData(node::CommonEnvironmentSetup* env_setup) + : isolate_(env_setup->isolate()), + v8_locker_(env_setup->isolate()), + isolate_scope_(env_setup->isolate()), + handle_scope_(env_setup->isolate()), + context_scope_(env_setup->context()) {} + + bool IsLocked() const { return v8::Locker::IsLocked(isolate_); } + + size_t IncrementNestLevel() { return ++nest_level_; } + + bool DecrementNestLevel() { return --nest_level_ == 0; } + + size_t nest_level() const { return nest_level_; } + + private: + int32_t nest_level_{1}; + v8::Isolate* isolate_; + v8::Locker v8_locker_; // TODO(vmoroz): can we remove it? + v8::Isolate::Scope isolate_scope_; + v8::HandleScope handle_scope_; + v8::Context::Scope context_scope_; + }; + + struct NodeApiScopeData { + napi_env__::CallModuleScopeData module_scope_data_; + size_t v8_scope_nest_level_; + }; + private: EmbeddedPlatform* platform_; bool is_initialized_{false}; - node_embedding_runtime_flags flags_{node_embedding_runtime_default_flags}; + node_embedding_runtime_flags flags_{node_embedding_runtime_flags_default}; std::vector args_; std::vector exec_args_; node::EmbedderPreloadCallback preload_cb_{}; node::StartExecutionCallback start_execution_cb_{}; + StdFunction handle_result_{}; napi_env node_api_env_{}; struct { @@ -389,15 +447,15 @@ class EmbeddedRuntime { std::unordered_map modules_; std::unique_ptr env_setup_; - std::optional isolate_locker_; + std::optional v8_scope_data_; - FunctorPtr event_loop_handler_{}; - uv_async_t dummy_async_polling_handle_{}; + StdFunction post_task_{}; + uv_async_t polling_async_handle_{}; uv_sem_t polling_sem_{}; uv_thread_t polling_thread_{}; bool polling_thread_closed_{false}; - napi_env__::CallModuleScope module_scope_{}; + SmallTrivialStack node_api_scope_data_{}; }; //----------------------------------------------------------------------------- @@ -405,23 +463,21 @@ class EmbeddedRuntime { //----------------------------------------------------------------------------- node_embedding_status EmbeddedErrorHandling::SetErrorHandler( - node_embedding_error_handler error_handler, void* error_handler_data) { - error_handler_ = error_handler; - error_handler_data_ = error_handler_data; + const node_embedding_handle_error_functor& error_handler) { + ErrorHandler() = AsStdFunction(error_handler); return node_embedding_status_ok; } node_embedding_status EmbeddedErrorHandling::HandleError( const std::string& message, node_embedding_status status) { const char* message_c_str = message.c_str(); - return error_handler()(error_handler_data_, &message_c_str, 1, status); + return ErrorHandler()(&message_c_str, 1, status); } node_embedding_status EmbeddedErrorHandling::HandleError( const std::vector& messages, node_embedding_status status) { CStringArray message_arr(messages); - return error_handler()( - error_handler_data_, message_arr.c_strs(), message_arr.size(), status); + return ErrorHandler()(message_arr.c_strs(), message_arr.size(), status); } node_embedding_status EmbeddedErrorHandling::HandleError( @@ -458,6 +514,14 @@ std::string EmbeddedErrorHandling::FormatString(const char* format, ...) { return result; } +StdFunction& +EmbeddedErrorHandling::ErrorHandler() { + static StdFunction error_handler = + AsStdFunction(node_embedding_handle_error_functor{ + nullptr, &DefaultErrorHandler, nullptr}); + return error_handler; +} + //----------------------------------------------------------------------------- // EmbeddedPlatform implementation. //----------------------------------------------------------------------------- @@ -482,15 +546,14 @@ node_embedding_status EmbeddedPlatform::RunMain( int32_t argc, char* argv[], const node_embedding_configure_platform_functor_ref& configure_platform, - const node_embedding_configure_runtime_functor_ref& configure_runtime, - const node_embedding_node_api_functor_ref& run_node_api) { + const node_embedding_configure_runtime_functor_ref& configure_runtime) { node_embedding_platform platform{}; CHECK_STATUS( EmbeddedPlatform::Create(argc, argv, configure_platform, &platform)); if (platform == nullptr) { return node_embedding_status_ok; // early return } - return EmbeddedRuntime::Run(platform, configure_runtime, run_node_api); + return EmbeddedRuntime::Run(platform, configure_runtime); } /*static*/ node_embedding_status EmbeddedPlatform::Create( @@ -551,7 +614,7 @@ node_embedding_status EmbeddedPlatform::Initialize( is_initialized_ = true; if (!optional_bits_.flags) { - flags_ = node_embedding_platform_no_flags; + flags_ = node_embedding_platform_flags_none; } init_result_ = node::InitializeOncePerProcess( @@ -587,12 +650,12 @@ node_embedding_status EmbeddedPlatform::GetParsedArgs( ASSERT(is_initialized_); if (get_args.invoke != nullptr) { - v8impl::CStringArray args(init_result_->args()); + node::CStringArray args(init_result_->args()); get_args.invoke(get_args.data, args.argc(), args.argv()); } if (get_exec_args.invoke != nullptr) { - v8impl::CStringArray exec_args(init_result_->exec_args()); + node::CStringArray exec_args(init_result_->exec_args()); get_exec_args.invoke( get_exec_args.data, exec_args.argc(), exec_args.argv()); } @@ -604,42 +667,45 @@ node::ProcessInitializationFlags::Flags EmbeddedPlatform::GetProcessInitializationFlags( node_embedding_platform_flags flags) { uint32_t result = node::ProcessInitializationFlags::kNoFlags; - if ((flags & node_embedding_platform_enable_stdio_inheritance) != 0) { + if ((flags & node_embedding_platform_flags_enable_stdio_inheritance) != 0) { result |= node::ProcessInitializationFlags::kEnableStdioInheritance; } - if ((flags & node_embedding_platform_disable_node_options_env) != 0) { + if ((flags & node_embedding_platform_flags_disable_node_options_env) != 0) { result |= node::ProcessInitializationFlags::kDisableNodeOptionsEnv; } - if ((flags & node_embedding_platform_disable_cli_options) != 0) { + if ((flags & node_embedding_platform_flags_disable_cli_options) != 0) { result |= node::ProcessInitializationFlags::kDisableCLIOptions; } - if ((flags & node_embedding_platform_no_icu) != 0) { + if ((flags & node_embedding_platform_flags_no_icu) != 0) { result |= node::ProcessInitializationFlags::kNoICU; } - if ((flags & node_embedding_platform_no_stdio_initialization) != 0) { + if ((flags & node_embedding_platform_flags_no_stdio_initialization) != 0) { result |= node::ProcessInitializationFlags::kNoStdioInitialization; } - if ((flags & node_embedding_platform_no_default_signal_handling) != 0) { + if ((flags & node_embedding_platform_flags_no_default_signal_handling) != 0) { result |= node::ProcessInitializationFlags::kNoDefaultSignalHandling; } result |= node::ProcessInitializationFlags::kNoInitializeV8; result |= node::ProcessInitializationFlags::kNoInitializeNodeV8Platform; - if ((flags & node_embedding_platform_no_init_openssl) != 0) { + if ((flags & node_embedding_platform_flags_no_init_openssl) != 0) { result |= node::ProcessInitializationFlags::kNoInitOpenSSL; } - if ((flags & node_embedding_platform_no_parse_global_debug_variables) != 0) { + if ((flags & node_embedding_platform_flags_no_parse_global_debug_variables) != + 0) { result |= node::ProcessInitializationFlags::kNoParseGlobalDebugVariables; } - if ((flags & node_embedding_platform_no_adjust_resource_limits) != 0) { + if ((flags & node_embedding_platform_flags_no_adjust_resource_limits) != 0) { result |= node::ProcessInitializationFlags::kNoAdjustResourceLimits; } - if ((flags & node_embedding_platform_no_use_large_pages) != 0) { + if ((flags & node_embedding_platform_flags_no_use_large_pages) != 0) { result |= node::ProcessInitializationFlags::kNoUseLargePages; } - if ((flags & node_embedding_platform_no_print_help_or_version_output) != 0) { + if ((flags & node_embedding_platform_flags_no_print_help_or_version_output) != + 0) { result |= node::ProcessInitializationFlags::kNoPrintHelpOrVersionOutput; } - if ((flags & node_embedding_platform_generate_predictable_snapshot) != 0) { + if ((flags & node_embedding_platform_flags_generate_predictable_snapshot) != + 0) { result |= node::ProcessInitializationFlags::kGeneratePredictableSnapshot; } return static_cast(result); @@ -651,15 +717,9 @@ EmbeddedPlatform::GetProcessInitializationFlags( /*static*/ node_embedding_status EmbeddedRuntime::Run( node_embedding_platform platform, - const node_embedding_configure_runtime_functor_ref& configure_runtime, - const node_embedding_node_api_functor_ref& run_node_api) { + const node_embedding_configure_runtime_functor_ref& configure_runtime) { node_embedding_runtime runtime{}; CHECK_STATUS(Create(platform, configure_runtime, &runtime)); - if (run_node_api.invoke != nullptr) { - CHECK_STATUS( - reinterpret_cast(runtime)->RunNodeApi(run_node_api)); - } - CHECK_STATUS(node_embedding_complete_event_loop(runtime)); CHECK_STATUS(node_embedding_delete_runtime(runtime)); return node_embedding_status_ok; @@ -731,7 +791,7 @@ node_embedding_status EmbeddedRuntime::OnPreload( if (run_preload.invoke != nullptr) { preload_cb_ = node::EmbedderPreloadCallback( [runtime = reinterpret_cast(this), - run_preload_ptr = MakeSharedFunctor(run_preload)]( + run_preload_ptr = MakeSharedFunctorPtr(run_preload)]( node::Environment* node_env, v8::Local process, v8::Local require) { @@ -758,12 +818,13 @@ node_embedding_status EmbeddedRuntime::OnPreload( } node_embedding_status EmbeddedRuntime::OnStartExecution( - node_embedding_start_execution_functor start_execution) { + const node_embedding_start_execution_functor& start_execution, + const node_embedding_handle_result_functor& handle_result) { ASSERT(!is_initialized_); if (start_execution.invoke != nullptr) { start_execution_cb_ = node::StartExecutionCallback( - [this, start_execution_ptr = MakeSharedFunctor(start_execution)]( + [this, start_execution_ptr = MakeSharedFunctorPtr(start_execution)]( const node::StartExecutionCallbackInfo& info) -> v8::MaybeLocal { napi_value result{}; @@ -794,6 +855,8 @@ node_embedding_status EmbeddedRuntime::OnStartExecution( start_execution_cb_ = {}; } + handle_result_ = AsStdFunction(handle_result); + return node_embedding_status_ok; } @@ -809,7 +872,7 @@ node_embedding_status EmbeddedRuntime::AddModule( modules_.try_emplace(module_name, reinterpret_cast(this), module_name, - MakeUniqueFunctor(init_module), + AsStdFunction(init_module), module_node_api_version); if (!insert_result.second) { return EmbeddedErrorHandling::HandleError( @@ -835,7 +898,7 @@ node_embedding_status EmbeddedRuntime::Initialize( is_initialized_ = true; node::EnvironmentFlags::Flags flags = GetEnvironmentFlags( - optional_bits_.flags ? flags_ : node_embedding_runtime_default_flags); + optional_bits_.flags ? flags_ : node_embedding_runtime_flags_default); const std::vector& args = optional_bits_.args ? args_ : platform_->init_result()->args(); @@ -855,7 +918,7 @@ node_embedding_status EmbeddedRuntime::Initialize( errors, node_embedding_status_generic_error); } - v8impl::IsolateLocker isolate_locker(env_setup_.get()); + V8ScopeLocker v8_scope_locker(*this); std::string filename = args_.size() > 1 ? args_[1] : ""; node_api_env_ = GetOrCreateNodeApiEnv(env_setup_->env(), filename); @@ -871,42 +934,96 @@ node_embedding_status EmbeddedRuntime::Initialize( return EmbeddedErrorHandling::HandleError( "Failed to load environment", node_embedding_status_generic_error); - InitializeEventLoopPollingThread(); + if (handle_result_) { + node_api_env_->CallIntoModule( + [&](napi_env env) { + handle_result_(reinterpret_cast(this), + env, + v8impl::JsValueFromV8LocalValue(ret.ToLocalChecked())); + }, + TriggerFatalException); + } + + InitializePollingThread(); + WakeupPollingThread(); return node_embedding_status_ok; } -void EmbeddedRuntime::InitializeEventLoopPollingThread() { - if (event_loop_handler_ == nullptr) return; +uv_loop_t* EmbeddedRuntime::EventLoop() { + return env_setup_->env()->event_loop(); +} + +void EmbeddedRuntime::InitializePollingThread() { + if (post_task_ == nullptr) return; + + uv_loop_t* event_loop = EventLoop(); - uv_loop_t* event_loop = env_setup_->env()->event_loop(); + { +#if defined(_WIN32) + + SYSTEM_INFO system_info = {}; + ::GetNativeSystemInfo(&system_info); + + // on single-core the IO completion port NumberOfConcurrentThreads needs to + // be 2 to avoid CPU pegging likely caused by a busy loop in PollEvents + if (system_info.dwNumberOfProcessors == 1) { + // the expectation is the event_loop has just been initialized + // which makes IOCP replacement safe + CHECK_EQ(0u, event_loop->active_handles); + CHECK_EQ(0u, event_loop->active_reqs.count); + + if (event_loop->iocp && event_loop->iocp != INVALID_HANDLE_VALUE) + ::CloseHandle(event_loop->iocp); + event_loop->iocp = + ::CreateIoCompletionPort(INVALID_HANDLE_VALUE, nullptr, 0, 2); + } + +#elif defined(__APPLE__) + + // Do nothing + +#elif defined(__linux) + + int backend_fd = uv_backend_fd(event_loop); + struct epoll_event ev = {0}; + ev.events = EPOLLIN; + ev.data.fd = backend_fd; + epoll_ctl(epoll_, EPOLL_CTL_ADD, backend_fd, &ev); + +#else + ERROR_AND_ABORT("The platform is not supported yet."); +#endif + } // keep the loop alive and allow waking up the polling thread - uv_async_init(event_loop, &dummy_async_polling_handle_, nullptr); + uv_async_init(event_loop, &polling_async_handle_, nullptr); + // Start worker thread that will post to the task runner when new uv events + // arrive. + polling_thread_closed_ = false; uv_sem_init(&polling_sem_, 0); uv_thread_create(&polling_thread_, RunPollingThread, this); - polling_thread_closed_ = false; } -void EmbeddedRuntime::DestroyEventLoopPollingThread() { - if (event_loop_handler_ == nullptr) return; +void EmbeddedRuntime::DestroyPollingThread() { + if (post_task_ == nullptr) return; if (polling_thread_closed_) return; polling_thread_closed_ = true; uv_sem_post(&polling_sem_); - // wake up polling thread - uv_async_send(&dummy_async_polling_handle_); - + // Wake up polling thread. + uv_async_send(&polling_async_handle_); + // Wait for polling thread to complete. uv_thread_join(&polling_thread_); + // Clear uv. uv_sem_destroy(&polling_sem_); - uv_close(reinterpret_cast(&dummy_async_polling_handle_), - nullptr); + uv_close(reinterpret_cast(&polling_async_handle_), nullptr); } -void EmbeddedRuntime::WakeupEventLoopPollingThread() { - if (event_loop_handler_ == nullptr) return; +void EmbeddedRuntime::WakeupPollingThread() { + if (post_task_ == nullptr) return; if (polling_thread_closed_) return; uv_sem_post(&polling_sem_); @@ -915,42 +1032,85 @@ void EmbeddedRuntime::WakeupEventLoopPollingThread() { void EmbeddedRuntime::RunPollingThread(void* data) { EmbeddedRuntime* runtime = static_cast(data); for (;;) { + // Wait for the task runner to deal with events. uv_sem_wait(&runtime->polling_sem_); if (runtime->polling_thread_closed_) break; - runtime->PollWin32(); + // Wait for something to happen in uv loop. + runtime->PollEvents(); if (runtime->polling_thread_closed_) break; - runtime->event_loop_handler_->invoke( - runtime->event_loop_handler_->data, - reinterpret_cast(runtime)); + // Deal with event in the task runner thread. + runtime->post_task_( + node::AsFunctor([runtime]() { + runtime->RunEventLoop(node_embedding_event_loop_run_mode_nowait, + nullptr); + })); } } -void EmbeddedRuntime::PollWin32() { - uv_loop_t* event_loop = env_setup_->env()->event_loop(); +void EmbeddedRuntime::PollEvents() { + uv_loop_t* event_loop = EventLoop(); // If there are other kinds of events pending, uv_backend_timeout will // instruct us not to wait. - DWORD timeout = static_cast(uv_backend_timeout(event_loop)); + int timeout = uv_backend_timeout(event_loop); +#if defined(_WIN32) + + DWORD timeout_msec = static_cast(timeout); DWORD byte_count; ULONG_PTR completion_key; OVERLAPPED* overlapped; - GetQueuedCompletionStatus( - event_loop->iocp, &byte_count, &completion_key, &overlapped, timeout); + ::GetQueuedCompletionStatus(event_loop->iocp, + &byte_count, + &completion_key, + &overlapped, + timeout_msec); // Give the event back so libuv can deal with it. if (overlapped != nullptr) - PostQueuedCompletionStatus( + ::PostQueuedCompletionStatus( event_loop->iocp, byte_count, completion_key, overlapped); + +#elif defined(__APPLE__) + + struct timeval tv; + if (timeout != -1) { + tv.tv_sec = timeout / 1000; + tv.tv_usec = (timeout % 1000) * 1000; + } + + fd_set readset; + int fd = uv_backend_fd(event_loop); + FD_ZERO(&readset); + FD_SET(fd, &readset); + + // Wait for new libuv events. + int r; + do { + r = select( + fd + 1, &readset, nullptr, nullptr, timeout == -1 ? nullptr : &tv); + } while (r == -1 && errno == EINTR); + +#elif defined(__linux) + + // Wait for new libuv events. + int r; + do { + struct epoll_event ev; + r = epoll_wait(epoll_, &ev, 1, timeout); + } while (r == -1 && errno == EINTR); + +#else + ERROR_AND_ABORT("The platform is not supported yet."); +#endif } -node_embedding_status EmbeddedRuntime::OnWakeUpEventLoop( - const node_embedding_event_loop_functor& run_event_loop) { +node_embedding_status EmbeddedRuntime::SetTaskRunner( + const node_embedding_post_task_functor& post_task) { ASSERT(!is_initialized_); - event_loop_handler_.reset( - new node_embedding_event_loop_functor(run_event_loop)); + post_task_ = AsStdFunction(post_task); return node_embedding_status_ok; } @@ -958,7 +1118,7 @@ node_embedding_status EmbeddedRuntime::RunEventLoop( node_embedding_event_loop_run_mode run_mode, bool* has_more_work) { ASSERT(is_initialized_); - IsolateLocker isolate_locker(env_setup_.get()); + V8ScopeLocker v8_scope_locker(*this); node::ExitCode exit_code = node::SpinEventLoopWithoutCleanup(env_setup_->env(), @@ -976,7 +1136,7 @@ node_embedding_status EmbeddedRuntime::RunEventLoop( *has_more_work = uv_loop_alive(env_setup_->env()->event_loop()); } - WakeupEventLoopPollingThread(); + WakeupPollingThread(); return node_embedding_status_ok; } @@ -984,9 +1144,9 @@ node_embedding_status EmbeddedRuntime::RunEventLoop( node_embedding_status EmbeddedRuntime::CompleteEventLoop() { ASSERT(is_initialized_); - IsolateLocker isolate_locker(env_setup_.get()); + V8ScopeLocker v8_scope_locker(*this); - DestroyEventLoopPollingThread(); + DestroyPollingThread(); int32_t exit_code = node::SpinEventLoop(env_setup_->env()).FromMaybe(1); if (exit_code != 0) { @@ -999,6 +1159,20 @@ node_embedding_status EmbeddedRuntime::CompleteEventLoop() { return node_embedding_status_ok; } +node_embedding_status EmbeddedRuntime::TerminateEventLoop() { + ASSERT(is_initialized_); + + V8ScopeLocker v8_scope_locker(*this); + int32_t exit_code = node::Stop(env_setup_->env(), node::StopFlags::kNoFlags); + if (exit_code != 0) { + return EmbeddedErrorHandling::HandleError( + "Failed while stopping the runtime", + static_cast( + node_embedding_status_error_exit_code + exit_code)); + } + return node_embedding_status_ok; +} + /*static*/ void EmbeddedRuntime::TriggerFatalException( napi_env env, v8::Local local_err) { node_napi_env__* node_napi_env = static_cast(env); @@ -1011,43 +1185,71 @@ node_embedding_status EmbeddedRuntime::CompleteEventLoop() { node_napi_env->trigger_fatal_exception(local_err); } +size_t EmbeddedRuntime::OpenV8Scope() { + if (v8_scope_data_.has_value()) { + CHECK(v8_scope_data_->IsLocked()); + return v8_scope_data_->IncrementNestLevel(); + } + + v8_scope_data_.emplace(env_setup_.get()); + return 1; +} + +void EmbeddedRuntime::CloseV8Scope(size_t nest_level) { + CHECK(v8_scope_data_.has_value()); + CHECK_EQ(v8_scope_data_->nest_level(), nest_level); + if (v8_scope_data_->DecrementNestLevel()) { + v8_scope_data_.reset(); + } +} + node_embedding_status EmbeddedRuntime::RunNodeApi( - const node_embedding_node_api_functor_ref& run_node_api) { + const node_embedding_run_node_api_functor_ref& run_node_api) { CHECK_ARG_NOT_NULL(run_node_api.invoke); + + node_embedding_node_api_scope node_api_scope{}; napi_env env{}; - CHECK_STATUS(OpenNodeApiScope(&env)); + CHECK_STATUS(OpenNodeApiScope(&node_api_scope, &env)); + auto nodeApiScopeLeave = + node::OnScopeLeave([&]() { CloseNodeApiScope(node_api_scope); }); + run_node_api.invoke( run_node_api.data, reinterpret_cast(this), env); - CHECK_STATUS(CloseNodeApiScope()); + return node_embedding_status_ok; } -node_embedding_status EmbeddedRuntime::OpenNodeApiScope(napi_env* env) { +node_embedding_status EmbeddedRuntime::OpenNodeApiScope( + node_embedding_node_api_scope* node_api_scope, napi_env* env) { + CHECK_ARG_NOT_NULL(node_api_scope); CHECK_ARG_NOT_NULL(env); - if (isolate_locker_.has_value()) { - ASSERT(isolate_locker_->IsLocked()); - isolate_locker_->IncrementLockCount(); - } else { - isolate_locker_.emplace(env_setup_.get()); - module_scope_ = node_api_env_->OpenCallModuleScope(); - } - *env = node_api_env_; + size_t v8_scope_nest_level = OpenV8Scope(); + node_api_scope_data_.Push( + {node_api_env_->OpenCallModuleScope(), v8_scope_nest_level}); + *node_api_scope = reinterpret_cast( + node_api_scope_data_.size()); + *env = node_api_env_; return node_embedding_status_ok; } -node_embedding_status EmbeddedRuntime::CloseNodeApiScope() { - ASSERT(IsNodeApiScopeOpened()); - if (isolate_locker_->DecrementLockCount()) { - node_api_env_->CloseCallModuleScope(module_scope_, TriggerFatalException); - isolate_locker_.reset(); - } +node_embedding_status EmbeddedRuntime::CloseNodeApiScope( + node_embedding_node_api_scope node_api_scope) { + CHECK_EQ(node_api_scope_data_.size(), + reinterpret_cast(node_api_scope)); + size_t v8_scope_nest_level = node_api_scope_data_.top().v8_scope_nest_level_; + + node_api_env_->CloseCallModuleScope( + node_api_scope_data_.top().module_scope_data_); + node_api_scope_data_.Pop(); + CloseV8Scope(v8_scope_nest_level); + return node_embedding_status_ok; } bool EmbeddedRuntime::IsNodeApiScopeOpened() const { - return isolate_locker_.has_value(); + return node_api_scope_data_.size() > 0; } napi_env EmbeddedRuntime::GetOrCreateNodeApiEnv( @@ -1061,9 +1263,9 @@ napi_env EmbeddedRuntime::GetOrCreateNodeApiEnv( } // Avoid creating the environment under the lock. - napi_env env = NewEnv(node_env->context(), - module_filename, - EmbeddedPlatform::node_api_version()); + napi_env env = v8impl::NewEnv(node_env->context(), + module_filename, + EmbeddedPlatform::node_api_version()); // In case if we cannot insert the new env, we are just going to have an // unused env which will be deleted in the end with other environments. @@ -1078,40 +1280,42 @@ napi_env EmbeddedRuntime::GetOrCreateNodeApiEnv( node::EnvironmentFlags::Flags EmbeddedRuntime::GetEnvironmentFlags( node_embedding_runtime_flags flags) { uint64_t result = node::EnvironmentFlags::kNoFlags; - if ((flags & node_embedding_runtime_default_flags) != 0) { + if ((flags & node_embedding_runtime_flags_default) != 0) { result |= node::EnvironmentFlags::kDefaultFlags; } - if ((flags & node_embedding_runtime_owns_process_state) != 0) { + if ((flags & node_embedding_runtime_flags_owns_process_state) != 0) { result |= node::EnvironmentFlags::kOwnsProcessState; } - if ((flags & node_embedding_runtime_owns_inspector) != 0) { + if ((flags & node_embedding_runtime_flags_owns_inspector) != 0) { result |= node::EnvironmentFlags::kOwnsInspector; } - if ((flags & node_embedding_runtime_no_register_esm_loader) != 0) { + if ((flags & node_embedding_runtime_flags_no_register_esm_loader) != 0) { result |= node::EnvironmentFlags::kNoRegisterESMLoader; } - if ((flags & node_embedding_runtime_track_unmanaged_fds) != 0) { + if ((flags & node_embedding_runtime_flags_track_unmanaged_fds) != 0) { result |= node::EnvironmentFlags::kTrackUnmanagedFds; } - if ((flags & node_embedding_runtime_hide_console_windows) != 0) { + if ((flags & node_embedding_runtime_flags_hide_console_windows) != 0) { result |= node::EnvironmentFlags::kHideConsoleWindows; } - if ((flags & node_embedding_runtime_no_native_addons) != 0) { + if ((flags & node_embedding_runtime_flags_no_native_addons) != 0) { result |= node::EnvironmentFlags::kNoNativeAddons; } - if ((flags & node_embedding_runtime_no_global_search_paths) != 0) { + if ((flags & node_embedding_runtime_flags_no_global_search_paths) != 0) { result |= node::EnvironmentFlags::kNoGlobalSearchPaths; } - if ((flags & node_embedding_runtime_no_browser_globals) != 0) { + if ((flags & node_embedding_runtime_flags_no_browser_globals) != 0) { result |= node::EnvironmentFlags::kNoBrowserGlobals; } - if ((flags & node_embedding_runtime_no_create_inspector) != 0) { + if ((flags & node_embedding_runtime_flags_no_create_inspector) != 0) { result |= node::EnvironmentFlags::kNoCreateInspector; } - if ((flags & node_embedding_runtime_no_start_debug_signal_handler) != 0) { + if ((flags & node_embedding_runtime_flags_no_start_debug_signal_handler) != + 0) { result |= node::EnvironmentFlags::kNoStartDebugSignalHandler; } - if ((flags & node_embedding_runtime_no_wait_for_inspector_frontend) != 0) { + if ((flags & node_embedding_runtime_flags_no_wait_for_inspector_frontend) != + 0) { result |= node::EnvironmentFlags::kNoWaitForInspectorFrontend; } return static_cast(result); @@ -1146,12 +1350,11 @@ void EmbeddedRuntime::RegisterModules() { napi_value node_api_exports = nullptr; env->CallIntoModule([&](napi_env env) { - node_api_exports = module_info->init_module->invoke( - module_info->init_module->data, - module_info->runtime, - env, - module_info->module_name.c_str(), - v8impl::JsValueFromV8LocalValue(exports)); + node_api_exports = + module_info->init_module(module_info->runtime, + env, + module_info->module_name.c_str(), + v8impl::JsValueFromV8LocalValue(exports)); }); // If register function returned a non-null exports object different from @@ -1165,28 +1368,26 @@ void EmbeddedRuntime::RegisterModules() { } } // end of anonymous namespace -} // end of namespace v8impl +} // namespace node -node_embedding_status NAPI_CDECL node_embedding_on_error( - node_embedding_error_handler error_handler, void* error_handler_data) { - return v8impl::EmbeddedErrorHandling::SetErrorHandler(error_handler, - error_handler_data); +node_embedding_status NAPI_CDECL +node_embedding_on_error(node_embedding_handle_error_functor error_handler) { + return node::EmbeddedErrorHandling::SetErrorHandler(error_handler); } NAPI_EXTERN node_embedding_status NAPI_CDECL node_embedding_set_api_version( int32_t embedding_api_version, int32_t node_api_version) { - return v8impl::EmbeddedPlatform::SetApiVersion(embedding_api_version, - node_api_version); + return node::EmbeddedPlatform::SetApiVersion(embedding_api_version, + node_api_version); } node_embedding_status NAPI_CDECL node_embedding_run_main( int32_t argc, char* argv[], node_embedding_configure_platform_functor_ref configure_platform, - node_embedding_configure_runtime_functor_ref configure_runtime, - node_embedding_node_api_functor_ref run_node_api) { - return v8impl::EmbeddedPlatform::RunMain( - argc, argv, configure_platform, configure_runtime, run_node_api); + node_embedding_configure_runtime_functor_ref configure_runtime) { + return node::EmbeddedPlatform::RunMain( + argc, argv, configure_platform, configure_runtime); } node_embedding_status NAPI_CDECL node_embedding_create_platform( @@ -1194,8 +1395,7 @@ node_embedding_status NAPI_CDECL node_embedding_create_platform( char* argv[], node_embedding_configure_platform_functor_ref configure_platform, node_embedding_platform* result) { - return v8impl::EmbeddedPlatform::Create( - argc, argv, configure_platform, result); + return node::EmbeddedPlatform::Create(argc, argv, configure_platform, result); } node_embedding_status NAPI_CDECL @@ -1212,23 +1412,21 @@ node_embedding_status NAPI_CDECL node_embedding_platform_set_flags( node_embedding_status NAPI_CDECL node_embedding_platform_get_parsed_args( node_embedding_platform platform, node_embedding_get_args_functor_ref get_args, - node_embedding_get_args_functor_ref get_exec_args) { - return EMBEDDED_PLATFORM(platform)->GetParsedArgs(get_args, get_exec_args); + node_embedding_get_args_functor_ref get_runtime_args) { + return EMBEDDED_PLATFORM(platform)->GetParsedArgs(get_args, get_runtime_args); } node_embedding_status NAPI_CDECL node_embedding_run_runtime( node_embedding_platform platform, - node_embedding_configure_runtime_functor_ref configure_runtime, - node_embedding_node_api_functor_ref run_node_api) { - return v8impl::EmbeddedRuntime::Run( - platform, configure_runtime, run_node_api); + node_embedding_configure_runtime_functor_ref configure_runtime) { + return node::EmbeddedRuntime::Run(platform, configure_runtime); } node_embedding_status NAPI_CDECL node_embedding_create_runtime( node_embedding_platform platform, node_embedding_configure_runtime_functor_ref configure_runtime, node_embedding_runtime* result) { - return v8impl::EmbeddedRuntime::Create(platform, configure_runtime, result); + return node::EmbeddedRuntime::Create(platform, configure_runtime, result); } node_embedding_status NAPI_CDECL @@ -1246,10 +1444,10 @@ node_embedding_status NAPI_CDECL node_embedding_runtime_set_args(node_embedding_runtime_config runtime_config, int32_t argc, const char* argv[], - int32_t exec_argc, - const char* exec_argv[]) { + int32_t runtime_argc, + const char* runtime_argv[]) { return EMBEDDED_RUNTIME(runtime_config) - ->SetArgs(argc, argv, exec_argc, exec_argv); + ->SetArgs(argc, argv, runtime_argc, runtime_argv); } node_embedding_status NAPI_CDECL @@ -1260,8 +1458,10 @@ node_embedding_runtime_on_preload(node_embedding_runtime_config runtime_config, node_embedding_status NAPI_CDECL node_embedding_runtime_on_start_execution( node_embedding_runtime_config runtime_config, - node_embedding_start_execution_functor start_execution) { - return EMBEDDED_RUNTIME(runtime_config)->OnStartExecution(start_execution); + node_embedding_start_execution_functor start_execution, + node_embedding_handle_result_functor handle_result) { + return EMBEDDED_RUNTIME(runtime_config) + ->OnStartExecution(start_execution, handle_result); } node_embedding_status NAPI_CDECL node_embedding_runtime_add_module( @@ -1273,10 +1473,10 @@ node_embedding_status NAPI_CDECL node_embedding_runtime_add_module( ->AddModule(module_name, init_module, module_node_api_version); } -node_embedding_status NAPI_CDECL node_embedding_on_wake_up_event_loop( +node_embedding_status NAPI_CDECL node_embedding_runtime_set_task_runner( node_embedding_runtime_config runtime_config, - node_embedding_event_loop_functor run_event_loop) { - return EMBEDDED_RUNTIME(runtime_config)->OnWakeUpEventLoop(run_event_loop); + node_embedding_post_task_functor post_task) { + return EMBEDDED_RUNTIME(runtime_config)->SetTaskRunner(post_task); } node_embedding_status NAPI_CDECL @@ -1292,17 +1492,25 @@ node_embedding_complete_event_loop(node_embedding_runtime runtime) { } node_embedding_status NAPI_CDECL -node_embedding_run_node_api(node_embedding_runtime runtime, - node_embedding_node_api_functor_ref run_node_api) { +node_embedding_terminate_event_loop(node_embedding_runtime runtime) { + return EMBEDDED_RUNTIME(runtime)->TerminateEventLoop(); +} + +node_embedding_status NAPI_CDECL node_embedding_run_node_api( + node_embedding_runtime runtime, + node_embedding_run_node_api_functor_ref run_node_api) { return EMBEDDED_RUNTIME(runtime)->RunNodeApi(run_node_api); } node_embedding_status NAPI_CDECL node_embedding_open_node_api_scope( - node_embedding_runtime runtime, napi_env* env) { - return EMBEDDED_RUNTIME(runtime)->OpenNodeApiScope(env); + node_embedding_runtime runtime, + node_embedding_node_api_scope* node_api_scope, + napi_env* env) { + return EMBEDDED_RUNTIME(runtime)->OpenNodeApiScope(node_api_scope, env); } -node_embedding_status NAPI_CDECL -node_embedding_close_node_api_scope(node_embedding_runtime runtime) { - return EMBEDDED_RUNTIME(runtime)->CloseNodeApiScope(); +node_embedding_status NAPI_CDECL node_embedding_close_node_api_scope( + node_embedding_runtime runtime, + node_embedding_node_api_scope node_api_scope) { + return EMBEDDED_RUNTIME(runtime)->CloseNodeApiScope(node_api_scope); } diff --git a/src/node_embedding_api.h b/src/node_embedding_api.h index b871e3d0c3c1e8..0fa4393bad05af 100644 --- a/src/node_embedding_api.h +++ b/src/node_embedding_api.h @@ -26,11 +26,13 @@ EXTERN_C_START // Data types //============================================================================== -typedef struct node_embedding_platform__* node_embedding_platform; -typedef struct node_embedding_runtime__* node_embedding_runtime; -typedef struct node_embedding_platform_config__* node_embedding_platform_config; -typedef struct node_embedding_runtime_config__* node_embedding_runtime_config; +typedef struct node_embedding_platform_s* node_embedding_platform; +typedef struct node_embedding_runtime_s* node_embedding_runtime; +typedef struct node_embedding_platform_config_s* node_embedding_platform_config; +typedef struct node_embedding_runtime_config_s* node_embedding_runtime_config; +typedef struct node_embedding_node_api_scope_s* node_embedding_node_api_scope; +// The status returned by the Node.js embedding API functions. typedef enum { node_embedding_status_ok = 0, node_embedding_status_generic_error = 1, @@ -41,107 +43,113 @@ typedef enum { node_embedding_status_error_exit_code = 512, } node_embedding_status; +// The flags for the Node.js platform initialization. +// They match the internal ProcessInitializationFlags::Flags enum. typedef enum { - node_embedding_platform_no_flags = 0, + node_embedding_platform_flags_none = 0, // Enable stdio inheritance, which is disabled by default. // This flag is also implied by - // node_embedding_platform_no_stdio_initialization. - node_embedding_platform_enable_stdio_inheritance = 1 << 0, + // node_embedding_platform_flags_no_stdio_initialization. + node_embedding_platform_flags_enable_stdio_inheritance = 1 << 0, // Disable reading the NODE_OPTIONS environment variable. - node_embedding_platform_disable_node_options_env = 1 << 1, + node_embedding_platform_flags_disable_node_options_env = 1 << 1, // Do not parse CLI options. - node_embedding_platform_disable_cli_options = 1 << 2, + node_embedding_platform_flags_disable_cli_options = 1 << 2, // Do not initialize ICU. - node_embedding_platform_no_icu = 1 << 3, + node_embedding_platform_flags_no_icu = 1 << 3, // Do not modify stdio file descriptor or TTY state. - node_embedding_platform_no_stdio_initialization = 1 << 4, + node_embedding_platform_flags_no_stdio_initialization = 1 << 4, // Do not register Node.js-specific signal handlers // and reset other signal handlers to default state. - node_embedding_platform_no_default_signal_handling = 1 << 5, + node_embedding_platform_flags_no_default_signal_handling = 1 << 5, // Do not initialize OpenSSL config. - node_embedding_platform_no_init_openssl = 1 << 8, + node_embedding_platform_flags_no_init_openssl = 1 << 8, // Do not initialize Node.js debugging based on environment variables. - node_embedding_platform_no_parse_global_debug_variables = 1 << 9, + node_embedding_platform_flags_no_parse_global_debug_variables = 1 << 9, // Do not adjust OS resource limits for this process. - node_embedding_platform_no_adjust_resource_limits = 1 << 10, + node_embedding_platform_flags_no_adjust_resource_limits = 1 << 10, // Do not map code segments into large pages for this process. - node_embedding_platform_no_use_large_pages = 1 << 11, + node_embedding_platform_flags_no_use_large_pages = 1 << 11, // Skip printing output for --help, --version, --v8-options. - node_embedding_platform_no_print_help_or_version_output = 1 << 12, + node_embedding_platform_flags_no_print_help_or_version_output = 1 << 12, // Initialize the process for predictable snapshot generation. - node_embedding_platform_generate_predictable_snapshot = 1 << 14, + node_embedding_platform_flags_generate_predictable_snapshot = 1 << 14, } node_embedding_platform_flags; +// The flags for the Node.js runtime initialization. +// They match the internal EnvironmentFlags::Flags enum. typedef enum { - node_embedding_runtime_no_flags = 0, + node_embedding_runtime_flags_none = 0, // Use the default behavior for Node.js instances. - node_embedding_runtime_default_flags = 1 << 0, + node_embedding_runtime_flags_default = 1 << 0, // Controls whether this Environment is allowed to affect per-process state // (e.g. cwd, process title, uid, etc.). - // This is set when using node_embedding_runtime_default_flags. - node_embedding_runtime_owns_process_state = 1 << 1, + // This is set when using node_embedding_runtime_flags_default. + node_embedding_runtime_flags_owns_process_state = 1 << 1, // Set if this Environment instance is associated with the global inspector // handling code (i.e. listening on SIGUSR1). - // This is set when using node_embedding_runtime_default_flags. - node_embedding_runtime_owns_inspector = 1 << 2, + // This is set when using node_embedding_runtime_flags_default. + node_embedding_runtime_flags_owns_inspector = 1 << 2, // Set if Node.js should not run its own esm loader. This is needed by some // embedders, because it's possible for the Node.js esm loader to conflict // with another one in an embedder environment, e.g. Blink's in Chromium. - node_embedding_runtime_no_register_esm_loader = 1 << 3, + node_embedding_runtime_flags_no_register_esm_loader = 1 << 3, // Set this flag to make Node.js track "raw" file descriptors, i.e. managed // by fs.open() and fs.close(), and close them during // node_embedding_delete_runtime(). - node_embedding_runtime_track_unmanaged_fds = 1 << 4, + node_embedding_runtime_flags_track_unmanaged_fds = 1 << 4, // Set this flag to force hiding console windows when spawning child // processes. This is usually used when embedding Node.js in GUI programs on // Windows. - node_embedding_runtime_hide_console_windows = 1 << 5, + node_embedding_runtime_flags_hide_console_windows = 1 << 5, // Set this flag to disable loading native addons via `process.dlopen`. // This environment flag is especially important for worker threads // so that a worker thread can't load a native addon even if `execArgv` // is overwritten and `--no-addons` is not specified but was specified // for this Environment instance. - node_embedding_runtime_no_native_addons = 1 << 6, + node_embedding_runtime_flags_no_native_addons = 1 << 6, // Set this flag to disable searching modules from global paths like // $HOME/.node_modules and $NODE_PATH. This is used by standalone apps that // do not expect to have their behaviors changed because of globally // installed modules. - node_embedding_runtime_no_global_search_paths = 1 << 7, + node_embedding_runtime_flags_no_global_search_paths = 1 << 7, // Do not export browser globals like setTimeout, console, etc. - node_embedding_runtime_no_browser_globals = 1 << 8, + node_embedding_runtime_flags_no_browser_globals = 1 << 8, // Controls whether or not the Environment should call V8Inspector::create(). // This control is needed by embedders who may not want to initialize the V8 // inspector in situations where one has already been created, // e.g. Blink's in Chromium. - node_embedding_runtime_no_create_inspector = 1 << 9, + node_embedding_runtime_flags_no_create_inspector = 1 << 9, // Controls whether or not the InspectorAgent for this Environment should // call StartDebugSignalHandler. This control is needed by embedders who may // not want to allow other processes to start the V8 inspector. - node_embedding_runtime_no_start_debug_signal_handler = 1 << 10, + node_embedding_runtime_flags_no_start_debug_signal_handler = 1 << 10, // Controls whether the InspectorAgent created for this Environment waits for // Inspector frontend events during the Environment creation. It's used to // call node::Stop(env) on a Worker thread that is waiting for the events. - node_embedding_runtime_no_wait_for_inspector_frontend = 1 << 11 + node_embedding_runtime_flags_no_wait_for_inspector_frontend = 1 << 11 } node_embedding_runtime_flags; typedef enum { // Run the event loop until it is completed. // It matches the UV_RUN_DEFAULT behavior. - node_embedding_event_loop_run_default = 0, + node_embedding_event_loop_run_mode_default = 0, // Run the event loop once and wait if there are no items. // It matches the UV_RUN_ONCE behavior. - node_embedding_event_loop_run_once = 1, + node_embedding_event_loop_run_mode_once = 1, // Run the event loop once and do not wait if there are no items. // It matches the UV_RUN_NOWAIT behavior. - node_embedding_event_loop_run_nowait = 2, + node_embedding_event_loop_run_mode_nowait = 2, } node_embedding_event_loop_run_mode; //============================================================================== // Callbacks //============================================================================== -typedef node_embedding_status(NAPI_CDECL* node_embedding_error_handler)( - void* handler_data, +typedef void(NAPI_CDECL* node_embedding_release_data_callback)(void* data); + +typedef node_embedding_status(NAPI_CDECL* node_embedding_handle_error_callback)( + void* cb_data, const char* messages[], size_t messages_size, node_embedding_status status); @@ -175,6 +183,12 @@ typedef napi_value(NAPI_CDECL* node_embedding_start_execution_callback)( napi_value require, napi_value run_cjs); +typedef void(NAPI_CDECL* node_embedding_handle_result_callback)( + void* cb_data, + node_embedding_runtime runtime, + napi_env env, + napi_value value); + typedef napi_value(NAPI_CDECL* node_embedding_initialize_module_callback)( void* cb_data, node_embedding_runtime runtime, @@ -182,14 +196,25 @@ typedef napi_value(NAPI_CDECL* node_embedding_initialize_module_callback)( const char* module_name, napi_value exports); -typedef void(NAPI_CDECL* node_embedding_event_loop_handler)( - void* handler_data, node_embedding_runtime runtime); +typedef void(NAPI_CDECL* node_embedding_run_task_callback)(void* cb_data); -typedef void(NAPI_CDECL* node_embedding_node_api_callback)( +typedef struct { + void* data; + node_embedding_run_task_callback invoke; + node_embedding_release_data_callback release; +} node_embedding_run_task_functor; + +typedef void(NAPI_CDECL* node_embedding_post_task_callback)( + void* cb_data, node_embedding_run_task_functor run_task); + +typedef void(NAPI_CDECL* node_embedding_run_node_api_callback)( void* cb_data, node_embedding_runtime runtime, napi_env env); -typedef void(NAPI_CDECL* node_embedding_release_callback)( - void* data_to_release); +typedef struct { + void* data; + node_embedding_handle_error_callback invoke; + node_embedding_release_data_callback release; +} node_embedding_handle_error_functor; typedef struct { void* data; @@ -203,8 +228,8 @@ typedef struct { typedef struct { void* data; - node_embedding_node_api_callback invoke; -} node_embedding_node_api_functor_ref; + node_embedding_run_node_api_callback invoke; +} node_embedding_run_node_api_functor_ref; typedef struct { void* data; @@ -214,26 +239,32 @@ typedef struct { typedef struct { void* data; node_embedding_preload_callback invoke; - node_embedding_release_callback release; + node_embedding_release_data_callback release; } node_embedding_preload_functor; typedef struct { void* data; node_embedding_start_execution_callback invoke; - node_embedding_release_callback release; + node_embedding_release_data_callback release; } node_embedding_start_execution_functor; +typedef struct { + void* data; + node_embedding_handle_result_callback invoke; + node_embedding_release_data_callback release; +} node_embedding_handle_result_functor; + typedef struct { void* data; node_embedding_initialize_module_callback invoke; - node_embedding_release_callback release; + node_embedding_release_data_callback release; } node_embedding_initialize_module_functor; typedef struct { void* data; - node_embedding_event_loop_handler invoke; - node_embedding_release_callback release; -} node_embedding_event_loop_functor; + node_embedding_post_task_callback invoke; + node_embedding_release_data_callback release; +} node_embedding_post_task_functor; //============================================================================== // Functions @@ -244,8 +275,8 @@ typedef struct { //------------------------------------------------------------------------------ // Sets the global error handing for the Node.js embedding API. -NAPI_EXTERN node_embedding_status NAPI_CDECL node_embedding_on_error( - node_embedding_error_handler error_handler, void* error_handler_data); +NAPI_EXTERN node_embedding_status NAPI_CDECL +node_embedding_on_error(node_embedding_handle_error_functor error_handler); //------------------------------------------------------------------------------ // Node.js global platform functions. @@ -260,8 +291,7 @@ NAPI_EXTERN node_embedding_status NAPI_CDECL node_embedding_run_main( int32_t argc, char* argv[], node_embedding_configure_platform_functor_ref configure_platform, - node_embedding_configure_runtime_functor_ref configure_runtime, - node_embedding_node_api_functor_ref run_node_api); + node_embedding_configure_runtime_functor_ref configure_runtime); // Creates and configures a new Node.js platform instance. NAPI_EXTERN node_embedding_status NAPI_CDECL node_embedding_create_platform( @@ -284,7 +314,7 @@ NAPI_EXTERN node_embedding_status NAPI_CDECL node_embedding_platform_get_parsed_args( node_embedding_platform platform, node_embedding_get_args_functor_ref get_args, - node_embedding_get_args_functor_ref get_exec_args); + node_embedding_get_args_functor_ref get_runtime_args); //------------------------------------------------------------------------------ // Node.js runtime functions. @@ -293,8 +323,7 @@ node_embedding_platform_get_parsed_args( // Runs the Node.js runtime with the provided configuration. NAPI_EXTERN node_embedding_status NAPI_CDECL node_embedding_run_runtime( node_embedding_platform platform, - node_embedding_configure_runtime_functor_ref configure_runtime, - node_embedding_node_api_functor_ref run_node_api); + node_embedding_configure_runtime_functor_ref configure_runtime); // Creates a new Node.js runtime instance. NAPI_EXTERN node_embedding_status NAPI_CDECL node_embedding_create_runtime( @@ -317,8 +346,8 @@ NAPI_EXTERN node_embedding_status NAPI_CDECL node_embedding_runtime_set_args(node_embedding_runtime_config runtime_config, int32_t argc, const char* argv[], - int32_t exec_argc, - const char* exec_argv[]); + int32_t runtime_argc, + const char* runtime_argv[]); // Sets the preload callback for the Node.js runtime initialization. NAPI_EXTERN node_embedding_status NAPI_CDECL @@ -329,7 +358,8 @@ node_embedding_runtime_on_preload(node_embedding_runtime_config runtime_config, NAPI_EXTERN node_embedding_status NAPI_CDECL node_embedding_runtime_on_start_execution( node_embedding_runtime_config runtime_config, - node_embedding_start_execution_functor start_execution); + node_embedding_start_execution_functor start_execution, + node_embedding_handle_result_functor handle_result); // Adds a new module to the Node.js runtime. // It is accessed as process._linkedBinding(module_name) in the main JS and in @@ -344,14 +374,15 @@ NAPI_EXTERN node_embedding_status NAPI_CDECL node_embedding_runtime_add_module( // Node.js runtime functions for the event loop. //------------------------------------------------------------------------------ -// Initializes the runtime to call the provided handler when the runtime event -// loop has some work to do. It starts an observer thread that is stopped by the -// `node_embedding_runtime_complete_event_loop` function call. This function -// helps to integrate the Node.js runtime event loop with the host UI loop. +// Sets the task runner for the Node.js runtime. +// This is an alternative way to run the Node.js runtime event loop that helps +// running it inside of an existing task scheduler. +// E.g. it enables running Node.js event loop inside of the application UI event +// loop or UI dispatcher. NAPI_EXTERN node_embedding_status NAPI_CDECL -node_embedding_on_wake_up_event_loop( +node_embedding_runtime_set_task_runner( node_embedding_runtime_config runtime_config, - node_embedding_event_loop_functor run_event_loop); + node_embedding_post_task_functor post_task); // Runs the Node.js runtime event loop. NAPI_EXTERN node_embedding_status NAPI_CDECL @@ -364,27 +395,46 @@ node_embedding_run_event_loop(node_embedding_runtime runtime, NAPI_EXTERN node_embedding_status NAPI_CDECL node_embedding_complete_event_loop(node_embedding_runtime runtime); +// Stops the Node.js runtime event loop. It cannot be resumed after this call. +NAPI_EXTERN node_embedding_status NAPI_CDECL +node_embedding_terminate_event_loop(node_embedding_runtime runtime); + //------------------------------------------------------------------------------ // Node.js runtime functions for the Node-API interop. //------------------------------------------------------------------------------ -// Runs Node-API code. -NAPI_EXTERN node_embedding_status NAPI_CDECL -node_embedding_run_node_api(node_embedding_runtime runtime, - node_embedding_node_api_functor_ref run_node_api); +// Runs Node-API code in the Node-API scope. +NAPI_EXTERN node_embedding_status NAPI_CDECL node_embedding_run_node_api( + node_embedding_runtime runtime, + node_embedding_run_node_api_functor_ref run_node_api); // Opens a new Node-API scope. NAPI_EXTERN node_embedding_status NAPI_CDECL node_embedding_open_node_api_scope( - node_embedding_runtime runtime, napi_env* env); + node_embedding_runtime runtime, + node_embedding_node_api_scope* node_api_scope, + napi_env* env); -// Closes the current Node-API scope. +// Closes the Node-API invocation scope. NAPI_EXTERN node_embedding_status NAPI_CDECL -node_embedding_close_node_api_scope(node_embedding_runtime runtime); +node_embedding_close_node_api_scope( + node_embedding_runtime runtime, + node_embedding_node_api_scope node_api_scope); EXTERN_C_END #ifdef __cplusplus +//============================================================================== +// C++ convenience functions for the C API. +// These functions are not ABI safe and can be changed in future versions. +//============================================================================== + +#include +#include +#include + +namespace node { + //------------------------------------------------------------------------------ // Convenience union operator for the Node.js flags. //------------------------------------------------------------------------------ @@ -401,6 +451,125 @@ inline constexpr node_embedding_runtime_flags operator|( static_cast(rhs)); } +//------------------------------------------------------------------------------ +// Convenience functor struct adapter for C++ function object or lambdas. +//------------------------------------------------------------------------------ + +namespace details { + +template +struct FunctorAdapter { + static_assert(sizeof(TLambda) == -1, "Unsupported signature"); +}; + +template +struct FunctorAdapter { + static TResult Invoke(void* data, TArgs... args) { + return reinterpret_cast(data)->operator()(args...); + } +}; + +template +struct FunctorAdapter { + static void Invoke(void* data, TArgs... args) { + reinterpret_cast(data)->operator()(args...); + } +}; + +template +struct FunctorDeleter { + void operator()(T* ptr) { + if (ptr->release != nullptr) { + ptr->release(ptr->data); + } + delete ptr; + } +}; + +template +struct StdFunctionAdapter { + static_assert(sizeof(TFunctor) == -1, "Unsupported signature"); +}; + +template +struct StdFunctionAdapter { + using FunctionType = std::function; + + template + static FunctionType Create(TFunctorSharedPtr&& ptr) { + return ptr ? FunctionType([ptr = std::move(ptr)](TArgs... args) { + return ptr->invoke(ptr->data, args...); + }) + : FunctionType(nullptr); + } +}; + +template +struct StdFunctionAdapter { + using FunctionType = std::function; + + template + static FunctionType Create(TFunctorSharedPtr&& ptr) { + return ptr ? FunctionType([ptr = std::move(ptr)](TArgs... args) { + ptr->invoke(ptr->data, args...); + }) + : FunctionType(nullptr); + } +}; + +} // namespace details + +template +inline TFunctor AsFunctorRef(TLambda&& lambda) { + using TLambdaType = std::remove_reference_t; + using TAdapter = details::FunctorAdapter< + TLambdaType, + std::remove_pointer_t< + decltype(std::remove_reference_t::invoke)>>; + return TFunctor{static_cast(&lambda), &TAdapter::Invoke}; +} + +template +inline TFunctor AsFunctor(TLambda&& lambda) { + using TLambdaType = std::remove_reference_t; + using TAdapter = details::FunctorAdapter< + TLambdaType, + std::remove_pointer_t< + decltype(std::remove_reference_t::invoke)>>; + return TFunctor{ + static_cast(new TLambdaType(std::forward(lambda))), + &TAdapter::Invoke, + [](void* data) { delete static_cast(data); }}; +} + +template +using FunctorPtr = std::unique_ptr>; + +template +FunctorPtr MakeUniqueFunctorPtr(const T& functor) { + return functor.invoke ? FunctorPtr(new T(functor)) : nullptr; +} + +template +std::shared_ptr MakeSharedFunctorPtr(const T& functor) { + return functor.invoke + ? std::shared_ptr(new T(functor), details::FunctorDeleter()) + : nullptr; +} + +template +using StdFunction = typename details::StdFunctionAdapter::invoke)>>::FunctionType; + +template +inline StdFunction AsStdFunction(TFunctor&& functor) { + using TAdapter = details::StdFunctionAdapter::invoke)>>; + return TAdapter::Create(std::move(MakeSharedFunctorPtr(functor))); +} + +} // namespace node + #endif #endif // SRC_NODE_EMBEDDING_API_H_ diff --git a/test/embedding/README.md b/test/embedding/README.md index 92cdcd12968064..7b5612c3a8761d 100644 --- a/test/embedding/README.md +++ b/test/embedding/README.md @@ -1,7 +1,7 @@ # C embedding API This file is an overview for C embedding API. -It is mostly to catch all the work in progress notes. +It is mostly to catch all the work in progress. ## The API overview @@ -40,32 +40,85 @@ It is mostly to catch all the work in progress notes. - `node_embedding_open_node_api_scope` - `node_embedding_close_node_api_scope` -### API 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. +## Functional overview + +### Platform API + +- Global handling of C API errors +- [ ] Global handling of Node.js/V8 errors (is it possible?) +- [ ] Global handling of unhandled JS errors + +- API version +- Node-API version + +- Global platform initialization +- Global platform uninitialization +- Parsing the command line parameters +- Controlled by the platform flags +- Get parsed command line arguments + - [ ] 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. +- [-] Will not support: custom thread pool + +- [ ] API v-table to avoid DLL named function binding + +### Runtime API + +- Runtime initialization +- Runtime uninitialization +- Set runtime args +- Set runtime flags +- Register a preload callback +- Register start execution callback +- [ ] Load default Node.js snapshot without the custom start execution callback +- [ ] Get the returned value from the start execution +- Register linked modules + +- [ ] Runtime handling of API errors (is it possible?) +- [ ] Runtime handling of Node.js/V8 errors (is it possible?) +- [ ] Runtime handling of unhandled JS errors + +- [ ] Events on Runtime destruction (beforeExit, exit) +- [ ] Main vs secondary Runtimes +- [ ] Worker thread runtimes (is it possible?) +- [ ] Associate Inspector with the Runtime +- [ ] Inspector for the secondary Runtimes +- [ ] Runtime destructor to clean up all related resources including the + pending tasks. +- [ ] Exit process only for unhandled main runtime errors. +- [ ] Have an internal list of all runtimes in the system. + It is a list of all secondary runtimes attached to the main runtime. + +### Event Loop API + +- Run event loop while it has events (default) +- Run event loop once and block for an event +- Run event loop once and no wait +- Run event loop till completion +- [ ] Interrupt the event loop (uv_stop stops the default loop and then + the loop resets it) +- [ ] Loop while some condition is true +- [ ] Custom foreground task runner (is it possible?) +- [ ] Notify if event loop has work to do (based on Electron PollEvents) + It must be blocked while the loop is running + It is only for the main runtime +- [ ] V8 Microtask posting and draining +- [ ] Complete the loop immediately if needed +- [ ] Protect from nested uv_run calls +- [ ] How to post setImmediate from another thread? + +### Node-API integration + +- Run Node-API code as a lambda +- Explicitly open and close the Node-API scope +- [ ] Handle JS errors ### Test TODOs +- [ ] Test passing the V8 thread pool size. - [ ] Add tests based on the environment and platform `cctest`s. - [ ] Enable the test_main_modules_node_api test. - [ ] Test failure in Preload callback. - [ ] Test failure in linked modules. - [ ] Add a test that handles JS errors. -- [ ] Make sure that the delete calls match the create calls. +- [ ] Make sure that the the delete calls match the create calls. diff --git a/test/embedding/embedtest_node_api.cc b/test/embedding/embedtest_c_api.cc similarity index 68% rename from test/embedding/embedtest_node_api.cc rename to test/embedding/embedtest_c_api.cc index 83412f982cc205..092ac72efa7880 100644 --- a/test/embedding/embedtest_node_api.cc +++ b/test/embedding/embedtest_c_api.cc @@ -1,21 +1,18 @@ -#include "embedtest_node_api.h" +#include "embedtest_c_api_common.h" #include #include #include #include -const char* main_script = - "globalThis.require = require('module').createRequire(process.execPath);\n" - "globalThis.embedVars = { nön_ascıı: '🏳️‍🌈' };\n" - "require('vm').runInThisContext(process.argv[1]);"; +using namespace node; void CallMe(node_embedding_runtime runtime, napi_env env); void WaitMe(node_embedding_runtime runtime, napi_env env); void WaitMeWithCheese(node_embedding_runtime runtime, napi_env env); extern "C" int32_t test_main_node_api(int32_t argc, char* argv[]) { - node_embedding_on_error(HandleTestError, argv[0]); + node_embedding_on_error({argv[0], HandleTestError, nullptr}); CHECK_STATUS_OR_EXIT(node_embedding_run_main( argc, @@ -24,92 +21,29 @@ extern "C" int32_t test_main_node_api(int32_t argc, char* argv[]) { [&](node_embedding_platform_config platform_config) { CHECK_STATUS(node_embedding_platform_set_flags( platform_config, - node_embedding_platform_disable_node_options_env)); + node_embedding_platform_flags_disable_node_options_env)); return node_embedding_status_ok; }), AsFunctorRef( [&](node_embedding_platform platform, node_embedding_runtime_config runtime_config) { - CHECK_STATUS(node_embedding_runtime_on_start_execution( - runtime_config, - AsFunctor( - [](node_embedding_runtime runtime, - napi_env env, - napi_value process, - napi_value require, - napi_value run_cjs) -> napi_value { - napi_status status{}; - napi_value script, undefined, result; - NODE_API_CALL(napi_create_string_utf8( - env, main_script, NAPI_AUTO_LENGTH, &script)); - NODE_API_CALL(napi_get_undefined(env, &undefined)); - NODE_API_CALL(napi_call_function( - env, undefined, run_cjs, 1, &script, &result)); - return result; - }))); + CHECK_STATUS( + LoadUtf8Script(runtime_config, + main_script, + AsFunctor( + [&](node_embedding_runtime runtime, + napi_env env, + napi_value /*value*/) { + CallMe(runtime, env); + WaitMe(runtime, env); + WaitMeWithCheese(runtime, env); + }))); return node_embedding_status_ok; - }), - AsFunctorRef( - [&](node_embedding_runtime runtime, napi_env env) { - CallMe(runtime, env); - WaitMe(runtime, env); - WaitMeWithCheese(runtime, env); }))); return node_embedding_status_ok; } -napi_status AddUtf8String(std::string& str, napi_env env, napi_value value) { - size_t str_size = 0; - napi_status status = - napi_get_value_string_utf8(env, value, nullptr, 0, &str_size); - if (status != napi_ok) { - return status; - } - size_t offset = str.size(); - str.resize(offset + str_size); - status = napi_get_value_string_utf8( - env, value, &str[0] + offset, str_size + 1, &str_size); - return status; -} - -void GetAndThrowLastErrorMessage(napi_env env) { - const napi_extended_error_info* error_info; - napi_get_last_error_info(env, &error_info); - bool is_pending; - const char* err_message = error_info->error_message; - napi_is_exception_pending((env), &is_pending); - /* If an exception is already pending, don't rethrow it */ - if (!is_pending) { - const char* error_message = - err_message != nullptr ? err_message : "empty error message"; - napi_throw_error((env), nullptr, error_message); - } -} - -void ThrowLastErrorMessage(napi_env env, const char* message) { - bool is_pending; - napi_is_exception_pending(env, &is_pending); - /* If an exception is already pending, don't rethrow it */ - if (!is_pending) { - const char* error_message = - message != nullptr ? message : "empty error message"; - napi_throw_error(env, nullptr, error_message); - } -} - -std::string FormatString(const char* format, ...) { - va_list args1; - va_start(args1, format); - va_list args2; - va_copy(args2, args1); // Required for some compilers like GCC. - std::string result(std::vsnprintf(nullptr, 0, format, args1), '\0'); - va_end(args1); - std::vsnprintf(&result[0], result.size() + 1, format, args2); - va_end(args2); - return result; -} - void CallMe(node_embedding_runtime runtime, napi_env env) { napi_value global; napi_value cb; @@ -190,7 +124,7 @@ void WaitMe(node_embedding_runtime runtime, napi_env env) { } node_embedding_run_event_loop( - runtime, node_embedding_event_loop_run_default, nullptr); + runtime, node_embedding_event_loop_run_mode_default, nullptr); if (strcmp(callback_buf, "waited you") != 0) { NODE_API_FAIL_RETURN_VOID("Invalid value received: %s\n", callback_buf); @@ -291,7 +225,7 @@ void WaitMeWithCheese(node_embedding_runtime runtime, napi_env env) { while (promise_state == PromiseState::kPending) { node_embedding_run_event_loop( - runtime, node_embedding_event_loop_run_nowait, nullptr); + runtime, node_embedding_event_loop_run_mode_nowait, nullptr); } expected = (promise_state == PromiseState::kFulfilled) diff --git a/test/embedding/embedtest_c_api_common.cc b/test/embedding/embedtest_c_api_common.cc new file mode 100644 index 00000000000000..732c53cc64b568 --- /dev/null +++ b/test/embedding/embedtest_c_api_common.cc @@ -0,0 +1,87 @@ +#include "embedtest_c_api_common.h" + +#include +#include +#include +#include + +using namespace node; + +const char* main_script = + "globalThis.require = require('module').createRequire(process.execPath);\n" + "globalThis.embedVars = { nön_ascıı: '🏳️‍🌈' };\n" + "require('vm').runInThisContext(process.argv[1]);"; + +napi_status AddUtf8String(std::string& str, napi_env env, napi_value value) { + size_t str_size = 0; + napi_status status = + napi_get_value_string_utf8(env, value, nullptr, 0, &str_size); + if (status != napi_ok) { + return status; + } + size_t offset = str.size(); + str.resize(offset + str_size); + status = napi_get_value_string_utf8( + env, value, &str[0] + offset, str_size + 1, &str_size); + return status; +} + +void GetAndThrowLastErrorMessage(napi_env env) { + const napi_extended_error_info* error_info; + napi_get_last_error_info(env, &error_info); + bool is_pending; + const char* err_message = error_info->error_message; + napi_is_exception_pending((env), &is_pending); + /* If an exception is already pending, don't rethrow it */ + if (!is_pending) { + const char* error_message = + err_message != nullptr ? err_message : "empty error message"; + napi_throw_error((env), nullptr, error_message); + } +} + +void ThrowLastErrorMessage(napi_env env, const char* message) { + bool is_pending; + napi_is_exception_pending(env, &is_pending); + /* If an exception is already pending, don't rethrow it */ + if (!is_pending) { + const char* error_message = + message != nullptr ? message : "empty error message"; + napi_throw_error(env, nullptr, error_message); + } +} + +std::string FormatString(const char* format, ...) { + va_list args1; + va_start(args1, format); + va_list args2; + va_copy(args2, args1); // Required for some compilers like GCC. + std::string result(std::vsnprintf(nullptr, 0, format, args1), '\0'); + va_end(args1); + std::vsnprintf(&result[0], result.size() + 1, format, args2); + va_end(args2); + return result; +} + +node_embedding_status LoadUtf8Script( + node_embedding_runtime_config runtime_config, + std::string script, + const node_embedding_handle_result_functor& handle_result) { + return node_embedding_runtime_on_start_execution( + runtime_config, + AsFunctor( + [script = std::move(script)](node_embedding_runtime /*runtime*/, + napi_env env, + napi_value /*process*/, + napi_value /*require*/, + napi_value run_cjs) -> napi_value { + napi_value script_value, null_value, result; + NODE_API_CALL(napi_create_string_utf8( + env, script.c_str(), script.size(), &script_value)); + NODE_API_CALL(napi_get_null(env, &null_value)); + NODE_API_CALL(napi_call_function( + env, null_value, run_cjs, 1, &script_value, &result)); + return result; + }), + handle_result); +} diff --git a/test/embedding/embedtest_node_api.h b/test/embedding/embedtest_c_api_common.h similarity index 79% rename from test/embedding/embedtest_node_api.h rename to test/embedding/embedtest_c_api_common.h index 01a37c896db22f..ee53b6f092ba9f 100644 --- a/test/embedding/embedtest_node_api.h +++ b/test/embedding/embedtest_c_api_common.h @@ -10,11 +10,37 @@ #include #include -extern "C" inline void NAPI_CDECL GetArgsVector(void* data, - int32_t argc, - const char* argv[]) { - static_cast*>(data)->assign(argv, argv + argc); -} +template +class CStringArray { + public: + explicit CStringArray(const std::vector& strings) noexcept + : size_(strings.size()) { + if (size_ <= inplace_buffer_.size()) { + c_strs_ = inplace_buffer_.data(); + } else { + allocated_buffer_ = std::make_unique(size_); + c_strs_ = allocated_buffer_.get(); + } + for (size_t i = 0; i < size_; ++i) { + c_strs_[i] = strings[i].c_str(); + } + } + + CStringArray(const CStringArray&) = delete; + CStringArray& operator=(const CStringArray&) = delete; + + const char** c_strs() const { return c_strs_; } + size_t size() const { return size_; } + + const char** argv() const { return c_strs_; } + int32_t argc() const { return static_cast(size_); } + + private: + const char** c_strs_{}; + size_t size_{}; + std::array inplace_buffer_; + std::unique_ptr allocated_buffer_; +}; extern "C" inline node_embedding_status NAPI_CDECL HandleTestError(void* handler_data, @@ -38,7 +64,7 @@ HandleTestError(void* handler_data, return node_embedding_status_ok; } -#endif +#endif // __cplusplus extern const char* main_script; @@ -50,40 +76,10 @@ void ThrowLastErrorMessage(napi_env env, const char* message); std::string FormatString(const char* format, ...); -template -struct Adapter { - static_assert(sizeof(TLambda) == -1, "Unsupported signature"); -}; - -template -struct Adapter { - static TResult Invoke(void* data, TArgs... args) { - return reinterpret_cast(data)->operator()(args...); - } -}; - -template -inline TFunctor AsFunctorRef(TLambda&& lambda) { - using TLambdaType = std::remove_reference_t; - using TAdapter = - Adapter::invoke)>>; - return TFunctor{static_cast(&lambda), &TAdapter::Invoke}; -} - -template -inline TFunctor AsFunctor(TLambda&& lambda) { - using TLambdaType = std::remove_reference_t; - using TAdapter = - Adapter::invoke)>>; - return TFunctor{ - static_cast(new TLambdaType(std::forward(lambda))), - &TAdapter::Invoke, - [](void* data) { delete static_cast(data); }}; -} +node_embedding_status LoadUtf8Script( + node_embedding_runtime_config runtime_config, + std::string script, + const node_embedding_handle_result_functor& handle_result = {}); // // Error handling macros copied from test/js_native_api/common.h diff --git a/test/embedding/embedtest_c_api_env.cc b/test/embedding/embedtest_c_api_env.cc new file mode 100644 index 00000000000000..45a998ee633dad --- /dev/null +++ b/test/embedding/embedtest_c_api_env.cc @@ -0,0 +1,113 @@ +#include "embedtest_c_api_common.h" + +using namespace node; + +// Test the no_browser_globals option. +extern "C" int32_t test_main_c_api_env_no_browser_globals(int32_t argc, + char* argv[]) { + return node_embedding_run_main( + argc, + argv, + {}, + AsFunctorRef( + [](node_embedding_platform platform, + node_embedding_runtime_config runtime_config) { + CHECK_STATUS(node_embedding_runtime_set_flags( + runtime_config, + node_embedding_runtime_flags_no_browser_globals)); + return LoadUtf8Script(runtime_config, + R"JS( +const assert = require('assert'); +const path = require('path'); +const relativeRequire = + require('module').createRequire(path.join(process.cwd(), 'stub.js')); +const { intrinsics, nodeGlobals } = + relativeRequire('./test/common/globals'); +const items = Object.getOwnPropertyNames(globalThis); +const leaks = []; +for (const item of items) { + if (intrinsics.has(item)) { + continue; + } + if (nodeGlobals.has(item)) { + continue; + } + if (item === '$jsDebugIsRegistered') { + continue; + } + leaks.push(item); +} +assert.deepStrictEqual(leaks, []); +)JS"); + })); +} + +// Test ESM loaded +extern "C" int32_t test_main_c_api_env_with_esm_loader(int32_t argc, + char* argv[]) { + // We currently cannot pass argument to command line arguments to the runtime. + // They must be parsed by the platform. + std::vector args_vec(argv, argv + argc); + args_vec.push_back("--experimental-vm-modules"); + CStringArray args(args_vec); + return node_embedding_run_main( + args.argc(), + const_cast(args.argv()), + {}, + AsFunctorRef( + [](node_embedding_platform platform, + node_embedding_runtime_config runtime_config) { + return LoadUtf8Script(runtime_config, + R"JS( +globalThis.require = require('module').createRequire(process.execPath); +const { SourceTextModule } = require('node:vm'); +(async () => { + const stmString = 'globalThis.importResult = import("")'; + const m = new SourceTextModule(stmString, { + importModuleDynamically: (async () => { + const m = new SourceTextModule(''); + await m.link(() => 0); + await m.evaluate(); + return m.namespace; + }), + }); + await m.link(() => 0); + await m.evaluate(); + delete globalThis.importResult; + process.exit(0); +})(); +)JS"); + })); +} + +// Test ESM loaded +extern "C" int32_t test_main_c_api_env_with_no_esm_loader(int32_t argc, + char* argv[]) { + return node_embedding_run_main( + argc, + argv, + {}, + AsFunctorRef( + [](node_embedding_platform platform, + node_embedding_runtime_config runtime_config) { + return LoadUtf8Script(runtime_config, + R"JS( +globalThis.require = require('module').createRequire(process.execPath); +const { SourceTextModule } = require('node:vm'); +(async () => { + const stmString = 'globalThis.importResult = import("")'; + const m = new SourceTextModule(stmString, { + importModuleDynamically: (async () => { + const m = new SourceTextModule(''); + await m.link(() => 0); + await m.evaluate(); + return m.namespace; + }), + }); + await m.link(() => 0); + await m.evaluate(); + delete globalThis.importResult; +})(); +)JS"); + })); +} diff --git a/test/embedding/embedtest_modules_node_api.cc b/test/embedding/embedtest_c_api_modules.cc similarity index 88% rename from test/embedding/embedtest_modules_node_api.cc rename to test/embedding/embedtest_c_api_modules.cc index ef902cf96e9474..4c7820d1730bb8 100644 --- a/test/embedding/embedtest_modules_node_api.cc +++ b/test/embedding/embedtest_c_api_modules.cc @@ -1,9 +1,11 @@ -#include "embedtest_node_api.h" +#include "embedtest_c_api_common.h" #include #include #include +using namespace node; + class GreeterModule { public: explicit GreeterModule(std::atomic* counter_ptr) @@ -89,7 +91,7 @@ extern "C" int32_t test_main_linked_modules_node_api(int32_t argc, std::atomic greeterModuleInitCallCount{0}; std::atomic replicatorModuleInitCallCount{0}; - node_embedding_on_error(HandleTestError, argv[0]); + node_embedding_on_error({argv[0], HandleTestError, nullptr}); CHECK_STATUS_OR_EXIT(node_embedding_run_main( argc, @@ -124,25 +126,10 @@ extern "C" int32_t test_main_linked_modules_node_api(int32_t argc, ReplicatorModule(&replicatorModuleInitCallCount)), NAPI_VERSION)); - CHECK_STATUS(node_embedding_runtime_on_start_execution( - runtime_config, - AsFunctor( - [](node_embedding_runtime runtime, - napi_env env, - napi_value process, - napi_value require, - napi_value run_cjs) -> napi_value { - napi_value script, undefined, result; - NODE_API_CALL(napi_create_string_utf8( - env, main_script, NAPI_AUTO_LENGTH, &script)); - NODE_API_CALL(napi_get_undefined(env, &undefined)); - NODE_API_CALL(napi_call_function( - env, undefined, run_cjs, 1, &script, &result)); - return result; - }))); + CHECK_STATUS(LoadUtf8Script(runtime_config, main_script)); + return node_embedding_status_ok; - }), - {})); + }))); ASSERT_OR_EXIT(greeterModuleInitCallCount == expectedGreeterModuleInitCallCount); diff --git a/test/embedding/embedtest_preload_node_api.cc b/test/embedding/embedtest_c_api_preload.cc similarity index 58% rename from test/embedding/embedtest_preload_node_api.cc rename to test/embedding/embedtest_c_api_preload.cc index f8cb2bfd22719e..8f1c617a33c4e5 100644 --- a/test/embedding/embedtest_preload_node_api.cc +++ b/test/embedding/embedtest_c_api_preload.cc @@ -1,8 +1,10 @@ -#include "embedtest_node_api.h" +#include "embedtest_c_api_common.h" #include #include +using namespace node; + // Tests that the same preload callback is called from the main thread and from // the worker thread. extern "C" int32_t test_main_preload_node_api(int32_t argc, char* argv[]) { @@ -28,26 +30,11 @@ extern "C" int32_t test_main_preload_node_api(int32_t argc, char* argv[]) { NODE_API_CALL_RETURN_VOID(napi_set_named_property( env, global, "preloadValue", value)); }))); - CHECK_STATUS(node_embedding_runtime_on_start_execution( - runtime_config, - AsFunctor( - [](node_embedding_runtime runtime, - napi_env env, - napi_value process, - napi_value require, - napi_value run_cjs) -> napi_value { - napi_value script, undefined, result; - NODE_API_CALL(napi_create_string_utf8( - env, main_script, NAPI_AUTO_LENGTH, &script)); - NODE_API_CALL(napi_get_undefined(env, &undefined)); - NODE_API_CALL(napi_call_function( - env, undefined, run_cjs, 1, &script, &result)); - return result; - }))); + + CHECK_STATUS(LoadUtf8Script(runtime_config, main_script)); return node_embedding_status_ok; - }), - {})); + }))); return 0; } diff --git a/test/embedding/embedtest_nodejs_main_node_api.cc b/test/embedding/embedtest_c_api_run_main.cc similarity index 85% rename from test/embedding/embedtest_nodejs_main_node_api.cc rename to test/embedding/embedtest_c_api_run_main.cc index 4e9e7ef7adf400..5646d471e7fa41 100644 --- a/test/embedding/embedtest_nodejs_main_node_api.cc +++ b/test/embedding/embedtest_c_api_run_main.cc @@ -4,5 +4,5 @@ // invoked from the libnode shared library as it would be run from the Node.js // CLI. No embedder customizations are available in this case. extern "C" int32_t test_main_nodejs_main_node_api(int32_t argc, char* argv[]) { - return node_embedding_run_main(argc, argv, {}, {}, {}); + return node_embedding_run_main(argc, argv, {}, {}); } diff --git a/test/embedding/embedtest_threading_node_api.cc b/test/embedding/embedtest_c_api_threading.cc similarity index 71% rename from test/embedding/embedtest_threading_node_api.cc rename to test/embedding/embedtest_c_api_threading.cc index 5b51381b5f0c90..27b601c00fdee4 100644 --- a/test/embedding/embedtest_threading_node_api.cc +++ b/test/embedding/embedtest_c_api_threading.cc @@ -1,10 +1,12 @@ -#include "embedtest_node_api.h" +#include "embedtest_c_api_common.h" #include #include #include #include +using namespace node; + // Tests that multiple runtimes can be run at the same time in their own // threads. The test creates 12 threads and 12 runtimes. Each runtime runs in it // own thread. @@ -35,36 +37,26 @@ extern "C" int32_t test_main_threading_runtime_per_thread_node_api( // process. CHECK_STATUS(node_embedding_runtime_set_flags( runtime_config, - node_embedding_runtime_default_flags | - node_embedding_runtime_no_create_inspector)); - CHECK_STATUS(node_embedding_runtime_on_start_execution( + node_embedding_runtime_flags_default | + node_embedding_runtime_flags_no_create_inspector)); + CHECK_STATUS(LoadUtf8Script( runtime_config, - AsFunctor( - [](node_embedding_runtime runtime, - napi_env env, - napi_value process, - napi_value require, - napi_value run_cjs) -> napi_value { - napi_value script, undefined, result; - NODE_API_CALL(napi_create_string_utf8( - env, main_script, NAPI_AUTO_LENGTH, &script)); - NODE_API_CALL(napi_get_undefined(env, &undefined)); - NODE_API_CALL(napi_call_function( - env, undefined, run_cjs, 1, &script, &result)); - return result; + main_script, + AsFunctor( + [&](node_embedding_runtime runtime, + napi_env env, + napi_value /*value*/) { + napi_value global, my_count; + NODE_API_CALL_RETURN_VOID( + napi_get_global(env, &global)); + NODE_API_CALL_RETURN_VOID(napi_get_named_property( + env, global, "myCount", &my_count)); + int32_t count; + NODE_API_CALL_RETURN_VOID( + napi_get_value_int32(env, my_count, &count)); + global_count.fetch_add(count); }))); return node_embedding_status_ok; - }), - AsFunctorRef( - [&](node_embedding_runtime runtime, napi_env env) { - napi_value global, my_count; - NODE_API_CALL_RETURN_VOID(napi_get_global(env, &global)); - NODE_API_CALL_RETURN_VOID(napi_get_named_property( - env, global, "myCount", &my_count)); - int32_t count; - NODE_API_CALL_RETURN_VOID( - napi_get_value_int32(env, my_count, &count)); - global_count.fetch_add(count); }))); return node_embedding_status_ok; }(); @@ -116,25 +108,9 @@ extern "C" int32_t test_main_threading_several_runtimes_per_thread_node_api( // process. CHECK_STATUS(node_embedding_runtime_set_flags( runtime_config, - node_embedding_runtime_default_flags | - node_embedding_runtime_no_create_inspector)); - CHECK_STATUS(node_embedding_runtime_on_start_execution( - runtime_config, - AsFunctor( - [](node_embedding_runtime runtime, - napi_env env, - napi_value process, - napi_value require, - napi_value run_cjs) -> napi_value { - napi_value script, undefined, result; - NODE_API_CALL(napi_create_string_utf8( - env, main_script, NAPI_AUTO_LENGTH, &script)); - NODE_API_CALL(napi_get_undefined(env, &undefined)); - NODE_API_CALL(napi_call_function( - env, undefined, run_cjs, 1, &script, &result)); - return result; - }))); - + node_embedding_runtime_flags_default | + node_embedding_runtime_flags_no_create_inspector)); + CHECK_STATUS(LoadUtf8Script(runtime_config, main_script)); return node_embedding_status_ok; }), &runtime)); @@ -142,7 +118,7 @@ extern "C" int32_t test_main_threading_several_runtimes_per_thread_node_api( CHECK_STATUS_OR_EXIT(node_embedding_run_node_api( runtime, - AsFunctorRef( + AsFunctorRef( [&](node_embedding_runtime runtime, napi_env env) { napi_value undefined, global, func; NODE_API_CALL_RETURN_VOID(napi_get_undefined(env, &undefined)); @@ -163,7 +139,7 @@ extern "C" int32_t test_main_threading_several_runtimes_per_thread_node_api( for (node_embedding_runtime runtime : runtimes) { bool has_more_work = false; CHECK_STATUS_OR_EXIT(node_embedding_run_event_loop( - runtime, node_embedding_event_loop_run_nowait, &has_more_work)); + runtime, node_embedding_event_loop_run_mode_nowait, &has_more_work)); more_work |= has_more_work; } } while (more_work); @@ -171,7 +147,7 @@ extern "C" int32_t test_main_threading_several_runtimes_per_thread_node_api( for (node_embedding_runtime runtime : runtimes) { CHECK_STATUS_OR_EXIT(node_embedding_run_node_api( runtime, - AsFunctorRef( + AsFunctorRef( [&](node_embedding_runtime runtime, napi_env env) { napi_value global, my_count; NODE_API_CALL_RETURN_VOID(napi_get_global(env, &global)); @@ -225,23 +201,7 @@ extern "C" int32_t test_main_threading_runtime_in_several_threads_node_api( AsFunctorRef( [&](node_embedding_platform platform, node_embedding_runtime_config runtime_config) { - CHECK_STATUS(node_embedding_runtime_on_start_execution( - runtime_config, - AsFunctor( - [](node_embedding_runtime runtime, - napi_env env, - napi_value process, - napi_value require, - napi_value run_cjs) -> napi_value { - napi_value script, undefined, result; - NODE_API_CALL(napi_create_string_utf8( - env, main_script, NAPI_AUTO_LENGTH, &script)); - NODE_API_CALL(napi_get_undefined(env, &undefined)); - NODE_API_CALL(napi_call_function( - env, undefined, run_cjs, 1, &script, &result)); - return result; - }))); - + CHECK_STATUS(LoadUtf8Script(runtime_config, main_script)); return node_embedding_status_ok; }), &runtime)); @@ -251,7 +211,7 @@ extern "C" int32_t test_main_threading_runtime_in_several_threads_node_api( std::scoped_lock lock(mutex); node_embedding_status status = node_embedding_run_node_api( runtime, - AsFunctorRef( + AsFunctorRef( [&](node_embedding_runtime runtime, napi_env env) { napi_value undefined, global, func, my_count; NODE_API_CALL_RETURN_VOID(napi_get_undefined(env, &undefined)); @@ -359,21 +319,25 @@ extern "C" int32_t test_main_threading_runtime_in_ui_thread_node_api( // The callback will be invoked from the runtime's event loop // observer thread. It must schedule the work to the UI thread's // event loop. - CHECK_STATUS(node_embedding_on_wake_up_event_loop( + CHECK_STATUS(node_embedding_runtime_set_task_runner( runtime_config, - AsFunctor( - [&ui_queue](node_embedding_runtime runtime) { - ui_queue.PostTask([runtime, &ui_queue]() { - CHECK_STATUS_OR_EXIT(node_embedding_run_event_loop( - runtime, - node_embedding_event_loop_run_nowait, - nullptr)); + AsFunctor( + // We capture the ui_queue by reference here because we + // guarantee it to be alive till the end of the test. In + // real applications, you should use a safer way to capture + // the dispatcher queue. + [&ui_queue, + &runtime](node_embedding_run_task_functor run_task) { + // TODO: figure out the termination scenario. + ui_queue.PostTask([run_task, &runtime, &ui_queue]() { + AsStdFunction(run_task)(); // Check myCount and stop the processing when it // reaches 5. CHECK_STATUS_OR_EXIT(node_embedding_run_node_api( runtime, - AsFunctorRef( + AsFunctorRef< + node_embedding_run_node_api_functor_ref>( [&](node_embedding_runtime runtime, napi_env env) { napi_value global, my_count; @@ -400,22 +364,7 @@ extern "C" int32_t test_main_threading_runtime_in_ui_thread_node_api( }); }))); - CHECK_STATUS(node_embedding_runtime_on_start_execution( - runtime_config, - AsFunctor( - [](node_embedding_runtime runtime, - napi_env env, - napi_value process, - napi_value require, - napi_value run_cjs) -> napi_value { - napi_value script, undefined, result; - NODE_API_CALL(napi_create_string_utf8( - env, main_script, NAPI_AUTO_LENGTH, &script)); - NODE_API_CALL(napi_get_undefined(env, &undefined)); - NODE_API_CALL(napi_call_function( - env, undefined, run_cjs, 1, &script, &result)); - return result; - }))); + CHECK_STATUS(LoadUtf8Script(runtime_config, main_script)); return node_embedding_status_ok; }), @@ -426,7 +375,7 @@ extern "C" int32_t test_main_threading_runtime_in_ui_thread_node_api( ui_queue.PostTask([runtime]() { node_embedding_status status = node_embedding_run_node_api( runtime, - AsFunctorRef( + AsFunctorRef( [&](node_embedding_runtime runtime, napi_env env) { napi_value undefined, global, func; NODE_API_CALL_RETURN_VOID(napi_get_undefined(env, &undefined)); @@ -441,7 +390,7 @@ extern "C" int32_t test_main_threading_runtime_in_ui_thread_node_api( env, undefined, func, 0, nullptr, nullptr)); node_embedding_run_event_loop( - runtime, node_embedding_event_loop_run_nowait, nullptr); + runtime, node_embedding_event_loop_run_mode_nowait, nullptr); })); CHECK_STATUS_OR_EXIT(status); }); diff --git a/test/embedding/embedtest_main.cc b/test/embedding/embedtest_main.cc index 990b1ae10ef512..8bb2bc88161aa1 100644 --- a/test/embedding/embedtest_main.cc +++ b/test/embedding/embedtest_main.cc @@ -6,12 +6,23 @@ extern "C" int32_t test_main_nodejs_main_node_api(int32_t argc, char* argv[]); extern "C" int32_t test_main_modules_node_api(int32_t argc, char* argv[]); extern "C" int32_t test_main_linked_modules_node_api(int32_t argc, char* argv[]); -extern "C" int32_t test_main_threading_runtime_per_thread_node_api(int32_t argc, char* argv[]); -extern "C" int32_t test_main_threading_several_runtimes_per_thread_node_api(int32_t argc, char* argv[]); -extern "C" int32_t test_main_threading_runtime_in_several_threads_node_api(int32_t argc, char* argv[]); -extern "C" int32_t test_main_threading_runtime_in_ui_thread_node_api(int32_t argc, char* argv[]); +extern "C" int32_t test_main_threading_runtime_per_thread_node_api( + int32_t argc, char* argv[]); +extern "C" int32_t test_main_threading_several_runtimes_per_thread_node_api( + int32_t argc, char* argv[]); +extern "C" int32_t test_main_threading_runtime_in_several_threads_node_api( + int32_t argc, char* argv[]); +extern "C" int32_t test_main_threading_runtime_in_ui_thread_node_api( + int32_t argc, char* argv[]); extern "C" int32_t test_main_preload_node_api(int32_t argc, char* argv[]); +extern "C" int32_t test_main_c_api_env_no_browser_globals(int32_t argc, + char* argv[]); +extern "C" int32_t test_main_c_api_env_with_esm_loader(int32_t argc, + char* argv[]); +extern "C" int32_t test_main_c_api_env_with_no_esm_loader(int32_t argc, + char* argv[]); + typedef int32_t (*main_callback)(int32_t argc, char* argv[]); int32_t CallWithoutArg1(main_callback main, int32_t argc, char** argv) { @@ -39,15 +50,29 @@ NODE_MAIN(int32_t argc, node::argv_type raw_argv[]) { } else if (strcmp(arg1, "linked-modules-node-api") == 0) { return CallWithoutArg1(test_main_linked_modules_node_api, argc, argv); } else if (strcmp(arg1, "threading-runtime-per-thread-node-api") == 0) { - return CallWithoutArg1(test_main_threading_runtime_per_thread_node_api, argc, argv); - } else if (strcmp(arg1, "threading-several-runtimes-per-thread-node-api") == 0) { - return CallWithoutArg1(test_main_threading_several_runtimes_per_thread_node_api, argc, argv); - } else if (strcmp(arg1, "threading-runtime-in-several-threads-node-api") == 0) { - return CallWithoutArg1(test_main_threading_runtime_in_several_threads_node_api, argc, argv); + return CallWithoutArg1( + test_main_threading_runtime_per_thread_node_api, argc, argv); + } else if (strcmp(arg1, "threading-several-runtimes-per-thread-node-api") == + 0) { + return CallWithoutArg1( + test_main_threading_several_runtimes_per_thread_node_api, argc, argv); + } else if (strcmp(arg1, "threading-runtime-in-several-threads-node-api") == + 0) { + return CallWithoutArg1( + test_main_threading_runtime_in_several_threads_node_api, argc, argv); } else if (strcmp(arg1, "threading-runtime-in-ui-thread-node-api") == 0) { - return CallWithoutArg1(test_main_threading_runtime_in_ui_thread_node_api, argc, argv); + return CallWithoutArg1( + test_main_threading_runtime_in_ui_thread_node_api, argc, argv); } else if (strcmp(arg1, "preload-node-api") == 0) { return CallWithoutArg1(test_main_preload_node_api, argc, argv); + } else if (strcmp(arg1, "c-api-env-no-browser-globals") == 0) { + return CallWithoutArg1( + test_main_c_api_env_no_browser_globals, argc, argv); + } else if (strcmp(arg1, "c-api-env-with-esm-loader") == 0) { + return CallWithoutArg1(test_main_c_api_env_with_esm_loader, argc, argv); + } else if (strcmp(arg1, "c-api-env-with-no-esm-loader") == 0) { + return CallWithoutArg1( + test_main_c_api_env_with_no_esm_loader, argc, argv); } } return test_main_cpp_api(argc, argv); diff --git a/test/embedding/test-embedding.js b/test/embedding/test-embedding.js index 9b6da7cc7d89b5..0e116a1bdd85c8 100644 --- a/test/embedding/test-embedding.js +++ b/test/embedding/test-embedding.js @@ -383,6 +383,34 @@ runSnapshotTests('cpp-api'); ); } +function runEnvTests(apiType) { + runTest( + `${apiType}: Env No Browser Globals`, + spawnSyncAndExitWithoutError, + [`${apiType}-env-no-browser-globals`], + {} + ); + + runTest( + `${apiType}: Env With ESM Loader`, + spawnSyncAndExitWithoutError, + [`${apiType}-env-with-esm-loader`], + {} + ); + + runTest( + `${apiType}: Env With No ESM Loader`, + spawnSyncAndExit, + [`${apiType}-env-with-no-esm-loader`], + { + status: 1, + signal: null, + } + ); +} + +runEnvTests('c-api'); + /* runTest( `modules-node-api: load modules`, From d895ce179223b1a1dd162409a0418335d827b336 Mon Sep 17 00:00:00 2001 From: Vladimir Morozov Date: Thu, 30 Jan 2025 21:14:02 -0800 Subject: [PATCH 21/22] update embedding API and add C tests --- .clang-format | 4 + node.gyp | 23 +- src/api/embed_helpers.cc | 14 +- src/js_native_api.h | 2 - src/js_native_api_types.h | 10 +- src/node_embedding_api.cc | 1299 ++++++++++------- src/node_embedding_api.h | 611 ++++---- src/node_embedding_api_cpp.h | 1125 ++++++++++++++ test/embedding/README.md | 2 +- test/embedding/cjs.cjs | 3 - test/embedding/embedtest.cc | 3 +- test/embedding/embedtest_c_api.c | 262 ++++ test/embedding/embedtest_c_api.cc | 239 --- test/embedding/embedtest_c_api_common.c | 141 ++ test/embedding/embedtest_c_api_common.cc | 87 -- test/embedding/embedtest_c_api_common.h | 184 +-- test/embedding/embedtest_c_api_env.c | 140 ++ test/embedding/embedtest_c_api_env.cc | 113 -- test/embedding/embedtest_c_api_modules.c | 162 ++ test/embedding/embedtest_c_api_modules.cc | 211 --- test/embedding/embedtest_c_api_preload.c | 37 + test/embedding/embedtest_c_api_preload.cc | 40 - test/embedding/embedtest_c_api_run_main.c | 12 + test/embedding/embedtest_c_api_run_main.cc | 8 - test/embedding/embedtest_c_api_threading.c | 600 ++++++++ test/embedding/embedtest_c_api_threading.cc | 404 ----- test/embedding/embedtest_c_cpp_api.cc | 247 ++++ test/embedding/embedtest_c_cpp_api_common.cc | 61 + test/embedding/embedtest_c_cpp_api_common.h | 97 ++ test/embedding/embedtest_c_cpp_api_env.cc | 116 ++ test/embedding/embedtest_c_cpp_api_modules.cc | 144 ++ test/embedding/embedtest_c_cpp_api_preload.cc | 36 + .../embedding/embedtest_c_cpp_api_run_main.cc | 15 + .../embedtest_c_cpp_api_threading.cc | 386 +++++ test/embedding/embedtest_main.cc | 144 +- test/embedding/es6.mjs | 1 - test/embedding/test-embedding.js | 66 +- 37 files changed, 4823 insertions(+), 2226 deletions(-) create mode 100644 src/node_embedding_api_cpp.h delete mode 100644 test/embedding/cjs.cjs create mode 100644 test/embedding/embedtest_c_api.c delete mode 100644 test/embedding/embedtest_c_api.cc create mode 100644 test/embedding/embedtest_c_api_common.c delete mode 100644 test/embedding/embedtest_c_api_common.cc create mode 100644 test/embedding/embedtest_c_api_env.c delete mode 100644 test/embedding/embedtest_c_api_env.cc create mode 100644 test/embedding/embedtest_c_api_modules.c delete mode 100644 test/embedding/embedtest_c_api_modules.cc create mode 100644 test/embedding/embedtest_c_api_preload.c delete mode 100644 test/embedding/embedtest_c_api_preload.cc create mode 100644 test/embedding/embedtest_c_api_run_main.c delete mode 100644 test/embedding/embedtest_c_api_run_main.cc create mode 100644 test/embedding/embedtest_c_api_threading.c delete mode 100644 test/embedding/embedtest_c_api_threading.cc create mode 100644 test/embedding/embedtest_c_cpp_api.cc create mode 100644 test/embedding/embedtest_c_cpp_api_common.cc create mode 100644 test/embedding/embedtest_c_cpp_api_common.h create mode 100644 test/embedding/embedtest_c_cpp_api_env.cc create mode 100644 test/embedding/embedtest_c_cpp_api_modules.cc create mode 100644 test/embedding/embedtest_c_cpp_api_preload.cc create mode 100644 test/embedding/embedtest_c_cpp_api_run_main.cc create mode 100644 test/embedding/embedtest_c_cpp_api_threading.cc delete mode 100644 test/embedding/es6.mjs diff --git a/.clang-format b/.clang-format index 4aad29c328abd4..d53efb21fce1ca 100644 --- a/.clang-format +++ b/.clang-format @@ -79,6 +79,10 @@ JavaScriptWrapImports: true KeepEmptyLinesAtTheStartOfBlocks: false MacroBlockBegin: '' MacroBlockEnd: '' +Macros: + - "NODE_ENUM(a,b) = enum class b" + - "NODE_ENUM_FLAGS(a,b) = enum class b" + - "NODE_ENUM_ITEM (a,b) = b" MaxEmptyLinesToKeep: 1 NamespaceIndentation: None ObjCBlockIndentWidth: 2 diff --git a/node.gyp b/node.gyp index da532c75e3d89b..1ddfe710c119ac 100644 --- a/node.gyp +++ b/node.gyp @@ -237,6 +237,7 @@ 'src/node_dir.h', 'src/node_dotenv.h', 'src/node_embedding_api.h', + 'src/node_embedding_api_cpp.h', 'src/node_errors.h', 'src/node_exit_code.h', 'src/node_external_reference.h', @@ -1263,14 +1264,22 @@ 'sources': [ 'src/node_snapshot_stub.cc', 'test/embedding/embedtest.cc', - 'test/embedding/embedtest_c_api.cc', - 'test/embedding/embedtest_c_api_common.cc', + 'test/embedding/embedtest_c_api_common.c', 'test/embedding/embedtest_c_api_common.h', - 'test/embedding/embedtest_c_api_env.cc', - 'test/embedding/embedtest_c_api_modules.cc', - 'test/embedding/embedtest_c_api_preload.cc', - 'test/embedding/embedtest_c_api_run_main.cc', - 'test/embedding/embedtest_c_api_threading.cc', + 'test/embedding/embedtest_c_api_env.c', + 'test/embedding/embedtest_c_api_modules.c', + 'test/embedding/embedtest_c_api_preload.c', + 'test/embedding/embedtest_c_api_run_main.c', + 'test/embedding/embedtest_c_api_threading.c', + 'test/embedding/embedtest_c_api.c', + 'test/embedding/embedtest_c_cpp_api_common.cc', + 'test/embedding/embedtest_c_cpp_api_common.h', + 'test/embedding/embedtest_c_cpp_api_env.cc', + 'test/embedding/embedtest_c_cpp_api_modules.cc', + 'test/embedding/embedtest_c_cpp_api_preload.cc', + 'test/embedding/embedtest_c_cpp_api_run_main.cc', + 'test/embedding/embedtest_c_cpp_api_threading.cc', + 'test/embedding/embedtest_c_cpp_api.cc', 'test/embedding/embedtest_main.cc', ], diff --git a/src/api/embed_helpers.cc b/src/api/embed_helpers.cc index b790e557f5d9c4..ab24d32ba3884d 100644 --- a/src/api/embed_helpers.cc +++ b/src/api/embed_helpers.cc @@ -102,19 +102,7 @@ Maybe SpinEventLoopInternalImpl(Environment* env) { env->PrintInfoForSnapshotIfDebug(); env->ForEachRealm([](Realm* realm) { realm->VerifyNoStrongBaseObjects(); }); - Maybe exit_code = EmitProcessExitInternal(env); - if (exit_code.FromMaybe(ExitCode::kGenericUserError) != - ExitCode::kNoFailure) { - return exit_code; - } - - auto unsettled_tla = env->CheckUnsettledTopLevelAwait(); - if (unsettled_tla.IsNothing()) { - return Nothing(); - } - if (!unsettled_tla.FromJust()) { - return Just(ExitCode::kUnsettledTopLevelAwait); - } + return EmitProcessExitInternal(env); } return Just(ExitCode::kNoFailure); } diff --git a/src/js_native_api.h b/src/js_native_api.h index 7a565355dbf652..39c2247f4b7fb2 100644 --- a/src/js_native_api.h +++ b/src/js_native_api.h @@ -2,8 +2,6 @@ #define SRC_JS_NATIVE_API_H_ // This file needs to be compatible with C compilers. -#include // NOLINT(modernize-deprecated-headers) -#include // NOLINT(modernize-deprecated-headers) // Use INT_MAX, this should only be consumed by the pre-processor anyway. #define NAPI_VERSION_EXPERIMENTAL 2147483647 diff --git a/src/js_native_api_types.h b/src/js_native_api_types.h index 43e7bb77ff94e7..842f18b1de11e6 100644 --- a/src/js_native_api_types.h +++ b/src/js_native_api_types.h @@ -4,8 +4,14 @@ // This file needs to be compatible with C compilers. // This is a public include file, and these includes have essentially // became part of it's API. -#include // NOLINT(modernize-deprecated-headers) -#include // NOLINT(modernize-deprecated-headers) +#ifdef __cplusplus +#include +#include +#else +#include // NOLINT(modernize-deprecated-headers) +#include // NOLINT(modernize-deprecated-headers) +#include // NOLINT(modernize-deprecated-headers) +#endif #if !defined __cplusplus || (defined(_MSC_VER) && _MSC_VER < 1900) typedef uint16_t char16_t; diff --git a/src/node_embedding_api.cc b/src/node_embedding_api.cc index 72224771fec4ee..41c47af046089f 100644 --- a/src/node_embedding_api.cc +++ b/src/node_embedding_api.cc @@ -1,5 +1,6 @@ -#define NAPI_EXPERIMENTAL -#include "node_embedding_api.h" +#include "node_version.h" // define NODE_VERSION first + +#include "node_embedding_api_cpp.h" #include "env-inl.h" #include "js_native_api_v8.h" @@ -9,67 +10,73 @@ #include #include +#include +#include + +#if defined(__APPLE__) +#include +#elif defined(__linux) +#include +#endif // Use macros to handle errors since they can record the failing argument name // or expression and their location in the source code. #define CAST_NOT_NULL_TO(value, type) \ - (value) == nullptr ? node::EmbeddedErrorHandling::HandleError( \ + (value) == nullptr ? EmbeddedErrorHandling::HandleError( \ + NodeStatus::kNullArg, \ "Argument must not be null: " #value, \ __FILE__, \ - __LINE__, \ - node_embedding_status_null_arg) \ + __LINE__) \ : reinterpret_cast(value) -#define EMBEDDED_PLATFORM(platform) \ - CAST_NOT_NULL_TO(platform, node::EmbeddedPlatform) +#define EMBEDDED_PLATFORM(platform) CAST_NOT_NULL_TO(platform, EmbeddedPlatform) -#define EMBEDDED_RUNTIME(runtime) \ - CAST_NOT_NULL_TO(runtime, node::EmbeddedRuntime) +#define EMBEDDED_RUNTIME(runtime) CAST_NOT_NULL_TO(runtime, EmbeddedRuntime) -#define CHECK_ARG_NOT_NULL(arg) \ +#define ASSERT_ARG_NOT_NULL(arg) \ do { \ if ((arg) == nullptr) { \ - return node::EmbeddedErrorHandling::HandleError( \ + return EmbeddedErrorHandling::HandleError( \ + NodeStatus::kNullArg, \ "Argument must not be null: " #arg, \ __FILE__, \ - __LINE__, \ - node_embedding_status_null_arg); \ + __LINE__); \ } \ } while (false) #define ASSERT_ARG(arg, expr) \ do { \ if (!(expr)) { \ - return node::EmbeddedErrorHandling::HandleError( \ - "Arg: " #arg " failed: " #expr, \ - __FILE__, \ - __LINE__, \ - node_embedding_status_bad_arg); \ + return EmbeddedErrorHandling::HandleError(NodeStatus::kBadArg, \ + "Arg: " #arg \ + " failed: " #expr, \ + __FILE__, \ + __LINE__); \ } \ } while (false) -#define ASSERT(expr) \ +#define ASSERT_EXPR(expr) \ do { \ if (!(expr)) { \ - return node::EmbeddedErrorHandling::HandleError( \ + return EmbeddedErrorHandling::HandleError( \ + NodeStatus::kGenericError, \ "Expression returned false: " #expr, \ __FILE__, \ - __LINE__, \ - node_embedding_status_generic_error); \ + __LINE__); \ } \ } while (false) #define CHECK_STATUS(expr) \ do { \ - node_embedding_status status = (expr); \ - if (status != node_embedding_status_ok) { \ + if (NodeStatus status = (expr); status != NodeStatus::kOk) { \ return status; \ } \ } while (false) namespace v8impl { +// Creates new Node-API environment. It is defined in node_api.cc. napi_env NewEnv(v8::Local context, const std::string& module_filename, int32_t module_api_version); @@ -81,47 +88,23 @@ namespace node { // Declare functions implemented in embed_helpers.cc v8::Maybe SpinEventLoopWithoutCleanup(Environment* env, uv_run_mode run_mode); +} // namespace node -namespace { - -// A helper class to convert std::vector to an array of C strings. -// If the number of strings is less than kInplaceBufferSize, the strings are -// stored in the inplace_buffer_ array. Otherwise, the strings are stored in the -// allocated_buffer_ array. -// Ideally the class must be allocated on the stack. -// In any case it must not outlive the passed vector since it keeps only the -// string pointers returned by std::string::c_str() method. -template -class CStringArray { - public: - explicit CStringArray(const std::vector& strings) noexcept - : size_(strings.size()) { - if (size_ <= inplace_buffer_.size()) { - c_strs_ = inplace_buffer_.data(); - } else { - allocated_buffer_ = std::make_unique(size_); - c_strs_ = allocated_buffer_.get(); - } - for (size_t i = 0; i < size_; ++i) { - c_strs_[i] = strings[i].c_str(); - } - } - - CStringArray(const CStringArray&) = delete; - CStringArray& operator=(const CStringArray&) = delete; - - const char** c_strs() const { return c_strs_; } - size_t size() const { return size_; } +namespace node::embedding { - const char** argv() const { return c_strs_; } - int32_t argc() const { return static_cast(size_); } +//------------------------------------------------------------------------------ +// Convenience functor struct adapter for C++ function object or lambdas. +//------------------------------------------------------------------------------ - private: - const char** c_strs_{}; - size_t size_{}; - std::array inplace_buffer_; - std::unique_ptr allocated_buffer_; -}; +template +std::shared_ptr> MakeSharedFunctorPtr( + TCallback callback, + void* callback_data, + node_embedding_data_release_callback release_callback_data) { + return callback ? std::make_shared>( + callback, callback_data, release_callback_data) + : nullptr; +} // Stack implementation that works only with trivially constructible, // destructible, and copyable types. It uses the small value optimization where @@ -136,7 +119,7 @@ class SmallTrivialStack { "T must be trivially copyable"); public: - SmallTrivialStack() noexcept : stack_(inplace_entries_.data()) {} + SmallTrivialStack() noexcept : stack_(this->inplace_entries_.data()) {} void Push(T&& value) { EnsureCapacity(size_ + 1); @@ -174,98 +157,102 @@ class SmallTrivialStack { } private: + std::array inplace_entries_; T* stack_{}; // Points to either inplace_entries_ or allocated_entries_. size_t size_{}; // Number of elements in the stack. size_t capacity_{kInplaceEntryCount}; - std::array inplace_entries_; std::unique_ptr allocated_entries_; }; class EmbeddedErrorHandling { public: - static node_embedding_status SetErrorHandler( - const node_embedding_handle_error_functor& error_handler); - - static node_embedding_status HandleError(const std::string& message, - node_embedding_status status); + static NodeStatus HandleError(NodeStatus status, std::string_view message); - static node_embedding_status HandleError( - const std::vector& messages, node_embedding_status status); + static NodeStatus HandleError(NodeStatus status, + const std::vector& messages); - static node_embedding_status HandleError(const char* message, - const char* filename, - int32_t line, - node_embedding_status status); + static NodeStatus HandleError(NodeStatus status, + std::string_view message, + std::string_view filename, + int32_t line); static std::string FormatString(const char* format, ...); - static StdFunction& ErrorHandler(); + static const char* GetLastErrorMessage(); + static void SetLastErrorMessage(std::string message); + static void ClearLastErrorMessage(); + + static NodeStatus ExitCodeToStatus(int32_t exit_code); private: - static node_embedding_status DefaultErrorHandler( - void* handler_data, - const char* messages[], - size_t messages_size, - node_embedding_status status); + enum class ErrorMessageAction { + kGet, + kSet, + kClear, + }; + + private: + static const char* DoErrorMessage(ErrorMessageAction action, + std::string message); }; class EmbeddedPlatform { public: - EmbeddedPlatform(int32_t argc, char* argv[]) noexcept + EmbeddedPlatform(int32_t argc, const char* argv[]) noexcept : args_(argv, argv + argc) {} EmbeddedPlatform(const EmbeddedPlatform&) = delete; EmbeddedPlatform& operator=(const EmbeddedPlatform&) = delete; - static node_embedding_status SetApiVersion(int32_t embedding_api_version, - int32_t node_api_version); - - static node_embedding_status RunMain( + static NodeStatus RunMain( + int32_t embedding_api_version, int32_t argc, - char* argv[], - const node_embedding_configure_platform_functor_ref& configure_platform, - const node_embedding_configure_runtime_functor_ref& configure_runtime); - - static node_embedding_status Create( + const char* argv[], + node_embedding_platform_configure_callback configure_platform, + void* configure_platform_data, + node_embedding_runtime_configure_callback configure_runtime, + void* configure_runtime_data); + + static NodeStatus Create( + int32_t embedding_api_version, int32_t argc, - char* argv[], - const node_embedding_configure_platform_functor_ref& configure_platform, + const char* argv[], + node_embedding_platform_configure_callback configure_platform, + void* configure_platform_data, node_embedding_platform* result); - node_embedding_status DeleteMe(); + NodeStatus DeleteMe(); + + NodeStatus SetApiVersion(int32_t embedding_api_version); - node_embedding_status SetFlags(node_embedding_platform_flags flags); + NodeStatus SetFlags(NodePlatformFlags flags); - node_embedding_status Initialize( - const node_embedding_configure_platform_functor_ref& configure_platform, + NodeStatus Initialize( + node_embedding_platform_configure_callback configure_platform, + void* configure_platform_data, bool* early_return); - node_embedding_status GetParsedArgs( - const node_embedding_get_args_functor_ref& get_args, - const node_embedding_get_args_functor_ref& get_exec_args); + NodeStatus GetParsedArgs(int32_t* args_count, + const char** args[], + int32_t* runtime_args_count, + const char** runtime_args[]); node::InitializationResult* init_result() { return init_result_.get(); } node::MultiIsolatePlatform* get_v8_platform() { return v8_platform_.get(); } - static int32_t embedding_api_version() { - return embedding_api_version_ == 0 ? NODE_EMBEDDING_VERSION - : embedding_api_version_; - } - - static int32_t node_api_version() { - return node_api_version_ == 0 ? NAPI_VERSION : node_api_version_; - } + int32_t embedding_api_version() { return embedding_api_version_; } private: static node::ProcessInitializationFlags::Flags GetProcessInitializationFlags( - node_embedding_platform_flags flags); + NodePlatformFlags flags); private: + int32_t embedding_api_version_{0}; bool is_initialized_{false}; bool v8_is_initialized_{false}; bool v8_is_uninitialized_{false}; - node_embedding_platform_flags flags_; + NodePlatformFlags flags_; std::vector args_; struct { bool flags : 1; @@ -273,14 +260,10 @@ class EmbeddedPlatform { std::shared_ptr init_result_; std::unique_ptr v8_platform_; - - static int32_t embedding_api_version_; - static int32_t node_api_version_; + NodeCStringArray<> c_args_; + NodeCStringArray<> c_runtime_args_; }; -int32_t EmbeddedPlatform::embedding_api_version_{}; -int32_t EmbeddedPlatform::node_api_version_{}; - class EmbeddedRuntime { public: explicit EmbeddedRuntime(EmbeddedPlatform* platform); @@ -288,59 +271,82 @@ class EmbeddedRuntime { EmbeddedRuntime(const EmbeddedRuntime&) = delete; EmbeddedRuntime& operator=(const EmbeddedRuntime&) = delete; - static node_embedding_status Run( + static NodeStatus Run( node_embedding_platform platform, - const node_embedding_configure_runtime_functor_ref& configure_runtime); + node_embedding_runtime_configure_callback configure_runtime, + void* configure_runtime_data); - static node_embedding_status Create( + static NodeStatus Create( node_embedding_platform platform, - const node_embedding_configure_runtime_functor_ref& configure_runtime, + node_embedding_runtime_configure_callback configure_runtime, + void* configure_runtime_data, node_embedding_runtime* result); - node_embedding_status DeleteMe(); + NodeStatus DeleteMe(); + + NodeStatus SetNodeApiVersion(int32_t node_api_version); + + NodeStatus SetFlags(NodeRuntimeFlags flags); - node_embedding_status SetFlags(node_embedding_runtime_flags flags); + NodeStatus SetArgs(int32_t argc, + const char* argv[], + int32_t exec_argc, + const char* exec_argv[]); - node_embedding_status SetArgs(int32_t argc, - const char* argv[], - int32_t exec_argc, - const char* exec_argv[]); + NodeStatus OnPreload( + node_embedding_runtime_preload_callback run_preload, + void* preload_data, + node_embedding_data_release_callback release_preload_data); - node_embedding_status OnPreload( - const node_embedding_preload_functor& run_preload); + NodeStatus OnStartExecution( + node_embedding_runtime_loading_callback start_execution, + void* start_execution_data, + node_embedding_data_release_callback release_start_execution_data); - node_embedding_status OnStartExecution( - const node_embedding_start_execution_functor& start_execution, - const node_embedding_handle_result_functor& handle_result); + NodeStatus OnHandleExecutionResult( + node_embedding_runtime_loaded_callback handle_result, + void* handle_result_data, + node_embedding_data_release_callback release_handle_result_data); - node_embedding_status AddModule( + NodeStatus AddModule( const char* module_name, - const node_embedding_initialize_module_functor& init_module, + node_embedding_module_initialize_callback init_module, + void* init_module_data, + node_embedding_data_release_callback release_init_module_data, int32_t module_node_api_version); - node_embedding_status Initialize( - const node_embedding_configure_runtime_functor_ref& configure_runtime); + NodeStatus Initialize( + node_embedding_runtime_configure_callback configure_runtime, + void* configure_runtime_data); - node_embedding_status SetTaskRunner( - const node_embedding_post_task_functor& post_task); + NodeStatus SetTaskRunner( + node_embedding_task_post_callback post_task, + void* post_task_data, + node_embedding_data_release_callback release_post_task_data); - node_embedding_status RunEventLoop( - node_embedding_event_loop_run_mode run_mode, bool* has_more_work); + NodeStatus SetUserData( + void* user_data, node_embedding_data_release_callback release_user_data); - node_embedding_status CompleteEventLoop(); - node_embedding_status TerminateEventLoop(); + NodeStatus GetUserData(void** user_data); - node_embedding_status RunNodeApi( - const node_embedding_run_node_api_functor_ref& run_node_api); + NodeStatus RunEventLoop(); - node_embedding_status OpenNodeApiScope( - node_embedding_node_api_scope* node_api_scope, napi_env* env); - node_embedding_status CloseNodeApiScope( - node_embedding_node_api_scope node_api_scope); + NodeStatus TerminateEventLoop(); + + NodeStatus RunEventLoopOnce(bool* has_more_work); + + NodeStatus RunEventLoopNoWait(bool* has_more_work); + + NodeStatus RunNodeApi(node_embedding_node_api_run_callback run_node_api, + void* run_node_api_data); + + NodeStatus OpenNodeApiScope(node_embedding_node_api_scope* node_api_scope, + napi_env* env); + NodeStatus CloseNodeApiScope(node_embedding_node_api_scope node_api_scope); bool IsNodeApiScopeOpened() const; - static napi_env GetOrCreateNodeApiEnv(node::Environment* node_env, - const std::string& module_filename); + napi_env GetOrCreateNodeApiEnv(node::Environment* node_env, + const std::string& module_filename); size_t OpenV8Scope(); void CloseV8Scope(size_t nest_level); @@ -349,7 +355,7 @@ class EmbeddedRuntime { static void TriggerFatalException(napi_env env, v8::Local local_err); static node::EnvironmentFlags::Flags GetEnvironmentFlags( - node_embedding_runtime_flags flags); + NodeRuntimeFlags flags); void RegisterModules(); @@ -369,20 +375,10 @@ class EmbeddedRuntime { struct ModuleInfo { node_embedding_runtime runtime; std::string module_name; - StdFunction init_module; + NodeInitializeModuleCallback init_module; int32_t module_node_api_version; }; - struct SharedData { - std::mutex mutex; - std::unordered_map node_env_to_node_api_env; - - static SharedData& Get() { - static SharedData shared_data; - return shared_data; - } - }; - struct V8ScopeLocker { explicit V8ScopeLocker(EmbeddedRuntime& runtime) : runtime_(runtime), nest_level_(runtime_.OpenV8Scope()) {} @@ -427,16 +423,32 @@ class EmbeddedRuntime { size_t v8_scope_nest_level_; }; + class ReleaseCallbackDeleter { + public: + explicit ReleaseCallbackDeleter( + node_embedding_data_release_callback release_callback) + : release_callback_(release_callback) {} + + void operator()(void* data) { + if (release_callback_) { + release_callback_(data); + } + } + + private: + node_embedding_data_release_callback release_callback_; + }; + private: EmbeddedPlatform* platform_; bool is_initialized_{false}; - node_embedding_runtime_flags flags_{node_embedding_runtime_flags_default}; + int32_t node_api_version_{NODE_API_DEFAULT_MODULE_API_VERSION}; + NodeRuntimeFlags flags_{NodeRuntimeFlags::kDefault}; std::vector args_; std::vector exec_args_; node::EmbedderPreloadCallback preload_cb_{}; node::StartExecutionCallback start_execution_cb_{}; - StdFunction handle_result_{}; - napi_env node_api_env_{}; + NodeHandleExecutionResultCallback handle_result_{}; struct { bool flags : 1; @@ -449,64 +461,71 @@ class EmbeddedRuntime { std::unique_ptr env_setup_; std::optional v8_scope_data_; - StdFunction post_task_{}; + std::unique_ptr user_data_{ + nullptr, ReleaseCallbackDeleter(nullptr)}; + + NodePostTaskCallback post_task_{}; uv_async_t polling_async_handle_{}; uv_sem_t polling_sem_{}; uv_thread_t polling_thread_{}; bool polling_thread_closed_{false}; +#if defined(__linux) + // Epoll to poll for uv's backend fd. + int epoll_{epoll_create(1)}; +#endif SmallTrivialStack node_api_scope_data_{}; + + // The node API associated with the runtime's environment. + napi_env node_api_env_{}; + + // Map from worker thread node::Environment* to napi_env. + std::mutex worker_env_mutex_; + std::unordered_map worker_env_to_node_api_; }; //----------------------------------------------------------------------------- // EmbeddedErrorHandling implementation. //----------------------------------------------------------------------------- -node_embedding_status EmbeddedErrorHandling::SetErrorHandler( - const node_embedding_handle_error_functor& error_handler) { - ErrorHandler() = AsStdFunction(error_handler); - return node_embedding_status_ok; -} - -node_embedding_status EmbeddedErrorHandling::HandleError( - const std::string& message, node_embedding_status status) { - const char* message_c_str = message.c_str(); - return ErrorHandler()(&message_c_str, 1, status); +/*static*/ NodeStatus EmbeddedErrorHandling::HandleError( + NodeStatus status, std::string_view message) { + if (status == NodeStatus::kOk) { + ClearLastErrorMessage(); + } else { + SetLastErrorMessage(message.data()); + } + return status; } -node_embedding_status EmbeddedErrorHandling::HandleError( - const std::vector& messages, node_embedding_status status) { - CStringArray message_arr(messages); - return ErrorHandler()(message_arr.c_strs(), message_arr.size(), status); +/*static*/ NodeStatus EmbeddedErrorHandling::HandleError( + NodeStatus status, const std::vector& messages) { + if (status == NodeStatus::kOk) { + ClearLastErrorMessage(); + } else { + NodeErrorInfo::SetLastErrorMessage(messages); + } + return status; } -node_embedding_status EmbeddedErrorHandling::HandleError( - const char* message, - const char* filename, - int32_t line, - node_embedding_status status) { +/*static*/ NodeStatus EmbeddedErrorHandling::HandleError( + NodeStatus status, + std::string_view message, + std::string_view filename, + int32_t line) { return HandleError( - FormatString("Error: %s at %s:%d", message, filename, line), status); -} - -node_embedding_status EmbeddedErrorHandling::DefaultErrorHandler( - void* /*handler_data*/, - const char* messages[], - size_t messages_size, - node_embedding_status status) { - FILE* stream = status != node_embedding_status_ok ? stderr : stdout; - for (size_t i = 0; i < messages_size; ++i) { - fprintf(stream, "%s\n", messages[i]); - } - fflush(stream); - return status; + status, + FormatString( + "Error: %s at %s:%d", message.data(), filename.data(), line)); } -std::string EmbeddedErrorHandling::FormatString(const char* format, ...) { +/*static*/ std::string EmbeddedErrorHandling::FormatString(const char* format, + ...) { va_list args1; va_start(args1, format); - va_list args2; - va_copy(args2, args1); // Required for some compilers like GCC. + va_list args2; // Required for some compilers like GCC since we go over the + // args twice. + va_copy(args2, args1); std::string result(std::vsnprintf(nullptr, 0, format, args1), '\0'); va_end(args1); std::vsnprintf(&result[0], result.size() + 1, format, args2); @@ -514,61 +533,101 @@ std::string EmbeddedErrorHandling::FormatString(const char* format, ...) { return result; } -StdFunction& -EmbeddedErrorHandling::ErrorHandler() { - static StdFunction error_handler = - AsStdFunction(node_embedding_handle_error_functor{ - nullptr, &DefaultErrorHandler, nullptr}); - return error_handler; +/*static*/ const char* EmbeddedErrorHandling::GetLastErrorMessage() { + return DoErrorMessage(ErrorMessageAction::kGet, ""); } -//----------------------------------------------------------------------------- -// EmbeddedPlatform implementation. -//----------------------------------------------------------------------------- +/*static*/ void EmbeddedErrorHandling::SetLastErrorMessage( + std::string message) { + DoErrorMessage(ErrorMessageAction::kSet, std::move(message)); +} -/*static*/ node_embedding_status EmbeddedPlatform::SetApiVersion( - int32_t embedding_api_version, int32_t node_api_version) { - ASSERT_ARG(embedding_api_version, - embedding_api_version > 0 && - embedding_api_version <= NODE_EMBEDDING_VERSION); - ASSERT_ARG(node_api_version, - node_api_version >= NODE_API_DEFAULT_MODULE_API_VERSION && - (node_api_version <= NAPI_VERSION || - node_api_version == NAPI_VERSION_EXPERIMENTAL)); +/*static*/ void EmbeddedErrorHandling::ClearLastErrorMessage() { + DoErrorMessage(ErrorMessageAction::kClear, ""); +} - embedding_api_version_ = embedding_api_version; - node_api_version_ = node_api_version; +/*static*/ const char* EmbeddedErrorHandling::DoErrorMessage( + EmbeddedErrorHandling::ErrorMessageAction action, std::string message) { + static thread_local const char* thread_message_ptr = nullptr; + static std::unordered_map> + thread_to_message; + static std::mutex mutex; - return node_embedding_status_ok; + switch (action) { + case ErrorMessageAction::kGet: + break; // Just return the message. + case ErrorMessageAction::kSet: { + auto message_ptr = std::make_unique(std::move(message)); + thread_message_ptr = message_ptr->c_str(); + std::scoped_lock lock(mutex); + thread_to_message[std::this_thread::get_id()] = std::move(message_ptr); + break; + } + case ErrorMessageAction::kClear: { + if (thread_message_ptr != nullptr) { + std::scoped_lock lock(mutex); + thread_to_message.erase(std::this_thread::get_id()); + thread_message_ptr = nullptr; + } + break; + } + } + + return thread_message_ptr != nullptr ? thread_message_ptr : ""; } -node_embedding_status EmbeddedPlatform::RunMain( +/*static*/ NodeStatus EmbeddedErrorHandling::ExitCodeToStatus( + int32_t exit_code) { + return exit_code != 0 + ? static_cast( + static_cast(NodeStatus::kErrorExitCode) | exit_code) + : NodeStatus::kOk; +} + +//----------------------------------------------------------------------------- +// EmbeddedPlatform implementation. +//----------------------------------------------------------------------------- + +NodeStatus EmbeddedPlatform::RunMain( + int32_t embedding_api_version, int32_t argc, - char* argv[], - const node_embedding_configure_platform_functor_ref& configure_platform, - const node_embedding_configure_runtime_functor_ref& configure_runtime) { + const char* argv[], + node_embedding_platform_configure_callback configure_platform, + void* configure_platform_data, + node_embedding_runtime_configure_callback configure_runtime, + void* configure_runtime_data) { node_embedding_platform platform{}; - CHECK_STATUS( - EmbeddedPlatform::Create(argc, argv, configure_platform, &platform)); + CHECK_STATUS(EmbeddedPlatform::Create(embedding_api_version, + argc, + argv, + configure_platform, + configure_platform_data, + &platform)); if (platform == nullptr) { - return node_embedding_status_ok; // early return + return NodeStatus::kOk; // early return } - return EmbeddedRuntime::Run(platform, configure_runtime); + return EmbeddedRuntime::Run( + platform, configure_runtime, configure_runtime_data); } -/*static*/ node_embedding_status EmbeddedPlatform::Create( +/*static*/ NodeStatus EmbeddedPlatform::Create( + int32_t embedding_api_version, int32_t argc, - char* argv[], - const node_embedding_configure_platform_functor_ref& configure_platform, + const char* argv[], + node_embedding_platform_configure_callback configure_platform, + void* configure_platform_data, node_embedding_platform* result) { - CHECK_ARG_NOT_NULL(result); + ASSERT_ARG_NOT_NULL(result); // Hack around with the argv pointer. Used for process.title = "blah". - argv = uv_setup_args(argc, argv); + argv = + const_cast(uv_setup_args(argc, const_cast(argv))); auto platform_ptr = std::make_unique(argc, argv); + platform_ptr->SetApiVersion(embedding_api_version); bool early_return = false; - CHECK_STATUS(platform_ptr->Initialize(configure_platform, &early_return)); + CHECK_STATUS(platform_ptr->Initialize( + configure_platform, configure_platform_data, &early_return)); if (early_return) { return platform_ptr.release()->DeleteMe(); } @@ -576,10 +635,10 @@ node_embedding_status EmbeddedPlatform::RunMain( // The initialization was successful, the caller is responsible for deleting // the platform instance. *result = reinterpret_cast(platform_ptr.release()); - return node_embedding_status_ok; + return NodeStatus::kOk; } -node_embedding_status EmbeddedPlatform::DeleteMe() { +NodeStatus EmbeddedPlatform::DeleteMe() { if (v8_is_initialized_ && !v8_is_uninitialized_) { v8_is_uninitialized_ = true; v8::V8::Dispose(); @@ -588,51 +647,58 @@ node_embedding_status EmbeddedPlatform::DeleteMe() { } delete this; - return node_embedding_status_ok; + return NodeStatus::kOk; } -node_embedding_status EmbeddedPlatform::SetFlags( - node_embedding_platform_flags flags) { - ASSERT(!is_initialized_); +NodeStatus EmbeddedPlatform::SetApiVersion(int32_t embedding_api_version) { + ASSERT_ARG(embedding_api_version, + embedding_api_version > 0 && + embedding_api_version <= NODE_EMBEDDING_VERSION); + return NodeStatus::kOk; +} + +NodeStatus EmbeddedPlatform::SetFlags(NodePlatformFlags flags) { + ASSERT_EXPR(!is_initialized_); flags_ = flags; optional_bits_.flags = true; - return node_embedding_status_ok; + return NodeStatus::kOk; } -node_embedding_status EmbeddedPlatform::Initialize( - const node_embedding_configure_platform_functor_ref& configure_platform, +NodeStatus EmbeddedPlatform::Initialize( + node_embedding_platform_configure_callback configure_platform, + void* configure_platform_data, bool* early_return) { - ASSERT(!is_initialized_); + ASSERT_EXPR(!is_initialized_); node_embedding_platform_config platform_config = reinterpret_cast(this); - if (configure_platform.invoke != nullptr) { - CHECK_STATUS( - configure_platform.invoke(configure_platform.data, platform_config)); + if (configure_platform != nullptr) { + CHECK_STATUS(configure_platform(configure_platform_data, platform_config)); } is_initialized_ = true; if (!optional_bits_.flags) { - flags_ = node_embedding_platform_flags_none; + flags_ = NodePlatformFlags::kNone; } init_result_ = node::InitializeOncePerProcess( args_, GetProcessInitializationFlags(flags_)); int32_t exit_code = init_result_->exit_code(); - if (exit_code != 0 || !init_result_->errors().empty()) { - CHECK_STATUS(EmbeddedErrorHandling::HandleError( - init_result_->errors(), - exit_code != 0 ? static_cast( - node_embedding_status_error_exit_code + exit_code) - : node_embedding_status_ok)); + if (exit_code != 0) { + NodeErrorInfo::SetLastErrorMessage(init_result_->errors()); + return EmbeddedErrorHandling::ExitCodeToStatus(exit_code); } if (init_result_->early_return()) { *early_return = true; - return node_embedding_status_ok; + NodeErrorInfo::SetLastErrorMessage(init_result_->errors()); + return NodeStatus::kOk; } + c_args_ = NodeCStringArray<>(init_result_->args()); + c_runtime_args_ = NodeCStringArray<>(init_result_->exec_args()); + int32_t thread_pool_size = static_cast(node::per_process::cli_options->v8_thread_pool_size); v8_platform_ = node::MultiIsolatePlatform::Create(thread_pool_size); @@ -641,71 +707,73 @@ node_embedding_status EmbeddedPlatform::Initialize( v8_is_initialized_ = true; - return node_embedding_status_ok; + return NodeStatus::kOk; } -node_embedding_status EmbeddedPlatform::GetParsedArgs( - const node_embedding_get_args_functor_ref& get_args, - const node_embedding_get_args_functor_ref& get_exec_args) { - ASSERT(is_initialized_); +NodeStatus EmbeddedPlatform::GetParsedArgs(int32_t* args_count, + const char** args[], + int32_t* runtime_args_count, + const char** runtime_args[]) { + ASSERT_EXPR(is_initialized_); - if (get_args.invoke != nullptr) { - node::CStringArray args(init_result_->args()); - get_args.invoke(get_args.data, args.argc(), args.argv()); + if (args_count != nullptr) { + *args_count = c_args_.size(); } - - if (get_exec_args.invoke != nullptr) { - node::CStringArray exec_args(init_result_->exec_args()); - get_exec_args.invoke( - get_exec_args.data, exec_args.argc(), exec_args.argv()); + if (args != nullptr) { + *args = c_args_.c_strs(); } - - return node_embedding_status_ok; + if (runtime_args_count != nullptr) { + *runtime_args_count = c_runtime_args_.size(); + } + if (runtime_args != nullptr) { + *runtime_args = c_runtime_args_.c_strs(); + } + return NodeStatus::kOk; } node::ProcessInitializationFlags::Flags -EmbeddedPlatform::GetProcessInitializationFlags( - node_embedding_platform_flags flags) { +EmbeddedPlatform::GetProcessInitializationFlags(NodePlatformFlags flags) { uint32_t result = node::ProcessInitializationFlags::kNoFlags; - if ((flags & node_embedding_platform_flags_enable_stdio_inheritance) != 0) { + if (embedding::IsFlagSet(flags, NodePlatformFlags::kEnableStdioInheritance)) { result |= node::ProcessInitializationFlags::kEnableStdioInheritance; } - if ((flags & node_embedding_platform_flags_disable_node_options_env) != 0) { + if (embedding::IsFlagSet(flags, NodePlatformFlags::kDisableNodeOptionsEnv)) { result |= node::ProcessInitializationFlags::kDisableNodeOptionsEnv; } - if ((flags & node_embedding_platform_flags_disable_cli_options) != 0) { + if (embedding::IsFlagSet(flags, NodePlatformFlags::kDisableCliOptions)) { result |= node::ProcessInitializationFlags::kDisableCLIOptions; } - if ((flags & node_embedding_platform_flags_no_icu) != 0) { + if (embedding::IsFlagSet(flags, NodePlatformFlags::kNoICU)) { result |= node::ProcessInitializationFlags::kNoICU; } - if ((flags & node_embedding_platform_flags_no_stdio_initialization) != 0) { + if (embedding::IsFlagSet(flags, NodePlatformFlags::kNoStdioInitialization)) { result |= node::ProcessInitializationFlags::kNoStdioInitialization; } - if ((flags & node_embedding_platform_flags_no_default_signal_handling) != 0) { + if (embedding::IsFlagSet(flags, + NodePlatformFlags::kNoDefaultSignalHandling)) { result |= node::ProcessInitializationFlags::kNoDefaultSignalHandling; } result |= node::ProcessInitializationFlags::kNoInitializeV8; result |= node::ProcessInitializationFlags::kNoInitializeNodeV8Platform; - if ((flags & node_embedding_platform_flags_no_init_openssl) != 0) { + if (embedding::IsFlagSet(flags, NodePlatformFlags::kNoInitOpenSSL)) { result |= node::ProcessInitializationFlags::kNoInitOpenSSL; } - if ((flags & node_embedding_platform_flags_no_parse_global_debug_variables) != - 0) { + if (embedding::IsFlagSet(flags, + NodePlatformFlags::kNoParseGlobalDebugVariables)) { result |= node::ProcessInitializationFlags::kNoParseGlobalDebugVariables; } - if ((flags & node_embedding_platform_flags_no_adjust_resource_limits) != 0) { + if (embedding::IsFlagSet(flags, NodePlatformFlags::kNoAdjustResourceLimits)) { result |= node::ProcessInitializationFlags::kNoAdjustResourceLimits; } - if ((flags & node_embedding_platform_flags_no_use_large_pages) != 0) { + if (embedding::IsFlagSet(flags, NodePlatformFlags::kNoUseLargePages)) { result |= node::ProcessInitializationFlags::kNoUseLargePages; } - if ((flags & node_embedding_platform_flags_no_print_help_or_version_output) != - 0) { + if (embedding::IsFlagSet(flags, + NodePlatformFlags::kNoPrintHelpOrVersionOutput)) { result |= node::ProcessInitializationFlags::kNoPrintHelpOrVersionOutput; } - if ((flags & node_embedding_platform_flags_generate_predictable_snapshot) != - 0) { + if (embedding::IsFlagSet(flags, + NodePlatformFlags::kGeneratePredictableSnapshot)) { result |= node::ProcessInitializationFlags::kGeneratePredictableSnapshot; } return static_cast(result); @@ -715,40 +783,44 @@ EmbeddedPlatform::GetProcessInitializationFlags( // EmbeddedRuntime implementation. //----------------------------------------------------------------------------- -/*static*/ node_embedding_status EmbeddedRuntime::Run( +/*static*/ NodeStatus EmbeddedRuntime::Run( node_embedding_platform platform, - const node_embedding_configure_runtime_functor_ref& configure_runtime) { + node_embedding_runtime_configure_callback configure_runtime, + void* configure_runtime_data) { node_embedding_runtime runtime{}; - CHECK_STATUS(Create(platform, configure_runtime, &runtime)); - CHECK_STATUS(node_embedding_complete_event_loop(runtime)); - CHECK_STATUS(node_embedding_delete_runtime(runtime)); - return node_embedding_status_ok; + CHECK_STATUS( + Create(platform, configure_runtime, configure_runtime_data, &runtime)); + CHECK_STATUS(node_embedding_runtime_event_loop_run(runtime)); + CHECK_STATUS(node_embedding_runtime_delete(runtime)); + return NodeStatus::kOk; } -/*static*/ node_embedding_status EmbeddedRuntime::Create( +/*static*/ NodeStatus EmbeddedRuntime::Create( node_embedding_platform platform, - const node_embedding_configure_runtime_functor_ref& configure_runtime, + node_embedding_runtime_configure_callback configure_runtime, + void* configure_runtime_data, node_embedding_runtime* result) { - CHECK_ARG_NOT_NULL(platform); - CHECK_ARG_NOT_NULL(result); + ASSERT_ARG_NOT_NULL(platform); + ASSERT_ARG_NOT_NULL(result); EmbeddedPlatform* platform_ptr = reinterpret_cast(platform); std::unique_ptr runtime_ptr = std::make_unique(platform_ptr); - CHECK_STATUS(runtime_ptr->Initialize(configure_runtime)); + CHECK_STATUS( + runtime_ptr->Initialize(configure_runtime, configure_runtime_data)); *result = reinterpret_cast(runtime_ptr.release()); - return node_embedding_status_ok; + return NodeStatus::kOk; } EmbeddedRuntime::EmbeddedRuntime(EmbeddedPlatform* platform) : platform_(platform) {} -node_embedding_status EmbeddedRuntime::DeleteMe() { - ASSERT(!IsNodeApiScopeOpened()); +NodeStatus EmbeddedRuntime::DeleteMe() { + ASSERT_EXPR(!IsNodeApiScopeOpened()); std::unique_ptr env_setup = std::move(env_setup_); @@ -757,22 +829,32 @@ node_embedding_status EmbeddedRuntime::DeleteMe() { } delete this; - return node_embedding_status_ok; + return NodeStatus::kOk; } -node_embedding_status EmbeddedRuntime::SetFlags( - node_embedding_runtime_flags flags) { - ASSERT(!is_initialized_); +NodeStatus EmbeddedRuntime::SetNodeApiVersion(int32_t node_api_version) { + ASSERT_ARG(node_api_version, + node_api_version >= NODE_API_DEFAULT_MODULE_API_VERSION && + (node_api_version <= NAPI_VERSION || + node_api_version == NAPI_VERSION_EXPERIMENTAL)); + + node_api_version_ = node_api_version; + + return NodeStatus::kOk; +} + +NodeStatus EmbeddedRuntime::SetFlags(NodeRuntimeFlags flags) { + ASSERT_EXPR(!is_initialized_); flags_ = flags; optional_bits_.flags = true; - return node_embedding_status_ok; + return NodeStatus::kOk; } -node_embedding_status EmbeddedRuntime::SetArgs(int32_t argc, - const char* argv[], - int32_t exec_argc, - const char* exec_argv[]) { - ASSERT(!is_initialized_); +NodeStatus EmbeddedRuntime::SetArgs(int32_t argc, + const char* argv[], + int32_t exec_argc, + const char* exec_argv[]) { + ASSERT_EXPR(!is_initialized_); if (argv != nullptr) { args_.assign(argv, argv + argc); optional_bits_.args = true; @@ -781,17 +863,20 @@ node_embedding_status EmbeddedRuntime::SetArgs(int32_t argc, exec_args_.assign(exec_argv, exec_argv + exec_argc); optional_bits_.exec_args = true; } - return node_embedding_status_ok; + return NodeStatus::kOk; } -node_embedding_status EmbeddedRuntime::OnPreload( - const node_embedding_preload_functor& run_preload) { - ASSERT(!is_initialized_); +NodeStatus EmbeddedRuntime::OnPreload( + node_embedding_runtime_preload_callback run_preload, + void* preload_data, + node_embedding_data_release_callback release_preload_data) { + ASSERT_EXPR(!is_initialized_); - if (run_preload.invoke != nullptr) { + if (run_preload != nullptr) { preload_cb_ = node::EmbedderPreloadCallback( - [runtime = reinterpret_cast(this), - run_preload_ptr = MakeSharedFunctorPtr(run_preload)]( + [this, + run_preload_ptr = MakeSharedFunctorPtr( + run_preload, preload_data, release_preload_data)]( node::Environment* node_env, v8::Local process, v8::Local require) { @@ -802,11 +887,11 @@ node_embedding_status EmbeddedRuntime::OnPreload( v8impl::JsValueFromV8LocalValue(process); napi_value require_value = v8impl::JsValueFromV8LocalValue(require); - run_preload_ptr->invoke(run_preload_ptr->data, - runtime, - env, - process_value, - require_value); + (*run_preload_ptr)( + reinterpret_cast(this), + env, + process_value, + require_value); }, TriggerFatalException); }); @@ -814,17 +899,22 @@ node_embedding_status EmbeddedRuntime::OnPreload( preload_cb_ = {}; } - return node_embedding_status_ok; + return NodeStatus::kOk; } -node_embedding_status EmbeddedRuntime::OnStartExecution( - const node_embedding_start_execution_functor& start_execution, - const node_embedding_handle_result_functor& handle_result) { - ASSERT(!is_initialized_); +NodeStatus EmbeddedRuntime::OnStartExecution( + node_embedding_runtime_loading_callback start_execution, + void* start_execution_data, + node_embedding_data_release_callback release_start_execution_data) { + ASSERT_EXPR(!is_initialized_); - if (start_execution.invoke != nullptr) { + if (start_execution != nullptr) { start_execution_cb_ = node::StartExecutionCallback( - [this, start_execution_ptr = MakeSharedFunctorPtr(start_execution)]( + [this, + start_execution_ptr = + MakeSharedFunctorPtr(start_execution, + start_execution_data, + release_start_execution_data)]( const node::StartExecutionCallbackInfo& info) -> v8::MaybeLocal { napi_value result{}; @@ -836,8 +926,7 @@ node_embedding_status EmbeddedRuntime::OnStartExecution( v8impl::JsValueFromV8LocalValue(info.native_require); napi_value run_cjs_value = v8impl::JsValueFromV8LocalValue(info.run_cjs); - result = start_execution_ptr->invoke( - start_execution_ptr->data, + result = (*start_execution_ptr)( reinterpret_cast(this), env, process_value, @@ -855,42 +944,56 @@ node_embedding_status EmbeddedRuntime::OnStartExecution( start_execution_cb_ = {}; } - handle_result_ = AsStdFunction(handle_result); + return NodeStatus::kOk; +} + +NodeStatus EmbeddedRuntime::OnHandleExecutionResult( + node_embedding_runtime_loaded_callback handle_result, + void* handle_result_data, + node_embedding_data_release_callback release_handle_result_data) { + ASSERT_EXPR(!is_initialized_); - return node_embedding_status_ok; + handle_result_ = NodeHandleExecutionResultCallback( + handle_result, handle_result_data, release_handle_result_data); + + return NodeStatus::kOk; } -node_embedding_status EmbeddedRuntime::AddModule( +NodeStatus EmbeddedRuntime::AddModule( const char* module_name, - const node_embedding_initialize_module_functor& init_module, + node_embedding_module_initialize_callback init_module, + void* init_module_data, + node_embedding_data_release_callback release_init_module_data, int32_t module_node_api_version) { - CHECK_ARG_NOT_NULL(module_name); - CHECK_ARG_NOT_NULL(init_module.invoke); - ASSERT(!is_initialized_); - - auto insert_result = - modules_.try_emplace(module_name, - reinterpret_cast(this), - module_name, - AsStdFunction(init_module), - module_node_api_version); + ASSERT_ARG_NOT_NULL(module_name); + ASSERT_ARG_NOT_NULL(init_module); + ASSERT_EXPR(!is_initialized_); + + auto insert_result = modules_.try_emplace( + module_name, + ModuleInfo{reinterpret_cast(this), + module_name, + NodeInitializeModuleCallback{ + init_module, init_module_data, release_init_module_data}, + module_node_api_version}); if (!insert_result.second) { return EmbeddedErrorHandling::HandleError( + NodeStatus::kBadArg, EmbeddedErrorHandling::FormatString( - "Module with name '%s' is already added.", module_name), - node_embedding_status_bad_arg); + "Module with name '%s' is already added.", module_name)); } - return node_embedding_status_ok; + return NodeStatus::kOk; } -node_embedding_status EmbeddedRuntime::Initialize( - const node_embedding_configure_runtime_functor_ref& configure_runtime) { - ASSERT(!is_initialized_); +NodeStatus EmbeddedRuntime::Initialize( + node_embedding_runtime_configure_callback configure_runtime, + void* configure_runtime_data) { + ASSERT_EXPR(!is_initialized_); - if (configure_runtime.invoke != nullptr) { - CHECK_STATUS(configure_runtime.invoke( - configure_runtime.data, + if (configure_runtime != nullptr) { + CHECK_STATUS(configure_runtime( + configure_runtime_data, reinterpret_cast(platform_), reinterpret_cast(this))); } @@ -898,7 +1001,7 @@ node_embedding_status EmbeddedRuntime::Initialize( is_initialized_ = true; node::EnvironmentFlags::Flags flags = GetEnvironmentFlags( - optional_bits_.flags ? flags_ : node_embedding_runtime_flags_default); + optional_bits_.flags ? flags_ : NodeRuntimeFlags::kDefault); const std::vector& args = optional_bits_.args ? args_ : platform_->init_result()->args(); @@ -914,14 +1017,15 @@ node_embedding_status EmbeddedRuntime::Initialize( v8_platform, &errors, args, exec_args, flags); if (env_setup_ == nullptr || !errors.empty()) { - return EmbeddedErrorHandling::HandleError( - errors, node_embedding_status_generic_error); + return EmbeddedErrorHandling::HandleError(NodeStatus::kGenericError, + errors); } V8ScopeLocker v8_scope_locker(*this); std::string filename = args_.size() > 1 ? args_[1] : ""; - node_api_env_ = GetOrCreateNodeApiEnv(env_setup_->env(), filename); + node_api_env_ = + v8impl::NewEnv(env_setup_->env()->context(), filename, node_api_version_); node::Environment* node_env = env_setup_->env(); @@ -930,9 +1034,10 @@ node_embedding_status EmbeddedRuntime::Initialize( v8::MaybeLocal ret = node::LoadEnvironment(node_env, start_execution_cb_, preload_cb_); - if (ret.IsEmpty()) - return EmbeddedErrorHandling::HandleError( - "Failed to load environment", node_embedding_status_generic_error); + if (ret.IsEmpty()) { + return EmbeddedErrorHandling::HandleError(NodeStatus::kGenericError, + "Failed to load environment"); + } if (handle_result_) { node_api_env_->CallIntoModule( @@ -947,7 +1052,21 @@ node_embedding_status EmbeddedRuntime::Initialize( InitializePollingThread(); WakeupPollingThread(); - return node_embedding_status_ok; + return NodeStatus::kOk; +} + +NodeStatus EmbeddedRuntime::SetUserData( + void* user_data, node_embedding_data_release_callback release_user_data) { + user_data_.release(); // Do not call the release callback on set + user_data_ = std::unique_ptr( + user_data, ReleaseCallbackDeleter(release_user_data)); + return NodeStatus::kOk; +} + +NodeStatus EmbeddedRuntime::GetUserData(void** user_data) { + ASSERT_ARG_NOT_NULL(user_data); + *user_data = user_data_.get(); + return NodeStatus::kOk; } uv_loop_t* EmbeddedRuntime::EventLoop() { @@ -955,7 +1074,7 @@ uv_loop_t* EmbeddedRuntime::EventLoop() { } void EmbeddedRuntime::InitializePollingThread() { - if (post_task_ == nullptr) return; + if (!post_task_) return; uv_loop_t* event_loop = EventLoop(); @@ -986,7 +1105,7 @@ void EmbeddedRuntime::InitializePollingThread() { #elif defined(__linux) int backend_fd = uv_backend_fd(event_loop); - struct epoll_event ev = {0}; + struct epoll_event ev = {}; ev.events = EPOLLIN; ev.data.fd = backend_fd; epoll_ctl(epoll_, EPOLL_CTL_ADD, backend_fd, &ev); @@ -1007,7 +1126,7 @@ void EmbeddedRuntime::InitializePollingThread() { } void EmbeddedRuntime::DestroyPollingThread() { - if (post_task_ == nullptr) return; + if (!post_task_) return; if (polling_thread_closed_) return; polling_thread_closed_ = true; @@ -1023,7 +1142,7 @@ void EmbeddedRuntime::DestroyPollingThread() { } void EmbeddedRuntime::WakeupPollingThread() { - if (post_task_ == nullptr) return; + if (!post_task_) return; if (polling_thread_closed_) return; uv_sem_post(&polling_sem_); @@ -1041,11 +1160,22 @@ void EmbeddedRuntime::RunPollingThread(void* data) { if (runtime->polling_thread_closed_) break; // Deal with event in the task runner thread. - runtime->post_task_( - node::AsFunctor([runtime]() { - runtime->RunEventLoop(node_embedding_event_loop_run_mode_nowait, - nullptr); - })); + bool succeeded = false; + NodeStatus post_result = runtime->post_task_( + [](void* task_data) { + auto* runtime = static_cast(task_data); + return runtime->RunEventLoopNoWait(nullptr); + }, + runtime, + nullptr, + &succeeded); + + // TODO: Handle post_result + (void)post_result; + if (!succeeded) { + // The task runner is shutting down. + break; + } } } @@ -1107,29 +1237,52 @@ void EmbeddedRuntime::PollEvents() { #endif } -node_embedding_status EmbeddedRuntime::SetTaskRunner( - const node_embedding_post_task_functor& post_task) { - ASSERT(!is_initialized_); - post_task_ = AsStdFunction(post_task); - return node_embedding_status_ok; +NodeStatus EmbeddedRuntime::SetTaskRunner( + node_embedding_task_post_callback post_task, + void* post_task_data, + node_embedding_data_release_callback release_post_task_data) { + ASSERT_EXPR(!is_initialized_); + post_task_ = + NodePostTaskCallback{post_task, post_task_data, release_post_task_data}; + return NodeStatus::kOk; +} + +NodeStatus EmbeddedRuntime::RunEventLoop() { + ASSERT_EXPR(is_initialized_); + + V8ScopeLocker v8_scope_locker(*this); + + DestroyPollingThread(); + + int32_t exit_code = node::SpinEventLoop(env_setup_->env()).FromMaybe(1); + return EmbeddedErrorHandling::HandleError( + EmbeddedErrorHandling::ExitCodeToStatus(exit_code), + "Failed while closing the runtime"); } -node_embedding_status EmbeddedRuntime::RunEventLoop( - node_embedding_event_loop_run_mode run_mode, bool* has_more_work) { - ASSERT(is_initialized_); +NodeStatus EmbeddedRuntime::TerminateEventLoop() { + ASSERT_EXPR(is_initialized_); + + V8ScopeLocker v8_scope_locker(*this); + int32_t exit_code = node::Stop(env_setup_->env(), node::StopFlags::kNoFlags); + return EmbeddedErrorHandling::HandleError( + EmbeddedErrorHandling::ExitCodeToStatus(exit_code), + "Failed while stopping the runtime"); +} + +NodeStatus EmbeddedRuntime::RunEventLoopOnce(bool* has_more_work) { + ASSERT_EXPR(is_initialized_); V8ScopeLocker v8_scope_locker(*this); node::ExitCode exit_code = - node::SpinEventLoopWithoutCleanup(env_setup_->env(), - static_cast(run_mode)) + node::SpinEventLoopWithoutCleanup(env_setup_->env(), UV_RUN_ONCE) .FromMaybe(node::ExitCode::kGenericUserError); if (exit_code != node::ExitCode::kNoFailure) { return EmbeddedErrorHandling::HandleError( - "Failed running the event loop", - static_cast( - node_embedding_status_error_exit_code + - static_cast(exit_code))); + EmbeddedErrorHandling::ExitCodeToStatus( + static_cast(exit_code)), + "Failed running the event loop"); } if (has_more_work != nullptr) { @@ -1138,39 +1291,31 @@ node_embedding_status EmbeddedRuntime::RunEventLoop( WakeupPollingThread(); - return node_embedding_status_ok; + return NodeStatus::kOk; } -node_embedding_status EmbeddedRuntime::CompleteEventLoop() { - ASSERT(is_initialized_); +NodeStatus EmbeddedRuntime::RunEventLoopNoWait(bool* has_more_work) { + ASSERT_EXPR(is_initialized_); V8ScopeLocker v8_scope_locker(*this); - DestroyPollingThread(); - - int32_t exit_code = node::SpinEventLoop(env_setup_->env()).FromMaybe(1); - if (exit_code != 0) { + node::ExitCode exit_code = + node::SpinEventLoopWithoutCleanup(env_setup_->env(), UV_RUN_ONCE) + .FromMaybe(node::ExitCode::kGenericUserError); + if (exit_code != node::ExitCode::kNoFailure) { return EmbeddedErrorHandling::HandleError( - "Failed while closing the runtime", - static_cast( - node_embedding_status_error_exit_code + exit_code)); + EmbeddedErrorHandling::ExitCodeToStatus( + static_cast(exit_code)), + "Failed running the event loop"); } - return node_embedding_status_ok; -} + if (has_more_work != nullptr) { + *has_more_work = uv_loop_alive(env_setup_->env()->event_loop()); + } -node_embedding_status EmbeddedRuntime::TerminateEventLoop() { - ASSERT(is_initialized_); + WakeupPollingThread(); - V8ScopeLocker v8_scope_locker(*this); - int32_t exit_code = node::Stop(env_setup_->env(), node::StopFlags::kNoFlags); - if (exit_code != 0) { - return EmbeddedErrorHandling::HandleError( - "Failed while stopping the runtime", - static_cast( - node_embedding_status_error_exit_code + exit_code)); - } - return node_embedding_status_ok; + return NodeStatus::kOk; } /*static*/ void EmbeddedRuntime::TriggerFatalException( @@ -1203,9 +1348,10 @@ void EmbeddedRuntime::CloseV8Scope(size_t nest_level) { } } -node_embedding_status EmbeddedRuntime::RunNodeApi( - const node_embedding_run_node_api_functor_ref& run_node_api) { - CHECK_ARG_NOT_NULL(run_node_api.invoke); +NodeStatus EmbeddedRuntime::RunNodeApi( + node_embedding_node_api_run_callback run_node_api, + void* run_node_api_data) { + ASSERT_ARG_NOT_NULL(run_node_api); node_embedding_node_api_scope node_api_scope{}; napi_env env{}; @@ -1213,16 +1359,15 @@ node_embedding_status EmbeddedRuntime::RunNodeApi( auto nodeApiScopeLeave = node::OnScopeLeave([&]() { CloseNodeApiScope(node_api_scope); }); - run_node_api.invoke( - run_node_api.data, reinterpret_cast(this), env); + run_node_api(run_node_api_data, env); - return node_embedding_status_ok; + return NodeStatus::kOk; } -node_embedding_status EmbeddedRuntime::OpenNodeApiScope( +NodeStatus EmbeddedRuntime::OpenNodeApiScope( node_embedding_node_api_scope* node_api_scope, napi_env* env) { - CHECK_ARG_NOT_NULL(node_api_scope); - CHECK_ARG_NOT_NULL(env); + ASSERT_ARG_NOT_NULL(node_api_scope); + ASSERT_ARG_NOT_NULL(env); size_t v8_scope_nest_level = OpenV8Scope(); node_api_scope_data_.Push( @@ -1231,10 +1376,10 @@ node_embedding_status EmbeddedRuntime::OpenNodeApiScope( *node_api_scope = reinterpret_cast( node_api_scope_data_.size()); *env = node_api_env_; - return node_embedding_status_ok; + return NodeStatus::kOk; } -node_embedding_status EmbeddedRuntime::CloseNodeApiScope( +NodeStatus EmbeddedRuntime::CloseNodeApiScope( node_embedding_node_api_scope node_api_scope) { CHECK_EQ(node_api_scope_data_.size(), reinterpret_cast(node_api_scope)); @@ -1245,7 +1390,7 @@ node_embedding_status EmbeddedRuntime::CloseNodeApiScope( node_api_scope_data_.Pop(); CloseV8Scope(v8_scope_nest_level); - return node_embedding_status_ok; + return NodeStatus::kOk; } bool EmbeddedRuntime::IsNodeApiScopeOpened() const { @@ -1254,68 +1399,89 @@ bool EmbeddedRuntime::IsNodeApiScopeOpened() const { napi_env EmbeddedRuntime::GetOrCreateNodeApiEnv( node::Environment* node_env, const std::string& module_filename) { - SharedData& shared_data = SharedData::Get(); + // Check if this is the main environment associated with the runtime. + if (node_env == env_setup_->env()) { + return node_api_env_; + } { - std::scoped_lock lock(shared_data.mutex); - auto it = shared_data.node_env_to_node_api_env.find(node_env); - if (it != shared_data.node_env_to_node_api_env.end()) return it->second; + // Return Node-API env if it already exists. + std::scoped_lock lock(worker_env_mutex_); + auto it = worker_env_to_node_api_.find(node_env); + if (it != worker_env_to_node_api_.end()) { + return it->second; + } } - // Avoid creating the environment under the lock. - napi_env env = v8impl::NewEnv(node_env->context(), - module_filename, - EmbeddedPlatform::node_api_version()); + // Create new Node-API env. We avoid creating the environment under the lock. + napi_env env = + v8impl::NewEnv(node_env->context(), module_filename, node_api_version_); // In case if we cannot insert the new env, we are just going to have an // unused env which will be deleted in the end with other environments. - std::scoped_lock lock(shared_data.mutex); - auto insert_result = - shared_data.node_env_to_node_api_env.try_emplace(node_env, env); + std::scoped_lock lock(worker_env_mutex_); + auto insert_result = worker_env_to_node_api_.try_emplace(node_env, env); + if (insert_result.second) { + // If the environment is successfully inserted, add a cleanup hook to delete + // it from the worker_env_to_node_api_ later. + struct CleanupContext { + EmbeddedRuntime* runtime_; + node::Environment* node_env_; + }; + node_env->AddCleanupHook( + [](void* arg) { + std::unique_ptr context{ + static_cast(arg)}; + std::scoped_lock lock( + context->runtime_->worker_env_mutex_); + context->runtime_->worker_env_to_node_api_.erase(context->node_env_); + }, + static_cast(new CleanupContext{this, node_env})); + } // Return either the inserted or the existing environment. return insert_result.first->second; } node::EnvironmentFlags::Flags EmbeddedRuntime::GetEnvironmentFlags( - node_embedding_runtime_flags flags) { + NodeRuntimeFlags flags) { uint64_t result = node::EnvironmentFlags::kNoFlags; - if ((flags & node_embedding_runtime_flags_default) != 0) { + if (embedding::IsFlagSet(flags, NodeRuntimeFlags::kDefault)) { result |= node::EnvironmentFlags::kDefaultFlags; } - if ((flags & node_embedding_runtime_flags_owns_process_state) != 0) { + if (embedding::IsFlagSet(flags, NodeRuntimeFlags::kOwnsProcessState)) { result |= node::EnvironmentFlags::kOwnsProcessState; } - if ((flags & node_embedding_runtime_flags_owns_inspector) != 0) { + if (embedding::IsFlagSet(flags, NodeRuntimeFlags::kOwnsInspector)) { result |= node::EnvironmentFlags::kOwnsInspector; } - if ((flags & node_embedding_runtime_flags_no_register_esm_loader) != 0) { + if (embedding::IsFlagSet(flags, NodeRuntimeFlags::kNoRegisterEsmLoader)) { result |= node::EnvironmentFlags::kNoRegisterESMLoader; } - if ((flags & node_embedding_runtime_flags_track_unmanaged_fds) != 0) { + if (embedding::IsFlagSet(flags, NodeRuntimeFlags::kTrackUnmanagedFds)) { result |= node::EnvironmentFlags::kTrackUnmanagedFds; } - if ((flags & node_embedding_runtime_flags_hide_console_windows) != 0) { + if (embedding::IsFlagSet(flags, NodeRuntimeFlags::kHideConsoleWindows)) { result |= node::EnvironmentFlags::kHideConsoleWindows; } - if ((flags & node_embedding_runtime_flags_no_native_addons) != 0) { + if (embedding::IsFlagSet(flags, NodeRuntimeFlags::kNoNativeAddons)) { result |= node::EnvironmentFlags::kNoNativeAddons; } - if ((flags & node_embedding_runtime_flags_no_global_search_paths) != 0) { + if (embedding::IsFlagSet(flags, NodeRuntimeFlags::kNoGlobalSearchPaths)) { result |= node::EnvironmentFlags::kNoGlobalSearchPaths; } - if ((flags & node_embedding_runtime_flags_no_browser_globals) != 0) { + if (embedding::IsFlagSet(flags, NodeRuntimeFlags::kNoBrowserGlobals)) { result |= node::EnvironmentFlags::kNoBrowserGlobals; } - if ((flags & node_embedding_runtime_flags_no_create_inspector) != 0) { + if (embedding::IsFlagSet(flags, NodeRuntimeFlags::kNoCreateInspector)) { result |= node::EnvironmentFlags::kNoCreateInspector; } - if ((flags & node_embedding_runtime_flags_no_start_debug_signal_handler) != - 0) { + if (embedding::IsFlagSet(flags, + NodeRuntimeFlags::kNoStartDebugSignalHandler)) { result |= node::EnvironmentFlags::kNoStartDebugSignalHandler; } - if ((flags & node_embedding_runtime_flags_no_wait_for_inspector_frontend) != - 0) { + if (embedding::IsFlagSet(flags, + NodeRuntimeFlags::kNoWaitForInspectorFrontend)) { result |= node::EnvironmentFlags::kNoWaitForInspectorFrontend; } return static_cast(result); @@ -1367,149 +1533,244 @@ void EmbeddedRuntime::RegisterModules() { } } -} // end of anonymous namespace -} // namespace node +} // namespace node::embedding -node_embedding_status NAPI_CDECL -node_embedding_on_error(node_embedding_handle_error_functor error_handler) { - return node::EmbeddedErrorHandling::SetErrorHandler(error_handler); +using namespace node::embedding; + +const char* NAPI_CDECL node_embedding_last_error_message_get() { + return EmbeddedErrorHandling::GetLastErrorMessage(); } -NAPI_EXTERN node_embedding_status NAPI_CDECL node_embedding_set_api_version( - int32_t embedding_api_version, int32_t node_api_version) { - return node::EmbeddedPlatform::SetApiVersion(embedding_api_version, - node_api_version); +void NAPI_CDECL node_embedding_last_error_message_set(const char* message) { + if (message == nullptr) { + EmbeddedErrorHandling::ClearLastErrorMessage(); + } else { + EmbeddedErrorHandling::SetLastErrorMessage(message); + } } -node_embedding_status NAPI_CDECL node_embedding_run_main( - int32_t argc, - char* argv[], - node_embedding_configure_platform_functor_ref configure_platform, - node_embedding_configure_runtime_functor_ref configure_runtime) { - return node::EmbeddedPlatform::RunMain( - argc, argv, configure_platform, configure_runtime); +void NAPI_CDECL node_embedding_last_error_message_set_format(const char* format, + ...) { + constexpr size_t buffer_size = 1024; + char buffer[buffer_size]; + std::unique_ptr dynamic_buffer; + + va_list args1; + va_start(args1, format); + va_list args2; // Required for some compilers like GCC since we go over the + // args twice. + va_copy(args2, args1); + + size_t string_size = std::vsnprintf(nullptr, 0, format, args1); + va_end(args1); + char* message = buffer; + if (string_size >= buffer_size) { + dynamic_buffer = std::make_unique(string_size + 1); + message = dynamic_buffer.get(); + } + std::vsnprintf(&message[0], string_size + 1, format, args2); + va_end(args2); + + EmbeddedErrorHandling::SetLastErrorMessage(message); } -node_embedding_status NAPI_CDECL node_embedding_create_platform( +node_embedding_status NAPI_CDECL node_embedding_main_run( + int32_t embedding_api_version, int32_t argc, - char* argv[], - node_embedding_configure_platform_functor_ref configure_platform, + const char* argv[], + node_embedding_platform_configure_callback configure_platform, + void* configure_platform_data, + node_embedding_runtime_configure_callback configure_runtime, + void* configure_runtime_data) { + return EmbeddedPlatform::RunMain(embedding_api_version, + argc, + argv, + configure_platform, + configure_platform_data, + configure_runtime, + configure_runtime_data); +} + +node_embedding_status NAPI_CDECL node_embedding_platform_create( + int32_t embedding_api_version, + int32_t argc, + const char* argv[], + node_embedding_platform_configure_callback configure_platform, + void* configure_platform_data, node_embedding_platform* result) { - return node::EmbeddedPlatform::Create(argc, argv, configure_platform, result); + return EmbeddedPlatform::Create(embedding_api_version, + argc, + argv, + configure_platform, + configure_platform_data, + result); } node_embedding_status NAPI_CDECL -node_embedding_delete_platform(node_embedding_platform platform) { +node_embedding_platform_delete(node_embedding_platform platform) { return EMBEDDED_PLATFORM(platform)->DeleteMe(); } -node_embedding_status NAPI_CDECL node_embedding_platform_set_flags( +node_embedding_status NAPI_CDECL node_embedding_platform_config_set_flags( node_embedding_platform_config platform_config, node_embedding_platform_flags flags) { return EMBEDDED_PLATFORM(platform_config)->SetFlags(flags); } -node_embedding_status NAPI_CDECL node_embedding_platform_get_parsed_args( - node_embedding_platform platform, - node_embedding_get_args_functor_ref get_args, - node_embedding_get_args_functor_ref get_runtime_args) { - return EMBEDDED_PLATFORM(platform)->GetParsedArgs(get_args, get_runtime_args); +node_embedding_status NAPI_CDECL +node_embedding_platform_get_parsed_args(node_embedding_platform platform, + int32_t* args_count, + const char** args[], + int32_t* runtime_args_count, + const char** runtime_args[]) { + return EMBEDDED_PLATFORM(platform)->GetParsedArgs( + args_count, args, runtime_args_count, runtime_args); } -node_embedding_status NAPI_CDECL node_embedding_run_runtime( +node_embedding_status NAPI_CDECL node_embedding_runtime_run( node_embedding_platform platform, - node_embedding_configure_runtime_functor_ref configure_runtime) { - return node::EmbeddedRuntime::Run(platform, configure_runtime); + node_embedding_runtime_configure_callback configure_runtime, + void* configure_runtime_data) { + return EmbeddedRuntime::Run( + platform, configure_runtime, configure_runtime_data); } -node_embedding_status NAPI_CDECL node_embedding_create_runtime( +node_embedding_status NAPI_CDECL node_embedding_runtime_create( node_embedding_platform platform, - node_embedding_configure_runtime_functor_ref configure_runtime, + node_embedding_runtime_configure_callback configure_runtime, + void* configure_runtime_data, node_embedding_runtime* result) { - return node::EmbeddedRuntime::Create(platform, configure_runtime, result); + return EmbeddedRuntime::Create( + platform, configure_runtime, configure_runtime_data, result); } node_embedding_status NAPI_CDECL -node_embedding_delete_runtime(node_embedding_runtime runtime) { +node_embedding_runtime_delete(node_embedding_runtime runtime) { return EMBEDDED_RUNTIME(runtime)->DeleteMe(); } node_embedding_status NAPI_CDECL -node_embedding_runtime_set_flags(node_embedding_runtime_config runtime_config, - node_embedding_runtime_flags flags) { +node_embedding_runtime_config_set_node_api_version( + node_embedding_runtime_config runtime_config, int32_t node_api_version) { + return EMBEDDED_RUNTIME(runtime_config)->SetNodeApiVersion(node_api_version); +} + +node_embedding_status NAPI_CDECL node_embedding_runtime_config_set_flags( + node_embedding_runtime_config runtime_config, + node_embedding_runtime_flags flags) { return EMBEDDED_RUNTIME(runtime_config)->SetFlags(flags); } -node_embedding_status NAPI_CDECL -node_embedding_runtime_set_args(node_embedding_runtime_config runtime_config, - int32_t argc, - const char* argv[], - int32_t runtime_argc, - const char* runtime_argv[]) { +node_embedding_status NAPI_CDECL node_embedding_runtime_config_set_args( + node_embedding_runtime_config runtime_config, + int32_t argc, + const char* argv[], + int32_t runtime_argc, + const char* runtime_argv[]) { return EMBEDDED_RUNTIME(runtime_config) ->SetArgs(argc, argv, runtime_argc, runtime_argv); } -node_embedding_status NAPI_CDECL -node_embedding_runtime_on_preload(node_embedding_runtime_config runtime_config, - node_embedding_preload_functor run_preload) { - return EMBEDDED_RUNTIME(runtime_config)->OnPreload(run_preload); +node_embedding_status NAPI_CDECL node_embedding_runtime_config_on_preload( + node_embedding_runtime_config runtime_config, + node_embedding_runtime_preload_callback run_preload, + void* preload_data, + node_embedding_data_release_callback release_preload_data) { + return EMBEDDED_RUNTIME(runtime_config) + ->OnPreload(run_preload, preload_data, release_preload_data); } -node_embedding_status NAPI_CDECL node_embedding_runtime_on_start_execution( +node_embedding_status NAPI_CDECL node_embedding_runtime_config_on_loading( node_embedding_runtime_config runtime_config, - node_embedding_start_execution_functor start_execution, - node_embedding_handle_result_functor handle_result) { + node_embedding_runtime_loading_callback start_execution, + void* start_execution_data, + node_embedding_data_release_callback release_start_execution_data) { return EMBEDDED_RUNTIME(runtime_config) - ->OnStartExecution(start_execution, handle_result); + ->OnStartExecution( + start_execution, start_execution_data, release_start_execution_data); } -node_embedding_status NAPI_CDECL node_embedding_runtime_add_module( +node_embedding_status NAPI_CDECL node_embedding_runtime_config_on_loaded( + node_embedding_runtime_config runtime_config, + node_embedding_runtime_loaded_callback handle_result, + void* handle_result_data, + node_embedding_data_release_callback release_handle_result_data) { + return EMBEDDED_RUNTIME(runtime_config) + ->OnHandleExecutionResult( + handle_result, handle_result_data, release_handle_result_data); +} + +node_embedding_status NAPI_CDECL node_embedding_runtime_config_add_module( node_embedding_runtime_config runtime_config, const char* module_name, - node_embedding_initialize_module_functor init_module, + node_embedding_module_initialize_callback init_module, + void* init_module_data, + node_embedding_data_release_callback release_init_module_data, int32_t module_node_api_version) { return EMBEDDED_RUNTIME(runtime_config) - ->AddModule(module_name, init_module, module_node_api_version); + ->AddModule(module_name, + init_module, + init_module_data, + release_init_module_data, + module_node_api_version); } -node_embedding_status NAPI_CDECL node_embedding_runtime_set_task_runner( - node_embedding_runtime_config runtime_config, - node_embedding_post_task_functor post_task) { - return EMBEDDED_RUNTIME(runtime_config)->SetTaskRunner(post_task); +node_embedding_status NAPI_CDECL node_embedding_runtime_user_data_set( + node_embedding_runtime runtime, + void* user_data, + node_embedding_data_release_callback release_user_data) { + return EMBEDDED_RUNTIME(runtime)->SetUserData(user_data, release_user_data); } -node_embedding_status NAPI_CDECL -node_embedding_run_event_loop(node_embedding_runtime runtime, - node_embedding_event_loop_run_mode run_mode, - bool* has_more_work) { - return EMBEDDED_RUNTIME(runtime)->RunEventLoop(run_mode, has_more_work); +node_embedding_status NAPI_CDECL node_embedding_runtime_user_data_get( + node_embedding_runtime runtime, void** user_data) { + return EMBEDDED_RUNTIME(runtime)->GetUserData(user_data); +} + +node_embedding_status NAPI_CDECL node_embedding_runtime_config_set_task_runner( + node_embedding_runtime_config runtime_config, + node_embedding_task_post_callback post_task, + void* post_task_data, + node_embedding_data_release_callback release_post_task_data) { + return EMBEDDED_RUNTIME(runtime_config) + ->SetTaskRunner(post_task, post_task_data, release_post_task_data); } node_embedding_status NAPI_CDECL -node_embedding_complete_event_loop(node_embedding_runtime runtime) { - return EMBEDDED_RUNTIME(runtime)->CompleteEventLoop(); +node_embedding_runtime_event_loop_run(node_embedding_runtime runtime) { + return EMBEDDED_RUNTIME(runtime)->RunEventLoop(); } node_embedding_status NAPI_CDECL -node_embedding_terminate_event_loop(node_embedding_runtime runtime) { +node_embedding_runtime_event_loop_terminate(node_embedding_runtime runtime) { return EMBEDDED_RUNTIME(runtime)->TerminateEventLoop(); } -node_embedding_status NAPI_CDECL node_embedding_run_node_api( +node_embedding_status NAPI_CDECL node_embedding_runtime_event_loop_run_once( + node_embedding_runtime runtime, bool* has_more_work) { + return EMBEDDED_RUNTIME(runtime)->RunEventLoopOnce(has_more_work); +} + +node_embedding_status NAPI_CDECL node_embedding_runtime_event_loop_run_no_wait( + node_embedding_runtime runtime, bool* has_more_work) { + return EMBEDDED_RUNTIME(runtime)->RunEventLoopNoWait(has_more_work); +} + +node_embedding_status NAPI_CDECL node_embedding_runtime_node_api_run( node_embedding_runtime runtime, - node_embedding_run_node_api_functor_ref run_node_api) { - return EMBEDDED_RUNTIME(runtime)->RunNodeApi(run_node_api); + node_embedding_node_api_run_callback run_node_api, + void* run_node_api_data) { + return EMBEDDED_RUNTIME(runtime)->RunNodeApi(run_node_api, run_node_api_data); } -node_embedding_status NAPI_CDECL node_embedding_open_node_api_scope( +node_embedding_status NAPI_CDECL node_embedding_runtime_node_api_scope_open( node_embedding_runtime runtime, node_embedding_node_api_scope* node_api_scope, napi_env* env) { return EMBEDDED_RUNTIME(runtime)->OpenNodeApiScope(node_api_scope, env); } -node_embedding_status NAPI_CDECL node_embedding_close_node_api_scope( +node_embedding_status NAPI_CDECL node_embedding_runtime_node_api_scope_close( node_embedding_runtime runtime, node_embedding_node_api_scope node_api_scope) { return EMBEDDED_RUNTIME(runtime)->CloseNodeApiScope(node_api_scope); diff --git a/src/node_embedding_api.h b/src/node_embedding_api.h index 0fa4393bad05af..9e468fc91d52a2 100644 --- a/src/node_embedding_api.h +++ b/src/node_embedding_api.h @@ -20,7 +20,37 @@ #define NODE_EMBEDDING_VERSION 1 -EXTERN_C_START +#if defined(__cplusplus) && !defined(NODE_EMBEDDING_DISABLE_CPP_ENUMS) + +#define NODE_ENUM(c_name, cpp_name) enum class cpp_name : int32_t + +#define NODE_ENUM_FLAGS(c_name, cpp_name) \ + enum class cpp_name : int32_t; \ + \ + inline constexpr cpp_name operator|(cpp_name lhs, cpp_name rhs) { \ + return static_cast(static_cast(lhs) | \ + static_cast(rhs)); \ + } \ + \ + inline constexpr bool IsFlagSet(cpp_name flags, cpp_name flag) { \ + return (static_cast(flags) & static_cast(flag)) != 0; \ + } \ + \ + enum class cpp_name : int32_t + +#define NODE_ENUM_ITEM(c_name, cpp_name) cpp_name + +#else + +#define NODE_ENUM(c_name, cpp_name) \ + typedef enum c_name c_name; \ + enum c_name + +#define NODE_ENUM_FLAGS(c_name, cpp_name) NODE_ENUM(c_name, cpp_name) + +#define NODE_ENUM_ITEM(c_name, cpp_name) c_name + +#endif //============================================================================== // Data types @@ -32,150 +62,166 @@ typedef struct node_embedding_platform_config_s* node_embedding_platform_config; typedef struct node_embedding_runtime_config_s* node_embedding_runtime_config; typedef struct node_embedding_node_api_scope_s* node_embedding_node_api_scope; +#ifdef __cplusplus +namespace node::embedding { +#endif + // The status returned by the Node.js embedding API functions. -typedef enum { - node_embedding_status_ok = 0, - node_embedding_status_generic_error = 1, - node_embedding_status_null_arg = 2, - node_embedding_status_bad_arg = 3, +NODE_ENUM(node_embedding_status, NodeStatus) { + NODE_ENUM_ITEM(node_embedding_status_ok, kOk) = 0, + NODE_ENUM_ITEM(node_embedding_status_generic_error, kGenericError) = 1, + NODE_ENUM_ITEM(node_embedding_status_null_arg, kNullArg) = 2, + NODE_ENUM_ITEM(node_embedding_status_bad_arg, kBadArg) = 3, + NODE_ENUM_ITEM(node_embedding_status_out_of_memory, kOutOfMemory) = 4, // This value is added to the exit code in cases when Node.js API returns // an error exit code. - node_embedding_status_error_exit_code = 512, -} node_embedding_status; + NODE_ENUM_ITEM(node_embedding_status_error_exit_code, kErrorExitCode) = 512, +}; // The flags for the Node.js platform initialization. // They match the internal ProcessInitializationFlags::Flags enum. -typedef enum { - node_embedding_platform_flags_none = 0, +NODE_ENUM_FLAGS(node_embedding_platform_flags, NodePlatformFlags) { + NODE_ENUM_ITEM(node_embedding_platform_flags_none, kNone) = 0, // Enable stdio inheritance, which is disabled by default. // This flag is also implied by // node_embedding_platform_flags_no_stdio_initialization. - node_embedding_platform_flags_enable_stdio_inheritance = 1 << 0, - // Disable reading the NODE_OPTIONS environment variable. - node_embedding_platform_flags_disable_node_options_env = 1 << 1, + NODE_ENUM_ITEM(node_embedding_platform_flags_enable_stdio_inheritance, + kEnableStdioInheritance) = 1 << 0, + // Disable reading the NODE_ENUM_ITEMS environment variable. + NODE_ENUM_ITEM(node_embedding_platform_flags_disable_node_options_env, + kDisableNodeOptionsEnv) = 1 << 1, // Do not parse CLI options. - node_embedding_platform_flags_disable_cli_options = 1 << 2, + NODE_ENUM_ITEM(node_embedding_platform_flags_disable_cli_options, + kDisableCliOptions) = 1 << 2, // Do not initialize ICU. - node_embedding_platform_flags_no_icu = 1 << 3, + NODE_ENUM_ITEM(node_embedding_platform_flags_no_icu, kNoICU) = 1 << 3, // Do not modify stdio file descriptor or TTY state. - node_embedding_platform_flags_no_stdio_initialization = 1 << 4, + NODE_ENUM_ITEM(node_embedding_platform_flags_no_stdio_initialization, + kNoStdioInitialization) = 1 << 4, // Do not register Node.js-specific signal handlers // and reset other signal handlers to default state. - node_embedding_platform_flags_no_default_signal_handling = 1 << 5, + NODE_ENUM_ITEM(node_embedding_platform_flags_no_default_signal_handling, + kNoDefaultSignalHandling) = 1 << 5, // Do not initialize OpenSSL config. - node_embedding_platform_flags_no_init_openssl = 1 << 8, + NODE_ENUM_ITEM(node_embedding_platform_flags_no_init_openssl, + kNoInitOpenSSL) = 1 << 8, // Do not initialize Node.js debugging based on environment variables. - node_embedding_platform_flags_no_parse_global_debug_variables = 1 << 9, + NODE_ENUM_ITEM(node_embedding_platform_flags_no_parse_global_debug_variables, + kNoParseGlobalDebugVariables) = 1 << 9, // Do not adjust OS resource limits for this process. - node_embedding_platform_flags_no_adjust_resource_limits = 1 << 10, + NODE_ENUM_ITEM(node_embedding_platform_flags_no_adjust_resource_limits, + kNoAdjustResourceLimits) = 1 << 10, // Do not map code segments into large pages for this process. - node_embedding_platform_flags_no_use_large_pages = 1 << 11, + NODE_ENUM_ITEM(node_embedding_platform_flags_no_use_large_pages, + kNoUseLargePages) = 1 << 11, // Skip printing output for --help, --version, --v8-options. - node_embedding_platform_flags_no_print_help_or_version_output = 1 << 12, + NODE_ENUM_ITEM(node_embedding_platform_flags_no_print_help_or_version_output, + kNoPrintHelpOrVersionOutput) = 1 << 12, // Initialize the process for predictable snapshot generation. - node_embedding_platform_flags_generate_predictable_snapshot = 1 << 14, -} node_embedding_platform_flags; + NODE_ENUM_ITEM(node_embedding_platform_flags_generate_predictable_snapshot, + kGeneratePredictableSnapshot) = 1 << 14, +}; // The flags for the Node.js runtime initialization. // They match the internal EnvironmentFlags::Flags enum. -typedef enum { - node_embedding_runtime_flags_none = 0, +NODE_ENUM_FLAGS(node_embedding_runtime_flags, NodeRuntimeFlags) { + NODE_ENUM_ITEM(node_embedding_runtime_flags_none, kNone) = 0, // Use the default behavior for Node.js instances. - node_embedding_runtime_flags_default = 1 << 0, + NODE_ENUM_ITEM(node_embedding_runtime_flags_default, kDefault) = 1 << 0, // Controls whether this Environment is allowed to affect per-process state // (e.g. cwd, process title, uid, etc.). // This is set when using node_embedding_runtime_flags_default. - node_embedding_runtime_flags_owns_process_state = 1 << 1, + NODE_ENUM_ITEM(node_embedding_runtime_flags_owns_process_state, + kOwnsProcessState) = 1 << 1, // Set if this Environment instance is associated with the global inspector // handling code (i.e. listening on SIGUSR1). // This is set when using node_embedding_runtime_flags_default. - node_embedding_runtime_flags_owns_inspector = 1 << 2, + NODE_ENUM_ITEM(node_embedding_runtime_flags_owns_inspector, + kOwnsInspector) = 1 << 2, // Set if Node.js should not run its own esm loader. This is needed by some // embedders, because it's possible for the Node.js esm loader to conflict // with another one in an embedder environment, e.g. Blink's in Chromium. - node_embedding_runtime_flags_no_register_esm_loader = 1 << 3, + NODE_ENUM_ITEM(node_embedding_runtime_flags_no_register_esm_loader, + kNoRegisterEsmLoader) = 1 << 3, // Set this flag to make Node.js track "raw" file descriptors, i.e. managed // by fs.open() and fs.close(), and close them during - // node_embedding_delete_runtime(). - node_embedding_runtime_flags_track_unmanaged_fds = 1 << 4, + // node_embedding_runtime_delete(). + NODE_ENUM_ITEM(node_embedding_runtime_flags_track_unmanaged_fds, + kTrackUnmanagedFds) = 1 << 4, // Set this flag to force hiding console windows when spawning child // processes. This is usually used when embedding Node.js in GUI programs on // Windows. - node_embedding_runtime_flags_hide_console_windows = 1 << 5, + NODE_ENUM_ITEM(node_embedding_runtime_flags_hide_console_windows, + kHideConsoleWindows) = 1 << 5, // Set this flag to disable loading native addons via `process.dlopen`. // This environment flag is especially important for worker threads // so that a worker thread can't load a native addon even if `execArgv` // is overwritten and `--no-addons` is not specified but was specified // for this Environment instance. - node_embedding_runtime_flags_no_native_addons = 1 << 6, + NODE_ENUM_ITEM(node_embedding_runtime_flags_no_native_addons, + kNoNativeAddons) = 1 << 6, // Set this flag to disable searching modules from global paths like // $HOME/.node_modules and $NODE_PATH. This is used by standalone apps that // do not expect to have their behaviors changed because of globally // installed modules. - node_embedding_runtime_flags_no_global_search_paths = 1 << 7, + NODE_ENUM_ITEM(node_embedding_runtime_flags_no_global_search_paths, + kNoGlobalSearchPaths) = 1 << 7, // Do not export browser globals like setTimeout, console, etc. - node_embedding_runtime_flags_no_browser_globals = 1 << 8, - // Controls whether or not the Environment should call V8Inspector::create(). - // This control is needed by embedders who may not want to initialize the V8 - // inspector in situations where one has already been created, - // e.g. Blink's in Chromium. - node_embedding_runtime_flags_no_create_inspector = 1 << 9, + NODE_ENUM_ITEM(node_embedding_runtime_flags_no_browser_globals, + kNoBrowserGlobals) = 1 << 8, + // Controls whether or not the Environment should call + // V8Inspector::create(). This control is needed by embedders who may not + // want to initialize the V8 inspector in situations where one has already + // been created, e.g. Blink's in Chromium. + NODE_ENUM_ITEM(node_embedding_runtime_flags_no_create_inspector, + kNoCreateInspector) = 1 << 9, // Controls whether or not the InspectorAgent for this Environment should // call StartDebugSignalHandler. This control is needed by embedders who may // not want to allow other processes to start the V8 inspector. - node_embedding_runtime_flags_no_start_debug_signal_handler = 1 << 10, - // Controls whether the InspectorAgent created for this Environment waits for - // Inspector frontend events during the Environment creation. It's used to - // call node::Stop(env) on a Worker thread that is waiting for the events. - node_embedding_runtime_flags_no_wait_for_inspector_frontend = 1 << 11 -} node_embedding_runtime_flags; - -typedef enum { - // Run the event loop until it is completed. - // It matches the UV_RUN_DEFAULT behavior. - node_embedding_event_loop_run_mode_default = 0, - // Run the event loop once and wait if there are no items. - // It matches the UV_RUN_ONCE behavior. - node_embedding_event_loop_run_mode_once = 1, - // Run the event loop once and do not wait if there are no items. - // It matches the UV_RUN_NOWAIT behavior. - node_embedding_event_loop_run_mode_nowait = 2, -} node_embedding_event_loop_run_mode; + NODE_ENUM_ITEM(node_embedding_runtime_flags_no_start_debug_signal_handler, + kNoStartDebugSignalHandler) = 1 << 10, + // Controls whether the InspectorAgent created for this Environment waits + // for Inspector frontend events during the Environment creation. It's used + // to call node::Stop(env) on a Worker thread that is waiting for the + // events. + NODE_ENUM_ITEM(node_embedding_runtime_flags_no_wait_for_inspector_frontend, + kNoWaitForInspectorFrontend) = 1 << 11, +}; + +#ifdef __cplusplus +} // namespace node::embedding +using node_embedding_status = node::embedding::NodeStatus; +using node_embedding_platform_flags = node::embedding::NodePlatformFlags; +using node_embedding_runtime_flags = node::embedding::NodeRuntimeFlags; +#endif //============================================================================== // Callbacks //============================================================================== -typedef void(NAPI_CDECL* node_embedding_release_data_callback)(void* data); - -typedef node_embedding_status(NAPI_CDECL* node_embedding_handle_error_callback)( - void* cb_data, - const char* messages[], - size_t messages_size, - node_embedding_status status); +typedef node_embedding_status(NAPI_CDECL* node_embedding_data_release_callback)( + void* data); typedef node_embedding_status( - NAPI_CDECL* node_embedding_configure_platform_callback)( + NAPI_CDECL* node_embedding_platform_configure_callback)( void* cb_data, node_embedding_platform_config platform_config); typedef node_embedding_status( - NAPI_CDECL* node_embedding_configure_runtime_callback)( + NAPI_CDECL* node_embedding_runtime_configure_callback)( void* cb_data, node_embedding_platform platform, node_embedding_runtime_config runtime_config); -typedef void(NAPI_CDECL* node_embedding_get_args_callback)(void* cb_data, - int32_t argc, - const char* argv[]); - -typedef void(NAPI_CDECL* node_embedding_preload_callback)( +// The error is to be handled by using napi_env. +typedef void(NAPI_CDECL* node_embedding_runtime_preload_callback)( void* cb_data, node_embedding_runtime runtime, napi_env env, napi_value process, napi_value require); -typedef napi_value(NAPI_CDECL* node_embedding_start_execution_callback)( +// The error is to be handled by using napi_env. +typedef napi_value(NAPI_CDECL* node_embedding_runtime_loading_callback)( void* cb_data, node_embedding_runtime runtime, napi_env env, @@ -183,193 +229,193 @@ typedef napi_value(NAPI_CDECL* node_embedding_start_execution_callback)( napi_value require, napi_value run_cjs); -typedef void(NAPI_CDECL* node_embedding_handle_result_callback)( +// The error is to be handled by using napi_env. +typedef void(NAPI_CDECL* node_embedding_runtime_loaded_callback)( void* cb_data, node_embedding_runtime runtime, napi_env env, - napi_value value); + napi_value load_result); -typedef napi_value(NAPI_CDECL* node_embedding_initialize_module_callback)( +// The error is to be handled by using napi_env. +typedef napi_value(NAPI_CDECL* node_embedding_module_initialize_callback)( void* cb_data, node_embedding_runtime runtime, napi_env env, const char* module_name, napi_value exports); -typedef void(NAPI_CDECL* node_embedding_run_task_callback)(void* cb_data); - -typedef struct { - void* data; - node_embedding_run_task_callback invoke; - node_embedding_release_data_callback release; -} node_embedding_run_task_functor; - -typedef void(NAPI_CDECL* node_embedding_post_task_callback)( - void* cb_data, node_embedding_run_task_functor run_task); - -typedef void(NAPI_CDECL* node_embedding_run_node_api_callback)( - void* cb_data, node_embedding_runtime runtime, napi_env env); - -typedef struct { - void* data; - node_embedding_handle_error_callback invoke; - node_embedding_release_data_callback release; -} node_embedding_handle_error_functor; - -typedef struct { - void* data; - node_embedding_configure_platform_callback invoke; -} node_embedding_configure_platform_functor_ref; - -typedef struct { - void* data; - node_embedding_configure_runtime_callback invoke; -} node_embedding_configure_runtime_functor_ref; - -typedef struct { - void* data; - node_embedding_run_node_api_callback invoke; -} node_embedding_run_node_api_functor_ref; - -typedef struct { - void* data; - node_embedding_get_args_callback invoke; -} node_embedding_get_args_functor_ref; - -typedef struct { - void* data; - node_embedding_preload_callback invoke; - node_embedding_release_data_callback release; -} node_embedding_preload_functor; - -typedef struct { - void* data; - node_embedding_start_execution_callback invoke; - node_embedding_release_data_callback release; -} node_embedding_start_execution_functor; - -typedef struct { - void* data; - node_embedding_handle_result_callback invoke; - node_embedding_release_data_callback release; -} node_embedding_handle_result_functor; - -typedef struct { - void* data; - node_embedding_initialize_module_callback invoke; - node_embedding_release_data_callback release; -} node_embedding_initialize_module_functor; - -typedef struct { - void* data; - node_embedding_post_task_callback invoke; - node_embedding_release_data_callback release; -} node_embedding_post_task_functor; +typedef node_embedding_status(NAPI_CDECL* node_embedding_task_run_callback)( + void* cb_data); + +typedef node_embedding_status(NAPI_CDECL* node_embedding_task_post_callback)( + void* cb_data, + node_embedding_task_run_callback run_task, + void* task_data, + node_embedding_data_release_callback release_task_data, + bool* is_posted); + +// The error is to be handled by using napi_env. +typedef void(NAPI_CDECL* node_embedding_node_api_run_callback)(void* cb_data, + napi_env env); //============================================================================== // Functions //============================================================================== +EXTERN_C_START + //------------------------------------------------------------------------------ // Error handling functions. //------------------------------------------------------------------------------ -// Sets the global error handing for the Node.js embedding API. -NAPI_EXTERN node_embedding_status NAPI_CDECL -node_embedding_on_error(node_embedding_handle_error_functor error_handler); +// Gets the last error message for the current thread. +NAPI_EXTERN const char* NAPI_CDECL node_embedding_last_error_message_get(); + +// Sets the last error message for the current thread. +NAPI_EXTERN void NAPI_CDECL +node_embedding_last_error_message_set(const char* message); + +// Sets the last error message for the current thread using C printf string +// formatting. +NAPI_EXTERN void NAPI_CDECL +node_embedding_last_error_message_set_format(const char* format, ...); //------------------------------------------------------------------------------ // Node.js global platform functions. //------------------------------------------------------------------------------ -// Sets the API version for the Node.js embedding API and the Node-API. -NAPI_EXTERN node_embedding_status NAPI_CDECL node_embedding_set_api_version( - int32_t embedding_api_version, int32_t node_api_version); - -// Runs Node.js main function as if it is invoked from Node.js CLI. -NAPI_EXTERN node_embedding_status NAPI_CDECL node_embedding_run_main( +// Runs Node.js main function. +// By default it is the same as running Node.js from CLI. +NAPI_EXTERN node_embedding_status NAPI_CDECL node_embedding_main_run( + int32_t embedding_api_version, int32_t argc, - char* argv[], - node_embedding_configure_platform_functor_ref configure_platform, - node_embedding_configure_runtime_functor_ref configure_runtime); + const char* argv[], + node_embedding_platform_configure_callback configure_platform, + void* configure_platform_data, + node_embedding_runtime_configure_callback configure_runtime, + void* configure_runtime_data); // Creates and configures a new Node.js platform instance. -NAPI_EXTERN node_embedding_status NAPI_CDECL node_embedding_create_platform( +NAPI_EXTERN node_embedding_status NAPI_CDECL node_embedding_platform_create( + int32_t embedding_api_version, int32_t argc, - char* argv[], - node_embedding_configure_platform_functor_ref configure_platform, + const char* argv[], + node_embedding_platform_configure_callback configure_platform, + void* configure_platform_data, node_embedding_platform* result); // Deletes the Node.js platform instance. NAPI_EXTERN node_embedding_status NAPI_CDECL -node_embedding_delete_platform(node_embedding_platform platform); +node_embedding_platform_delete(node_embedding_platform platform); // Sets the flags for the Node.js platform initialization. -NAPI_EXTERN node_embedding_status NAPI_CDECL node_embedding_platform_set_flags( +NAPI_EXTERN node_embedding_status NAPI_CDECL +node_embedding_platform_config_set_flags( node_embedding_platform_config platform_config, node_embedding_platform_flags flags); // Gets the parsed list of non-Node.js and Node.js arguments. NAPI_EXTERN node_embedding_status NAPI_CDECL -node_embedding_platform_get_parsed_args( - node_embedding_platform platform, - node_embedding_get_args_functor_ref get_args, - node_embedding_get_args_functor_ref get_runtime_args); +node_embedding_platform_get_parsed_args(node_embedding_platform platform, + int32_t* args_count, + const char** args[], + int32_t* runtime_args_count, + const char** runtime_args[]); //------------------------------------------------------------------------------ // Node.js runtime functions. //------------------------------------------------------------------------------ // Runs the Node.js runtime with the provided configuration. -NAPI_EXTERN node_embedding_status NAPI_CDECL node_embedding_run_runtime( +NAPI_EXTERN node_embedding_status NAPI_CDECL node_embedding_runtime_run( node_embedding_platform platform, - node_embedding_configure_runtime_functor_ref configure_runtime); + node_embedding_runtime_configure_callback configure_runtime, + void* configure_runtime_data); // Creates a new Node.js runtime instance. -NAPI_EXTERN node_embedding_status NAPI_CDECL node_embedding_create_runtime( +NAPI_EXTERN node_embedding_status NAPI_CDECL node_embedding_runtime_create( node_embedding_platform platform, - node_embedding_configure_runtime_functor_ref configure_runtime, + node_embedding_runtime_configure_callback configure_runtime, + void* configure_runtime_data, node_embedding_runtime* result); // Deletes the Node.js runtime instance. NAPI_EXTERN node_embedding_status NAPI_CDECL -node_embedding_delete_runtime(node_embedding_runtime runtime); +node_embedding_runtime_delete(node_embedding_runtime runtime); + +// Sets the Node-API version used for Node.js runtime. +NAPI_EXTERN node_embedding_status NAPI_CDECL +node_embedding_runtime_config_set_node_api_version( + node_embedding_runtime_config runtime_config, int32_t node_api_version); // Sets the flags for the Node.js runtime initialization. NAPI_EXTERN node_embedding_status NAPI_CDECL -node_embedding_runtime_set_flags(node_embedding_runtime_config runtime_config, - node_embedding_runtime_flags flags); +node_embedding_runtime_config_set_flags( + node_embedding_runtime_config runtime_config, + node_embedding_runtime_flags flags); // Sets the non-Node.js and Node.js CLI arguments for the Node.js runtime // initialization. NAPI_EXTERN node_embedding_status NAPI_CDECL -node_embedding_runtime_set_args(node_embedding_runtime_config runtime_config, - int32_t argc, - const char* argv[], - int32_t runtime_argc, - const char* runtime_argv[]); +node_embedding_runtime_config_set_args( + node_embedding_runtime_config runtime_config, + int32_t argc, + const char* argv[], + int32_t runtime_argc, + const char* runtime_argv[]); // Sets the preload callback for the Node.js runtime initialization. +// It is invoked before any other code execution for the runtime Node-API +// environment and for its worker thread environments. NAPI_EXTERN node_embedding_status NAPI_CDECL -node_embedding_runtime_on_preload(node_embedding_runtime_config runtime_config, - node_embedding_preload_functor run_preload); +node_embedding_runtime_config_on_preload( + node_embedding_runtime_config runtime_config, + node_embedding_runtime_preload_callback preload, + void* preload_data, + node_embedding_data_release_callback release_preload_data); // Sets the start execution callback for the Node.js runtime initialization. NAPI_EXTERN node_embedding_status NAPI_CDECL -node_embedding_runtime_on_start_execution( +node_embedding_runtime_config_on_loading( node_embedding_runtime_config runtime_config, - node_embedding_start_execution_functor start_execution, - node_embedding_handle_result_functor handle_result); + node_embedding_runtime_loading_callback run_load, + void* load_data, + node_embedding_data_release_callback release_load_data); + +// Handles the execution result for the Node.js runtime initialization. +NAPI_EXTERN node_embedding_status NAPI_CDECL +node_embedding_runtime_config_on_loaded( + node_embedding_runtime_config runtime_config, + node_embedding_runtime_loaded_callback handle_loaded, + void* handle_loaded_data, + node_embedding_data_release_callback release_handle_loaded_data); // Adds a new module to the Node.js runtime. // It is accessed as process._linkedBinding(module_name) in the main JS and in // the related worker threads. -NAPI_EXTERN node_embedding_status NAPI_CDECL node_embedding_runtime_add_module( +NAPI_EXTERN node_embedding_status NAPI_CDECL +node_embedding_runtime_config_add_module( node_embedding_runtime_config runtime_config, const char* module_name, - node_embedding_initialize_module_functor init_module, + node_embedding_module_initialize_callback init_module, + void* init_module_data, + node_embedding_data_release_callback release_init_module_data, int32_t module_node_api_version); +// Sets user data for the Node.js runtime. +// The release callback is not called for the existing user data. +// It is only called when the runtime is deleted. +NAPI_EXTERN node_embedding_status NAPI_CDECL +node_embedding_runtime_user_data_set( + node_embedding_runtime runtime, + void* user_data, + node_embedding_data_release_callback release_user_data); + +// Gets user data for the Node.js runtime. +NAPI_EXTERN node_embedding_status NAPI_CDECL +node_embedding_runtime_user_data_get(node_embedding_runtime runtime, + void** user_data); + //------------------------------------------------------------------------------ // Node.js runtime functions for the event loop. //------------------------------------------------------------------------------ @@ -380,196 +426,59 @@ NAPI_EXTERN node_embedding_status NAPI_CDECL node_embedding_runtime_add_module( // E.g. it enables running Node.js event loop inside of the application UI event // loop or UI dispatcher. NAPI_EXTERN node_embedding_status NAPI_CDECL -node_embedding_runtime_set_task_runner( +node_embedding_runtime_config_set_task_runner( node_embedding_runtime_config runtime_config, - node_embedding_post_task_functor post_task); + node_embedding_task_post_callback post_task, + void* post_task_data, + node_embedding_data_release_callback release_post_task_data); -// Runs the Node.js runtime event loop. +// Runs the Node.js runtime event loop in UV_RUN_DEFAULT mode. +// It finishes it with emitting the beforeExit and exit process events. NAPI_EXTERN node_embedding_status NAPI_CDECL -node_embedding_run_event_loop(node_embedding_runtime runtime, - node_embedding_event_loop_run_mode run_mode, - bool* has_more_work); +node_embedding_runtime_event_loop_run(node_embedding_runtime runtime); -// Runs the Node.js runtime event loop in node_embedding_event_loop_run_default -// mode and finishes it with emitting the beforeExit and exit process events. +// Stops the Node.js runtime event loop. It cannot be resumed after this call. +// It does not emit the beforeExit and exit process events if they were not +// emitted before. NAPI_EXTERN node_embedding_status NAPI_CDECL -node_embedding_complete_event_loop(node_embedding_runtime runtime); +node_embedding_runtime_event_loop_terminate(node_embedding_runtime runtime); -// Stops the Node.js runtime event loop. It cannot be resumed after this call. +// Runs the Node.js runtime event loop once. It may block the current thread. +// It matches the UV_RUN_ONCE behavior NAPI_EXTERN node_embedding_status NAPI_CDECL -node_embedding_terminate_event_loop(node_embedding_runtime runtime); +node_embedding_runtime_event_loop_run_once(node_embedding_runtime runtime, + bool* has_more_work); + +// Runs the Node.js runtime event loop once. It does not block the thread. +// It matches the UV_RUN_NOWAIT behavior. +NAPI_EXTERN node_embedding_status NAPI_CDECL +node_embedding_runtime_event_loop_run_no_wait(node_embedding_runtime runtime, + bool* has_more_work); //------------------------------------------------------------------------------ // Node.js runtime functions for the Node-API interop. //------------------------------------------------------------------------------ // Runs Node-API code in the Node-API scope. -NAPI_EXTERN node_embedding_status NAPI_CDECL node_embedding_run_node_api( +NAPI_EXTERN node_embedding_status NAPI_CDECL +node_embedding_runtime_node_api_run( node_embedding_runtime runtime, - node_embedding_run_node_api_functor_ref run_node_api); + node_embedding_node_api_run_callback run_node_api, + void* run_node_api_data); // Opens a new Node-API scope. -NAPI_EXTERN node_embedding_status NAPI_CDECL node_embedding_open_node_api_scope( +NAPI_EXTERN node_embedding_status NAPI_CDECL +node_embedding_runtime_node_api_scope_open( node_embedding_runtime runtime, node_embedding_node_api_scope* node_api_scope, napi_env* env); // Closes the Node-API invocation scope. NAPI_EXTERN node_embedding_status NAPI_CDECL -node_embedding_close_node_api_scope( +node_embedding_runtime_node_api_scope_close( node_embedding_runtime runtime, node_embedding_node_api_scope node_api_scope); EXTERN_C_END -#ifdef __cplusplus - -//============================================================================== -// C++ convenience functions for the C API. -// These functions are not ABI safe and can be changed in future versions. -//============================================================================== - -#include -#include -#include - -namespace node { - -//------------------------------------------------------------------------------ -// Convenience union operator for the Node.js flags. -//------------------------------------------------------------------------------ - -inline constexpr node_embedding_platform_flags operator|( - node_embedding_platform_flags lhs, node_embedding_platform_flags rhs) { - return static_cast(static_cast(lhs) | - static_cast(rhs)); -} - -inline constexpr node_embedding_runtime_flags operator|( - node_embedding_runtime_flags lhs, node_embedding_runtime_flags rhs) { - return static_cast(static_cast(lhs) | - static_cast(rhs)); -} - -//------------------------------------------------------------------------------ -// Convenience functor struct adapter for C++ function object or lambdas. -//------------------------------------------------------------------------------ - -namespace details { - -template -struct FunctorAdapter { - static_assert(sizeof(TLambda) == -1, "Unsupported signature"); -}; - -template -struct FunctorAdapter { - static TResult Invoke(void* data, TArgs... args) { - return reinterpret_cast(data)->operator()(args...); - } -}; - -template -struct FunctorAdapter { - static void Invoke(void* data, TArgs... args) { - reinterpret_cast(data)->operator()(args...); - } -}; - -template -struct FunctorDeleter { - void operator()(T* ptr) { - if (ptr->release != nullptr) { - ptr->release(ptr->data); - } - delete ptr; - } -}; - -template -struct StdFunctionAdapter { - static_assert(sizeof(TFunctor) == -1, "Unsupported signature"); -}; - -template -struct StdFunctionAdapter { - using FunctionType = std::function; - - template - static FunctionType Create(TFunctorSharedPtr&& ptr) { - return ptr ? FunctionType([ptr = std::move(ptr)](TArgs... args) { - return ptr->invoke(ptr->data, args...); - }) - : FunctionType(nullptr); - } -}; - -template -struct StdFunctionAdapter { - using FunctionType = std::function; - - template - static FunctionType Create(TFunctorSharedPtr&& ptr) { - return ptr ? FunctionType([ptr = std::move(ptr)](TArgs... args) { - ptr->invoke(ptr->data, args...); - }) - : FunctionType(nullptr); - } -}; - -} // namespace details - -template -inline TFunctor AsFunctorRef(TLambda&& lambda) { - using TLambdaType = std::remove_reference_t; - using TAdapter = details::FunctorAdapter< - TLambdaType, - std::remove_pointer_t< - decltype(std::remove_reference_t::invoke)>>; - return TFunctor{static_cast(&lambda), &TAdapter::Invoke}; -} - -template -inline TFunctor AsFunctor(TLambda&& lambda) { - using TLambdaType = std::remove_reference_t; - using TAdapter = details::FunctorAdapter< - TLambdaType, - std::remove_pointer_t< - decltype(std::remove_reference_t::invoke)>>; - return TFunctor{ - static_cast(new TLambdaType(std::forward(lambda))), - &TAdapter::Invoke, - [](void* data) { delete static_cast(data); }}; -} - -template -using FunctorPtr = std::unique_ptr>; - -template -FunctorPtr MakeUniqueFunctorPtr(const T& functor) { - return functor.invoke ? FunctorPtr(new T(functor)) : nullptr; -} - -template -std::shared_ptr MakeSharedFunctorPtr(const T& functor) { - return functor.invoke - ? std::shared_ptr(new T(functor), details::FunctorDeleter()) - : nullptr; -} - -template -using StdFunction = typename details::StdFunctionAdapter::invoke)>>::FunctionType; - -template -inline StdFunction AsStdFunction(TFunctor&& functor) { - using TAdapter = details::StdFunctionAdapter::invoke)>>; - return TAdapter::Create(std::move(MakeSharedFunctorPtr(functor))); -} - -} // namespace node - -#endif - #endif // SRC_NODE_EMBEDDING_API_H_ diff --git a/src/node_embedding_api_cpp.h b/src/node_embedding_api_cpp.h new file mode 100644 index 00000000000000..c03796b24193de --- /dev/null +++ b/src/node_embedding_api_cpp.h @@ -0,0 +1,1125 @@ +// +// Description: C-based API for embedding Node.js. +// +// !!! WARNING !!! WARNING !!! WARNING !!! +// This is a new API and is subject to change. +// While it is C-based, it is not ABI safe yet. +// Consider all functions and data structures as experimental. +// !!! WARNING !!! WARNING !!! WARNING !!! +// +// This file contains the C-based API for embedding Node.js in a host +// application. The API is designed to be used by applications that want to +// embed Node.js as a shared library (.so or .dll) and can interop with +// C-based API. +// + +#ifndef SRC_NODE_EMBEDDING_API_CPP_H_ +#define SRC_NODE_EMBEDDING_API_CPP_H_ + +//============================================================================== +// The C++ wrappers for the Node.js embedding API. +//============================================================================== + +#include "node_embedding_api.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace node::embedding { + +//============================================================================== +// C++ convenience functions for the C API. +// These functions are not ABI safe and can be changed in future versions. +//============================================================================== + +// Move-only pointer wrapper. +// The class does not own the pointer and does not delete it. +// It simplifies implementation of the C++ API classes that wrap pointers. +template +class NodePointer { + public: + NodePointer() = default; + + explicit NodePointer(TPointer ptr) : ptr_(ptr) {} + + NodePointer(const NodePointer&) = delete; + NodePointer& operator=(const NodePointer&) = delete; + + NodePointer(NodePointer&& other) noexcept + : ptr_(std::exchange(other.ptr_, nullptr)) {} + + NodePointer& operator=(NodePointer&& other) noexcept { + if (this != &other) { + ptr_ = std::exchange(other.ptr_, nullptr); + } + return *this; + } + + NodePointer& operator=(std::nullptr_t) { + ptr_ = nullptr; + return *this; + } + + TPointer ptr() const { return ptr_; } + + explicit operator bool() const { return ptr_ != nullptr; } + + private: + TPointer ptr_{}; +}; + +template +class [[nodiscard]] NodeExpected { + public: + explicit NodeExpected(T value) : value_(std::move(value)) {} + + explicit NodeExpected(NodeStatus status) : status_(status) {} + + NodeExpected(const NodeExpected&) = delete; + NodeExpected& operator=(const NodeExpected&) = delete; + + NodeExpected(NodeExpected&& other) noexcept : status_(other.status_) { + if (other.has_value()) { + new (std::addressof(value_)) T(std::move(other.value_)); + } + } + + NodeExpected& operator=(NodeExpected&& other) noexcept { + if (this != &other) { + if (has_value()) { + value_.~T(); + } + status_ = other.status_; + if (other.has_value()) { + new (std::addressof(value_)) T(std::move(other.value_)); + } + } + return *this; + } + + ~NodeExpected() { + if (has_value()) { + value_.~T(); + } + } + + bool has_value() const { return status_ == NodeStatus::kOk; } + bool has_error() const { return status_ != NodeStatus::kOk; } + + T& value() & { return value_; } + const T& value() const& { return value_; } + T&& value() && { return std::move(value_); } + const T&& value() const&& { return std::move(value_); } + + NodeStatus status() const { return status_; } + + int32_t exit_code() const { + if (status_ == NodeStatus::kOk) { + return 0; + } else if ((static_cast(status_) & + static_cast(NodeStatus::kErrorExitCode)) != 0) { + return static_cast(status_) & + ~static_cast(NodeStatus::kErrorExitCode); + } + return 1; + } + + private: + NodeStatus status_{NodeStatus::kOk}; + union { + T value_; // The value is uninitialized if status_ is not kOk. + char padding_[sizeof(T)]; + }; +}; + +template <> +class [[nodiscard]] NodeExpected { + public: + NodeExpected() = default; + + explicit NodeExpected(NodeStatus status) : status_(status) {} + + NodeExpected(const NodeExpected&) = delete; + NodeExpected& operator=(const NodeExpected&) = delete; + + NodeExpected(NodeExpected&& other) = default; + NodeExpected& operator=(NodeExpected&& other) = default; + + bool has_value() const { return status_ == NodeStatus::kOk; } + bool has_error() const { return status_ != NodeStatus::kOk; } + + NodeStatus status() const { return status_; } + + int32_t exit_code() const { + if (status_ == NodeStatus::kOk) { + return 0; + } else if ((static_cast(status_) & + static_cast(NodeStatus::kErrorExitCode)) != 0) { + return static_cast(status_) & + ~static_cast(NodeStatus::kErrorExitCode); + } + return 1; + } + + private: + NodeStatus status_{NodeStatus::kOk}; +}; + +// A helper class to convert std::vector to an array of C strings. +// If the number of strings is less than kInplaceBufferSize, the strings are +// stored in the inplace_buffer_ array. Otherwise, the strings are stored in the +// allocated_buffer_ array. +// Ideally the class must be allocated on the stack. +// In any case it must not outlive the passed vector since it keeps only the +// string pointers returned by std::string::c_str() method. +template +class NodeCStringArray { + public: + NodeCStringArray() = default; + + explicit NodeCStringArray(const std::vector& strings) noexcept + : size_(strings.size()) { + if (size_ <= inplace_buffer_.size()) { + c_strs_ = inplace_buffer_.data(); + } else { + allocated_buffer_ = std::make_unique(size_); + c_strs_ = allocated_buffer_.get(); + } + for (size_t i = 0; i < size_; ++i) { + c_strs_[i] = strings[i].c_str(); + } + } + + NodeCStringArray(const NodeCStringArray&) = delete; + NodeCStringArray& operator=(const NodeCStringArray&) = delete; + + NodeCStringArray(NodeCStringArray&& other) noexcept + : size_(std::exchange(other.size_, 0)), + c_strs_(std::exchange(other.c_strs_, 0)), + allocated_buffer_(std::exchange(other.allocated_buffer_, nullptr)) { + if (size_ <= inplace_buffer_.size()) { + c_strs_ = inplace_buffer_.data(); + std::memcpy(inplace_buffer_.data(), + other.inplace_buffer_.data(), + size_ * sizeof(const char*)); + } + } + + NodeCStringArray& operator=(NodeCStringArray&& other) noexcept { + if (this != &other) { + size_ = std::exchange(other.size_, 0); + c_strs_ = std::exchange(other.c_strs_, nullptr); + allocated_buffer_ = std::exchange(other.allocated_buffer_, nullptr); + if (size_ <= inplace_buffer_.size()) { + c_strs_ = inplace_buffer_.data(); + std::memcpy(inplace_buffer_.data(), + other.inplace_buffer_.data(), + size_ * sizeof(const char*)); + } + } + return *this; + } + + int32_t size() const { return static_cast(size_); } + const char** c_strs() const { return c_strs_; } + + private: + size_t size_{}; + const char** c_strs_{}; + std::array inplace_buffer_; + std::unique_ptr allocated_buffer_; +}; + +// Wraps command line arguments. +class NodeArgs { + public: + NodeArgs(int32_t argc, const char* argv[]) : argc_(argc), argv_(argv) {} + + NodeArgs(int32_t argc, char* argv[]) + : argc_(argc), argv_(const_cast(argv)) {} + + NodeArgs(const NodeCStringArray<>& string_array_view) + : argc_(string_array_view.size()), argv_(string_array_view.c_strs()) {} + + int32_t argc() const { return argc_; } + const char** argv() const { return argv_; } + + private: + int32_t argc_{}; + const char** argv_{}; +}; + +template +class NodeFunctorInvoker; + +template +class NodeFunctorRef; + +template +class NodeFunctorRef { + using TCallback = TResult (*)(void*, TArgs...); + + public: + NodeFunctorRef(std::nullptr_t) {} + + NodeFunctorRef(TCallback callback, void* data) + : callback_(callback), data_(data) {} + + template + NodeFunctorRef(TFunctor&& functor) + : callback_(&NodeFunctorInvoker::Invoke), + data_(&functor) {} + + NodeFunctorRef(NodeFunctorRef&& other) = default; + NodeFunctorRef& operator=(NodeFunctorRef&& other) = default; + + TCallback callback() const { return callback_.ptr(); } + + void* data() const { return data_.ptr(); } + + explicit operator bool() const { return static_cast(callback_); } + + private: + NodePointer callback_; + NodePointer data_; +}; + +template +class NodeFunctor; + +template +class NodeFunctor { + using TCallback = TResult (*)(void*, TArgs...); + + public: + NodeFunctor() = default; + NodeFunctor(std::nullptr_t) {} + + NodeFunctor(TCallback callback, + void* data, + node_embedding_data_release_callback data_release) + : callback_(callback), data_(data), data_release_(data_release) {} + + // TODO: add overload for stateless lambdas. + template + NodeFunctor(TFunctor&& functor) + : callback_(&NodeFunctorInvoker::Invoke), + data_(new TFunctor(std::forward(functor))), + data_release_(&ReleaseFunctor) {} + + NodeFunctor(NodeFunctor&& other) = default; + NodeFunctor& operator=(NodeFunctor&& other) = default; + + TCallback callback() const { return callback_.ptr(); } + + void* data() const { return data_.ptr(); } + + node_embedding_data_release_callback data_release() const { + return data_release_.ptr(); + } + + explicit operator bool() const { return static_cast(callback_); } + + TResult operator()(TArgs... args) const { + return (*callback_.ptr())(data_.ptr(), args...); + } + + private: + template + static NodeStatus ReleaseFunctor(void* data) { + // TODO: Handle exceptions. + delete reinterpret_cast(data); + return NodeStatus::kOk; + } + + private: + NodePointer callback_; + NodePointer data_; + NodePointer data_release_; +}; + +// NodeConfigurePlatformCallback supported signatures: +// - NodeExpected(const NodePlatformConfig& platform_config); +using NodeConfigurePlatformCallback = + NodeFunctorRef; + +// NodeConfigureRuntimeCallback supported signatures: +// - NodeExpected(const NodePlatform& platform, +// const NodeRuntimeConfig& runtime_config); +using NodeConfigureRuntimeCallback = + NodeFunctorRef; + +// NodePreloadCallback supported signatures: +// - void(const NodeRuntime& runtime, +// napi_env env, +// napi_value process, +// napi_value require); +using NodePreloadCallback = + NodeFunctor; + +// NodeStartExecutionCallback supported signatures: +// - napi_value(const NodeRuntime& runtime, +// napi_env env, +// napi_value process, +// napi_value require, +// napi_value run_cjs); +using NodeStartExecutionCallback = + NodeFunctor; + +// NodeHandleExecutionResultCallback supported signatures: +// - void(const NodeRuntime& runtime, +// napi_env env, +// napi_value execution_result); +using NodeHandleExecutionResultCallback = + NodeFunctor; + +// NodeInitializeModuleCallback supported signatures: +// - napi_value(const NodeRuntime& runtime, +// napi_env env, +// std::string_view module_name, +// napi_value exports); +using NodeInitializeModuleCallback = + NodeFunctor; + +// NodeRunTaskCallback supported signatures: +// - NodeExpected(); +using NodeRunTaskCallback = NodeFunctor; + +// NodePostTaskCallback supported signatures: +// - NodeExpected(NodeRunTaskCallback run_task); +using NodePostTaskCallback = NodeFunctor; + +// NodeRunNodeApiCallback supported signatures: +// - void(const NodeRuntime& runtime, napi_env env); +using NodeRunNodeApiCallback = + NodeFunctorRef; + +inline std::string NodeFormatStringHelper(const char* format, va_list args) { + va_list args2; // Required for some compilers like GCC since we go over the + // args twice. + va_copy(args2, args); + std::string result(std::vsnprintf(nullptr, 0, format, args), '\0'); + std::vsnprintf(&result[0], result.size() + 1, format, args2); + va_end(args2); + return result; +} + +inline std::string NodeFormatString(const char* format, ...) { + va_list args; + va_start(args, format); + std::string result = NodeFormatStringHelper(format, args); + va_end(args); + return result; +} + +class NodeErrorInfo { + public: + static const char* GetLastErrorMessage() { + return node_embedding_last_error_message_get(); + } + + static void SetLastErrorMessage(const char* message) { + return node_embedding_last_error_message_set(message); + } + + static void SetLastErrorMessage(std::string_view message, + std::string_view filename, + int32_t line) { + SetLastErrorMessage( + NodeFormatString( + "Error: %s at %s:%d", message.data(), filename.data(), line) + .c_str()); + } + + static void SetLastErrorMessage(const std::vector& message) { + std::string message_str; + bool first = true; + for (const std::string& part : message) { + if (!first) { + message_str += '\n'; + } else { + first = false; + } + message_str += part; + } + SetLastErrorMessage(message_str.c_str()); + } + + static void ClearLastErrorMessage() { + node_embedding_last_error_message_set(nullptr); + } + + static std::string GetAndClearLastErrorMessage() { + std::string result = GetLastErrorMessage(); + ClearLastErrorMessage(); + return result; + } +}; + +class NodeEmbeddingErrorHandler { + public: + NodeEmbeddingErrorHandler() = default; + + ~NodeEmbeddingErrorHandler() { current_internal() = previous_handler_; } + + void SetLastErrorMessage(const char* format, ...) { + va_list args; + va_start(args, format); + std::string message = NodeFormatString(format, args); + va_end(args); + node_embedding_last_error_message_set(message.c_str()); + embedding_status_ = NodeStatus::kGenericError; + } + + NodeExpected ReportResult() const { + return NodeExpected(embedding_status_); + } + + bool has_embedding_error() const { + return embedding_status_ != NodeStatus::kOk; + } + + NodeStatus embedding_status() const { return embedding_status_; } + + void set_embedding_status(NodeStatus status) { embedding_status_ = status; } + + void set_embedding_status(const NodeExpected& expected) { + embedding_status_ = expected.status(); + } + + static NodeEmbeddingErrorHandler* current() { return current_internal(); } + + static void CurrentSetEmbeddingStatus(NodeStatus status) { + if (NodeEmbeddingErrorHandler* current = current_internal()) { + current->set_embedding_status(status); + } + } + + NodeEmbeddingErrorHandler(const NodeEmbeddingErrorHandler&) = delete; + NodeEmbeddingErrorHandler& operator=(const NodeEmbeddingErrorHandler&) = + delete; + + private: + // Declaring operator new and delete as deleted is not spec compliant. + // Therefore declare them private instead to disable dynamic alloc + void* operator new(size_t size) { std::abort(); } + void* operator new[](size_t size) { std::abort(); } + void operator delete(void*, size_t) { std::abort(); } + void operator delete[](void*, size_t) { std::abort(); } + + static NodeEmbeddingErrorHandler*& current_internal() { + static thread_local NodeEmbeddingErrorHandler* current_handler = nullptr; + return current_handler; + } + + private: + NodeEmbeddingErrorHandler* previous_handler_{ + std::exchange(current_internal(), this)}; + NodeStatus embedding_status_{NodeStatus::kOk}; +}; + +class NodeApiErrorHandlerBase { + public: + void SetLastErrorMessage(const char* format, ...) { + va_list args; + va_start(args, format); + std::string message = NodeFormatString(format, args); + va_end(args); + ThrowLastErrorMessage(env_, message.c_str()); + node_api_status_ = napi_pending_exception; + } + + static void GetAndThrowLastErrorMessage(napi_env env) { + const napi_extended_error_info* error_info; + napi_get_last_error_info(env, &error_info); + ThrowLastErrorMessage(env, error_info->error_message); + } + + static void ThrowLastErrorMessage(napi_env env, const char* message) { + bool is_pending; + napi_is_exception_pending(env, &is_pending); + /* If an exception is already pending, don't rethrow it */ + if (!is_pending) { + const char* error_message = + message != nullptr ? message : "empty error message"; + napi_throw_error(env, nullptr, error_message); + } + } + + napi_status ReportResult() const { return node_api_status_; } + + napi_env env() const { return env_; } + + bool has_node_api_error() const { return node_api_status_ != napi_ok; } + + napi_status node_api_status() const { return node_api_status_; } + + void set_node_api_status(napi_status status) { node_api_status_ = status; } + + bool has_embedding_error() const { + return embedding_status_ != NodeStatus::kOk; + } + + NodeStatus embedding_status() const { return embedding_status_; } + + void set_embedding_status(NodeStatus status) { + embedding_status_ = status; + ThrowLastErrorMessage(env_, node_embedding_last_error_message_get()); + node_api_status_ = napi_pending_exception; + } + + void set_embedding_status(const NodeExpected& expected) { + set_embedding_status(expected.status()); + } + + NodeApiErrorHandlerBase(const NodeApiErrorHandlerBase&) = delete; + NodeApiErrorHandlerBase& operator=(const NodeApiErrorHandlerBase&) = delete; + + protected: + NodeApiErrorHandlerBase(napi_env env) : env_(env) {} + + private: + // Declaring operator new and delete as deleted is not spec compliant. + // Therefore declare them private instead to disable dynamic alloc + void* operator new(size_t size) { std::abort(); } + void* operator new[](size_t size) { std::abort(); } + void operator delete(void*, size_t) { std::abort(); } + void operator delete[](void*, size_t) { std::abort(); } + + private: + napi_env env_{nullptr}; + napi_status node_api_status_{napi_ok}; + NodeStatus embedding_status_{NodeStatus::kOk}; +}; + +template +class NodeApiErrorHandler : public NodeApiErrorHandlerBase { + public: + NodeApiErrorHandler(napi_env env) : NodeApiErrorHandlerBase(env) {} + + TValue ReportResult() const { + if constexpr (std::is_same_v) { + return NodeApiErrorHandlerBase::ReportResult(); + } else { + GetAndThrowLastErrorMessage(env()); + return TValue(); + } + } +}; + +// Wraps the Node.js platform instance. +class NodePlatform { + public: + explicit NodePlatform(node_embedding_platform platform) + : platform_(platform) {} + + NodePlatform(NodePlatform&& other) = default; + NodePlatform& operator=(NodePlatform&& other) = default; + + ~NodePlatform() { + if (platform_) { + NodeStatus status = node_embedding_platform_delete(platform_.ptr()); + if (NodeEmbeddingErrorHandler* current = + NodeEmbeddingErrorHandler::current()) { + current->set_embedding_status(status); + } + } + } + + explicit operator bool() const { return static_cast(platform_); } + + operator node_embedding_platform() const { return platform_.ptr(); } + + node_embedding_platform Detach() { + return std::exchange(platform_, nullptr).ptr(); + } + + static NodeExpected RunMain( + NodeArgs args, + NodeConfigurePlatformCallback configure_platform, + NodeConfigureRuntimeCallback configure_runtime) { + return NodeExpected( + node_embedding_main_run(NODE_EMBEDDING_VERSION, + args.argc(), + args.argv(), + configure_platform.callback(), + configure_platform.data(), + configure_runtime.callback(), + configure_runtime.data())); + } + + static NodeExpected Create( + NodeArgs args, NodeConfigurePlatformCallback configure_platform) { + node_embedding_platform platform{}; + NodeStatus status = + node_embedding_platform_create(NODE_EMBEDDING_VERSION, + args.argc(), + args.argv(), + configure_platform.callback(), + configure_platform.data(), + &platform); + if (status != NodeStatus::kOk) { + return NodeExpected(status); + } + return NodeExpected(NodePlatform(platform)); + } + + NodeExpected> GetArgs() const { + int32_t argc = 0; + const char** argv = nullptr; + NodeStatus status = node_embedding_platform_get_parsed_args( + platform_.ptr(), &argc, &argv, nullptr, nullptr); + if (status != NodeStatus::kOk) { + return NodeExpected>(status); + } + return NodeExpected>( + std::vector(argv, argv + argc)); + } + + NodeExpected> GetRuntimeArgs() const { + int32_t argc = 0; + const char** argv = nullptr; + NodeStatus status = node_embedding_platform_get_parsed_args( + platform_.ptr(), nullptr, nullptr, &argc, &argv); + if (status != NodeStatus::kOk) { + return NodeExpected>(status); + } + return NodeExpected>( + std::vector(argv, argv + argc)); + } + + private: + NodePointer platform_; +}; + +// The NodePlatform that does not delete the platform on destruction. +class NodeDetachedPlatform : public NodePlatform { + public: + explicit NodeDetachedPlatform(node_embedding_platform platform) + : NodePlatform(platform) {} + + ~NodeDetachedPlatform() { Detach(); } +}; + +class NodePlatformConfig { + public: + explicit NodePlatformConfig(node_embedding_platform_config platform_config) + : platform_config_(platform_config) {} + + NodePlatformConfig(NodePlatformConfig&& other) = default; + NodePlatformConfig& operator=(NodePlatformConfig&& other) = default; + + operator node_embedding_platform_config() const { + return platform_config_.ptr(); + } + + NodeExpected SetFlags(NodePlatformFlags flags) const { + return NodeExpected(node_embedding_platform_config_set_flags( + platform_config_.ptr(), flags)); + } + + private: + NodePointer platform_config_{}; +}; + +class NodeApiScope { + public: + static NodeExpected Open(node_embedding_runtime runtime) { + node_embedding_node_api_scope node_api_scope{}; + napi_env env{}; + NodeStatus status = node_embedding_runtime_node_api_scope_open( + runtime, &node_api_scope, &env); + if (status != NodeStatus::kOk) { + return NodeExpected(status); + } + return NodeExpected( + NodeApiScope(runtime, node_api_scope, env)); + } + + explicit NodeApiScope(node_embedding_runtime runtime, + node_embedding_node_api_scope node_api_scope, + napi_env env) + : runtime_(runtime), node_api_scope_(node_api_scope), env_(env) {} + + NodeApiScope(NodeApiScope&&) = default; + NodeApiScope& operator=(NodeApiScope&&) = default; + + ~NodeApiScope() { + if (runtime_) { + NodeEmbeddingErrorHandler::CurrentSetEmbeddingStatus( + node_embedding_runtime_node_api_scope_close(runtime_.ptr(), + node_api_scope_.ptr())); + } + } + + napi_env env() const { return env_.ptr(); } + + private: + NodePointer runtime_; + NodePointer node_api_scope_; + NodePointer env_; +}; + +class NodeRuntime { + public: + static NodeExpected Run( + const NodePlatform& platform, + NodeConfigureRuntimeCallback configure_runtime) { + return NodeExpected(node_embedding_runtime_run( + static_cast(platform), + configure_runtime.callback(), + configure_runtime.data())); + } + + static NodeExpected Create( + const NodePlatform& platform, + NodeConfigureRuntimeCallback configure_runtime) { + node_embedding_runtime runtime; + NodeStatus status = + node_embedding_runtime_create(platform, + configure_runtime.callback(), + configure_runtime.data(), + &runtime); + if (status != NodeStatus::kOk) { + return NodeExpected(status); + } + return NodeExpected(NodeRuntime(runtime)); + } + + explicit NodeRuntime(node_embedding_runtime runtime) : runtime_(runtime) {} + + NodeRuntime(NodeRuntime&& other) = default; + NodeRuntime& operator=(NodeRuntime&& other) = default; + + ~NodeRuntime() { + if (runtime_) { + NodeEmbeddingErrorHandler::CurrentSetEmbeddingStatus( + node_embedding_runtime_delete(runtime_.ptr())); + ; + } + } + + operator node_embedding_runtime() const { return runtime_.ptr(); } + + node_embedding_runtime Detach() { + return std::exchange(runtime_, nullptr).ptr(); + } + + NodeExpected RunEventLoop() const { + return NodeExpected( + node_embedding_runtime_event_loop_run(runtime_.ptr())); + } + + NodeExpected TerminateEventLoop() const { + return NodeExpected( + node_embedding_runtime_event_loop_terminate(runtime_.ptr())); + } + + NodeExpected RunEventLoopOnce() const { + bool has_more_work{}; + NodeStatus status = node_embedding_runtime_event_loop_run_once( + runtime_.ptr(), &has_more_work); + if (status != NodeStatus::kOk) { + return NodeExpected(status); + } + return NodeExpected(has_more_work); + } + + NodeExpected RunEventLoopNoWait() const { + bool has_more_work{}; + NodeStatus status = node_embedding_runtime_event_loop_run_no_wait( + runtime_.ptr(), &has_more_work); + if (status != NodeStatus::kOk) { + return NodeExpected(status); + } + return NodeExpected(has_more_work); + } + + NodeExpected RunNodeApi(NodeRunNodeApiCallback run_node_api) const { + return NodeExpected(node_embedding_runtime_node_api_run( + runtime_.ptr(), run_node_api.callback(), run_node_api.data())); + } + + NodeExpected OpenNodeApiScope() const { + return NodeApiScope::Open(runtime_.ptr()); + } + + private: + NodePointer runtime_{}; +}; + +class NodeDetachedRuntime : public NodeRuntime { + public: + explicit NodeDetachedRuntime(node_embedding_runtime runtime) + : NodeRuntime(runtime) {} + ~NodeDetachedRuntime() { Detach(); } +}; + +class NodeRuntimeConfig { + public: + explicit NodeRuntimeConfig(node_embedding_runtime_config runtime_config) + : runtime_config_(runtime_config) {} + + NodeRuntimeConfig(NodeRuntimeConfig&& other) = default; + NodeRuntimeConfig& operator=(NodeRuntimeConfig&& other) = default; + + operator node_embedding_runtime_config() const { + return runtime_config_.ptr(); + } + + NodeExpected SetNodeApiVersion(int32_t node_api_version) const { + return NodeExpected( + node_embedding_runtime_config_set_node_api_version( + runtime_config_.ptr(), node_api_version)); + } + + NodeExpected SetFlags(NodeRuntimeFlags flags) const { + return NodeExpected( + node_embedding_runtime_config_set_flags(runtime_config_.ptr(), flags)); + } + + NodeExpected SetArgs(NodeArgs args, NodeArgs runtime_args) const { + return NodeExpected( + node_embedding_runtime_config_set_args(runtime_config_.ptr(), + args.argc(), + args.argv(), + runtime_args.argc(), + runtime_args.argv())); + } + + NodeExpected OnPreload(NodePreloadCallback preload) const { + return NodeExpected( + node_embedding_runtime_config_on_preload(runtime_config_.ptr(), + preload.callback(), + preload.data(), + preload.data_release())); + } + + NodeExpected OnStartExecution( + NodeStartExecutionCallback start_execution) const { + return NodeExpected(node_embedding_runtime_config_on_loading( + runtime_config_.ptr(), + start_execution.callback(), + start_execution.data(), + start_execution.data_release())); + } + + NodeExpected OnLoaded( + NodeHandleExecutionResultCallback handle_start_result) const { + return NodeExpected(node_embedding_runtime_config_on_loaded( + runtime_config_.ptr(), + handle_start_result.callback(), + handle_start_result.data(), + handle_start_result.data_release())); + } + + NodeExpected AddModule(std::string_view module_name, + NodeInitializeModuleCallback init_module, + int32_t moduleNodeApiVersion) const { + return NodeExpected( + node_embedding_runtime_config_add_module(runtime_config_.ptr(), + module_name.data(), + init_module.callback(), + init_module.data(), + init_module.data_release(), + moduleNodeApiVersion)); + } + + NodeExpected SetTaskRunner(NodePostTaskCallback post_task) const { + return NodeExpected(node_embedding_runtime_config_set_task_runner( + runtime_config_.ptr(), + post_task.callback(), + post_task.data(), + post_task.data_release())); + } + + private: + NodePointer runtime_config_{}; +}; + +template +class NodeFunctorInvoker< + node_embedding_platform_configure_callback, + TFunctor, + std::enable_if_t, + TFunctor, + const NodePlatformConfig&>>> { + public: + static NodeStatus Invoke(void* cb_data, + node_embedding_platform_config platform_config) { + TFunctor* callback = reinterpret_cast(cb_data); + NodePlatformConfig platform_config_cpp(platform_config); + NodeExpected result_cpp = (*callback)(platform_config_cpp); + return result_cpp.status(); + } +}; + +template +class NodeFunctorInvoker< + node_embedding_runtime_configure_callback, + TFunctor, + std::enable_if_t, + TFunctor, + const NodePlatform&, + const NodeRuntimeConfig&>>> { + public: + static NodeStatus Invoke(void* cb_data, + node_embedding_platform platform, + node_embedding_runtime_config runtime_config) { + TFunctor* callback = reinterpret_cast(cb_data); + NodeDetachedPlatform platform_cpp(platform); + NodeRuntimeConfig runtime_config_cpp(runtime_config); + NodeExpected result_cpp = + (*callback)(platform_cpp, runtime_config_cpp); + return result_cpp.status(); + } +}; + +template +class NodeFunctorInvoker< + node_embedding_runtime_preload_callback, + TFunctor, + std::enable_if_t>> { + public: + static void Invoke(void* cb_data, + node_embedding_runtime runtime, + napi_env env, + napi_value process, + napi_value require) { + TFunctor* callback = reinterpret_cast(cb_data); + NodeDetachedRuntime runtime_cpp(runtime); + (*callback)(runtime_cpp, env, process, require); + } +}; + +template +class NodeFunctorInvoker< + node_embedding_runtime_loading_callback, + TFunctor, + std::enable_if_t>> { + public: + static napi_value Invoke(void* cb_data, + node_embedding_runtime runtime, + napi_env env, + napi_value process, + napi_value require, + napi_value run_cjs) { + TFunctor* callback = reinterpret_cast(cb_data); + NodeDetachedRuntime runtime_cpp(runtime); + return (*callback)(runtime_cpp, env, process, require, run_cjs); + } +}; + +template +class NodeFunctorInvoker< + node_embedding_runtime_loaded_callback, + TFunctor, + std::enable_if_t>> { + public: + static void Invoke(void* cb_data, + node_embedding_runtime runtime, + napi_env env, + napi_value execution_result) { + TFunctor* callback = reinterpret_cast(cb_data); + NodeDetachedRuntime runtime_cpp(runtime); + (*callback)(runtime_cpp, env, execution_result); + } +}; + +template +class NodeFunctorInvoker< + node_embedding_module_initialize_callback, + TFunctor, + std::enable_if_t>> { + public: + static napi_value Invoke(void* cb_data, + node_embedding_runtime runtime, + napi_env env, + const char* module_name, + napi_value exports) { + TFunctor* callback = reinterpret_cast(cb_data); + NodeDetachedRuntime runtime_cpp(runtime); + return (*callback)(runtime_cpp, env, module_name, exports); + } +}; + +template +class NodeFunctorInvoker< + node_embedding_task_run_callback, + TFunctor, + std::enable_if_t, TFunctor>>> { + public: + static NodeStatus Invoke(void* cb_data) { + TFunctor* callback = reinterpret_cast(cb_data); + return (*callback)().status(); + } +}; + +template +class NodeFunctorInvoker< + node_embedding_task_post_callback, + TFunctor, + std::enable_if_t, + TFunctor, + NodeRunTaskCallback>>> { + public: + static NodeStatus Invoke( + void* cb_data, + node_embedding_task_run_callback run_task, + void* task_data, + node_embedding_data_release_callback release_task_data, + bool* is_posted) { + TFunctor* callback = reinterpret_cast(cb_data); + NodeExpected result_cpp = (*callback)( + NodeRunTaskCallback(run_task, task_data, release_task_data)); + if (is_posted != nullptr) { + *is_posted = result_cpp.value(); + } + return result_cpp.status(); + } +}; + +template +class NodeFunctorInvoker< + node_embedding_node_api_run_callback, + TFunctor, + std::enable_if_t>> { + public: + static void Invoke(void* cb_data, napi_env env) { + TFunctor* callback = reinterpret_cast(cb_data); + (*callback)(env); + } +}; + +} // namespace node::embedding + +#endif // SRC_NODE_EMBEDDING_API_CPP_H_ diff --git a/test/embedding/README.md b/test/embedding/README.md index 7b5612c3a8761d..4f82228484b47d 100644 --- a/test/embedding/README.md +++ b/test/embedding/README.md @@ -3,7 +3,7 @@ This file is an overview for C embedding API. It is mostly to catch all the work in progress. -## The API overview +## The API overview ### Error handling API - `node_embedding_on_error` diff --git a/test/embedding/cjs.cjs b/test/embedding/cjs.cjs deleted file mode 100644 index df0ddbf40cf291..00000000000000 --- a/test/embedding/cjs.cjs +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = { - value: "original" -}; diff --git a/test/embedding/embedtest.cc b/test/embedding/embedtest.cc index a0aed752d3146d..482451285625bf 100644 --- a/test/embedding/embedtest.cc +++ b/test/embedding/embedtest.cc @@ -3,6 +3,7 @@ #endif #include #include "node.h" +#include "uv.h" #include @@ -26,7 +27,7 @@ static int RunNodeInstance(MultiIsolatePlatform* platform, const std::vector& args, const std::vector& exec_args); -extern "C" int32_t test_main_cpp_api(int32_t argc, char* argv[]) { +int32_t test_main_cpp_api(int32_t argc, const char* argv[]) { std::vector args(argv, argv + argc); std::shared_ptr result = node::InitializeOncePerProcess( diff --git a/test/embedding/embedtest_c_api.c b/test/embedding/embedtest_c_api.c new file mode 100644 index 00000000000000..a865058e4f5735 --- /dev/null +++ b/test/embedding/embedtest_c_api.c @@ -0,0 +1,262 @@ +#include "embedtest_c_api_common.h" + +static napi_status CallMe(node_embedding_runtime runtime, napi_env env); +static napi_status WaitMe(node_embedding_runtime runtime, napi_env env); +static napi_status WaitMeWithCheese(node_embedding_runtime runtime, + napi_env env); + +static node_embedding_status ConfigurePlatform( + void* cb_data, node_embedding_platform_config platform_config) { + return node_embedding_platform_config_set_flags( + platform_config, node_embedding_platform_flags_disable_node_options_env); +} + +static void HandleExecutionResult(void* cb_data, + node_embedding_runtime runtime, + napi_env env, + napi_value execution_result) { + napi_status status = napi_ok; + NODE_API_CALL(CallMe(runtime, env)); + NODE_API_CALL(WaitMe(runtime, env)); + NODE_API_CALL(WaitMeWithCheese(runtime, env)); +on_exit: + GetAndThrowLastErrorMessage(env, status); +} + +static node_embedding_status ConfigureRuntime( + void* cb_data, + node_embedding_platform platform, + node_embedding_runtime_config runtime_config) { + node_embedding_status embedding_status = node_embedding_status_ok; + NODE_EMBEDDING_CALL(LoadUtf8Script(runtime_config, main_script)); + NODE_EMBEDDING_CALL(node_embedding_runtime_config_on_loaded( + runtime_config, HandleExecutionResult, NULL, NULL)); +on_exit: + return embedding_status; +} + +int32_t test_main_c_api(int32_t argc, const char* argv[]) { + node_embedding_status embedding_status = node_embedding_status_ok; + NODE_EMBEDDING_CALL(node_embedding_main_run(NODE_EMBEDDING_VERSION, + argc, + argv, + ConfigurePlatform, + NULL, + ConfigureRuntime, + NULL)); +on_exit: + return StatusToExitCode(PrintErrorMessage(argv[0], embedding_status)); +} + +static napi_status CallMe(node_embedding_runtime runtime, napi_env env) { + napi_status status = napi_ok; + napi_value global, cb, key; + + NODE_API_CALL(napi_get_global(env, &global)); + NODE_API_CALL(napi_create_string_utf8(env, "callMe", NAPI_AUTO_LENGTH, &key)); + NODE_API_CALL(napi_get_property(env, global, key, &cb)); + + napi_valuetype cb_type; + NODE_API_CALL(napi_typeof(env, cb, &cb_type)); + + // Only evaluate callMe if it was registered as a function. + if (cb_type == napi_function) { + napi_value undef; + NODE_API_CALL(napi_get_undefined(env, &undef)); + napi_value arg; + NODE_API_CALL( + napi_create_string_utf8(env, "called", NAPI_AUTO_LENGTH, &arg)); + napi_value result; + NODE_API_CALL(napi_call_function(env, undef, cb, 1, &arg, &result)); + + char buf[32]; + size_t len; + NODE_API_CALL(napi_get_value_string_utf8(env, result, buf, 32, &len)); + if (strcmp(buf, "called you") != 0) { + NODE_API_FAIL("Invalid value received: %s\n", buf); + } + printf("%s", buf); + } else if (cb_type != napi_undefined) { + NODE_API_FAIL("Invalid callMe value\n"); + } +on_exit: + return status; +} + +// TODO: remove static variables +char callback_buf[32]; +size_t callback_buf_len; +static napi_value c_cb(napi_env env, napi_callback_info info) { + napi_status status = napi_ok; + size_t argc = 1; + napi_value arg; + NODE_API_CALL(napi_get_cb_info(env, info, &argc, &arg, NULL, NULL)); + NODE_API_CALL(napi_get_value_string_utf8( + env, arg, callback_buf, 32, &callback_buf_len)); +on_exit: + GetAndThrowLastErrorMessage(env, status); + return NULL; +} + +static napi_status WaitMe(node_embedding_runtime runtime, napi_env env) { + napi_status status = napi_ok; + node_embedding_status embedding_status = node_embedding_status_ok; + napi_value global, cb, key; + + NODE_API_CALL(napi_get_global(env, &global)); + NODE_API_CALL(napi_create_string_utf8(env, "waitMe", NAPI_AUTO_LENGTH, &key)); + NODE_API_CALL(napi_get_property(env, global, key, &cb)); + + napi_valuetype cb_type; + NODE_API_CALL(napi_typeof(env, cb, &cb_type)); + + // Only evaluate waitMe if it was registered as a function. + if (cb_type == napi_function) { + napi_value undef; + NODE_API_CALL(napi_get_undefined(env, &undef)); + napi_value args[2]; + NODE_API_CALL( + napi_create_string_utf8(env, "waited", NAPI_AUTO_LENGTH, &args[0])); + NODE_API_CALL(napi_create_function( + env, "wait_cb", strlen("wait_cb"), c_cb, NULL, &args[1])); + + napi_value result; + memset(callback_buf, 0, 32); + NODE_API_CALL(napi_call_function(env, undef, cb, 2, args, &result)); + if (strcmp(callback_buf, "waited you") == 0) { + NODE_API_FAIL("Anachronism detected: %s\n", callback_buf); + } + + bool has_more_events = true; + while (has_more_events) { + NODE_EMBEDDING_CALL(node_embedding_runtime_event_loop_run_once( + runtime, &has_more_events)); + } + + if (strcmp(callback_buf, "waited you") != 0) { + NODE_API_FAIL("Invalid value received: %s\n", callback_buf); + } + printf("%s", callback_buf); + } else if (cb_type != napi_undefined) { + NODE_API_FAIL("Invalid waitMe value\n"); + } + +on_exit: + if (embedding_status != node_embedding_status_ok) { + NODE_API_FAIL("WaitMe failed: %s\n", + node_embedding_last_error_message_get()); + } + return status; +} + +typedef enum { + kPromiseStatePending, + kPromiseStateFulfilled, + kPromiseStateRejected, +} PromiseState; + +static napi_value OnFulfilled(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value result; + void* data; + napi_get_cb_info(env, info, &argc, &result, NULL, &data); + napi_get_value_string_utf8(env, result, callback_buf, 32, &callback_buf_len); + *(PromiseState*)data = kPromiseStateFulfilled; + return NULL; +} + +static napi_value OnRejected(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value result; + void* data; + napi_get_cb_info(env, info, &argc, &result, NULL, &data); + napi_get_value_string_utf8(env, result, callback_buf, 32, &callback_buf_len); + *(PromiseState*)data = kPromiseStateRejected; + return NULL; +} + +static napi_status WaitMeWithCheese(node_embedding_runtime runtime, + napi_env env) { + napi_status status = napi_ok; + node_embedding_status embedding_status = node_embedding_status_ok; + PromiseState promise_state = kPromiseStatePending; + napi_value global, wait_promise, undefined; + napi_value on_fulfilled, on_rejected; + napi_value then_args[2]; + const char* expected; + + NODE_API_CALL(napi_get_global(env, &global)); + NODE_API_CALL(napi_get_undefined(env, &undefined)); + + NODE_API_CALL( + napi_get_named_property(env, global, "waitPromise", &wait_promise)); + + napi_valuetype wait_promise_type; + NODE_API_CALL(napi_typeof(env, wait_promise, &wait_promise_type)); + + // Only evaluate waitPromise if it was registered as a function. + if (wait_promise_type == napi_undefined) { + return napi_ok; + } else if (wait_promise_type != napi_function) { + NODE_API_FAIL("Invalid waitPromise value\n"); + } + + napi_value arg; + NODE_API_CALL(napi_create_string_utf8(env, "waited", NAPI_AUTO_LENGTH, &arg)); + + memset(callback_buf, 0, 32); + napi_value promise; + NODE_API_CALL( + napi_call_function(env, undefined, wait_promise, 1, &arg, &promise)); + + if (strcmp(callback_buf, "waited with cheese") == 0) { + NODE_API_FAIL("Anachronism detected: %s\n", callback_buf); + } + + bool is_promise; + NODE_API_CALL(napi_is_promise(env, promise, &is_promise)); + if (!is_promise) { + NODE_API_FAIL("Result is not a Promise\n"); + } + + NODE_API_CALL(napi_create_function(env, + "onFulfilled", + NAPI_AUTO_LENGTH, + OnFulfilled, + &promise_state, + &on_fulfilled)); + NODE_API_CALL(napi_create_function(env, + "rejected", + NAPI_AUTO_LENGTH, + OnRejected, + &promise_state, + &on_rejected)); + napi_value then; + NODE_API_CALL(napi_get_named_property(env, promise, "then", &then)); + then_args[0] = on_fulfilled; + then_args[1] = on_rejected; + NODE_API_CALL(napi_call_function(env, promise, then, 2, then_args, NULL)); + + bool has_more_events = true; + while (has_more_events && promise_state == kPromiseStatePending) { + NODE_EMBEDDING_CALL( + node_embedding_runtime_event_loop_run_once(runtime, &has_more_events)); + } + + expected = (promise_state == kPromiseStateFulfilled) + ? "waited with cheese" + : "waited without cheese"; + + if (strcmp(callback_buf, expected) != 0) { + NODE_API_FAIL("Invalid value received: %s\n", callback_buf); + } + printf("%s", callback_buf); + +on_exit: + if (embedding_status != node_embedding_status_ok) { + ThrowLastErrorMessage(env, + "WaitMeWithCheese failed: %s", + node_embedding_last_error_message_get()); + } + return status; +} diff --git a/test/embedding/embedtest_c_api.cc b/test/embedding/embedtest_c_api.cc deleted file mode 100644 index 092ac72efa7880..00000000000000 --- a/test/embedding/embedtest_c_api.cc +++ /dev/null @@ -1,239 +0,0 @@ -#include "embedtest_c_api_common.h" - -#include -#include -#include -#include - -using namespace node; - -void CallMe(node_embedding_runtime runtime, napi_env env); -void WaitMe(node_embedding_runtime runtime, napi_env env); -void WaitMeWithCheese(node_embedding_runtime runtime, napi_env env); - -extern "C" int32_t test_main_node_api(int32_t argc, char* argv[]) { - node_embedding_on_error({argv[0], HandleTestError, nullptr}); - - CHECK_STATUS_OR_EXIT(node_embedding_run_main( - argc, - argv, - AsFunctorRef( - [&](node_embedding_platform_config platform_config) { - CHECK_STATUS(node_embedding_platform_set_flags( - platform_config, - node_embedding_platform_flags_disable_node_options_env)); - return node_embedding_status_ok; - }), - AsFunctorRef( - [&](node_embedding_platform platform, - node_embedding_runtime_config runtime_config) { - CHECK_STATUS( - LoadUtf8Script(runtime_config, - main_script, - AsFunctor( - [&](node_embedding_runtime runtime, - napi_env env, - napi_value /*value*/) { - CallMe(runtime, env); - WaitMe(runtime, env); - WaitMeWithCheese(runtime, env); - }))); - return node_embedding_status_ok; - }))); - - return node_embedding_status_ok; -} - -void CallMe(node_embedding_runtime runtime, napi_env env) { - napi_value global; - napi_value cb; - napi_value key; - - NODE_API_CALL_RETURN_VOID(napi_get_global(env, &global)); - NODE_API_CALL_RETURN_VOID( - napi_create_string_utf8(env, "callMe", NAPI_AUTO_LENGTH, &key)); - NODE_API_CALL_RETURN_VOID(napi_get_property(env, global, key, &cb)); - - napi_valuetype cb_type; - NODE_API_CALL_RETURN_VOID(napi_typeof(env, cb, &cb_type)); - - // Only evaluate callMe if it was registered as a function. - if (cb_type == napi_function) { - napi_value undef; - NODE_API_CALL_RETURN_VOID(napi_get_undefined(env, &undef)); - napi_value arg; - NODE_API_CALL_RETURN_VOID( - napi_create_string_utf8(env, "called", NAPI_AUTO_LENGTH, &arg)); - napi_value result; - NODE_API_CALL_RETURN_VOID( - napi_call_function(env, undef, cb, 1, &arg, &result)); - - char buf[32]; - size_t len; - NODE_API_CALL_RETURN_VOID( - napi_get_value_string_utf8(env, result, buf, 32, &len)); - if (strcmp(buf, "called you") != 0) { - NODE_API_FAIL_RETURN_VOID("Invalid value received: %s\n", buf); - } - printf("%s", buf); - } else if (cb_type != napi_undefined) { - NODE_API_FAIL_RETURN_VOID("Invalid callMe value\n"); - } -} - -char callback_buf[32]; -size_t callback_buf_len; -napi_value c_cb(napi_env env, napi_callback_info info) { - size_t argc = 1; - napi_value arg; - NODE_API_CALL(napi_get_cb_info(env, info, &argc, &arg, nullptr, nullptr)); - NODE_API_CALL(napi_get_value_string_utf8( - env, arg, callback_buf, 32, &callback_buf_len)); - return nullptr; -} - -void WaitMe(node_embedding_runtime runtime, napi_env env) { - napi_value global; - napi_value cb; - napi_value key; - - NODE_API_CALL_RETURN_VOID(napi_get_global(env, &global)); - NODE_API_CALL_RETURN_VOID( - napi_create_string_utf8(env, "waitMe", NAPI_AUTO_LENGTH, &key)); - NODE_API_CALL_RETURN_VOID(napi_get_property(env, global, key, &cb)); - - napi_valuetype cb_type; - NODE_API_CALL_RETURN_VOID(napi_typeof(env, cb, &cb_type)); - - // Only evaluate waitMe if it was registered as a function. - if (cb_type == napi_function) { - napi_value undef; - NODE_API_CALL_RETURN_VOID(napi_get_undefined(env, &undef)); - napi_value args[2]; - NODE_API_CALL_RETURN_VOID( - napi_create_string_utf8(env, "waited", NAPI_AUTO_LENGTH, &args[0])); - NODE_API_CALL_RETURN_VOID(napi_create_function( - env, "wait_cb", strlen("wait_cb"), c_cb, NULL, &args[1])); - - napi_value result; - memset(callback_buf, 0, 32); - NODE_API_CALL_RETURN_VOID( - napi_call_function(env, undef, cb, 2, args, &result)); - if (strcmp(callback_buf, "waited you") == 0) { - NODE_API_FAIL_RETURN_VOID("Anachronism detected: %s\n", callback_buf); - } - - node_embedding_run_event_loop( - runtime, node_embedding_event_loop_run_mode_default, nullptr); - - if (strcmp(callback_buf, "waited you") != 0) { - NODE_API_FAIL_RETURN_VOID("Invalid value received: %s\n", callback_buf); - } - printf("%s", callback_buf); - } else if (cb_type != napi_undefined) { - NODE_API_FAIL_RETURN_VOID("Invalid waitMe value\n"); - } -} - -void WaitMeWithCheese(node_embedding_runtime runtime, napi_env env) { - enum class PromiseState { - kPending, - kFulfilled, - kRejected, - }; - - PromiseState promise_state = PromiseState::kPending; - napi_value global{}, wait_promise{}, undefined{}; - napi_value on_fulfilled{}, on_rejected{}; - napi_value then_args[2] = {}; - const char* expected{}; - - NODE_API_CALL_RETURN_VOID(napi_get_global(env, &global)); - NODE_API_CALL_RETURN_VOID(napi_get_undefined(env, &undefined)); - - NODE_API_CALL_RETURN_VOID( - napi_get_named_property(env, global, "waitPromise", &wait_promise)); - - napi_valuetype wait_promise_type; - NODE_API_CALL_RETURN_VOID(napi_typeof(env, wait_promise, &wait_promise_type)); - - // Only evaluate waitPromise if it was registered as a function. - if (wait_promise_type == napi_undefined) { - return; - } else if (wait_promise_type != napi_function) { - NODE_API_FAIL_RETURN_VOID("Invalid waitPromise value\n"); - } - - napi_value arg; - NODE_API_CALL_RETURN_VOID( - napi_create_string_utf8(env, "waited", NAPI_AUTO_LENGTH, &arg)); - - memset(callback_buf, 0, 32); - napi_value promise; - NODE_API_CALL_RETURN_VOID( - napi_call_function(env, undefined, wait_promise, 1, &arg, &promise)); - - if (strcmp(callback_buf, "waited with cheese") == 0) { - NODE_API_FAIL_RETURN_VOID("Anachronism detected: %s\n", callback_buf); - } - - bool is_promise; - NODE_API_CALL_RETURN_VOID(napi_is_promise(env, promise, &is_promise)); - if (!is_promise) { - NODE_API_FAIL_RETURN_VOID("Result is not a Promise\n"); - } - - NODE_API_CALL_RETURN_VOID(napi_create_function( - env, - "onFulfilled", - NAPI_AUTO_LENGTH, - [](napi_env env, napi_callback_info info) -> napi_value { - size_t argc = 1; - napi_value result; - void* data; - napi_get_cb_info(env, info, &argc, &result, nullptr, &data); - napi_get_value_string_utf8( - env, result, callback_buf, 32, &callback_buf_len); - *static_cast(data) = PromiseState::kFulfilled; - return nullptr; - }, - &promise_state, - &on_fulfilled)); - NODE_API_CALL_RETURN_VOID(napi_create_function( - env, - "rejected", - NAPI_AUTO_LENGTH, - [](napi_env env, napi_callback_info info) -> napi_value { - size_t argc = 1; - napi_value result; - void* data; - napi_get_cb_info(env, info, &argc, &result, nullptr, &data); - napi_get_value_string_utf8( - env, result, callback_buf, 32, &callback_buf_len); - *static_cast(data) = PromiseState::kRejected; - return nullptr; - }, - &promise_state, - &on_rejected)); - napi_value then; - NODE_API_CALL_RETURN_VOID( - napi_get_named_property(env, promise, "then", &then)); - then_args[0] = on_fulfilled; - then_args[1] = on_rejected; - NODE_API_CALL_RETURN_VOID( - napi_call_function(env, promise, then, 2, then_args, nullptr)); - - while (promise_state == PromiseState::kPending) { - node_embedding_run_event_loop( - runtime, node_embedding_event_loop_run_mode_nowait, nullptr); - } - - expected = (promise_state == PromiseState::kFulfilled) - ? "waited with cheese" - : "waited without cheese"; - - if (strcmp(callback_buf, expected) != 0) { - NODE_API_FAIL_RETURN_VOID("Invalid value received: %s\n", callback_buf); - } - printf("%s", callback_buf); -} diff --git a/test/embedding/embedtest_c_api_common.c b/test/embedding/embedtest_c_api_common.c new file mode 100644 index 00000000000000..f87d72176b86bd --- /dev/null +++ b/test/embedding/embedtest_c_api_common.c @@ -0,0 +1,141 @@ +#include "embedtest_c_api_common.h" + +// #include +#include +// #include +// #include + +const char* main_script = + "globalThis.require = require('module').createRequire(process.execPath);\n" + "globalThis.embedVars = { nön_ascıı: '🏳️‍🌈' };\n" + "require('vm').runInThisContext(process.argv[1]);"; + +napi_status GetAndThrowLastErrorMessage(napi_env env, napi_status status) { + if (status == napi_ok) { + return status; + } + const napi_extended_error_info* error_info; + napi_get_last_error_info(env, &error_info); + ThrowLastErrorMessage(env, error_info->error_message); + return status; +} + +void ThrowLastErrorMessage(napi_env env, const char* format, ...) { + bool is_pending; + napi_is_exception_pending(env, &is_pending); + /* If an exception is already pending, don't rethrow it */ + if (is_pending) { + return; + } + char error_message_buf[1024]; + char* error_message = error_message_buf; + const char* error_format = format != NULL ? format : "empty error message"; + + // TODO: use dynamic_string_t + va_list args1; + va_start(args1, format); + va_list args2; // Required for some compilers like GCC since we go over the + // args twice. + va_copy(args2, args1); + int32_t error_message_size = vsnprintf(NULL, 0, error_format, args1); + if (error_message_size > 1024 - 1) { + error_message = (char*)malloc(error_message_size + 1); + } + va_end(args1); + vsnprintf(error_message, error_message_size + 1, error_format, args2); + va_end(args2); + + napi_throw_error(env, NULL, error_message); + + if (error_message_size > 1024 - 1) { + free(error_message); + } +} + +static napi_value LoadScript(void* cb_data, + node_embedding_runtime runtime, + napi_env env, + napi_value process, + napi_value require, + napi_value run_cjs) { + napi_status status = napi_ok; + napi_value script_value, null_value; + napi_value result = NULL; + const char* script = (const char*)cb_data; + NODE_API_CALL( + napi_create_string_utf8(env, script, NAPI_AUTO_LENGTH, &script_value)); + NODE_API_CALL(napi_get_null(env, &null_value)); + NODE_API_CALL( + napi_call_function(env, null_value, run_cjs, 1, &script_value, &result)); +on_exit: + GetAndThrowLastErrorMessage(env, status); + return result; +} + +node_embedding_status LoadUtf8Script( + node_embedding_runtime_config runtime_config, const char* script) { + node_embedding_status embedding_status = node_embedding_status_ok; + NODE_EMBEDDING_CALL(node_embedding_runtime_config_on_loading( + runtime_config, LoadScript, (void*)script, NULL)); +on_exit: + return embedding_status; +} + +int32_t StatusToExitCode(node_embedding_status status) { + if (status == node_embedding_status_ok) { + return 0; + } else if ((status & node_embedding_status_error_exit_code) != 0) { + return status & ~node_embedding_status_error_exit_code; + } + return 1; +} + +node_embedding_status PrintErrorMessage(const char* exe_name, + node_embedding_status status) { + const char* error_message = node_embedding_last_error_message_get(); + if (status != node_embedding_status_ok) { + fprintf(stderr, "%s: %s\n", exe_name, error_message); + } else if (error_message != NULL) { + fprintf(stdout, "%s", error_message); + } + node_embedding_last_error_message_set(NULL); + return status; +} + +void dynamic_string_init(dynamic_string_t* str) { + if (str == NULL) return; + str->data = str->buffer; + str->length = 0; + str->buffer[0] = '\0'; +} + +void dynamic_string_destroy(dynamic_string_t* str) { + if (str == NULL) return; + if (str->data != str->buffer) { + free(str->data); + } + dynamic_string_init(str); +} + +void dynamic_string_set(dynamic_string_t* str, const char* value) { + if (str == NULL) return; + dynamic_string_destroy(str); + dynamic_string_append(str, value); +} + +void dynamic_string_append(dynamic_string_t* str, const char* value) { + if (str == NULL) return; + if (value == NULL) return; + size_t new_length = str->length + strlen(value); + char* new_data = (new_length + 1 > DYNAMIC_STRING_BUFFER_SIZE) + ? new_data = (char*)malloc(new_length + 1) + : str->buffer; + if (new_data == NULL) return; + strcpy(new_data, str->data); + strcpy(new_data + str->length, value); + if (str->data != str->buffer) { + free(str->data); + } + str->data = new_data; + str->length = new_length; +} diff --git a/test/embedding/embedtest_c_api_common.cc b/test/embedding/embedtest_c_api_common.cc deleted file mode 100644 index 732c53cc64b568..00000000000000 --- a/test/embedding/embedtest_c_api_common.cc +++ /dev/null @@ -1,87 +0,0 @@ -#include "embedtest_c_api_common.h" - -#include -#include -#include -#include - -using namespace node; - -const char* main_script = - "globalThis.require = require('module').createRequire(process.execPath);\n" - "globalThis.embedVars = { nön_ascıı: '🏳️‍🌈' };\n" - "require('vm').runInThisContext(process.argv[1]);"; - -napi_status AddUtf8String(std::string& str, napi_env env, napi_value value) { - size_t str_size = 0; - napi_status status = - napi_get_value_string_utf8(env, value, nullptr, 0, &str_size); - if (status != napi_ok) { - return status; - } - size_t offset = str.size(); - str.resize(offset + str_size); - status = napi_get_value_string_utf8( - env, value, &str[0] + offset, str_size + 1, &str_size); - return status; -} - -void GetAndThrowLastErrorMessage(napi_env env) { - const napi_extended_error_info* error_info; - napi_get_last_error_info(env, &error_info); - bool is_pending; - const char* err_message = error_info->error_message; - napi_is_exception_pending((env), &is_pending); - /* If an exception is already pending, don't rethrow it */ - if (!is_pending) { - const char* error_message = - err_message != nullptr ? err_message : "empty error message"; - napi_throw_error((env), nullptr, error_message); - } -} - -void ThrowLastErrorMessage(napi_env env, const char* message) { - bool is_pending; - napi_is_exception_pending(env, &is_pending); - /* If an exception is already pending, don't rethrow it */ - if (!is_pending) { - const char* error_message = - message != nullptr ? message : "empty error message"; - napi_throw_error(env, nullptr, error_message); - } -} - -std::string FormatString(const char* format, ...) { - va_list args1; - va_start(args1, format); - va_list args2; - va_copy(args2, args1); // Required for some compilers like GCC. - std::string result(std::vsnprintf(nullptr, 0, format, args1), '\0'); - va_end(args1); - std::vsnprintf(&result[0], result.size() + 1, format, args2); - va_end(args2); - return result; -} - -node_embedding_status LoadUtf8Script( - node_embedding_runtime_config runtime_config, - std::string script, - const node_embedding_handle_result_functor& handle_result) { - return node_embedding_runtime_on_start_execution( - runtime_config, - AsFunctor( - [script = std::move(script)](node_embedding_runtime /*runtime*/, - napi_env env, - napi_value /*process*/, - napi_value /*require*/, - napi_value run_cjs) -> napi_value { - napi_value script_value, null_value, result; - NODE_API_CALL(napi_create_string_utf8( - env, script.c_str(), script.size(), &script_value)); - NODE_API_CALL(napi_get_null(env, &null_value)); - NODE_API_CALL(napi_call_function( - env, null_value, run_cjs, 1, &script_value, &result)); - return result; - }), - handle_result); -} diff --git a/test/embedding/embedtest_c_api_common.h b/test/embedding/embedtest_c_api_common.h index ee53b6f092ba9f..da13a2c0ae2020 100644 --- a/test/embedding/embedtest_c_api_common.h +++ b/test/embedding/embedtest_c_api_common.h @@ -1,168 +1,86 @@ -#ifndef TEST_EMBEDDING_EMBEDTEST_NODE_API_H_ -#define TEST_EMBEDDING_EMBEDTEST_NODE_API_H_ +#ifndef TEST_EMBEDDING_EMBEDTEST_C_API_COMMON_H_ +#define TEST_EMBEDDING_EMBEDTEST_C_API_COMMON_H_ #define NAPI_EXPERIMENTAL -#include -#ifdef __cplusplus - -#include -#include -#include - -template -class CStringArray { - public: - explicit CStringArray(const std::vector& strings) noexcept - : size_(strings.size()) { - if (size_ <= inplace_buffer_.size()) { - c_strs_ = inplace_buffer_.data(); - } else { - allocated_buffer_ = std::make_unique(size_); - c_strs_ = allocated_buffer_.get(); - } - for (size_t i = 0; i < size_; ++i) { - c_strs_[i] = strings[i].c_str(); - } - } - - CStringArray(const CStringArray&) = delete; - CStringArray& operator=(const CStringArray&) = delete; - - const char** c_strs() const { return c_strs_; } - size_t size() const { return size_; } - - const char** argv() const { return c_strs_; } - int32_t argc() const { return static_cast(size_); } - - private: - const char** c_strs_{}; - size_t size_{}; - std::array inplace_buffer_; - std::unique_ptr allocated_buffer_; -}; - -extern "C" inline node_embedding_status NAPI_CDECL -HandleTestError(void* handler_data, - const char* messages[], - size_t messages_size, - node_embedding_status status) { - const char* exe_name = static_cast(handler_data); - if (status != node_embedding_status_ok) { - for (size_t i = 0; i < messages_size; ++i) { - fprintf(stderr, "%s: %s\n", exe_name, messages[i]); - } - int32_t exit_code = ((status & node_embedding_status_error_exit_code) != 0) - ? status & ~node_embedding_status_error_exit_code - : 1; - exit(exit_code); - } else { - for (size_t i = 0; i < messages_size; ++i) { - printf("%s\n", messages[i]); - } - } - return node_embedding_status_ok; -} - -#endif // __cplusplus +#include +#include +#include +#include extern const char* main_script; -napi_status AddUtf8String(std::string& str, napi_env env, napi_value value); - -void GetAndThrowLastErrorMessage(napi_env env); - -void ThrowLastErrorMessage(napi_env env, const char* message); +int32_t StatusToExitCode(node_embedding_status status); -std::string FormatString(const char* format, ...); +node_embedding_status PrintErrorMessage(const char* exe_name, + node_embedding_status status); node_embedding_status LoadUtf8Script( - node_embedding_runtime_config runtime_config, - std::string script, - const node_embedding_handle_result_functor& handle_result = {}); + node_embedding_runtime_config runtime_config, const char* script); -// -// Error handling macros copied from test/js_native_api/common.h -// +napi_status GetAndThrowLastErrorMessage(napi_env env, napi_status status); -// Empty value so that macros here are able to return NULL or void -#define NODE_API_RETVAL_NOTHING // Intentionally blank #define +void ThrowLastErrorMessage(napi_env env, const char* format, ...); -#define NODE_API_FAIL_BASE(ret_val, ...) \ - do { \ - ThrowLastErrorMessage(env, FormatString(__VA_ARGS__).c_str()); \ - return ret_val; \ - } while (0) +#define DYNAMIC_STRING_BUFFER_SIZE 256 -// Returns NULL on failed assertion. -// This is meant to be used inside napi_callback methods. -#define NODE_API_FAIL(...) NODE_API_FAIL_BASE(NULL, __VA_ARGS__) +typedef struct { + char* data; + size_t length; + char buffer[DYNAMIC_STRING_BUFFER_SIZE]; +} dynamic_string_t; -// Returns empty on failed assertion. -// This is meant to be used inside functions with void return type. -#define NODE_API_FAIL_RETURN_VOID(...) \ - NODE_API_FAIL_BASE(NODE_API_RETVAL_NOTHING, __VA_ARGS__) +void dynamic_string_init(dynamic_string_t* str); +void dynamic_string_destroy(dynamic_string_t* str); +void dynamic_string_set(dynamic_string_t* str, const char* value); +void dynamic_string_append(dynamic_string_t* str, const char* value); -#define NODE_API_ASSERT_BASE(expr, ret_val) \ +//============================================================================== +// Error handing macros +//============================================================================== + +#define NODE_API_CALL(expr) \ do { \ - if (!(expr)) { \ - napi_throw_error(env, NULL, "Failed: (" #expr ")"); \ - return ret_val; \ + status = (expr); \ + if (status != napi_ok) { \ + goto on_exit; \ } \ } while (0) -// Returns NULL on failed assertion. -// This is meant to be used inside napi_callback methods. -#define NODE_API_ASSERT(expr) NODE_API_ASSERT_BASE(expr, NULL) - -// Returns empty on failed assertion. -// This is meant to be used inside functions with void return type. -#define NODE_API_ASSERT_RETURN_VOID(expr) \ - NODE_API_ASSERT_BASE(expr, NODE_API_RETVAL_NOTHING) - -#define NODE_API_CALL_BASE(expr, ret_val) \ +// TODO: The GetAndThrowLastErrorMessage is not going to work in this mode. +// TODO: Use the napi_is_exception_pending to check if there is an exception +#define NODE_API_ASSERT(expr) \ do { \ - if ((expr) != napi_ok) { \ - GetAndThrowLastErrorMessage(env); \ - return ret_val; \ + if (!(expr)) { \ + status = napi_generic_failure; \ + napi_throw_error(env, NULL, "Failed: (" #expr ")"); \ + goto on_exit; \ } \ } while (0) -// Returns NULL if the_call doesn't return napi_ok. -#define NODE_API_CALL(expr) NODE_API_CALL_BASE(expr, NULL) - -// Returns empty if the_call doesn't return napi_ok. -#define NODE_API_CALL_RETURN_VOID(expr) \ - NODE_API_CALL_BASE(expr, NODE_API_RETVAL_NOTHING) - -#define CHECK_STATUS(expr) \ +#define NODE_API_FAIL(format, ...) \ do { \ - node_embedding_status status_ = (expr); \ - if (status_ != node_embedding_status_ok) { \ - return status_; \ - } \ + status = napi_generic_failure; \ + ThrowLastErrorMessage(env, format, ##__VA_ARGS__); \ + goto on_exit; \ } while (0) -#define CHECK_STATUS_OR_EXIT(expr) \ +#define NODE_EMBEDDING_CALL(expr) \ do { \ - node_embedding_status status_ = (expr); \ - if (status_ != node_embedding_status_ok) { \ - int32_t exit_code = \ - ((status_ & node_embedding_status_error_exit_code) != 0) \ - ? status_ & ~node_embedding_status_error_exit_code \ - : 1; \ - exit(exit_code); \ + embedding_status = (expr); \ + if (embedding_status != node_embedding_status_ok) { \ + goto on_exit; \ } \ } while (0) -#define ASSERT_OR_EXIT(expr) \ +#define NODE_EMBEDDING_ASSERT(expr) \ do { \ if (!(expr)) { \ - fprintf(stderr, "Failed: %s\n", #expr); \ - fprintf(stderr, "File: %s\n", __FILE__); \ - fprintf(stderr, "Line: %d\n", __LINE__); \ - exit(1); \ + embedding_status = node_embedding_status_generic_error; \ + node_embedding_last_error_message_set_format( \ + "Failed: %s\nFile: %s\nLine: %d\n", #expr, __FILE__, __LINE__); \ + goto on_exit; \ } \ } while (0) -#endif // TEST_EMBEDDING_EMBEDTEST_NODE_API_H_ +#endif // TEST_EMBEDDING_EMBEDTEST_C_API_COMMON_H_ diff --git a/test/embedding/embedtest_c_api_env.c b/test/embedding/embedtest_c_api_env.c new file mode 100644 index 00000000000000..21343ff2dcafbf --- /dev/null +++ b/test/embedding/embedtest_c_api_env.c @@ -0,0 +1,140 @@ +#include "embedtest_c_api_common.h" + +static node_embedding_status ConfigureRuntimeNoBrowserGlobals( + void* cb_data, + node_embedding_platform platform, + node_embedding_runtime_config runtime_config) { + node_embedding_status embedding_status = node_embedding_status_ok; + NODE_EMBEDDING_CALL(node_embedding_runtime_config_set_flags( + runtime_config, node_embedding_runtime_flags_no_browser_globals)); + NODE_EMBEDDING_CALL(LoadUtf8Script( + runtime_config, + "const assert = require('assert');\n" + "const path = require('path');\n" + "const relativeRequire =" + " require('module').createRequire(path.join(process.cwd(), 'stub.js'));\n" + "const { intrinsics, nodeGlobals } =" + " relativeRequire('./test/common/globals');\n" + "const items = Object.getOwnPropertyNames(globalThis);\n" + "const leaks = [];\n" + "for (const item of items) {\n " + " if (intrinsics.has(item)) {\n" + " continue;\n" + " }\n" + " if (nodeGlobals.has(item)) {\n" + " continue;\n" + " }\n" + " if (item === '$jsDebugIsRegistered') {\n" + " continue;\n" + " }\n" + " leaks.push(item);\n" + "}\n" + "assert.deepStrictEqual(leaks, []);\n")); +on_exit: + return embedding_status; +} + +// Test the no_browser_globals option. +int32_t test_main_c_api_env_no_browser_globals(int32_t argc, + const char* argv[]) { + node_embedding_status embedding_status = node_embedding_status_ok; + NODE_EMBEDDING_CALL(node_embedding_main_run(NODE_EMBEDDING_VERSION, + argc, + argv, + NULL, + NULL, + ConfigureRuntimeNoBrowserGlobals, + NULL)); +on_exit: + return StatusToExitCode(PrintErrorMessage(argv[0], embedding_status)); +} + +static node_embedding_status ConfigureRuntimeWithEsmLoader( + void* cb_data, + node_embedding_platform platform, + node_embedding_runtime_config runtime_config) { + node_embedding_status embedding_status = node_embedding_status_ok; + NODE_EMBEDDING_CALL(LoadUtf8Script( + runtime_config, + "globalThis.require =" + " require('module').createRequire(process.execPath);\n" + "const { SourceTextModule } = require('node:vm');\n" + "(async () => {\n" + " const stmString = 'globalThis.importResult = import(\"\")';\n" + " const m = new SourceTextModule(stmString, {\n" + " importModuleDynamically: (async () => {\n" + " const m = new SourceTextModule('');\n" + " await m.link(() => 0);\n" + " await m.evaluate();\n" + " return m.namespace;\n" + " }),\n" + " });\n" + " await m.link(() => 0);\n" + " await m.evaluate();\n" + " delete globalThis.importResult;\n" + " process.exit(0);\n" + "})();\n")); +on_exit: + return embedding_status; +} + +// Test ESM loaded +int32_t test_main_c_api_env_with_esm_loader(int32_t argc, const char* argv[]) { + node_embedding_status embedding_status = node_embedding_status_ok; + // We currently cannot pass argument to command line arguments to the runtime. + // They must be parsed by the platform. + const char* argv2[64]; + for (int32_t i = 0; i < argc; ++i) { + argv2[i] = argv[i]; + } + argv2[argc] = "--experimental-vm-modules"; + NODE_EMBEDDING_CALL(node_embedding_main_run(NODE_EMBEDDING_VERSION, + argc + 1, + argv2, + NULL, + NULL, + ConfigureRuntimeWithEsmLoader, + NULL)); +on_exit: + return StatusToExitCode(PrintErrorMessage(argv[0], embedding_status)); +} + +static node_embedding_status ConfigureRuntimeWithNoEsmLoader( + void* cb_data, + node_embedding_platform platform, + node_embedding_runtime_config runtime_config) { + return LoadUtf8Script( + runtime_config, + "globalThis.require =" + " require('module').createRequire(process.execPath);\n" + "const { SourceTextModule } = require('node:vm');\n" + "(async () => {\n" + " const stmString = 'globalThis.importResult = import(\"\")';\n" + " const m = new SourceTextModule(stmString, {\n" + " importModuleDynamically: (async () => {\n" + " const m = new SourceTextModule('');\n" + " await m.link(() => 0);\n" + " await m.evaluate();\n" + " return m.namespace;\n" + " }),\n" + " });\n" + " await m.link(() => 0);\n" + " await m.evaluate();\n" + " delete globalThis.importResult;\n" + "})();\n"); +} + +// Test ESM loaded +int32_t test_main_c_api_env_with_no_esm_loader(int32_t argc, + const char* argv[]) { + node_embedding_status embedding_status = node_embedding_status_ok; + NODE_EMBEDDING_CALL(node_embedding_main_run(NODE_EMBEDDING_VERSION, + argc, + argv, + NULL, + NULL, + ConfigureRuntimeWithNoEsmLoader, + NULL)); +on_exit: + return StatusToExitCode(PrintErrorMessage(argv[0], embedding_status)); +} diff --git a/test/embedding/embedtest_c_api_env.cc b/test/embedding/embedtest_c_api_env.cc deleted file mode 100644 index 45a998ee633dad..00000000000000 --- a/test/embedding/embedtest_c_api_env.cc +++ /dev/null @@ -1,113 +0,0 @@ -#include "embedtest_c_api_common.h" - -using namespace node; - -// Test the no_browser_globals option. -extern "C" int32_t test_main_c_api_env_no_browser_globals(int32_t argc, - char* argv[]) { - return node_embedding_run_main( - argc, - argv, - {}, - AsFunctorRef( - [](node_embedding_platform platform, - node_embedding_runtime_config runtime_config) { - CHECK_STATUS(node_embedding_runtime_set_flags( - runtime_config, - node_embedding_runtime_flags_no_browser_globals)); - return LoadUtf8Script(runtime_config, - R"JS( -const assert = require('assert'); -const path = require('path'); -const relativeRequire = - require('module').createRequire(path.join(process.cwd(), 'stub.js')); -const { intrinsics, nodeGlobals } = - relativeRequire('./test/common/globals'); -const items = Object.getOwnPropertyNames(globalThis); -const leaks = []; -for (const item of items) { - if (intrinsics.has(item)) { - continue; - } - if (nodeGlobals.has(item)) { - continue; - } - if (item === '$jsDebugIsRegistered') { - continue; - } - leaks.push(item); -} -assert.deepStrictEqual(leaks, []); -)JS"); - })); -} - -// Test ESM loaded -extern "C" int32_t test_main_c_api_env_with_esm_loader(int32_t argc, - char* argv[]) { - // We currently cannot pass argument to command line arguments to the runtime. - // They must be parsed by the platform. - std::vector args_vec(argv, argv + argc); - args_vec.push_back("--experimental-vm-modules"); - CStringArray args(args_vec); - return node_embedding_run_main( - args.argc(), - const_cast(args.argv()), - {}, - AsFunctorRef( - [](node_embedding_platform platform, - node_embedding_runtime_config runtime_config) { - return LoadUtf8Script(runtime_config, - R"JS( -globalThis.require = require('module').createRequire(process.execPath); -const { SourceTextModule } = require('node:vm'); -(async () => { - const stmString = 'globalThis.importResult = import("")'; - const m = new SourceTextModule(stmString, { - importModuleDynamically: (async () => { - const m = new SourceTextModule(''); - await m.link(() => 0); - await m.evaluate(); - return m.namespace; - }), - }); - await m.link(() => 0); - await m.evaluate(); - delete globalThis.importResult; - process.exit(0); -})(); -)JS"); - })); -} - -// Test ESM loaded -extern "C" int32_t test_main_c_api_env_with_no_esm_loader(int32_t argc, - char* argv[]) { - return node_embedding_run_main( - argc, - argv, - {}, - AsFunctorRef( - [](node_embedding_platform platform, - node_embedding_runtime_config runtime_config) { - return LoadUtf8Script(runtime_config, - R"JS( -globalThis.require = require('module').createRequire(process.execPath); -const { SourceTextModule } = require('node:vm'); -(async () => { - const stmString = 'globalThis.importResult = import("")'; - const m = new SourceTextModule(stmString, { - importModuleDynamically: (async () => { - const m = new SourceTextModule(''); - await m.link(() => 0); - await m.evaluate(); - return m.namespace; - }), - }); - await m.link(() => 0); - await m.evaluate(); - delete globalThis.importResult; -})(); -)JS"); - })); -} diff --git a/test/embedding/embedtest_c_api_modules.c b/test/embedding/embedtest_c_api_modules.c new file mode 100644 index 00000000000000..7fc701b99c5f1f --- /dev/null +++ b/test/embedding/embedtest_c_api_modules.c @@ -0,0 +1,162 @@ +#include +#include "embedtest_c_api_common.h" + +typedef struct { + uv_mutex_t mutex; + int32_t greeter_module_init_call_count; + int32_t replicator_module_init_call_count; +} test_data_t; + +static napi_value GreeterFunction(napi_env env, napi_callback_info info) { + napi_status status = napi_ok; + char greeting_buf[256] = {0}; + strcpy(greeting_buf, "Hello, "); + size_t offset = strlen(greeting_buf); + napi_value result = NULL; + + napi_value arg; + size_t arg_count = 1; + NODE_API_CALL(napi_get_cb_info(env, info, &arg_count, &arg, NULL, NULL)); + NODE_API_CALL(napi_get_value_string_utf8( + env, arg, greeting_buf + offset, 256 - offset, NULL)); + NODE_API_CALL( + napi_create_string_utf8(env, greeting_buf, NAPI_AUTO_LENGTH, &result)); + +on_exit: + GetAndThrowLastErrorMessage(env, status); + return result; +} + +static napi_value InitGreeterModule(void* cb_data, + node_embedding_runtime runtime, + napi_env env, + const char* module_name, + napi_value exports) { + napi_status status = napi_ok; + test_data_t* test_data = (test_data_t*)cb_data; + uv_mutex_lock(&test_data->mutex); + ++test_data->greeter_module_init_call_count; + uv_mutex_unlock(&test_data->mutex); + + napi_value greet_func; + NODE_API_CALL(napi_create_function( + env, "greet", NAPI_AUTO_LENGTH, GreeterFunction, NULL, &greet_func)); + NODE_API_CALL(napi_set_named_property(env, exports, "greet", greet_func)); + +on_exit: + GetAndThrowLastErrorMessage(env, status); + return exports; +} + +static napi_value ReplicatorFunction(napi_env env, napi_callback_info info) { + napi_status status = napi_ok; + char replicator_buf[256] = {0}; + + napi_value result = NULL; + napi_value arg; + size_t arg_count = 1; + NODE_API_CALL(napi_get_cb_info(env, info, &arg_count, &arg, NULL, NULL)); + size_t str_size = 0; + NODE_API_CALL( + napi_get_value_string_utf8(env, arg, replicator_buf, 256, &str_size)); + strcpy(replicator_buf + str_size, " "); + strncpy(replicator_buf + str_size + 1, replicator_buf, str_size); + NODE_API_CALL( + napi_create_string_utf8(env, replicator_buf, NAPI_AUTO_LENGTH, &result)); + +on_exit: + GetAndThrowLastErrorMessage(env, status); + return result; +} + +static napi_value InitReplicatorModule(void* cb_data, + node_embedding_runtime runtime, + napi_env env, + const char* module_name, + napi_value exports) { + napi_status status = napi_ok; + test_data_t* test_data = (test_data_t*)cb_data; + uv_mutex_lock(&test_data->mutex); + ++test_data->replicator_module_init_call_count; + uv_mutex_unlock(&test_data->mutex); + + napi_value greet_func; + NODE_API_CALL(napi_create_function(env, + "replicate", + NAPI_AUTO_LENGTH, + ReplicatorFunction, + NULL, + &greet_func)); + NODE_API_CALL(napi_set_named_property(env, exports, "replicate", greet_func)); + +on_exit: + GetAndThrowLastErrorMessage(env, status); + return exports; +} + +static void OnPreload(void* cb_data, + node_embedding_runtime runtime, + napi_env env, + napi_value process, + napi_value require) { + napi_status status = napi_ok; + napi_value global; + NODE_API_CALL(napi_get_global(env, &global)); + NODE_API_CALL(napi_set_named_property(env, global, "process", process)); +on_exit: + GetAndThrowLastErrorMessage(env, status); +} + +static node_embedding_status ConfigureRuntime( + void* cb_data, + node_embedding_platform platform, + node_embedding_runtime_config runtime_config) { + node_embedding_status embedding_status = node_embedding_status_ok; + NODE_EMBEDDING_CALL(node_embedding_runtime_config_on_preload( + runtime_config, OnPreload, NULL, NULL)); + NODE_EMBEDDING_CALL( + node_embedding_runtime_config_add_module(runtime_config, + "greeter_module", + InitGreeterModule, + cb_data, + NULL, + NAPI_VERSION)); + NODE_EMBEDDING_CALL( + node_embedding_runtime_config_add_module(runtime_config, + "replicator_module", + InitReplicatorModule, + cb_data, + NULL, + NAPI_VERSION)); + NODE_EMBEDDING_CALL(LoadUtf8Script(runtime_config, main_script)); +on_exit: + return embedding_status; +} + +int32_t test_main_c_api_linked_modules(int32_t argc, const char* argv[]) { + node_embedding_status embedding_status = node_embedding_status_ok; + int32_t expected_greeter_module_init_call_count = atoi(argv[2]); + int32_t expected_replicator_module_init_call_count = atoi(argv[2]); + + test_data_t test_data = {0}; + uv_mutex_init(&test_data.mutex); + + NODE_EMBEDDING_ASSERT(argc == 4); + + NODE_EMBEDDING_CALL(node_embedding_main_run(NODE_EMBEDDING_VERSION, + argc, + argv, + NULL, + NULL, + ConfigureRuntime, + &test_data)); + + NODE_EMBEDDING_ASSERT(test_data.greeter_module_init_call_count == + expected_greeter_module_init_call_count); + NODE_EMBEDDING_ASSERT(test_data.replicator_module_init_call_count == + expected_replicator_module_init_call_count); + +on_exit: + uv_mutex_destroy(&test_data.mutex); + return StatusToExitCode(PrintErrorMessage(argv[0], embedding_status)); +} diff --git a/test/embedding/embedtest_c_api_modules.cc b/test/embedding/embedtest_c_api_modules.cc deleted file mode 100644 index 4c7820d1730bb8..00000000000000 --- a/test/embedding/embedtest_c_api_modules.cc +++ /dev/null @@ -1,211 +0,0 @@ -#include "embedtest_c_api_common.h" - -#include -#include -#include - -using namespace node; - -class GreeterModule { - public: - explicit GreeterModule(std::atomic* counter_ptr) - : counter_ptr_(counter_ptr) {} - - napi_value operator()(node_embedding_runtime runtime, - napi_env env, - const char* module_name, - napi_value exports) { - counter_ptr_->fetch_add(1); - - napi_value greet_func{}; - napi_create_function( - env, - "greet", - NAPI_AUTO_LENGTH, - [](napi_env env, napi_callback_info info) -> napi_value { - std::string greeting = "Hello, "; - napi_value arg{}; - size_t arg_count = 1; - NODE_API_CALL( - napi_get_cb_info(env, info, &arg_count, &arg, nullptr, nullptr)); - NODE_API_CALL(AddUtf8String(greeting, env, arg)); - napi_value result; - NODE_API_CALL(napi_create_string_utf8( - env, greeting.c_str(), greeting.size(), &result)); - return result; - }, - nullptr, - &greet_func); - napi_set_named_property(env, exports, "greet", greet_func); - return exports; - } - - private: - std::atomic* counter_ptr_; -}; - -class ReplicatorModule { - public: - explicit ReplicatorModule(std::atomic* counter_ptr) - : counter_ptr_(counter_ptr) {} - - napi_value operator()(node_embedding_runtime runtime, - napi_env env, - const char* module_name, - napi_value exports) { - counter_ptr_->fetch_add(1); - - napi_value greet_func{}; - napi_create_function( - env, - "replicate", - NAPI_AUTO_LENGTH, - [](napi_env env, napi_callback_info info) -> napi_value { - std::string str; - napi_value arg{}; - size_t arg_count = 1; - NODE_API_CALL( - napi_get_cb_info(env, info, &arg_count, &arg, nullptr, nullptr)); - NODE_API_CALL(AddUtf8String(str, env, arg)); - str += " " + str; - napi_value result; - napi_create_string_utf8(env, str.c_str(), str.size(), &result); - return result; - }, - nullptr, - &greet_func); - napi_set_named_property(env, exports, "replicate", greet_func); - return exports; - } - - private: - std::atomic* counter_ptr_; -}; - -extern "C" int32_t test_main_linked_modules_node_api(int32_t argc, - char* argv[]) { - ASSERT_OR_EXIT(argc == 4); - int32_t expectedGreeterModuleInitCallCount = atoi(argv[2]); - int32_t expectedReplicatorModuleInitCallCount = atoi(argv[2]); - - std::atomic greeterModuleInitCallCount{0}; - std::atomic replicatorModuleInitCallCount{0}; - - node_embedding_on_error({argv[0], HandleTestError, nullptr}); - - CHECK_STATUS_OR_EXIT(node_embedding_run_main( - argc, - argv, - {}, - AsFunctorRef( - [&](node_embedding_platform platform, - node_embedding_runtime_config runtime_config) { - CHECK_STATUS(node_embedding_runtime_on_preload( - runtime_config, - AsFunctor( - [](node_embedding_runtime runtime, - napi_env env, - napi_value process, - napi_value /*require*/ - ) { - napi_value global; - napi_get_global(env, &global); - napi_set_named_property(env, global, "process", process); - }))); - - CHECK_STATUS(node_embedding_runtime_add_module( - runtime_config, - "greeter_module", - AsFunctor( - GreeterModule(&greeterModuleInitCallCount)), - NAPI_VERSION)); - CHECK_STATUS(node_embedding_runtime_add_module( - runtime_config, - "replicator_module", - AsFunctor( - ReplicatorModule(&replicatorModuleInitCallCount)), - NAPI_VERSION)); - - CHECK_STATUS(LoadUtf8Script(runtime_config, main_script)); - - return node_embedding_status_ok; - }))); - - ASSERT_OR_EXIT(greeterModuleInitCallCount == - expectedGreeterModuleInitCallCount); - ASSERT_OR_EXIT(replicatorModuleInitCallCount == - expectedReplicatorModuleInitCallCount); - - return 0; -} - -extern "C" int32_t test_main_modules_node_api(int32_t argc, char* argv[]) { - /* - if (argc < 3) { - fprintf(stderr, "node_api_modules \n"); - return 2; - } - - CHECK(node_embedding_on_error(HandleTestError, argv[0])); - - node_embedding_platform platform; - CHECK(node_embedding_create_platform(NODE_EMBEDDING_VERSION, &platform)); - CHECK(node_embedding_platform_set_args(platform, argc, argv)); - bool early_return = false; - CHECK(node_embedding_platform_initialize(platform, &early_return)); - if (early_return) { - return 0; - } - - node_embedding_runtime runtime; - CHECK(node_embedding_create_runtime(platform, &runtime)); - CHECK(node_embedding_runtime_initialize_from_script(runtime)); - int32_t exit_code = 0; - CHECK(InvokeNodeApi(runtime, [&](napi_env env) { - napi_value global, import_name, require_name, import, require, cjs, es6, - value; - NODE_API_CALL(napi_get_global(env, &global)); - NODE_API_CALL( - napi_create_string_utf8(env, "import", strlen("import"), &import_name)); - NODE_API_CALL(napi_create_string_utf8( - env, "require", strlen("require"), &require_name)); - NODE_API_CALL(napi_get_property(env, global, import_name, &import)); - NODE_API_CALL(napi_get_property(env, global, require_name, &require)); - - NODE_API_CALL(napi_create_string_utf8(env, argv[1], strlen(argv[1]), &cjs)); - NODE_API_CALL(napi_create_string_utf8(env, argv[2], strlen(argv[2]), &es6)); - NODE_API_CALL( - napi_create_string_utf8(env, "value", strlen("value"), &value)); - - napi_value es6_module, es6_promise, cjs_module, es6_result, cjs_result; - char buffer[32]; - size_t bufferlen; - - NODE_API_CALL( - napi_call_function(env, global, import, 1, &es6, &es6_promise)); - node_embedding_promise_state es6_promise_state; - CHECK_RETURN_VOID(node_embedding_runtime_await_promise( - runtime, es6_promise, &es6_promise_state, &es6_module, nullptr)); - - NODE_API_CALL(napi_get_property(env, es6_module, value, &es6_result)); - NODE_API_CALL(napi_get_value_string_utf8( - env, es6_result, buffer, sizeof(buffer), &bufferlen)); - if (strncmp(buffer, "genuine", bufferlen) != 0) { - FAIL_RETURN_VOID("Unexpected value: %s\n", buffer); - } - - NODE_API_CALL( - napi_call_function(env, global, require, 1, &cjs, &cjs_module)); - NODE_API_CALL(napi_get_property(env, cjs_module, value, &cjs_result)); - NODE_API_CALL(napi_get_value_string_utf8( - env, cjs_result, buffer, sizeof(buffer), &bufferlen)); - if (strncmp(buffer, "original", bufferlen) != 0) { - FAIL_RETURN_VOID("Unexpected value: %s\n", buffer); - } - })); - CHECK(exit_code); - CHECK(node_embedding_delete_runtime(runtime)); - CHECK(node_embedding_delete_platform(platform)); -*/ - return 0; -} diff --git a/test/embedding/embedtest_c_api_preload.c b/test/embedding/embedtest_c_api_preload.c new file mode 100644 index 00000000000000..b1aba720d8f7b9 --- /dev/null +++ b/test/embedding/embedtest_c_api_preload.c @@ -0,0 +1,37 @@ +#include "embedtest_c_api_common.h" + +static void OnPreload(void* cb_data, + node_embedding_runtime runtime, + napi_env env, + napi_value process, + napi_value require) { + napi_status status = napi_ok; + napi_value global, value; + NODE_API_CALL(napi_get_global(env, &global)); + NODE_API_CALL(napi_create_int32(env, 42, &value)); + NODE_API_CALL(napi_set_named_property(env, global, "preloadValue", value)); +on_exit: + GetAndThrowLastErrorMessage(env, status); +} + +static node_embedding_status ConfigureRuntime( + void* cb_data, + node_embedding_platform platform, + node_embedding_runtime_config runtime_config) { + node_embedding_status embedding_status = node_embedding_status_ok; + NODE_EMBEDDING_CALL(node_embedding_runtime_config_on_preload( + runtime_config, OnPreload, NULL, NULL)); + NODE_EMBEDDING_CALL(LoadUtf8Script(runtime_config, main_script)); +on_exit: + return embedding_status; +} + +// Tests that the same preload callback is called from the main thread and from +// the worker thread. +int32_t test_main_c_api_preload(int32_t argc, const char* argv[]) { + node_embedding_status embedding_status = node_embedding_status_ok; + NODE_EMBEDDING_CALL(node_embedding_main_run( + NODE_EMBEDDING_VERSION, argc, argv, NULL, NULL, ConfigureRuntime, NULL)); +on_exit: + return StatusToExitCode(PrintErrorMessage(argv[0], embedding_status)); +} diff --git a/test/embedding/embedtest_c_api_preload.cc b/test/embedding/embedtest_c_api_preload.cc deleted file mode 100644 index 8f1c617a33c4e5..00000000000000 --- a/test/embedding/embedtest_c_api_preload.cc +++ /dev/null @@ -1,40 +0,0 @@ -#include "embedtest_c_api_common.h" - -#include -#include - -using namespace node; - -// Tests that the same preload callback is called from the main thread and from -// the worker thread. -extern "C" int32_t test_main_preload_node_api(int32_t argc, char* argv[]) { - CHECK_STATUS_OR_EXIT(node_embedding_run_main( - argc, - argv, - {}, - AsFunctorRef( - [&](node_embedding_platform platform, - node_embedding_runtime_config runtime_config) { - CHECK_STATUS(node_embedding_runtime_on_preload( - runtime_config, - AsFunctor( - [](node_embedding_runtime runtime, - napi_env env, - napi_value /*process*/, - napi_value /*require*/ - ) { - napi_value global, value; - NODE_API_CALL_RETURN_VOID(napi_get_global(env, &global)); - NODE_API_CALL_RETURN_VOID( - napi_create_int32(env, 42, &value)); - NODE_API_CALL_RETURN_VOID(napi_set_named_property( - env, global, "preloadValue", value)); - }))); - - CHECK_STATUS(LoadUtf8Script(runtime_config, main_script)); - - return node_embedding_status_ok; - }))); - - return 0; -} diff --git a/test/embedding/embedtest_c_api_run_main.c b/test/embedding/embedtest_c_api_run_main.c new file mode 100644 index 00000000000000..e854ce39ffa1d7 --- /dev/null +++ b/test/embedding/embedtest_c_api_run_main.c @@ -0,0 +1,12 @@ +#include "embedtest_c_api_common.h" + +// The simplest Node.js embedding scenario where the Node.js main function is +// invoked from the libnode shared library as it would be run from the Node.js +// CLI. No embedder customizations are available in this case. +int32_t test_main_c_api_nodejs_main(int32_t argc, const char* argv[]) { + node_embedding_status embedding_status = node_embedding_status_ok; + NODE_EMBEDDING_CALL(node_embedding_main_run( + NODE_EMBEDDING_VERSION, argc, argv, NULL, NULL, NULL, NULL)); +on_exit: + return StatusToExitCode(PrintErrorMessage(argv[0], embedding_status)); +} diff --git a/test/embedding/embedtest_c_api_run_main.cc b/test/embedding/embedtest_c_api_run_main.cc deleted file mode 100644 index 5646d471e7fa41..00000000000000 --- a/test/embedding/embedtest_c_api_run_main.cc +++ /dev/null @@ -1,8 +0,0 @@ -#include - -// The simplest Node.js embedding scenario where the Node.js main function is -// invoked from the libnode shared library as it would be run from the Node.js -// CLI. No embedder customizations are available in this case. -extern "C" int32_t test_main_nodejs_main_node_api(int32_t argc, char* argv[]) { - return node_embedding_run_main(argc, argv, {}, {}); -} diff --git a/test/embedding/embedtest_c_api_threading.c b/test/embedding/embedtest_c_api_threading.c new file mode 100644 index 00000000000000..ad85a86dedcf7e --- /dev/null +++ b/test/embedding/embedtest_c_api_threading.c @@ -0,0 +1,600 @@ +#include "embedtest_c_api_common.h" + +#include // Tests in this file use libuv for threading. + +//============================================================================== +// Test that multiple runtimes can be run at the same time in their own threads. +//============================================================================== + +typedef struct { + node_embedding_platform platform; + uv_mutex_t mutex; + int32_t global_count; + node_embedding_status global_status; +} test_data1_t; + +static void OnLoaded1(void* cb_data, + node_embedding_runtime runtime, + napi_env env, + napi_value execution_result) { + napi_status status = napi_ok; + test_data1_t* data = (test_data1_t*)cb_data; + napi_value global, my_count; + NODE_API_CALL(napi_get_global(env, &global)); + NODE_API_CALL(napi_get_named_property(env, global, "myCount", &my_count)); + int32_t count; + NODE_API_CALL(napi_get_value_int32(env, my_count, &count)); + uv_mutex_lock(&data->mutex); + ++data->global_count; + uv_mutex_unlock(&data->mutex); +on_exit: + GetAndThrowLastErrorMessage(env, status); +} + +static node_embedding_status ConfigureRuntime( + void* cb_data, + node_embedding_platform platform, + node_embedding_runtime_config runtime_config) { + node_embedding_status embedding_status = node_embedding_status_ok; + // Inspector can be associated with only one + // runtime in the process. + NODE_EMBEDDING_CALL(node_embedding_runtime_config_set_flags( + runtime_config, + node_embedding_runtime_flags_default | + node_embedding_runtime_flags_no_create_inspector)); + NODE_EMBEDDING_CALL(LoadUtf8Script(runtime_config, main_script)); + NODE_EMBEDDING_CALL(node_embedding_runtime_config_on_loaded( + runtime_config, OnLoaded1, cb_data, NULL)); +on_exit: + return embedding_status; +} + +static void ThreadCallback1(void* arg) { + test_data1_t* data = (test_data1_t*)arg; + node_embedding_status status = + node_embedding_runtime_run(data->platform, ConfigureRuntime, arg); + if (status != node_embedding_status_ok) { + uv_mutex_lock(&data->mutex); + data->global_status = status; + uv_mutex_unlock(&data->mutex); + } +} + +#define TEST_THREAD_COUNT1 12 + +// Tests that multiple runtimes can be run at the same time in their own +// threads. The test creates 12 threads and 12 runtimes. Each runtime runs in it +// own thread. +int32_t test_main_c_api_threading_runtime_per_thread(int32_t argc, + const char* argv[]) { + node_embedding_status embedding_status = node_embedding_status_ok; + size_t thread_count = TEST_THREAD_COUNT1; + uv_thread_t threads[TEST_THREAD_COUNT1] = {0}; + test_data1_t test_data = {0}; + uv_mutex_init(&test_data.mutex); + + NODE_EMBEDDING_CALL(node_embedding_platform_create( + NODE_EMBEDDING_VERSION, argc, argv, NULL, NULL, &test_data.platform)); + if (test_data.platform == NULL) { + goto on_exit; // early return + } + + for (size_t i = 0; i < thread_count; i++) { + uv_thread_create(&threads[i], ThreadCallback1, &test_data); + } + + for (size_t i = 0; i < thread_count; i++) { + uv_thread_join(&threads[i]); + } + + // TODO: Add passing error message + NODE_EMBEDDING_CALL(test_data.global_status); + NODE_EMBEDDING_CALL(node_embedding_platform_delete(test_data.platform)); + + fprintf(stdout, "%d\n", test_data.global_count); + +on_exit: + uv_mutex_destroy(&test_data.mutex); + return StatusToExitCode(PrintErrorMessage(argv[0], embedding_status)); +} + +//============================================================================== +// Test that multiple runtimes can run in the same thread. +//============================================================================== + +static node_embedding_status ConfigureRuntime2( + void* cb_data, + node_embedding_platform platform, + node_embedding_runtime_config runtime_config) { + node_embedding_status embedding_status = node_embedding_status_ok; + // Inspector can be associated with only one runtime in the process. + NODE_EMBEDDING_CALL(node_embedding_runtime_config_set_flags( + runtime_config, + node_embedding_runtime_flags_default | + node_embedding_runtime_flags_no_create_inspector)); + NODE_EMBEDDING_CALL(LoadUtf8Script(runtime_config, main_script)); +on_exit: + return embedding_status; +} + +static void IncMyCount2(void* cb_data, napi_env env) { + napi_status status = napi_ok; + napi_value undefined, global, func; + NODE_API_CALL(napi_get_undefined(env, &undefined)); + NODE_API_CALL(napi_get_global(env, &global)); + NODE_API_CALL(napi_get_named_property(env, global, "incMyCount", &func)); + + napi_valuetype func_type; + NODE_API_CALL(napi_typeof(env, func, &func_type)); + NODE_API_ASSERT(func_type == napi_function); + NODE_API_CALL(napi_call_function(env, undefined, func, 0, NULL, NULL)); +on_exit: + GetAndThrowLastErrorMessage(env, status); +} + +static void SumMyCount2(void* cb_data, napi_env env) { + napi_status status = napi_ok; + int32_t* global_count = (int32_t*)cb_data; + napi_value global, my_count; + NODE_API_CALL(napi_get_global(env, &global)); + NODE_API_CALL(napi_get_named_property(env, global, "myCount", &my_count)); + + napi_valuetype my_count_type; + NODE_API_CALL(napi_typeof(env, my_count, &my_count_type)); + NODE_API_ASSERT(my_count_type == napi_number); + int32_t count; + NODE_API_CALL(napi_get_value_int32(env, my_count, &count)); + + *global_count += count; +on_exit: + GetAndThrowLastErrorMessage(env, status); +} + +// Tests that multiple runtimes can run in the same thread. +// The runtime scope must be opened and closed for each use. +// There are 12 runtimes that share the same main thread. +int32_t test_main_c_api_threading_several_runtimes_per_thread( + int32_t argc, const char* argv[]) { + node_embedding_status embedding_status = node_embedding_status_ok; + const size_t runtime_count = 12; + bool more_work = false; + int32_t global_count = 0; + node_embedding_runtime runtimes[12] = {0}; + + node_embedding_platform platform; + NODE_EMBEDDING_CALL(node_embedding_platform_create( + NODE_EMBEDDING_VERSION, argc, argv, NULL, NULL, &platform)); + if (platform == NULL) { + goto on_exit; // early return + } + + for (size_t i = 0; i < runtime_count; ++i) { + NODE_EMBEDDING_CALL(node_embedding_runtime_create( + platform, ConfigureRuntime2, NULL, &runtimes[i])); + + NODE_EMBEDDING_CALL( + node_embedding_runtime_node_api_run(runtimes[i], IncMyCount2, NULL)); + } + + do { + more_work = false; + for (size_t i = 0; i < runtime_count; ++i) { + bool has_more_work = false; + NODE_EMBEDDING_CALL(node_embedding_runtime_event_loop_run_no_wait( + runtimes[i], &has_more_work)); + more_work |= has_more_work; + } + } while (more_work); + + for (size_t i = 0; i < runtime_count; ++i) { + NODE_EMBEDDING_CALL(node_embedding_runtime_node_api_run( + runtimes[i], SumMyCount2, &global_count)); + NODE_EMBEDDING_CALL(node_embedding_runtime_event_loop_run(runtimes[i])); + NODE_EMBEDDING_CALL(node_embedding_runtime_delete(runtimes[i])); + } + + NODE_EMBEDDING_CALL(node_embedding_platform_delete(platform)); + + fprintf(stdout, "%d\n", global_count); +on_exit: + return StatusToExitCode(PrintErrorMessage(argv[0], embedding_status)); +} + +//============================================================================== +// Test that a runtime can be invoked from different threads as long as only +// one thread uses it at a time. +//============================================================================== + +typedef struct { + node_embedding_runtime runtime; + uv_mutex_t mutex; + int32_t result_count; + node_embedding_status result_status; +} thread_data3; + +static node_embedding_status ConfigureRuntime3( + void* cb_data, + node_embedding_platform platform, + node_embedding_runtime_config runtime_config) { + return LoadUtf8Script(runtime_config, main_script); +} + +static void RunNodeApi3(void* cb_data, napi_env env) { + napi_status status = napi_ok; + thread_data3* data = (thread_data3*)cb_data; + napi_value undefined, global, func, my_count; + NODE_API_CALL(napi_get_undefined(env, &undefined)); + NODE_API_CALL(napi_get_global(env, &global)); + NODE_API_CALL(napi_get_named_property(env, global, "incMyCount", &func)); + + napi_valuetype func_type; + NODE_API_CALL(napi_typeof(env, func, &func_type)); + NODE_API_ASSERT(func_type == napi_function); + NODE_API_CALL(napi_call_function(env, undefined, func, 0, NULL, NULL)); + + NODE_API_CALL(napi_get_named_property(env, global, "myCount", &my_count)); + napi_valuetype count_type; + NODE_API_CALL(napi_typeof(env, my_count, &count_type)); + NODE_API_ASSERT(count_type == napi_number); + int32_t count; + NODE_API_CALL(napi_get_value_int32(env, my_count, &count)); + data->result_count = count; +on_exit: + GetAndThrowLastErrorMessage(env, status); +} + +static void ThreadCallback3(void* arg) { + thread_data3* data = (thread_data3*)arg; + uv_mutex_lock(&data->mutex); + node_embedding_status status = + node_embedding_runtime_node_api_run(data->runtime, RunNodeApi3, arg); + if (status != node_embedding_status_ok) { + data->result_status = status; + } + uv_mutex_unlock(&data->mutex); +} + +// Tests that a runtime can be invoked from different threads as long as only +// one thread uses it at a time. +int32_t test_main_c_api_threading_runtime_in_several_threads( + int32_t argc, const char* argv[]) { + // Use mutex to synchronize access to the runtime. + node_embedding_status embedding_status = node_embedding_status_ok; + thread_data3 data = {0}; + uv_mutex_init(&data.mutex); + + const size_t thread_count = 5; + uv_thread_t threads[5] = {0}; + + node_embedding_platform platform; + NODE_EMBEDDING_CALL(node_embedding_platform_create( + NODE_EMBEDDING_VERSION, argc, argv, NULL, NULL, &platform)); + if (platform == NULL) { + goto on_exit; // early return + } + + NODE_EMBEDDING_CALL(node_embedding_runtime_create( + platform, ConfigureRuntime3, NULL, &data.runtime)); + + for (size_t i = 0; i < thread_count; ++i) { + uv_thread_create(&threads[i], ThreadCallback3, &data); + } + + for (size_t i = 0; i < thread_count; ++i) { + uv_thread_join(&threads[i]); + } + + NODE_EMBEDDING_CALL(data.result_status); + NODE_EMBEDDING_CALL(node_embedding_runtime_event_loop_run(data.runtime)); + + fprintf(stdout, "%d\n", data.result_count); +on_exit: + uv_mutex_destroy(&data.mutex); + return StatusToExitCode(PrintErrorMessage(argv[0], embedding_status)); +} + +//============================================================================== +// Test that a runtime's event loop can be called from the UI thread event loop. +//============================================================================== + +//------------------------------------------------------------------------------ +// Simulation of the UI queue. +//------------------------------------------------------------------------------ + +struct test_deq_item_t { + struct test_deq_item_t* prev; + struct test_deq_item_t* next; +}; +typedef struct test_deq_item_t test_deq_item_t; + +static void test_deq_item_init(test_deq_item_t* item) { + item->prev = NULL; + item->next = NULL; +} + +typedef struct { + test_deq_item_t* head; + test_deq_item_t* tail; +} test_deq_t; + +static void test_deq_init(test_deq_t* deq) { + deq->head = NULL; + deq->tail = NULL; +} + +static void test_deq_push_back(test_deq_t* deq, test_deq_item_t* item) { + if (deq->tail == NULL) { + deq->head = item; + deq->tail = item; + } else { + deq->tail->next = item; + item->prev = deq->tail; + deq->tail = item; + } +} + +static test_deq_item_t* test_deq_pop_front(test_deq_t* deq) { + test_deq_item_t* item = deq->head; + if (item != NULL) { + deq->head = item->next; + if (deq->head == NULL) { + deq->tail = NULL; + } else { + deq->head->prev = NULL; + } + } + return item; +} + +static bool test_deq_empty(test_deq_t* deq) { + return deq->head == NULL; +} + +typedef struct { + test_deq_item_t deq_item; + void* task_data; + void (*run_task)(void*); + void (*release_task_data)(void*); +} test_task_t; + +static void test_task_init(test_task_t* task, + void* task_data, + void (*run_task)(void*), + void (*release_task_data)(void*)) { + test_deq_item_init(&task->deq_item); + task->task_data = task_data; + task->run_task = run_task; + task->release_task_data = release_task_data; +} + +typedef struct { + uv_mutex_t mutex; + uv_cond_t wakeup; + test_deq_t tasks; + bool is_finished; +} test_ui_queue_t; + +static void test_ui_queue_init(test_ui_queue_t* queue) { + uv_mutex_init(&queue->mutex); + uv_cond_init(&queue->wakeup); + test_deq_init(&queue->tasks); + queue->is_finished = false; +} + +static void test_ui_queue_post_task(test_ui_queue_t* queue, test_task_t* task) { + uv_mutex_lock(&queue->mutex); + if (!queue->is_finished) { + test_deq_push_back(&queue->tasks, &task->deq_item); + uv_cond_signal(&queue->wakeup); + } + uv_mutex_unlock(&queue->mutex); +} + +static void test_ui_queue_run(test_ui_queue_t* queue) { + for (;;) { + uv_mutex_lock(&queue->mutex); + while (!queue->is_finished && test_deq_empty(&queue->tasks)) { + uv_cond_wait(&queue->wakeup, &queue->mutex); + } + if (queue->is_finished) { + uv_mutex_unlock(&queue->mutex); + return; + } + test_task_t* task = (test_task_t*)test_deq_pop_front(&queue->tasks); + uv_mutex_unlock(&queue->mutex); + if (task->run_task != NULL) { + task->run_task(task->task_data); + } + if (task->release_task_data != NULL) { + task->release_task_data(task->task_data); + } + } +} + +static void test_ui_queue_stop(test_ui_queue_t* queue) { + uv_mutex_lock(&queue->mutex); + if (!queue->is_finished) { + queue->is_finished = true; + uv_cond_signal(&queue->wakeup); + } + uv_mutex_unlock(&queue->mutex); +} + +static void test_ui_queue_destroy(test_ui_queue_t* queue) { + uv_mutex_destroy(&queue->mutex); + uv_cond_destroy(&queue->wakeup); +} + +//------------------------------------------------------------------------------ +// Test data and task implementation. +//------------------------------------------------------------------------------ + +typedef struct { + test_ui_queue_t ui_queue; + node_embedding_runtime runtime; +} test_data4_t; + +typedef struct { + test_task_t parent_task; + node_embedding_task_run_callback run_task; + void* task_data; + node_embedding_data_release_callback release_task_data; + test_data4_t* test_data; +} test_ext_task_t; + +static void RunTestTask4(void* cb_data) { + node_embedding_status embedding_status = node_embedding_status_ok; + napi_status status = napi_ok; + test_ext_task_t* test_task = (test_ext_task_t*)cb_data; + + test_task->run_task(test_task->task_data); // TODO: handle result + + // Check myCount and stop the processing when it reaches 5. + int32_t count; + node_embedding_runtime runtime = test_task->test_data->runtime; + node_embedding_node_api_scope node_api_scope; + napi_env env; + NODE_EMBEDDING_CALL(node_embedding_runtime_node_api_scope_open( + runtime, &node_api_scope, &env)); + + napi_value global, my_count; + NODE_API_CALL(napi_get_global(env, &global)); + NODE_API_CALL(napi_get_named_property(env, global, "myCount", &my_count)); + napi_valuetype count_type; + NODE_API_CALL(napi_typeof(env, my_count, &count_type)); + NODE_API_ASSERT(count_type == napi_number); + NODE_API_CALL(napi_get_value_int32(env, my_count, &count)); + + NODE_EMBEDDING_CALL( + node_embedding_runtime_node_api_scope_close(runtime, node_api_scope)); + if (count == 5) { + NODE_EMBEDDING_CALL(node_embedding_runtime_event_loop_run(runtime)); + fprintf(stdout, "%d\n", count); + test_ui_queue_stop(&test_task->test_data->ui_queue); + } +on_exit: + if (embedding_status != node_embedding_status_ok) { + ThrowLastErrorMessage(env, + "RunTestTask failed: %s\n", + node_embedding_last_error_message_get()); + status = napi_generic_failure; + } + GetAndThrowLastErrorMessage(env, status); +} + +static void ReleaseTestTask4(void* cb_data) { + test_ext_task_t* test_task = (test_ext_task_t*)cb_data; + if (test_task->release_task_data != NULL) { + test_task->release_task_data(test_task->task_data); + } + free(test_task); +} + +// The callback will be invoked from the runtime's event loop observer thread. +// It must schedule the work to the UI thread's event loop. +static node_embedding_status PostTask4( + void* cb_data, + node_embedding_task_run_callback run_task, + void* task_data, + node_embedding_data_release_callback release_task_data, + bool* succeeded) { + test_data4_t* test_data = (test_data4_t*)cb_data; + test_ext_task_t* test_task = + (test_ext_task_t*)malloc(sizeof(test_ext_task_t)); + if (test_task == NULL) { + return node_embedding_status_out_of_memory; + } + memset(test_task, 0, sizeof(test_ext_task_t)); + test_task_init( + &test_task->parent_task, test_task, RunTestTask4, ReleaseTestTask4); + test_task->run_task = run_task; + test_task->task_data = task_data; + test_task->release_task_data = release_task_data; + test_task->test_data = test_data; + + test_ui_queue_post_task(&test_data->ui_queue, &test_task->parent_task); + if (succeeded != NULL) { + *succeeded = true; + } + return node_embedding_status_ok; +} + +static node_embedding_status ConfigureRuntime4( + void* cb_data, + node_embedding_platform platform, + node_embedding_runtime_config runtime_config) { + node_embedding_status embedding_status = node_embedding_status_ok; + NODE_EMBEDDING_CALL(node_embedding_runtime_config_set_task_runner( + runtime_config, PostTask4, cb_data, NULL)); + NODE_EMBEDDING_CALL(LoadUtf8Script(runtime_config, main_script)); +on_exit: + return embedding_status; +} + +static void StartProcessing4(void* cb_data) { + napi_status status = napi_ok; + node_embedding_status embedding_status = node_embedding_status_ok; + test_data4_t* data = (test_data4_t*)cb_data; + node_embedding_node_api_scope node_api_scope; + napi_env env; + NODE_EMBEDDING_CALL(node_embedding_runtime_node_api_scope_open( + data->runtime, &node_api_scope, &env)); + + napi_value undefined, global, func; + NODE_API_CALL(napi_get_undefined(env, &undefined)); + NODE_API_CALL(napi_get_global(env, &global)); + NODE_API_CALL(napi_get_named_property(env, global, "incMyCount", &func)); + + napi_valuetype func_type; + NODE_API_CALL(napi_typeof(env, func, &func_type)); + NODE_API_ASSERT(func_type == napi_function); + NODE_API_CALL(napi_call_function(env, undefined, func, 0, NULL, NULL)); + + NODE_EMBEDDING_CALL(node_embedding_runtime_node_api_scope_close( + data->runtime, node_api_scope)); +on_exit: + // TODO: extract into a separate function + if (embedding_status != node_embedding_status_ok) { + ThrowLastErrorMessage(env, + "RunTestTask failed: %s\n", + node_embedding_last_error_message_get()); + status = napi_pending_exception; + } + GetAndThrowLastErrorMessage(env, status); +} + +// Tests that a the runtime's event loop can be called from the UI thread +// event loop. +int32_t test_main_c_api_threading_runtime_in_ui_thread(int32_t argc, + const char* argv[]) { + // A simulation of the UI thread's event loop implemented as a dispatcher + // queue. Note that it is a very simplistic implementation not suitable + // for the real apps. + node_embedding_status embedding_status = node_embedding_status_ok; + test_data4_t test_data = {0}; + test_ui_queue_init(&test_data.ui_queue); + + node_embedding_platform platform; + NODE_EMBEDDING_CALL(node_embedding_platform_create( + NODE_EMBEDDING_VERSION, argc, argv, NULL, NULL, &platform)); + if (platform == NULL) { + goto on_exit; // early return + } + + NODE_EMBEDDING_CALL(node_embedding_runtime_create( + platform, ConfigureRuntime4, &test_data, &test_data.runtime)); + + // The initial task starts the JS code that then will do the timer + // scheduling. The timer supposed to be handled by the runtime's event loop. + test_task_t task; + test_task_init(&task, &test_data, StartProcessing4, NULL); + test_ui_queue_post_task(&test_data.ui_queue, &task); + + test_ui_queue_run(&test_data.ui_queue); + test_ui_queue_destroy(&test_data.ui_queue); + + NODE_EMBEDDING_CALL(node_embedding_runtime_delete(test_data.runtime)); + NODE_EMBEDDING_CALL(node_embedding_platform_delete(platform)); +on_exit: + return StatusToExitCode(PrintErrorMessage(argv[0], embedding_status)); +} diff --git a/test/embedding/embedtest_c_api_threading.cc b/test/embedding/embedtest_c_api_threading.cc deleted file mode 100644 index 27b601c00fdee4..00000000000000 --- a/test/embedding/embedtest_c_api_threading.cc +++ /dev/null @@ -1,404 +0,0 @@ -#include "embedtest_c_api_common.h" - -#include -#include -#include -#include - -using namespace node; - -// Tests that multiple runtimes can be run at the same time in their own -// threads. The test creates 12 threads and 12 runtimes. Each runtime runs in it -// own thread. -extern "C" int32_t test_main_threading_runtime_per_thread_node_api( - int32_t argc, char* argv[]) { - node_embedding_platform platform; - const size_t thread_count = 12; - std::vector threads; - threads.reserve(thread_count); - std::atomic global_count{0}; - std::atomic global_status{}; - - CHECK_STATUS_OR_EXIT( - node_embedding_create_platform(argc, argv, {}, &platform)); - if (!platform) { - return 0; // early return - } - - for (size_t i = 0; i < thread_count; i++) { - threads.emplace_back([platform, &global_count, &global_status] { - node_embedding_status status = [&]() { - CHECK_STATUS(node_embedding_run_runtime( - platform, - AsFunctorRef( - [&](node_embedding_platform platform, - node_embedding_runtime_config runtime_config) { - // Inspector can be associated with only one runtime in the - // process. - CHECK_STATUS(node_embedding_runtime_set_flags( - runtime_config, - node_embedding_runtime_flags_default | - node_embedding_runtime_flags_no_create_inspector)); - CHECK_STATUS(LoadUtf8Script( - runtime_config, - main_script, - AsFunctor( - [&](node_embedding_runtime runtime, - napi_env env, - napi_value /*value*/) { - napi_value global, my_count; - NODE_API_CALL_RETURN_VOID( - napi_get_global(env, &global)); - NODE_API_CALL_RETURN_VOID(napi_get_named_property( - env, global, "myCount", &my_count)); - int32_t count; - NODE_API_CALL_RETURN_VOID( - napi_get_value_int32(env, my_count, &count)); - global_count.fetch_add(count); - }))); - return node_embedding_status_ok; - }))); - return node_embedding_status_ok; - }(); - if (status != node_embedding_status_ok) { - global_status.store(status); - } - }); - } - - for (size_t i = 0; i < thread_count; i++) { - threads[i].join(); - } - - CHECK_STATUS_OR_EXIT(global_status.load()); - - CHECK_STATUS_OR_EXIT(node_embedding_delete_platform(platform)); - - fprintf(stdout, "%d\n", global_count.load()); - - return 0; -} - -// Tests that multiple runtimes can run in the same thread. -// The runtime scope must be opened and closed for each use. -// There are 12 runtimes that share the same main thread. -extern "C" int32_t test_main_threading_several_runtimes_per_thread_node_api( - int32_t argc, char* argv[]) { - node_embedding_platform platform; - const size_t runtime_count = 12; - std::vector runtimes; - runtimes.reserve(runtime_count); - bool more_work = false; - int32_t global_count = 0; - - CHECK_STATUS_OR_EXIT( - node_embedding_create_platform(argc, argv, {}, &platform)); - if (!platform) { - return 0; // early return - } - - for (size_t i = 0; i < runtime_count; i++) { - node_embedding_runtime runtime; - CHECK_STATUS_OR_EXIT(node_embedding_create_runtime( - platform, - AsFunctorRef( - [&](node_embedding_platform platform, - node_embedding_runtime_config runtime_config) { - // Inspector can be associated with only one runtime in the - // process. - CHECK_STATUS(node_embedding_runtime_set_flags( - runtime_config, - node_embedding_runtime_flags_default | - node_embedding_runtime_flags_no_create_inspector)); - CHECK_STATUS(LoadUtf8Script(runtime_config, main_script)); - return node_embedding_status_ok; - }), - &runtime)); - runtimes.push_back(runtime); - - CHECK_STATUS_OR_EXIT(node_embedding_run_node_api( - runtime, - AsFunctorRef( - [&](node_embedding_runtime runtime, napi_env env) { - napi_value undefined, global, func; - NODE_API_CALL_RETURN_VOID(napi_get_undefined(env, &undefined)); - NODE_API_CALL_RETURN_VOID(napi_get_global(env, &global)); - NODE_API_CALL_RETURN_VOID( - napi_get_named_property(env, global, "incMyCount", &func)); - - napi_valuetype func_type; - NODE_API_CALL_RETURN_VOID(napi_typeof(env, func, &func_type)); - NODE_API_ASSERT_RETURN_VOID(func_type == napi_function); - NODE_API_CALL_RETURN_VOID(napi_call_function( - env, undefined, func, 0, nullptr, nullptr)); - }))); - } - - do { - more_work = false; - for (node_embedding_runtime runtime : runtimes) { - bool has_more_work = false; - CHECK_STATUS_OR_EXIT(node_embedding_run_event_loop( - runtime, node_embedding_event_loop_run_mode_nowait, &has_more_work)); - more_work |= has_more_work; - } - } while (more_work); - - for (node_embedding_runtime runtime : runtimes) { - CHECK_STATUS_OR_EXIT(node_embedding_run_node_api( - runtime, - AsFunctorRef( - [&](node_embedding_runtime runtime, napi_env env) { - napi_value global, my_count; - NODE_API_CALL_RETURN_VOID(napi_get_global(env, &global)); - NODE_API_CALL_RETURN_VOID( - napi_get_named_property(env, global, "myCount", &my_count)); - - napi_valuetype my_count_type; - NODE_API_CALL_RETURN_VOID( - napi_typeof(env, my_count, &my_count_type)); - NODE_API_ASSERT_RETURN_VOID(my_count_type == napi_number); - int32_t count; - NODE_API_CALL_RETURN_VOID( - napi_get_value_int32(env, my_count, &count)); - - global_count += count; - }))); - CHECK_STATUS_OR_EXIT(node_embedding_complete_event_loop(runtime)); - CHECK_STATUS_OR_EXIT(node_embedding_delete_runtime(runtime)); - } - - CHECK_STATUS_OR_EXIT(node_embedding_delete_platform(platform)); - - fprintf(stdout, "%d\n", global_count); - - return 0; -} - -// Tests that a runtime can be invoked from different threads as long as only -// one thread uses it at a time. -extern "C" int32_t test_main_threading_runtime_in_several_threads_node_api( - int32_t argc, char* argv[]) { - node_embedding_platform platform; - - // Use mutex to synchronize access to the runtime. - std::mutex mutex; - std::atomic result_count{0}; - std::atomic result_status{node_embedding_status_ok}; - const size_t thread_count = 5; - std::vector threads; - threads.reserve(thread_count); - - CHECK_STATUS_OR_EXIT( - node_embedding_create_platform(argc, argv, {}, &platform)); - if (!platform) { - return 0; // early return - } - - node_embedding_runtime runtime; - CHECK_STATUS_OR_EXIT(node_embedding_create_runtime( - platform, - AsFunctorRef( - [&](node_embedding_platform platform, - node_embedding_runtime_config runtime_config) { - CHECK_STATUS(LoadUtf8Script(runtime_config, main_script)); - return node_embedding_status_ok; - }), - &runtime)); - - for (size_t i = 0; i < thread_count; i++) { - threads.emplace_back([runtime, &result_count, &result_status, &mutex] { - std::scoped_lock lock(mutex); - node_embedding_status status = node_embedding_run_node_api( - runtime, - AsFunctorRef( - [&](node_embedding_runtime runtime, napi_env env) { - napi_value undefined, global, func, my_count; - NODE_API_CALL_RETURN_VOID(napi_get_undefined(env, &undefined)); - NODE_API_CALL_RETURN_VOID(napi_get_global(env, &global)); - NODE_API_CALL_RETURN_VOID( - napi_get_named_property(env, global, "incMyCount", &func)); - - napi_valuetype func_type; - NODE_API_CALL_RETURN_VOID(napi_typeof(env, func, &func_type)); - NODE_API_ASSERT_RETURN_VOID(func_type == napi_function); - NODE_API_CALL_RETURN_VOID(napi_call_function( - env, undefined, func, 0, nullptr, nullptr)); - - NODE_API_CALL_RETURN_VOID( - napi_get_named_property(env, global, "myCount", &my_count)); - napi_valuetype count_type; - NODE_API_CALL_RETURN_VOID( - napi_typeof(env, my_count, &count_type)); - NODE_API_ASSERT_RETURN_VOID(count_type == napi_number); - int32_t count; - NODE_API_CALL_RETURN_VOID( - napi_get_value_int32(env, my_count, &count)); - result_count.store(count); - })); - if (status != node_embedding_status_ok) { - result_status.store(status); - } - }); - } - - for (size_t i = 0; i < thread_count; i++) { - threads[i].join(); - } - - CHECK_STATUS_OR_EXIT(result_status.load()); - - CHECK_STATUS_OR_EXIT(node_embedding_complete_event_loop(runtime)); - CHECK_STATUS_OR_EXIT(node_embedding_delete_runtime(runtime)); - CHECK_STATUS_OR_EXIT(node_embedding_delete_platform(platform)); - - fprintf(stdout, "%d\n", result_count.load()); - - return 0; -} - -// Tests that a the runtime's event loop can be called from the UI thread -// event loop. -extern "C" int32_t test_main_threading_runtime_in_ui_thread_node_api( - int32_t argc, char* argv[]) { - // A simulation of the UI thread's event loop implemented as a dispatcher - // queue. Note that it is a very simplistic implementation not suitable - // for the real apps. - class UIQueue { - public: - void PostTask(std::function&& task) { - std::scoped_lock lock(mutex_); - if (!is_finished_) { - tasks_.push_back(std::move(task)); - wakeup_.notify_one(); - } - } - - void Run() { - for (;;) { - std::function task; - { - std::unique_lock lock(mutex_); - wakeup_.wait(lock, [&] { return is_finished_ || !tasks_.empty(); }); - if (is_finished_) break; - task = std::move(tasks_.front()); - tasks_.pop_front(); - } - task(); - } - } - - void Stop() { - std::scoped_lock lock(mutex_); - if (!is_finished_) { - is_finished_ = true; - wakeup_.notify_one(); - } - } - - private: - std::mutex mutex_; - std::condition_variable wakeup_; - std::deque> tasks_; - bool is_finished_{false}; - } ui_queue; - - node_embedding_platform platform; - CHECK_STATUS_OR_EXIT( - node_embedding_create_platform(argc, argv, {}, &platform)); - if (!platform) { - return 0; // early return - } - - node_embedding_runtime runtime; - CHECK_STATUS_OR_EXIT(node_embedding_create_runtime( - platform, - AsFunctorRef( - [&](node_embedding_platform platform, - node_embedding_runtime_config runtime_config) { - // The callback will be invoked from the runtime's event loop - // observer thread. It must schedule the work to the UI thread's - // event loop. - CHECK_STATUS(node_embedding_runtime_set_task_runner( - runtime_config, - AsFunctor( - // We capture the ui_queue by reference here because we - // guarantee it to be alive till the end of the test. In - // real applications, you should use a safer way to capture - // the dispatcher queue. - [&ui_queue, - &runtime](node_embedding_run_task_functor run_task) { - // TODO: figure out the termination scenario. - ui_queue.PostTask([run_task, &runtime, &ui_queue]() { - AsStdFunction(run_task)(); - - // Check myCount and stop the processing when it - // reaches 5. - CHECK_STATUS_OR_EXIT(node_embedding_run_node_api( - runtime, - AsFunctorRef< - node_embedding_run_node_api_functor_ref>( - [&](node_embedding_runtime runtime, - napi_env env) { - napi_value global, my_count; - NODE_API_CALL_RETURN_VOID( - napi_get_global(env, &global)); - NODE_API_CALL_RETURN_VOID( - napi_get_named_property( - env, global, "myCount", &my_count)); - napi_valuetype count_type; - NODE_API_CALL_RETURN_VOID( - napi_typeof(env, my_count, &count_type)); - NODE_API_ASSERT_RETURN_VOID(count_type == - napi_number); - int32_t count; - NODE_API_CALL_RETURN_VOID( - napi_get_value_int32( - env, my_count, &count)); - if (count == 5) { - node_embedding_complete_event_loop(runtime); - fprintf(stdout, "%d\n", count); - ui_queue.Stop(); - } - }))); - }); - }))); - - CHECK_STATUS(LoadUtf8Script(runtime_config, main_script)); - - return node_embedding_status_ok; - }), - &runtime)); - - // The initial task starts the JS code that then will do the timer - // scheduling. The timer supposed to be handled by the runtime's event loop. - ui_queue.PostTask([runtime]() { - node_embedding_status status = node_embedding_run_node_api( - runtime, - AsFunctorRef( - [&](node_embedding_runtime runtime, napi_env env) { - napi_value undefined, global, func; - NODE_API_CALL_RETURN_VOID(napi_get_undefined(env, &undefined)); - NODE_API_CALL_RETURN_VOID(napi_get_global(env, &global)); - NODE_API_CALL_RETURN_VOID( - napi_get_named_property(env, global, "incMyCount", &func)); - - napi_valuetype func_type; - NODE_API_CALL_RETURN_VOID(napi_typeof(env, func, &func_type)); - NODE_API_ASSERT_RETURN_VOID(func_type == napi_function); - NODE_API_CALL_RETURN_VOID(napi_call_function( - env, undefined, func, 0, nullptr, nullptr)); - - node_embedding_run_event_loop( - runtime, node_embedding_event_loop_run_mode_nowait, nullptr); - })); - CHECK_STATUS_OR_EXIT(status); - }); - - ui_queue.Run(); - - CHECK_STATUS_OR_EXIT(node_embedding_delete_runtime(runtime)); - CHECK_STATUS_OR_EXIT(node_embedding_delete_platform(platform)); - - return 0; -} diff --git a/test/embedding/embedtest_c_cpp_api.cc b/test/embedding/embedtest_c_cpp_api.cc new file mode 100644 index 00000000000000..4ce11e3dc2e7fd --- /dev/null +++ b/test/embedding/embedtest_c_cpp_api.cc @@ -0,0 +1,247 @@ +#include "embedtest_c_cpp_api_common.h" + +#include +#include +#include +#include + +namespace node::embedding { + +static napi_status CallMe(const NodeRuntime& runtime, napi_env env); +static napi_status WaitMe(const NodeRuntime& runtime, napi_env env); +static napi_status WaitMeWithCheese(const NodeRuntime& runtime, napi_env env); + +int32_t test_main_c_cpp_api(int32_t argc, const char* argv[]) { + TestExitCodeHandler error_handler(argv[0]); + NODE_EMBEDDING_CALL(NodePlatform::RunMain( + NodeArgs(argc, argv), + [](const NodePlatformConfig& platform_config) { + return platform_config.SetFlags( + NodePlatformFlags::kDisableNodeOptionsEnv); + }, + [](const NodePlatform& platform, + const NodeRuntimeConfig& runtime_config) { + NodeEmbeddingErrorHandler error_handler; + NODE_EMBEDDING_CALL(LoadUtf8Script(runtime_config, main_script)); + NODE_EMBEDDING_CALL(runtime_config.OnLoaded( + [](const NodeRuntime& runtime, napi_env env, napi_value + /*value*/) { + NodeApiErrorHandler error_handler(env); + NODE_API_CALL(CallMe(runtime, env)); + NODE_API_CALL(WaitMe(runtime, env)); + NODE_API_CALL(WaitMeWithCheese(runtime, env)); + })); + return error_handler.ReportResult(); + })); + return error_handler.ReportResult(); +} + +static napi_status CallMe(const NodeRuntime& runtime, napi_env env) { + NodeApiErrorHandler error_handler(env); + napi_value global{}, cb{}, key{}; + + NODE_API_CALL(napi_get_global(env, &global)); + NODE_API_CALL(napi_create_string_utf8(env, "callMe", NAPI_AUTO_LENGTH, &key)); + NODE_API_CALL(napi_get_property(env, global, key, &cb)); + + napi_valuetype cb_type{}; + NODE_API_CALL(napi_typeof(env, cb, &cb_type)); + + // Only evaluate callMe if it was registered as a function. + if (cb_type == napi_function) { + napi_value undef{}; + NODE_API_CALL(napi_get_undefined(env, &undef)); + napi_value arg{}; + NODE_API_CALL( + napi_create_string_utf8(env, "called", NAPI_AUTO_LENGTH, &arg)); + napi_value result{}; + NODE_API_CALL(napi_call_function(env, undef, cb, 1, &arg, &result)); + + char buf[32]{}; + size_t len{}; + NODE_API_CALL(napi_get_value_string_utf8(env, result, buf, 32, &len)); + if (strcmp(buf, "called you") != 0) { + NODE_FAIL("Invalid value received: %s", buf); + } + printf("%s", buf); + } else if (cb_type != napi_undefined) { + NODE_FAIL("Invalid callMe value"); + } + return napi_ok; +} + +// TODO: remove static variables +char callback_buf[32]; +size_t callback_buf_len; +static napi_value c_cb(napi_env env, napi_callback_info info) { + NodeApiErrorHandler error_handler(env); + size_t argc = 1; + napi_value arg{}; + NODE_API_CALL(napi_get_cb_info(env, info, &argc, &arg, nullptr, nullptr)); + NODE_API_CALL(napi_get_value_string_utf8( + env, arg, callback_buf, 32, &callback_buf_len)); + return nullptr; +} + +static napi_status WaitMe(const NodeRuntime& runtime, napi_env env) { + NodeApiErrorHandler error_handler(env); + napi_value global{}, cb{}, key{}; + + NODE_API_CALL(napi_get_global(env, &global)); + NODE_API_CALL(napi_create_string_utf8(env, "waitMe", NAPI_AUTO_LENGTH, &key)); + NODE_API_CALL(napi_get_property(env, global, key, &cb)); + + napi_valuetype cb_type{}; + NODE_API_CALL(napi_typeof(env, cb, &cb_type)); + + // Only evaluate waitMe if it was registered as a function. + if (cb_type == napi_function) { + napi_value undef{}; + NODE_API_CALL(napi_get_undefined(env, &undef)); + napi_value args[2]{}; + NODE_API_CALL( + napi_create_string_utf8(env, "waited", NAPI_AUTO_LENGTH, &args[0])); + NODE_API_CALL(napi_create_function( + env, "wait_cb", strlen("wait_cb"), c_cb, NULL, &args[1])); + + napi_value result{}; + memset(callback_buf, 0, 32); + NODE_API_CALL(napi_call_function(env, undef, cb, 2, args, &result)); + if (strcmp(callback_buf, "waited you") == 0) { + NODE_FAIL("Anachronism detected: %s", callback_buf); + } + + for (;;) { + NodeExpected loop_result = runtime.RunEventLoopOnce(); + if (loop_result.has_error()) { + NODE_FAIL("Failed to run event loop: %s", + NodeErrorInfo::GetLastErrorMessage()); + } + if (!loop_result.value()) { + break; + } + } + + if (strcmp(callback_buf, "waited you") != 0) { + NODE_FAIL("Invalid value received: %s", callback_buf); + } + printf("%s", callback_buf); + } else if (cb_type != napi_undefined) { + NODE_FAIL("Invalid waitMe value"); + } + return napi_ok; +} + +static napi_status WaitMeWithCheese(const NodeRuntime& runtime, napi_env env) { + enum class PromiseState { + kPending, + kFulfilled, + kRejected, + }; + + NodeApiErrorHandler error_handler(env); + PromiseState promise_state = PromiseState::kPending; + napi_value global{}, wait_promise{}, undefined{}; + napi_value on_fulfilled{}, on_rejected{}; + napi_value then_args[2] = {}; + const char* expected{}; + + NODE_API_CALL(napi_get_global(env, &global)); + NODE_API_CALL(napi_get_undefined(env, &undefined)); + + NODE_API_CALL( + napi_get_named_property(env, global, "waitPromise", &wait_promise)); + + napi_valuetype wait_promise_type; + NODE_API_CALL(napi_typeof(env, wait_promise, &wait_promise_type)); + + // Only evaluate waitPromise if it was registered as a function. + if (wait_promise_type == napi_undefined) { + return napi_ok; + } else if (wait_promise_type != napi_function) { + NODE_FAIL("Invalid waitPromise value"); + } + + napi_value arg; + NODE_API_CALL(napi_create_string_utf8(env, "waited", NAPI_AUTO_LENGTH, &arg)); + + memset(callback_buf, 0, 32); + napi_value promise; + NODE_API_CALL( + napi_call_function(env, undefined, wait_promise, 1, &arg, &promise)); + + if (strcmp(callback_buf, "waited with cheese") == 0) { + NODE_FAIL("Anachronism detected: %s", callback_buf); + } + + bool is_promise; + NODE_API_CALL(napi_is_promise(env, promise, &is_promise)); + if (!is_promise) { + NODE_FAIL("Result is not a Promise"); + } + + NODE_API_CALL(napi_create_function( + env, + "onFulfilled", + NAPI_AUTO_LENGTH, + [](napi_env env, napi_callback_info info) -> napi_value { + NodeApiErrorHandler error_handler(env); + size_t argc = 1; + napi_value result; + void* data; + NODE_API_CALL( + napi_get_cb_info(env, info, &argc, &result, nullptr, &data)); + NODE_API_CALL(napi_get_value_string_utf8( + env, result, callback_buf, 32, &callback_buf_len)); + *static_cast(data) = PromiseState::kFulfilled; + return nullptr; + }, + &promise_state, + &on_fulfilled)); + NODE_API_CALL(napi_create_function( + env, + "rejected", + NAPI_AUTO_LENGTH, + [](napi_env env, napi_callback_info info) -> napi_value { + NodeApiErrorHandler error_handler(env); + size_t argc = 1; + napi_value result; + void* data; + NODE_API_CALL( + napi_get_cb_info(env, info, &argc, &result, nullptr, &data)); + NODE_API_CALL(napi_get_value_string_utf8( + env, result, callback_buf, 32, &callback_buf_len)); + *static_cast(data) = PromiseState::kRejected; + return nullptr; + }, + &promise_state, + &on_rejected)); + napi_value then; + NODE_API_CALL(napi_get_named_property(env, promise, "then", &then)); + then_args[0] = on_fulfilled; + then_args[1] = on_rejected; + NODE_API_CALL(napi_call_function(env, promise, then, 2, then_args, nullptr)); + + while (promise_state == PromiseState::kPending) { + NodeExpected loop_result = runtime.RunEventLoopOnce(); + if (loop_result.has_error()) { + NODE_FAIL("Failed to run event loop: %s", + NodeErrorInfo::GetLastErrorMessage()); + } + if (!loop_result.value()) { + break; + } + } + + expected = (promise_state == PromiseState::kFulfilled) + ? "waited with cheese" + : "waited without cheese"; + + if (strcmp(callback_buf, expected) != 0) { + NODE_FAIL("Invalid value received: %s", callback_buf); + } + printf("%s", callback_buf); + return napi_ok; +} + +} // namespace node::embedding diff --git a/test/embedding/embedtest_c_cpp_api_common.cc b/test/embedding/embedtest_c_cpp_api_common.cc new file mode 100644 index 00000000000000..c45d203e3a3b43 --- /dev/null +++ b/test/embedding/embedtest_c_cpp_api_common.cc @@ -0,0 +1,61 @@ +#include "embedtest_c_cpp_api_common.h" + +#include +#include +#include +#include + +namespace node::embedding { + +const char* main_script = + "globalThis.require = require('module').createRequire(process.execPath);\n" + "globalThis.embedVars = { nön_ascıı: '🏳️‍🌈' };\n" + "require('vm').runInThisContext(process.argv[1]);"; + +napi_status AddUtf8String(std::string& str, napi_env env, napi_value value) { + size_t str_size = 0; + napi_status status = + napi_get_value_string_utf8(env, value, nullptr, 0, &str_size); + if (status != napi_ok) { + return status; + } + size_t offset = str.size(); + str.resize(offset + str_size); + status = napi_get_value_string_utf8( + env, value, &str[0] + offset, str_size + 1, &str_size); + return status; +} + +NodeExpected LoadUtf8Script(const NodeRuntimeConfig& runtime_config, + std::string_view script) { + NodeEmbeddingErrorHandler error_handler; + NODE_EMBEDDING_CALL(runtime_config.OnStartExecution( + [script = std::string(script)](const NodeRuntime& /*runtime*/, + napi_env env, + napi_value /*process*/, + napi_value /*require*/, + napi_value run_cjs) -> napi_value { + NodeApiErrorHandler error_handler(env); + napi_value script_value{}, null_value{}, result{}; + NODE_API_CALL(napi_create_string_utf8( + env, script.c_str(), script.size(), &script_value)); + NODE_API_CALL(napi_get_null(env, &null_value)); + NODE_API_CALL(napi_call_function( + env, null_value, run_cjs, 1, &script_value, &result)); + return result; + })); + return error_handler.ReportResult(); +} + +NodeExpected PrintErrorMessage(std::string_view exe_name, + NodeStatus status) { + std::string error_message = NodeErrorInfo::GetAndClearLastErrorMessage(); + if (status != NodeStatus::kOk) { + fprintf(stderr, "%s: %s\n", exe_name.data(), error_message.c_str()); + } else if (!error_message.empty()) { + fprintf(stdout, "%s\n", error_message.c_str()); + } + return NodeExpected(status); +} + +} // namespace node::embedding diff --git a/test/embedding/embedtest_c_cpp_api_common.h b/test/embedding/embedtest_c_cpp_api_common.h new file mode 100644 index 00000000000000..5841fcfb98c805 --- /dev/null +++ b/test/embedding/embedtest_c_cpp_api_common.h @@ -0,0 +1,97 @@ +#ifndef TEST_EMBEDDING_EMBEDTEST_C_CPP_API_COMMON_H_ +#define TEST_EMBEDDING_EMBEDTEST_C_CPP_API_COMMON_H_ + +#define NAPI_EXPERIMENTAL + +#include + +namespace node::embedding { + +extern const char* main_script; + +napi_status AddUtf8String(std::string& str, napi_env env, napi_value value); + +void GetAndThrowLastErrorMessage(napi_env env); + +void ThrowLastErrorMessage(napi_env env, const char* message); + +NodeExpected LoadUtf8Script(const NodeRuntimeConfig& runtime_config, + std::string_view script); + +NodeExpected PrintErrorMessage(std::string_view exe_name, + NodeStatus status); + +class TestExitCodeHandler : public NodeEmbeddingErrorHandler { + public: + TestExitCodeHandler(const char* exe_name) : exe_name_(exe_name) {} + + int32_t ReportResult() { + return PrintErrorMessage(exe_name_, + NodeEmbeddingErrorHandler::ReportResult().status()) + .exit_code(); + } + + private: + const char* exe_name_ = nullptr; +}; + +class TestExitOnErrorHandler : public NodeEmbeddingErrorHandler { + public: + TestExitOnErrorHandler(const char* exe_name) : exe_name_(exe_name) {} + + void ReportResult() { + int32_t exit_code = + PrintErrorMessage(exe_name_, + NodeEmbeddingErrorHandler::ReportResult().status()) + .exit_code(); + if (exit_code != 0) { + exit(exit_code); + } + } + + private: + const char* exe_name_ = nullptr; +}; + +} // namespace node::embedding + +//============================================================================== +// Error handing macros +//============================================================================== + +#define NODE_ASSERT(expr) \ + do { \ + if (!(expr)) { \ + error_handler.SetLastErrorMessage( \ + "Failed: %s\nFile: %s\nLine: %d\n", #expr, __FILE__, __LINE__); \ + return error_handler.ReportResult(); \ + } \ + } while (0) + +#define NODE_FAIL(format, ...) \ + do { \ + error_handler.SetLastErrorMessage("Failed: " format \ + "\nFile: %s\nLine: %d\n", \ + ##__VA_ARGS__, \ + __FILE__, \ + __LINE__); \ + return error_handler.ReportResult(); \ + } while (0) + +#define NODE_API_CALL(expr) \ + do { \ + error_handler.set_node_api_status(expr); \ + if (error_handler.has_node_api_error()) { \ + return error_handler.ReportResult(); \ + } \ + } while (0) + +#define NODE_EMBEDDING_CALL(expr) \ + do { \ + error_handler.set_embedding_status(expr); \ + if (error_handler.has_embedding_error()) { \ + return error_handler.ReportResult(); \ + } \ + } while (0) + +#endif // TEST_EMBEDDING_EMBEDTEST_C_CPP_API_COMMON_H_ diff --git a/test/embedding/embedtest_c_cpp_api_env.cc b/test/embedding/embedtest_c_cpp_api_env.cc new file mode 100644 index 00000000000000..c620b374df9c1b --- /dev/null +++ b/test/embedding/embedtest_c_cpp_api_env.cc @@ -0,0 +1,116 @@ +#include "embedtest_c_cpp_api_common.h" + +namespace node::embedding { + +// Test the no_browser_globals option. +int32_t test_main_c_cpp_api_env_no_browser_globals(int32_t argc, + const char* argv[]) { + TestExitCodeHandler error_handler(argv[0]); + NODE_EMBEDDING_CALL(NodePlatform::RunMain( + NodeArgs(argc, argv), + nullptr, + [](const NodePlatform& platform, + const NodeRuntimeConfig& runtime_config) { + NodeEmbeddingErrorHandler error_handler; + NODE_EMBEDDING_CALL( + runtime_config.SetFlags(NodeRuntimeFlags::kNoBrowserGlobals)); + NODE_EMBEDDING_CALL(LoadUtf8Script(runtime_config, + R"JS( +const assert = require('assert'); +const path = require('path'); +const relativeRequire = + require('module').createRequire(path.join(process.cwd(), 'stub.js')); +const { intrinsics, nodeGlobals } = + relativeRequire('./test/common/globals'); +const items = Object.getOwnPropertyNames(globalThis); +const leaks = []; +for (const item of items) { + if (intrinsics.has(item)) { + continue; + } + if (nodeGlobals.has(item)) { + continue; + } + if (item === '$jsDebugIsRegistered') { + continue; + } + leaks.push(item); +} +assert.deepStrictEqual(leaks, []); +)JS")); + return NodeExpected(); + })); + return error_handler.ReportResult(); +} + +// Test ESM loaded +int32_t test_main_c_cpp_api_env_with_esm_loader(int32_t argc, + const char* argv[]) { + TestExitCodeHandler error_handler(argv[0]); + // We currently cannot pass argument to command line arguments to the runtime. + // They must be parsed by the platform. + std::vector args_vec(argv, argv + argc); + args_vec.push_back("--experimental-vm-modules"); + NodeCStringArray args(args_vec); + NODE_EMBEDDING_CALL( + NodePlatform::RunMain(NodeArgs(args), + nullptr, + [](const NodePlatform& platform, + const NodeRuntimeConfig& runtime_config) { + return LoadUtf8Script(runtime_config, + R"JS( +globalThis.require = require('module').createRequire(process.execPath); +const { SourceTextModule } = require('node:vm'); +(async () => { + const stmString = 'globalThis.importResult = import("")'; + const m = new SourceTextModule(stmString, { + importModuleDynamically: (async () => { + const m = new SourceTextModule(''); + await m.link(() => 0); + await m.evaluate(); + return m.namespace; + }), + }); + await m.link(() => 0); + await m.evaluate(); + delete globalThis.importResult; + process.exit(0); +})(); +)JS"); + })); + return error_handler.ReportResult(); +} + +// Test ESM loaded +int32_t test_main_c_cpp_api_env_with_no_esm_loader(int32_t argc, + const char* argv[]) { + TestExitCodeHandler error_handler(argv[0]); + NODE_EMBEDDING_CALL( + NodePlatform::RunMain(NodeArgs(argc, argv), + nullptr, + [](const NodePlatform& platform, + const NodeRuntimeConfig& runtime_config) { + return LoadUtf8Script(runtime_config, + R"JS( +globalThis.require = require('module').createRequire(process.execPath); +const { SourceTextModule } = require('node:vm'); +(async () => { + const stmString = 'globalThis.importResult = import("")'; + const m = new SourceTextModule(stmString, { + importModuleDynamically: (async () => { + const m = new SourceTextModule(''); + await m.link(() => 0); + await m.evaluate(); + return m.namespace; + }), + }); + await m.link(() => 0); + await m.evaluate(); + delete globalThis.importResult; +})(); +)JS"); + })); + return error_handler.ReportResult(); +} + +} // namespace node::embedding diff --git a/test/embedding/embedtest_c_cpp_api_modules.cc b/test/embedding/embedtest_c_cpp_api_modules.cc new file mode 100644 index 00000000000000..a13b4bbb2ec0e5 --- /dev/null +++ b/test/embedding/embedtest_c_cpp_api_modules.cc @@ -0,0 +1,144 @@ +#include "embedtest_c_cpp_api_common.h" + +#include +#include +#include + +namespace node::embedding { + +namespace { + +class GreeterModule { + public: + explicit GreeterModule(std::atomic* counter_ptr) + : counter_ptr_(counter_ptr) {} + + napi_value operator()(const NodeRuntime& runtime, + napi_env env, + std::string_view module_name, + napi_value exports) { + NodeApiErrorHandler error_handler(env); + counter_ptr_->fetch_add(1); + + napi_value greet_func{}; + NODE_API_CALL(napi_create_function( + env, + "greet", + NAPI_AUTO_LENGTH, + [](napi_env env, napi_callback_info info) -> napi_value { + NodeApiErrorHandler error_handler(env); + std::string greeting = "Hello, "; + napi_value arg{}; + size_t arg_count = 1; + NODE_API_CALL( + napi_get_cb_info(env, info, &arg_count, &arg, nullptr, nullptr)); + NODE_API_CALL(AddUtf8String(greeting, env, arg)); + napi_value result; + NODE_API_CALL(napi_create_string_utf8( + env, greeting.c_str(), greeting.size(), &result)); + return result; + }, + nullptr, + &greet_func)); + NODE_API_CALL(napi_set_named_property(env, exports, "greet", greet_func)); + return exports; + } + + private: + std::atomic* counter_ptr_; +}; + +class ReplicatorModule { + public: + explicit ReplicatorModule(std::atomic* counter_ptr) + : counter_ptr_(counter_ptr) {} + + napi_value operator()(const NodeRuntime& runtime, + napi_env env, + std::string_view module_name, + napi_value exports) { + NodeApiErrorHandler error_handler(env); + counter_ptr_->fetch_add(1); + + napi_value greet_func{}; + NODE_API_CALL(napi_create_function( + env, + "replicate", + NAPI_AUTO_LENGTH, + [](napi_env env, napi_callback_info info) -> napi_value { + NodeApiErrorHandler error_handler(env); + std::string str; + napi_value arg{}; + size_t arg_count = 1; + NODE_API_CALL( + napi_get_cb_info(env, info, &arg_count, &arg, nullptr, nullptr)); + NODE_API_CALL(AddUtf8String(str, env, arg)); + str += " " + str; + napi_value result; + NODE_API_CALL( + napi_create_string_utf8(env, str.c_str(), str.size(), &result)); + return result; + }, + nullptr, + &greet_func)); + NODE_API_CALL( + napi_set_named_property(env, exports, "replicate", greet_func)); + return exports; + } + + private: + std::atomic* counter_ptr_; +}; + +} // namespace + +int32_t test_main_c_cpp_api_linked_modules(int32_t argc, const char* argv[]) { + TestExitCodeHandler error_handler(argv[0]); + NODE_ASSERT(argc == 4); + int32_t expectedGreeterModuleInitCallCount = atoi(argv[2]); + int32_t expectedReplicatorModuleInitCallCount = atoi(argv[2]); + + std::atomic greeterModuleInitCallCount{0}; + std::atomic replicatorModuleInitCallCount{0}; + + NODE_EMBEDDING_CALL(NodePlatform::RunMain( + NodeArgs(argc, argv), + nullptr, + NodeConfigureRuntimeCallback( + [&](const NodePlatform& platform, + const NodeRuntimeConfig& runtime_config) { + NodeEmbeddingErrorHandler error_handler; + NODE_EMBEDDING_CALL( + runtime_config.OnPreload([](const NodeRuntime& runtime, + napi_env env, + napi_value process, + napi_value /*require*/ + ) { + napi_value global; + napi_get_global(env, &global); + napi_set_named_property(env, global, "process", process); + })); + + NODE_EMBEDDING_CALL(runtime_config.AddModule( + "greeter_module", + GreeterModule(&greeterModuleInitCallCount), + NAPI_VERSION)); + + NODE_EMBEDDING_CALL(runtime_config.AddModule( + "replicator_module", + ReplicatorModule(&replicatorModuleInitCallCount), + NAPI_VERSION)); + + NODE_EMBEDDING_CALL(LoadUtf8Script(runtime_config, main_script)); + + return error_handler.ReportResult(); + }))); + + NODE_ASSERT(greeterModuleInitCallCount == expectedGreeterModuleInitCallCount); + NODE_ASSERT(replicatorModuleInitCallCount == + expectedReplicatorModuleInitCallCount); + + return error_handler.ReportResult(); +} + +} // namespace node::embedding diff --git a/test/embedding/embedtest_c_cpp_api_preload.cc b/test/embedding/embedtest_c_cpp_api_preload.cc new file mode 100644 index 00000000000000..7c7d2c186fb68c --- /dev/null +++ b/test/embedding/embedtest_c_cpp_api_preload.cc @@ -0,0 +1,36 @@ +#include "embedtest_c_cpp_api_common.h" + +namespace node::embedding { + +// Tests that the same preload callback is called from the main thread and from +// the worker thread. +int32_t test_main_c_cpp_api_preload(int32_t argc, const char* argv[]) { + TestExitCodeHandler error_handler(argv[0]); + NODE_EMBEDDING_CALL(NodePlatform::RunMain( + NodeArgs(argc, argv), + nullptr, + [](const NodePlatform& platform, + const NodeRuntimeConfig& runtime_config) { + NodeEmbeddingErrorHandler error_handler; + NODE_EMBEDDING_CALL( + runtime_config.OnPreload([](const NodeRuntime& runtime, + napi_env env, + napi_value /*process*/, + napi_value /*require*/ + ) { + NodeApiErrorHandler error_handler(env); + napi_value global, value; + NODE_API_CALL(napi_get_global(env, &global)); + NODE_API_CALL(napi_create_int32(env, 42, &value)); + NODE_API_CALL( + napi_set_named_property(env, global, "preloadValue", value)); + })); + + NODE_EMBEDDING_CALL(LoadUtf8Script(runtime_config, main_script)); + + return error_handler.ReportResult(); + })); + return error_handler.ReportResult(); +} + +} // namespace node::embedding diff --git a/test/embedding/embedtest_c_cpp_api_run_main.cc b/test/embedding/embedtest_c_cpp_api_run_main.cc new file mode 100644 index 00000000000000..126ee51c8f0079 --- /dev/null +++ b/test/embedding/embedtest_c_cpp_api_run_main.cc @@ -0,0 +1,15 @@ +#include "embedtest_c_cpp_api_common.h" + +namespace node::embedding { + +// The simplest Node.js embedding scenario where the Node.js main function is +// invoked from the libnode shared library as it would be run from the Node.js +// CLI. No embedder customizations are available in this case. +int32_t test_main_c_cpp_api_nodejs_main(int32_t argc, const char* argv[]) { + TestExitCodeHandler error_handler(argv[0]); + NODE_EMBEDDING_CALL( + NodePlatform::RunMain(NodeArgs(argc, argv), nullptr, nullptr)); + return 0; +} + +} // namespace node::embedding diff --git a/test/embedding/embedtest_c_cpp_api_threading.cc b/test/embedding/embedtest_c_cpp_api_threading.cc new file mode 100644 index 00000000000000..68e8e5690a83e2 --- /dev/null +++ b/test/embedding/embedtest_c_cpp_api_threading.cc @@ -0,0 +1,386 @@ +#include "embedtest_c_cpp_api_common.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace node::embedding { + +// Tests that multiple runtimes can be run at the same time in their own +// threads. The test creates 12 threads and 12 runtimes. Each runtime runs in it +// own thread. +int32_t test_main_c_cpp_api_threading_runtime_per_thread(int32_t argc, + const char* argv[]) { + const size_t thread_count = 12; + std::vector threads; + threads.reserve(thread_count); + std::atomic global_count{0}; + std::atomic global_status{NodeStatus::kOk}; + + TestExitCodeHandler error_handler(argv[0]); + { + NodeExpected expected_platform = + NodePlatform::Create(NodeArgs(argc, argv), nullptr); + NODE_EMBEDDING_CALL(expected_platform.status()); + NodePlatform platform = std::move(expected_platform).value(); + if (!platform) { + return error_handler.ReportResult(); // early return + } + + for (size_t i = 0; i < thread_count; i++) { + threads.emplace_back([&platform, &global_count, &global_status] { + NodeExpected result = NodeRuntime::Run( + platform, + [&](const NodePlatform& platform, + const NodeRuntimeConfig& runtime_config) { + NodeEmbeddingErrorHandler error_handler; + // Inspector can be associated with only one + // runtime in the process. + NODE_EMBEDDING_CALL(runtime_config.SetFlags( + NodeRuntimeFlags::kDefault | + NodeRuntimeFlags::kNoCreateInspector)); + NODE_EMBEDDING_CALL(LoadUtf8Script(runtime_config, main_script)); + NODE_EMBEDDING_CALL( + runtime_config.OnLoaded([&](const NodeRuntime& runtime, + napi_env env, + napi_value /*value*/) { + NodeApiErrorHandler error_handler(env); + napi_value global, my_count; + NODE_API_CALL(napi_get_global(env, &global)); + NODE_API_CALL(napi_get_named_property( + env, global, "myCount", &my_count)); + int32_t count; + NODE_API_CALL(napi_get_value_int32(env, my_count, &count)); + global_count.fetch_add(count); + })); + return error_handler.ReportResult(); + }); + if (result.has_error()) { + global_status.store(result.status()); + } + }); + } + + for (size_t i = 0; i < thread_count; i++) { + threads[i].join(); + } + + NODE_EMBEDDING_CALL(NodeExpected(global_status.load())); + } + + fprintf(stdout, "%d\n", global_count.load()); + return error_handler.ReportResult(); +} + +// Tests that multiple runtimes can run in the same thread. +// The runtime scope must be opened and closed for each use. +// There are 12 runtimes that share the same main thread. +int32_t test_main_c_cpp_api_threading_several_runtimes_per_thread( + int32_t argc, const char* argv[]) { + const size_t runtime_count = 12; + bool more_work = false; + int32_t global_count = 0; + + TestExitCodeHandler error_handler(argv[0]); + { + NodeExpected expected_platform = + NodePlatform::Create(NodeArgs(argc, argv), nullptr); + NODE_EMBEDDING_CALL(expected_platform.status()); + NodePlatform platform = std::move(expected_platform).value(); + + // We declared list of NodeRuntime after NodePlatform to ensure that they + // are released before the platform. + std::vector runtimes; + runtimes.reserve(runtime_count); + + for (size_t i = 0; i < runtime_count; i++) { + NodeExpected expected_runtime = NodeRuntime::Create( + platform, + [](const NodePlatform& platform, + const NodeRuntimeConfig& runtime_config) { + NodeEmbeddingErrorHandler error_handler; + // Inspector can be associated with only one runtime in the process. + NODE_EMBEDDING_CALL( + runtime_config.SetFlags(NodeRuntimeFlags::kDefault | + NodeRuntimeFlags::kNoCreateInspector)); + NODE_EMBEDDING_CALL(LoadUtf8Script(runtime_config, main_script)); + return error_handler.ReportResult(); + }); + + NODE_EMBEDDING_CALL(expected_runtime.status()); + NodeRuntime runtime = std::move(expected_runtime).value(); + + NODE_EMBEDDING_CALL(runtime.RunNodeApi([&](napi_env env) { + NodeApiErrorHandler error_handler(env); + napi_value undefined, global, func; + NODE_API_CALL(napi_get_undefined(env, &undefined)); + NODE_API_CALL(napi_get_global(env, &global)); + NODE_API_CALL( + napi_get_named_property(env, global, "incMyCount", &func)); + + napi_valuetype func_type; + NODE_API_CALL(napi_typeof(env, func, &func_type)); + NODE_ASSERT(func_type == napi_function); + NODE_API_CALL( + napi_call_function(env, undefined, func, 0, nullptr, nullptr)); + })); + + runtimes.push_back(std::move(runtime)); + } + + do { + more_work = false; + for (const NodeRuntime& runtime : runtimes) { + NodeExpected has_more_work = runtime.RunEventLoopNoWait(); + NODE_EMBEDDING_CALL(has_more_work.status()); + more_work |= has_more_work.value(); + } + } while (more_work); + + for (const NodeRuntime& runtime : runtimes) { + NODE_EMBEDDING_CALL(runtime.RunNodeApi([&](napi_env env) { + NodeApiErrorHandler error_handler(env); + napi_value global, my_count; + NODE_API_CALL(napi_get_global(env, &global)); + NODE_API_CALL( + napi_get_named_property(env, global, "myCount", &my_count)); + + napi_valuetype my_count_type; + NODE_API_CALL(napi_typeof(env, my_count, &my_count_type)); + NODE_ASSERT(my_count_type == napi_number); + int32_t count; + NODE_API_CALL(napi_get_value_int32(env, my_count, &count)); + + global_count += count; + })); + NODE_EMBEDDING_CALL(runtime.RunEventLoop()); + } + } + + fprintf(stdout, "%d\n", global_count); + return error_handler.ReportResult(); +} + +// Tests that a runtime can be invoked from different threads as long as only +// one thread uses it at a time. +int32_t test_main_c_cpp_api_threading_runtime_in_several_threads( + int32_t argc, const char* argv[]) { + // Use mutex to synchronize access to the runtime. + std::mutex mutex; + std::atomic result_count{0}; + std::atomic result_status{NodeStatus::kOk}; + const size_t thread_count = 5; + std::vector threads; + threads.reserve(thread_count); + + TestExitCodeHandler error_handler(argv[0]); + { + NodeExpected expected_platform = + NodePlatform::Create(NodeArgs(argc, argv), nullptr); + NODE_EMBEDDING_CALL(expected_platform.status()); + NodePlatform platform = std::move(expected_platform).value(); + if (!platform) { + return error_handler.ReportResult(); // early return + } + + NodeExpected expected_runtime = NodeRuntime::Create( + platform, + [](const NodePlatform& platform, + const NodeRuntimeConfig& runtime_config) { + return LoadUtf8Script(runtime_config, main_script); + }); + + NODE_EMBEDDING_CALL(expected_runtime.status()); + NodeRuntime runtime = std::move(expected_runtime).value(); + + for (size_t i = 0; i < thread_count; i++) { + threads.emplace_back([&runtime, &result_count, &result_status, &mutex] { + std::scoped_lock lock(mutex); + NodeExpected run_result = runtime.RunNodeApi([&](napi_env env) { + NodeApiErrorHandler error_handler(env); + napi_value undefined, global, func, my_count; + NODE_API_CALL(napi_get_undefined(env, &undefined)); + NODE_API_CALL(napi_get_global(env, &global)); + NODE_API_CALL( + napi_get_named_property(env, global, "incMyCount", &func)); + + napi_valuetype func_type; + NODE_API_CALL(napi_typeof(env, func, &func_type)); + NODE_ASSERT(func_type == napi_function); + NODE_API_CALL( + napi_call_function(env, undefined, func, 0, nullptr, nullptr)); + + NODE_API_CALL( + napi_get_named_property(env, global, "myCount", &my_count)); + napi_valuetype count_type; + NODE_API_CALL(napi_typeof(env, my_count, &count_type)); + NODE_ASSERT(count_type == napi_number); + int32_t count; + NODE_API_CALL(napi_get_value_int32(env, my_count, &count)); + result_count.store(count); + }); + if (run_result.has_error()) { + result_status.store(run_result.status()); + } + }); + } + + for (size_t i = 0; i < thread_count; i++) { + threads[i].join(); + } + + NODE_EMBEDDING_CALL(NodeExpected(result_status.load())); + NODE_EMBEDDING_CALL(runtime.RunEventLoop()); + } + + fprintf(stdout, "%d\n", result_count.load()); + return error_handler.ReportResult(); +} + +namespace { + +// A simulation of the UI thread's event loop implemented as a dispatcher +// queue. Note that it is a very simplistic implementation not suitable +// for the real apps. +class UIQueue { + public: + void PostTask(std::function&& task) { + std::scoped_lock lock(mutex_); + if (!is_finished_) { + tasks_.push_back(std::move(task)); + wakeup_.notify_one(); + } + } + + void Run() { + for (;;) { + // Invoke task outside of the lock. + std::function task; + { + std::unique_lock lock(mutex_); + wakeup_.wait(lock, [&] { return is_finished_ || !tasks_.empty(); }); + if (is_finished_) return; + task = std::move(tasks_.front()); + tasks_.pop_front(); + } + task(); + } + } + + void Stop() { + std::scoped_lock lock(mutex_); + if (!is_finished_) { + is_finished_ = true; + wakeup_.notify_one(); + } + } + + private: + std::mutex mutex_; + std::condition_variable wakeup_; + std::deque> tasks_; + bool is_finished_{false}; +}; + +} // namespace + +// Tests that a the runtime's event loop can be called from the UI thread +// event loop. +int32_t test_main_c_cpp_api_threading_runtime_in_ui_thread(int32_t argc, + const char* argv[]) { + UIQueue ui_queue; + const char* exe_name = argv[0]; + TestExitCodeHandler error_handler(exe_name); + { + NodeExpected expected_platform = + NodePlatform::Create(NodeArgs(argc, argv), nullptr); + NODE_EMBEDDING_CALL(expected_platform.status()); + NodePlatform platform = std::move(expected_platform).value(); + if (!platform) { + return error_handler.ReportResult(); // early return + } + + NodeRuntime runtime{nullptr}; + NodeExpected expected_runtime = NodeRuntime::Create( + platform, + [&](const NodePlatform& platform, + const NodeRuntimeConfig& runtime_config) { + NodeEmbeddingErrorHandler error_handler; + // The callback will be invoked from the runtime's event loop + // observer thread. It must schedule the work to the UI thread's + // event loop. + NODE_EMBEDDING_CALL(runtime_config.SetTaskRunner( + // We capture the ui_queue by reference here because we + // guarantee it to be alive till the end of the test. In + // real applications, you should use a safer way to + // capture the dispatcher queue. + [&ui_queue, &runtime, exe_name](NodeRunTaskCallback run_task) { + // TODO: figure out the termination scenario. + // TODO: Release run_task data. + ui_queue.PostTask([run_task = + std::make_shared( + std::move(run_task)), + &runtime, + &ui_queue, + exe_name]() { + TestExitOnErrorHandler error_handler(exe_name); + NODE_EMBEDDING_CALL((*run_task)()); + // Check myCount and stop the processing when it reaches 5. + int32_t count{}; + NODE_EMBEDDING_CALL(runtime.RunNodeApi([&](napi_env env) { + NodeApiErrorHandler error_handler(env); + napi_value global, my_count; + NODE_API_CALL(napi_get_global(env, &global)); + NODE_API_CALL(napi_get_named_property( + env, global, "myCount", &my_count)); + napi_valuetype count_type; + NODE_API_CALL(napi_typeof(env, my_count, &count_type)); + NODE_ASSERT(count_type == napi_number); + NODE_API_CALL(napi_get_value_int32(env, my_count, &count)); + })); + if (count == 5) { + NODE_EMBEDDING_CALL(runtime.RunEventLoop()); + fprintf(stdout, "%d\n", count); + ui_queue.Stop(); + } + }); + return NodeExpected(true); + })); + + NODE_EMBEDDING_CALL(LoadUtf8Script(runtime_config, main_script)); + return error_handler.ReportResult(); + }); + NODE_EMBEDDING_CALL(expected_runtime.status()); + runtime = std::move(expected_runtime).value(); + + // The initial task starts the JS code that then will do the timer + // scheduling. The timer supposed to be handled by the runtime's event loop. + ui_queue.PostTask([&runtime, exe_name]() { + TestExitOnErrorHandler error_handler(exe_name); + NODE_EMBEDDING_CALL(runtime.RunNodeApi([&](napi_env env) { + NodeApiErrorHandler error_handler(env); + napi_value undefined, global, func; + NODE_API_CALL(napi_get_undefined(env, &undefined)); + NODE_API_CALL(napi_get_global(env, &global)); + NODE_API_CALL( + napi_get_named_property(env, global, "incMyCount", &func)); + + napi_valuetype func_type; + NODE_API_CALL(napi_typeof(env, func, &func_type)); + NODE_ASSERT(func_type == napi_function); + NODE_API_CALL( + napi_call_function(env, undefined, func, 0, nullptr, nullptr)); + })); + }); + + ui_queue.Run(); + } + + return error_handler.ReportResult(); +} + +} // namespace node::embedding diff --git a/test/embedding/embedtest_main.cc b/test/embedding/embedtest_main.cc index 8bb2bc88161aa1..7fd0df90977ce6 100644 --- a/test/embedding/embedtest_main.cc +++ b/test/embedding/embedtest_main.cc @@ -1,31 +1,55 @@ +#include +#include +#include #include "executable_wrapper.h" -extern "C" int32_t test_main_cpp_api(int32_t argc, char* argv[]); -extern "C" int32_t test_main_node_api(int32_t argc, char* argv[]); -extern "C" int32_t test_main_nodejs_main_node_api(int32_t argc, char* argv[]); -extern "C" int32_t test_main_modules_node_api(int32_t argc, char* argv[]); -extern "C" int32_t test_main_linked_modules_node_api(int32_t argc, - char* argv[]); -extern "C" int32_t test_main_threading_runtime_per_thread_node_api( - int32_t argc, char* argv[]); -extern "C" int32_t test_main_threading_several_runtimes_per_thread_node_api( - int32_t argc, char* argv[]); -extern "C" int32_t test_main_threading_runtime_in_several_threads_node_api( - int32_t argc, char* argv[]); -extern "C" int32_t test_main_threading_runtime_in_ui_thread_node_api( - int32_t argc, char* argv[]); -extern "C" int32_t test_main_preload_node_api(int32_t argc, char* argv[]); +int32_t test_main_cpp_api(int32_t argc, const char* argv[]); +extern "C" int32_t test_main_c_api(int32_t argc, const char* argv[]); +extern "C" int32_t test_main_c_api_nodejs_main(int32_t argc, + const char* argv[]); +extern "C" int32_t test_main_c_api_threading_runtime_per_thread( + int32_t argc, const char* argv[]); +extern "C" int32_t test_main_c_api_threading_several_runtimes_per_thread( + int32_t argc, const char* argv[]); +extern "C" int32_t test_main_c_api_threading_runtime_in_several_threads( + int32_t argc, const char* argv[]); +extern "C" int32_t test_main_c_api_threading_runtime_in_ui_thread( + int32_t argc, const char* argv[]); +extern "C" int32_t test_main_c_api_preload(int32_t argc, const char* argv[]); +extern "C" int32_t test_main_c_api_linked_modules(int32_t argc, + const char* argv[]); extern "C" int32_t test_main_c_api_env_no_browser_globals(int32_t argc, - char* argv[]); + const char* argv[]); extern "C" int32_t test_main_c_api_env_with_esm_loader(int32_t argc, - char* argv[]); + const char* argv[]); extern "C" int32_t test_main_c_api_env_with_no_esm_loader(int32_t argc, - char* argv[]); + const char* argv[]); +namespace node::embedding { -typedef int32_t (*main_callback)(int32_t argc, char* argv[]); +int32_t test_main_c_cpp_api(int32_t argc, const char* argv[]); +int32_t test_main_c_cpp_api_nodejs_main(int32_t argc, const char* argv[]); +int32_t test_main_c_cpp_api_threading_runtime_per_thread(int32_t argc, + const char* argv[]); +int32_t test_main_c_cpp_api_threading_several_runtimes_per_thread( + int32_t argc, const char* argv[]); +int32_t test_main_c_cpp_api_threading_runtime_in_several_threads( + int32_t argc, const char* argv[]); +int32_t test_main_c_cpp_api_threading_runtime_in_ui_thread(int32_t argc, + const char* argv[]); +int32_t test_main_c_cpp_api_preload(int32_t argc, const char* argv[]); +int32_t test_main_c_cpp_api_linked_modules(int32_t argc, const char* argv[]); +int32_t test_main_c_cpp_api_env_no_browser_globals(int32_t argc, + const char* argv[]); +int32_t test_main_c_cpp_api_env_with_esm_loader(int32_t argc, + const char* argv[]); +int32_t test_main_c_cpp_api_env_with_no_esm_loader(int32_t argc, + const char* argv[]); +} // namespace node::embedding -int32_t CallWithoutArg1(main_callback main, int32_t argc, char** argv) { +typedef int32_t (*main_callback)(int32_t argc, const char* argv[]); + +int32_t CallWithoutArg1(main_callback main, int32_t argc, const char* argv[]) { for (int32_t i = 2; i < argc; i++) { argv[i - 1] = argv[i]; } @@ -37,43 +61,53 @@ NODE_MAIN(int32_t argc, node::argv_type raw_argv[]) { char** argv = nullptr; node::FixupMain(argc, raw_argv, &argv); + const std::unordered_map main_map = { + {"cpp-api", test_main_cpp_api}, + {"c-api", test_main_c_api}, + {"c-api-nodejs-main", test_main_c_api_nodejs_main}, + {"c-api-threading-runtime-per-thread", + test_main_c_api_threading_runtime_per_thread}, + {"c-api-threading-several-runtimes-per-thread", + test_main_c_api_threading_several_runtimes_per_thread}, + {"c-api-threading-runtime-in-several-threads", + test_main_c_api_threading_runtime_in_several_threads}, + {"c-api-threading-runtime-in-ui-thread", + test_main_c_api_threading_runtime_in_ui_thread}, + {"c-api-preload", test_main_c_api_preload}, + {"c-api-linked-modules", test_main_c_api_linked_modules}, + {"c-api-env-no-browser-globals", test_main_c_api_env_no_browser_globals}, + {"c-api-env-with-esm-loader", test_main_c_api_env_with_esm_loader}, + {"c-api-env-with-no-esm-loader", test_main_c_api_env_with_no_esm_loader}, + {"c-cpp-api", node::embedding::test_main_c_cpp_api}, + {"c-cpp-api-nodejs-main", + node::embedding::test_main_c_cpp_api_nodejs_main}, + {"c-cpp-api-threading-runtime-per-thread", + node::embedding::test_main_c_cpp_api_threading_runtime_per_thread}, + {"c-cpp-api-threading-several-runtimes-per-thread", + node::embedding:: + test_main_c_cpp_api_threading_several_runtimes_per_thread}, + {"c-cpp-api-threading-runtime-in-several-threads", + node::embedding:: + test_main_c_cpp_api_threading_runtime_in_several_threads}, + {"c-cpp-api-threading-runtime-in-ui-thread", + node::embedding::test_main_c_cpp_api_threading_runtime_in_ui_thread}, + {"c-cpp-api-preload", node::embedding::test_main_c_cpp_api_preload}, + {"c-cpp-api-linked-modules", + node::embedding::test_main_c_cpp_api_linked_modules}, + {"c-cpp-api-env-no-browser-globals", + node::embedding::test_main_c_cpp_api_env_no_browser_globals}, + {"c-cpp-api-env-with-esm-loader", + node::embedding::test_main_c_cpp_api_env_with_esm_loader}, + {"c-cpp-api-env-with-no-esm-loader", + node::embedding::test_main_c_cpp_api_env_with_no_esm_loader}, + }; if (argc > 1) { const char* arg1 = argv[1]; - if (strcmp(arg1, "cpp-api") == 0) { - return CallWithoutArg1(test_main_cpp_api, argc, argv); - } else if (strcmp(arg1, "node-api") == 0) { - return CallWithoutArg1(test_main_node_api, argc, argv); - } else if (strcmp(arg1, "nodejs-main-node-api") == 0) { - return CallWithoutArg1(test_main_nodejs_main_node_api, argc, argv); - } else if (strcmp(arg1, "modules-node-api") == 0) { - return CallWithoutArg1(test_main_modules_node_api, argc, argv); - } else if (strcmp(arg1, "linked-modules-node-api") == 0) { - return CallWithoutArg1(test_main_linked_modules_node_api, argc, argv); - } else if (strcmp(arg1, "threading-runtime-per-thread-node-api") == 0) { - return CallWithoutArg1( - test_main_threading_runtime_per_thread_node_api, argc, argv); - } else if (strcmp(arg1, "threading-several-runtimes-per-thread-node-api") == - 0) { - return CallWithoutArg1( - test_main_threading_several_runtimes_per_thread_node_api, argc, argv); - } else if (strcmp(arg1, "threading-runtime-in-several-threads-node-api") == - 0) { - return CallWithoutArg1( - test_main_threading_runtime_in_several_threads_node_api, argc, argv); - } else if (strcmp(arg1, "threading-runtime-in-ui-thread-node-api") == 0) { - return CallWithoutArg1( - test_main_threading_runtime_in_ui_thread_node_api, argc, argv); - } else if (strcmp(arg1, "preload-node-api") == 0) { - return CallWithoutArg1(test_main_preload_node_api, argc, argv); - } else if (strcmp(arg1, "c-api-env-no-browser-globals") == 0) { - return CallWithoutArg1( - test_main_c_api_env_no_browser_globals, argc, argv); - } else if (strcmp(arg1, "c-api-env-with-esm-loader") == 0) { - return CallWithoutArg1(test_main_c_api_env_with_esm_loader, argc, argv); - } else if (strcmp(arg1, "c-api-env-with-no-esm-loader") == 0) { - return CallWithoutArg1( - test_main_c_api_env_with_no_esm_loader, argc, argv); + for (const auto& [key, value] : main_map) { + if (key == arg1) { + return CallWithoutArg1(value, argc, (const char**)argv); + } } } - return test_main_cpp_api(argc, argv); + return test_main_cpp_api(argc, (const char**)argv); } diff --git a/test/embedding/es6.mjs b/test/embedding/es6.mjs deleted file mode 100644 index 01b505b2c5fb19..00000000000000 --- a/test/embedding/es6.mjs +++ /dev/null @@ -1 +0,0 @@ -export const value = 'genuine'; diff --git a/test/embedding/test-embedding.js b/test/embedding/test-embedding.js index 0e116a1bdd85c8..8eae3ef1dd3066 100644 --- a/test/embedding/test-embedding.js +++ b/test/embedding/test-embedding.js @@ -128,7 +128,8 @@ function runCommonApiTests(apiType) { } runCommonApiTests('cpp-api'); -runCommonApiTests('node-api'); +runCommonApiTests('c-api'); +runCommonApiTests('c-cpp-api'); function getReadFileCodeForPath(path) { return `(require("fs").readFileSync(${JSON.stringify(path)}, "utf8"))`; @@ -235,12 +236,12 @@ function runSnapshotTests(apiType) { runSnapshotTests('cpp-api'); -// Node-API specific tests -{ +// C-API specific tests +function runCApiTests(apiType) { runTest( - 'nodejs-main-node-api: run Node.js CLI', + `${apiType}-nodejs-main: run Node.js CLI`, spawnSyncAndAssert, - ['nodejs-main-node-api', '--eval', 'console.log("Hello World")'], + [`${apiType}-nodejs-main`, '--eval', 'console.log("Hello World")'], { trim: true, stdout: 'Hello World', @@ -248,27 +249,27 @@ runSnapshotTests('cpp-api'); ); runTest( - `node-api: callMe`, + `${apiType}: callMe`, spawnSyncAndAssert, - ['node-api', 'function callMe(text) { return text + " you"; }'], + [apiType, 'function callMe(text) { return text + " you"; }'], { stdout: 'called you' } ); runTest( - `node-api: waitMe`, + `${apiType}: waitMe`, spawnSyncAndAssert, [ - 'node-api', + apiType, 'function waitMe(text, cb) { setTimeout(() => cb(text + " you"), 1); }', ], { stdout: 'waited you' } ); runTest( - `node-api: waitPromise`, + `${apiType}: waitPromise`, spawnSyncAndAssert, [ - 'node-api', + apiType, 'function waitPromise(text) { ' + 'return new Promise((res) => ' + ' setTimeout(() => res(text + " with cheese"), 1)); ' + @@ -278,10 +279,10 @@ runSnapshotTests('cpp-api'); ); runTest( - `node-api: waitPromise reject`, + `${apiType}: waitPromise reject`, spawnSyncAndAssert, [ - 'node-api', + apiType, 'function waitPromise(text) { ' + 'return new Promise((res, rej) => ' + ' setTimeout(() => rej(text + " without cheese"), 1)); ' + @@ -291,9 +292,9 @@ runSnapshotTests('cpp-api'); ); runTest( - `threading-runtime-per-thread-node-api: run 12 environments concurrently`, + `${apiType}-threading-runtime-per-thread: run 12 environments concurrently`, spawnSyncAndAssert, - ['threading-runtime-per-thread-node-api', 'myCount = 1'], + [`${apiType}-threading-runtime-per-thread`, 'myCount = 1'], { trim: true, stdout: '12', @@ -301,10 +302,10 @@ runSnapshotTests('cpp-api'); ); runTest( - 'threading-several-runtimes-per-thread-node-api: run 12 environments in the same thread', + `${apiType}-threading-several-runtimes-per-thread: run 12 environments in the same thread`, spawnSyncAndAssert, [ - 'threading-several-runtimes-per-thread-node-api', + `${apiType}-threading-several-runtimes-per-thread`, 'myCount = 0; ' + 'function incMyCount() { ' + ' ++myCount; ' + @@ -318,10 +319,10 @@ runSnapshotTests('cpp-api'); ); runTest( - 'threading-runtime-in-several-threads-node-api: run and environment from multiple threads', + `${apiType}-threading-runtime-in-several-threads: run an environment from multiple threads`, spawnSyncAndAssert, [ - 'threading-runtime-in-several-threads-node-api', + `${apiType}-threading-runtime-in-several-threads`, 'myCount = 0; ' + 'function incMyCount() { ' + ' ++myCount; ' + @@ -335,10 +336,10 @@ runSnapshotTests('cpp-api'); ); runTest( - 'threading-runtime-in-ui-thread-node-api: run and environment from multiple threads', + `${apiType}-threading-runtime-in-ui-thread: run an environment from UI thread`, spawnSyncAndAssert, [ - 'threading-runtime-in-ui-thread-node-api', + `${apiType}-threading-runtime-in-ui-thread`, 'myCount = 0; ' + 'function incMyCount() { ' + ' ++myCount; ' + @@ -354,9 +355,9 @@ runSnapshotTests('cpp-api'); const preloadScriptPath = path.join(__dirname, 'preload-with-worker.js'); runTest( - 'preload-node-api: run preload callback', + `${apiType}-preload: run preload callback`, spawnSyncAndAssert, - ['preload-node-api', `eval(${getReadFileCodeForPath(preloadScriptPath)})`], + [`${apiType}-preload`, `eval(${getReadFileCodeForPath(preloadScriptPath)})`], { cwd: __dirname, trim: true, @@ -367,10 +368,10 @@ runSnapshotTests('cpp-api'); const linkedModulesScriptPath = path.join(__dirname, 'use-linked-modules.js'); runTest( - 'linked-modules-node-api: run with two linked modules', + `${apiType}-linked-modules: run with two linked modules`, spawnSyncAndAssert, [ - 'linked-modules-node-api', + `${apiType}-linked-modules`, `eval(${getReadFileCodeForPath(linkedModulesScriptPath)})`, 2, // expected number of greeter module calls 2, // expected number of replicator module calls @@ -383,6 +384,9 @@ runSnapshotTests('cpp-api'); ); } +runCApiTests('c-api'); +runCApiTests('c-cpp-api'); + function runEnvTests(apiType) { runTest( `${apiType}: Env No Browser Globals`, @@ -410,14 +414,4 @@ function runEnvTests(apiType) { } runEnvTests('c-api'); - -/* -runTest( - `modules-node-api: load modules`, - spawnSyncAndExitWithoutError, - ['modules-node-api', 'cjs.cjs', 'es6.mjs', ], - { - cwd: __dirname, - } -); -*/ +runEnvTests('c-cpp-api'); From 59faec34c06c01a92a1193d507fbae43d07c8774 Mon Sep 17 00:00:00 2001 From: Vladimir Morozov Date: Fri, 31 Jan 2025 11:13:12 -0800 Subject: [PATCH 22/22] update embedding docs --- doc/api/embedding.md | 813 +++++++++++++++++++++++-------------------- 1 file changed, 438 insertions(+), 375 deletions(-) diff --git a/doc/api/embedding.md b/doc/api/embedding.md index 3200a0427b0552..63c27f8073ce97 100644 --- a/doc/api/embedding.md +++ b/doc/api/embedding.md @@ -261,6 +261,7 @@ typedef enum { node_embedding_status_generic_error = 1, node_embedding_status_null_arg = 2, node_embedding_status_bad_arg = 3, + node_embedding_status_out_of_memory = 4, node_embedding_status_error_exit_code = 512, } node_embedding_status; ``` @@ -270,6 +271,7 @@ typedef enum { - `node_embedding_status_null_arg` - One of non-optional arguments passed as NULL value. - `node_embedding_status_bad_arg` - One of the arguments has wrong value. +- `node_embedding_status_out_of_memory` - Failed to allocate memory. - `node_embedding_status_error_exit_code` - A bit flag added to the Node.js exit code value if the error status is associated with an error code. @@ -342,25 +344,17 @@ added: REPLACEME > Stability: 1 - Experimental ```c -typedef node_embedding_status(NAPI_CDECL* node_embedding_handle_error_callback)( - void* handler_data, - const char* messages[], - size_t messages_size, - node_embedding_status exit_code); +typedef node_embedding_status(NAPI_CDECL* node_embedding_data_release_callback)( + void* data); ``` -Function pointer type for user-provided native function that handles the list -of error messages and the exit code. +Function pointer type to release data. The callback parameters: -- `[in] handler_data`: The user data associated with this callback. -- `[in] messages`: Pointer to an array of zero terminating strings. -- `[in] messages_size`: Size of the `messages` string array. -- `[in] exit_code`: The suggested process exit code in case of error. If the - `exit_code` is zero, then the callback is used to output non-error messages. +- `[in] data`: The data to be released. -##### `node_embedding_configure_platform_callback` +##### `node_embedding_platform_configure_callback` - -> Stability: 1 - Experimental - -```c -typedef void(NAPI_CDECL* node_embedding_get_args_callback)(void* cb_data, - int32_t argc, - const char* argv[]); -``` - -Function pointer type for user-provided native function that receives list of -CLI arguments from the `node_embedding_platform`. - -The callback parameters: - -- `[in] cb_data`: The user data associated with this callback. -- `[in] argc`: Number of items in the `argv` array. -- `[in] argv`: CLI arguments as an array of zero terminating strings. - #### Functions -##### `node_embedding_on_error` +##### `node_embedding_last_error_message_get` + +> Stability: 1 - Experimental + +```c +typedef napi_value(NAPI_CDECL* node_embedding_module_initialize_callback)( void* cb_data, node_embedding_runtime runtime, napi_env env, @@ -826,7 +817,7 @@ and returned. #### Functions -##### `node_embedding_run_runtime` +##### `node_embedding_runtime_run` + +> Stability: 1 - Experimental + +Sets the Node.js runtime instance flags. -##### `node_embedding_runtime_set_flags` +```c +node_embedding_status NAPI_CDECL +node_embedding_runtime_config_set_node_api_version( + node_embedding_runtime_config runtime_config, + int32_t node_api_version); +``` + +- `[in] runtime_config`: The Node.js runtime configuration. +- `[in] node_api_version`: The Node-API version to use by the runtime. + +Returns `node_embedding_status_ok` if there were no issues. + +##### `node_embedding_runtime_config_set_flags` + +> Stability: 1 - Experimental + +This is an opaque pointer for the Node-API scope where the provided `napi_env` +is valid. -#### Functor types +#### Callback types -##### `node_embedding_run_node_api_functor_ref` +##### `node_embedding_run_node_api_callback`