From c55bf5082ca57f4f6785dfa9f95fe407316b1ed2 Mon Sep 17 00:00:00 2001 From: Momtchil Momtchev Date: Wed, 1 Feb 2023 14:36:17 +0100 Subject: [PATCH 01/16] node-api: libnode embedding --- Makefile | 8 +- deps/uv/src/unix/core.c | 2 + doc/api/embedding.md | 39 +++ doc/api/n-api.md | 141 ++++++++++ .../bootstrap/switches/is_embedded_env.js | 21 ++ node.gyp | 104 +++++++ src/api/embed_helpers.cc | 81 ++++-- src/env-inl.h | 11 + src/env.h | 14 + src/js_native_api_types.h | 1 + src/js_native_api_v8.cc | 214 ++++++++++++++- src/node.h | 22 +- src/node_api.cc | 1 + src/node_api_embedding.h | 47 ++++ src/node_builtins.cc | 15 +- test/embedding/.eslintrc.yaml | 3 + test/embedding/cjs.cjs | 3 + test/embedding/es6.mjs | 1 + test/embedding/napi_embedding.c | 256 ++++++++++++++++++ test/embedding/napi_modules | Bin 0 -> 21728 bytes test/embedding/napi_modules.c | 81 ++++++ test/embedding/test-napi-embedding.js | 75 +++++ .../test-http-client-response-timeout.js | 2 +- .../sequential/test-timers-block-eventloop.js | 2 + 24 files changed, 1115 insertions(+), 29 deletions(-) create mode 100644 lib/internal/bootstrap/switches/is_embedded_env.js 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/es6.mjs create mode 100644 test/embedding/napi_embedding.c create mode 100755 test/embedding/napi_modules create mode 100644 test/embedding/napi_modules.c create mode 100644 test/embedding/test-napi-embedding.js diff --git a/Makefile b/Makefile index 7bd80d06c17125..79495cf7c6ac68 100644 --- a/Makefile +++ b/Makefile @@ -286,7 +286,9 @@ coverage-report-js: # Runs the C++ tests using the built `cctest` executable. cctest: all @out/$(BUILDTYPE)/$@ --gtest_filter=$(GTEST_FILTER) - $(NODE) ./test/embedding/test-embedding.js + @out/$(BUILDTYPE)/embedtest "require('./test/embedding/test-embedding.js')" + @out/$(BUILDTYPE)/napi_embedding "require('./test/embedding/test-napi-embedding.js')" + @out/$(BUILDTYPE)/napi_modules ../../test/embedding/cjs.cjs ../../test/embedding/es6.mjs .PHONY: list-gtests list-gtests: @@ -565,7 +567,9 @@ test-ci: | clear-stalled bench-addons-build build-addons build-js-native-api-tes $(PYTHON) tools/test.py $(PARALLEL_ARGS) -p tap --logfile test.tap \ --mode=$(BUILDTYPE_LOWER) --flaky-tests=$(FLAKY_TESTS) \ $(TEST_CI_ARGS) $(CI_JS_SUITES) $(CI_NATIVE_SUITES) $(CI_DOC) - $(NODE) ./test/embedding/test-embedding.js + out/Release/embedtest 'require("./test/embedding/test-embedding.js")' + out/Release/napi_embedding 'require("./test/embedding/test-napi-embedding.js")' + out/Release/napi_modules ../../test/embedding/cjs.cjs ../../test/embedding/es6.mjs $(info Clean up any leftover processes, error if found.) ps awwx | grep Release/node | grep -v grep | cat @PS_OUT=`ps awwx | grep Release/node | grep -v grep | awk '{print $$1}'`; \ diff --git a/deps/uv/src/unix/core.c b/deps/uv/src/unix/core.c index 965e7f775250cf..bcb72a601f38b8 100644 --- a/deps/uv/src/unix/core.c +++ b/deps/uv/src/unix/core.c @@ -459,6 +459,8 @@ int uv_run(uv_loop_t* loop, uv_run_mode mode) { */ uv__metrics_update_idle_time(loop); + uv__run_timers(loop); + uv__run_check(loop); uv__run_closing_handles(loop); diff --git a/doc/api/embedding.md b/doc/api/embedding.md index 9f831b342c2705..14623874db1f88 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, 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}, &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 503df17be005c6..318ce864ee506f 100644 --- a/doc/api/n-api.md +++ b/doc/api/n-api.md @@ -6581,6 +6581,147 @@ 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, + int exec_argc, + char** exec_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] exec_argc`: Node.js CLI options count. +* `[in] exec_argv`: Node.js CLI options. +* `[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, int *exit_code); +``` + +* `[in] platform`: platform handle. +* `[out] exit_code`: if not NULL will receive the process exit code. + +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, + 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. +* `[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/lib/internal/bootstrap/switches/is_embedded_env.js b/lib/internal/bootstrap/switches/is_embedded_env.js new file mode 100644 index 00000000000000..491fff0dcb2192 --- /dev/null +++ b/lib/internal/bootstrap/switches/is_embedded_env.js @@ -0,0 +1,21 @@ +'use strict'; + +// This is the bootstrapping code used when creating a new environment +// through N-API + +// Set up globalThis.require and globalThis.import so that they can +// be easily accessed from C/C++ + +/* global path, primordials */ + +const { globalThis, ObjectCreate } = primordials; +const CJSLoader = require('internal/modules/cjs/loader'); +const ESMLoader = require('internal/modules/esm/loader').ESMLoader; + +globalThis.module = new CJSLoader.Module(); +globalThis.require = require('module').createRequire(path); + +const internalLoader = new ESMLoader(); +const parent_path = require('url').pathToFileURL(path); +globalThis.import = (mod) => internalLoader.import(mod, parent_path, ObjectCreate(null)); +globalThis.import.meta = { url: parent_path }; diff --git a/node.gyp b/node.gyp index e5ca012a822f34..81f88be98d2df3 100644 --- a/node.gyp +++ b/node.gyp @@ -1188,6 +1188,110 @@ ], }, # embedtest + { + 'target_name': 'napi_embedding', + 'type': 'executable', + + 'dependencies': [ + '<(node_lib_target_name)', + 'deps/histogram/histogram.gyp:histogram', + 'deps/uvwasi/uvwasi.gyp:uvwasi', + ], + + 'includes': [ + 'node.gypi' + ], + + 'include_dirs': [ + 'src', + 'tools/msvs/genfiles', + 'deps/v8/include', + 'deps/cares/include', + 'deps/uv/include', + 'deps/uvwasi/include', + 'test/embedding', + ], + + 'sources': [ + 'src/node_snapshot_stub.cc', + 'test/embedding/napi_embedding.c', + ], + + 'conditions': [ + ['OS=="solaris"', { + 'ldflags': [ '-I<(SHARED_INTERMEDIATE_DIR)' ] + }], + # Skip cctest while building shared lib node for Windows + [ 'OS=="win" and node_shared=="true"', { + 'type': 'none', + }], + [ 'node_shared=="true"', { + 'xcode_settings': { + 'OTHER_LDFLAGS': [ '-Wl,-rpath,@loader_path', ], + }, + }], + ['OS=="win"', { + 'libraries': [ + 'Dbghelp.lib', + 'winmm.lib', + 'Ws2_32.lib', + ], + }], + ], + }, # napi_embedding + + { + 'target_name': 'napi_modules', + 'type': 'executable', + + 'dependencies': [ + '<(node_lib_target_name)', + 'deps/histogram/histogram.gyp:histogram', + 'deps/uvwasi/uvwasi.gyp:uvwasi', + ], + + 'includes': [ + 'node.gypi' + ], + + 'include_dirs': [ + 'src', + 'tools/msvs/genfiles', + 'deps/v8/include', + 'deps/cares/include', + 'deps/uv/include', + 'deps/uvwasi/include', + 'test/embedding', + ], + + 'sources': [ + 'src/node_snapshot_stub.cc', + 'test/embedding/napi_modules.c', + ], + + 'conditions': [ + ['OS=="solaris"', { + 'ldflags': [ '-I<(SHARED_INTERMEDIATE_DIR)' ] + }], + # Skip cctest while building shared lib node for Windows + [ 'OS=="win" and node_shared=="true"', { + 'type': 'none', + }], + [ 'node_shared=="true"', { + 'xcode_settings': { + 'OTHER_LDFLAGS': [ '-Wl,-rpath,@loader_path', ], + }, + }], + ['OS=="win"', { + 'libraries': [ + 'Dbghelp.lib', + 'winmm.lib', + 'Ws2_32.lib', + ], + }], + ], + }, # napi_modules + { 'target_name': 'overlapped-checker', 'type': 'executable', diff --git a/src/api/embed_helpers.cc b/src/api/embed_helpers.cc index 6fac48d1b534d2..609415fce5e04b 100644 --- a/src/api/embed_helpers.cc +++ b/src/api/embed_helpers.cc @@ -19,7 +19,16 @@ using v8::TryCatch; namespace node { -Maybe SpinEventLoopInternal(Environment* env) { +static const auto AlwaysTrue = []() { return true; }; + +/** + * Spin the event loop until there are no pending callbacks or + * the condition returns false. + * Returns an error if the environment died and no failure if the environment is + * reusable. + */ +Maybe SpinEventLoopWithoutCleanupInternal( + Environment* env, const std::function& condition) { CHECK_NOT_NULL(env); MultiIsolatePlatform* platform = GetMultiIsolatePlatform(env); CHECK_NOT_NULL(platform); @@ -32,23 +41,54 @@ Maybe SpinEventLoopInternal(Environment* env) { if (env->is_stopping()) return Nothing(); env->set_trace_sync_io(env->options()->trace_sync_io); - { - bool more; - env->performance_state()->Mark( - node::performance::NODE_PERFORMANCE_MILESTONE_LOOP_START); + bool more; + env->performance_state()->Mark( + node::performance::NODE_PERFORMANCE_MILESTONE_LOOP_START); + do { + if (env->is_stopping()) return Nothing(); + int loop; do { - if (env->is_stopping()) break; - uv_run(env->event_loop(), UV_RUN_DEFAULT); - if (env->is_stopping()) break; + loop = uv_run(env->event_loop(), UV_RUN_ONCE); + } while (loop && condition() && !env->is_stopping()); + if (env->is_stopping()) return Nothing(); - platform->DrainTasks(isolate); + platform->DrainTasks(isolate); - more = uv_loop_alive(env->event_loop()); - if (more && !env->is_stopping()) continue; + more = uv_loop_alive(env->event_loop()); + } while (more); + env->performance_state()->Mark( + node::performance::NODE_PERFORMANCE_MILESTONE_LOOP_EXIT); + env->set_trace_sync_io(false); + return Just(ExitCode::kNoFailure); +} + +/** + * 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. + */ +Maybe SpinEventLoopInternal(Environment* env) { + CHECK_NOT_NULL(env); + MultiIsolatePlatform* platform = GetMultiIsolatePlatform(env); + CHECK_NOT_NULL(platform); + + Isolate* isolate = env->isolate(); + HandleScope handle_scope(isolate); + Context::Scope context_scope(env->context()); + SealHandleScope seal(isolate); - if (EmitProcessBeforeExit(env).IsNothing()) + if (env->is_stopping()) return Nothing(); + + env->set_trace_sync_io(env->options()->trace_sync_io); + { + bool more; + + do { + if (SpinEventLoopWithoutCleanupInternal(env, AlwaysTrue).IsNothing()) break; + if (EmitProcessBeforeExit(env).IsNothing()) break; + { HandleScope handle_scope(isolate); if (env->RunSnapshotSerializeCallback().IsEmpty()) { @@ -56,16 +96,12 @@ Maybe SpinEventLoopInternal(Environment* env) { } } - // Emit `beforeExit` if the loop became alive either after emitting - // event, or after running some callbacks. + // Loop if after `beforeExit` the loop became alive more = uv_loop_alive(env->event_loop()); } while (more == true && !env->is_stopping()); - env->performance_state()->Mark( - node::performance::NODE_PERFORMANCE_MILESTONE_LOOP_EXIT); } 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. @@ -256,6 +292,17 @@ Maybe SpinEventLoop(Environment* env) { return Just(static_cast(result.FromJust())); } +Maybe SpinEventLoopWithoutCleanup(Environment* env) { + return SpinEventLoopWithoutCleanup(env, AlwaysTrue); +} +Maybe SpinEventLoopWithoutCleanup( + Environment* env, const std::function& condition) { + Maybe result = SpinEventLoopWithoutCleanupInternal(env, condition); + 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/env-inl.h b/src/env-inl.h index 666dad97b021f4..efd672a022a8c1 100644 --- a/src/env-inl.h +++ b/src/env-inl.h @@ -642,6 +642,10 @@ inline bool Environment::is_main_thread() const { return worker_context() == nullptr; } +inline bool Environment::is_embedded_env() const { + return embedded_ != nullptr; +} + inline bool Environment::no_native_addons() const { return (flags_ & EnvironmentFlags::kNoNativeAddons) || !options_->allow_native_addons; @@ -812,6 +816,13 @@ void Environment::set_process_exit_handler( process_exit_handler_ = std::move(handler); } +inline EmbeddedEnvironment* Environment::get_embedded() { + return embedded_; +} +inline void Environment::set_embedded(EmbeddedEnvironment* env) { + embedded_ = env; +} + #define VP(PropertyName, StringValue) V(v8::Private, PropertyName) #define VY(PropertyName, StringValue) V(v8::Symbol, PropertyName) #define VS(PropertyName, StringValue) V(v8::String, PropertyName) diff --git a/src/env.h b/src/env.h index ff09da28b2cadc..f58a46d3dfacb2 100644 --- a/src/env.h +++ b/src/env.h @@ -588,11 +588,18 @@ void DefaultProcessExitHandlerInternal(Environment* env, ExitCode exit_code); v8::Maybe SpinEventLoopInternal(Environment* env); v8::Maybe EmitProcessExitInternal(Environment* env); +/** + * EmbeddedEnvironment is the JavaScript engine-neutral part of an + * embedded environment controlled by a C/C++ caller of libnode + */ +class EmbeddedEnvironment {}; + /** * Environment is a per-isolate data structure that represents an execution * environment. Each environment has a principal realm. An environment can * create multiple subsidiary synthetic realms. */ + class Environment : public MemoryRetainer { public: Environment(const Environment&) = delete; @@ -793,6 +800,7 @@ class Environment : public MemoryRetainer { inline void set_has_serialized_options(bool has_serialized_options); inline bool is_main_thread() const; + inline bool is_embedded_env() const; inline bool no_native_addons() const; inline bool should_not_register_esm_loader() const; inline bool should_create_inspector() const; @@ -1008,6 +1016,9 @@ class Environment : public MemoryRetainer { inline void set_process_exit_handler( std::function&& handler); + inline EmbeddedEnvironment* get_embedded(); + inline void set_embedded(EmbeddedEnvironment* env); + void RunAndClearNativeImmediates(bool only_refed = false); void RunAndClearInterrupts(); @@ -1216,6 +1227,9 @@ class Environment : public MemoryRetainer { // track of the BackingStore for a given pointer. std::unordered_map> released_allocated_buffers_; + + // Used for embedded instances + EmbeddedEnvironment* embedded_; }; } // namespace node diff --git a/src/js_native_api_types.h b/src/js_native_api_types.h index 7cb5b080cc377a..c8a37a1286c257 100644 --- a/src/js_native_api_types.h +++ b/src/js_native_api_types.h @@ -57,6 +57,7 @@ typedef struct napi_handle_scope__* napi_handle_scope; typedef struct napi_escapable_handle_scope__* napi_escapable_handle_scope; typedef struct napi_callback_info__* napi_callback_info; typedef struct napi_deferred__* napi_deferred; +typedef struct napi_platform__* napi_platform; typedef enum { napi_default = 0, diff --git a/src/js_native_api_v8.cc b/src/js_native_api_v8.cc index 3c5dc70330cd7f..0640a99d22582d 100644 --- a/src/js_native_api_v8.cc +++ b/src/js_native_api_v8.cc @@ -5,6 +5,8 @@ #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 "util-inl.h" #define CHECK_MAYBE_NOTHING(env, maybe, status) \ @@ -260,12 +262,52 @@ inline v8impl::Persistent* NodePersistentFromJsDeferred( return reinterpret_cast*>(local); } +struct PlatformWrapper { + explicit PlatformWrapper(int argc, + char** argv, + int exec_argc, + char** exec_argv) + : args(argv, argv + argc), exec_args(exec_argv, exec_argv + exec_argc) {} + std::unique_ptr platform; + std::vector args; + std::vector exec_args; +}; + +class EmbeddedEnvironment : public node::EmbeddedEnvironment { + public: + explicit EmbeddedEnvironment( + std::unique_ptr&& setup) + : setup_(std::move(setup)), + locker_(setup_->isolate()), + isolate_scope_(setup_->isolate()), + handle_scope_(setup_->isolate()), + context_scope_(setup_->context()), + seal_scope_(nullptr) {} + + inline node::CommonEnvironmentSetup* setup() { return setup_.get(); } + inline void seal() { + seal_scope_ = + std::make_unique(setup_->isolate()); + } + + private: + std::unique_ptr setup_; + v8::Locker locker_; + v8::Isolate::Scope isolate_scope_; + v8::HandleScope handle_scope_; + v8::Context::Scope context_scope_; + // As this handle scope will remain open for the lifetime + // of the environment, we seal it to prevent it from + // becoming everyone's favorite trash bin + std::unique_ptr seal_scope_; +}; + class HandleScopeWrapper { public: - explicit HandleScopeWrapper(v8::Isolate* isolate) : scope(isolate) {} + explicit HandleScopeWrapper(v8::Isolate* isolate) : scope_(isolate) {} private: - v8::HandleScope scope; + v8::HandleScope scope_; }; // In node v0.10 version of v8, there is no EscapableHandleScope and the @@ -933,6 +975,174 @@ napi_status NAPI_CDECL napi_get_last_error_info( return napi_ok; } +napi_status NAPI_CDECL +napi_create_platform(int argc, + char** argv, + int exec_argc, + char** exec_argv, + napi_error_message_handler err_handler, + napi_platform* result) { + argv = uv_setup_args(argc, argv); + std::vector errors_vec; + + v8impl::PlatformWrapper* platform = + new v8impl::PlatformWrapper(argc, argv, exec_argc, exec_argv); + if (platform->args.size() < 1) platform->args.push_back("libnode"); + + int exit_code = node::InitializeNodeWithArgs( + &platform->args, &platform->exec_args, &errors_vec); + + for (const std::string& error : errors_vec) { + if (err_handler != nullptr) { + err_handler(error.c_str()); + } else { + fprintf(stderr, "%s\n", error.c_str()); + } + } + + if (exit_code != 0) { + return napi_generic_failure; + } + + auto thread_pool_size = node::per_process::cli_options->v8_thread_pool_size; + platform->platform = node::MultiIsolatePlatform::Create(thread_pool_size); + v8::V8::InitializePlatform(platform->platform.get()); + v8::V8::Initialize(); + *result = reinterpret_cast(platform); + return napi_ok; +} + +napi_status NAPI_CDECL napi_destroy_platform(napi_platform platform) { + auto wrapper = reinterpret_cast(platform); + v8::V8::Dispose(); + v8::V8::DisposePlatform(); + + // The node::CommonEnvironmentSetup::Create uniq_ptr is destroyed here + delete wrapper; + return napi_ok; +} + +napi_status NAPI_CDECL +napi_create_environment(napi_platform platform, + napi_error_message_handler err_handler, + const char* main_script, + napi_env* result) { + auto wrapper = reinterpret_cast(platform); + std::vector errors_vec; + + auto setup = node::CommonEnvironmentSetup::Create( + wrapper->platform.get(), &errors_vec, wrapper->args, wrapper->exec_args); + + for (const std::string& error : errors_vec) { + if (err_handler != nullptr) { + err_handler(error.c_str()); + } else { + fprintf(stderr, "%s\n", error.c_str()); + } + } + if (setup == nullptr) { + return napi_generic_failure; + } + auto emb_env = new v8impl::EmbeddedEnvironment(std::move(setup)); + + std::string filename = + wrapper->args.size() > 1 ? wrapper->args[1] : ""; + auto env__ = new node_napi_env__(emb_env->setup()->context(), + filename, NODE_API_DEFAULT_MODULE_API_VERSION); + emb_env->setup()->env()->set_embedded(emb_env); + env__->node_env()->AddCleanupHook( + [](void* arg) { static_cast(arg)->Unref(); }, + static_cast(env__)); + *result = env__; + + auto env = emb_env->setup()->env(); + if (main_script == nullptr) main_script = ""; + + auto ret = node::LoadEnvironment(env, main_script); + if (ret.IsEmpty()) return napi_pending_exception; + + node::Realm* realm = env->principal_realm(); + ret = + realm->ExecuteBootstrapper("internal/bootstrap/switches/is_embedded_env"); + if (ret.IsEmpty()) return napi_pending_exception; + + emb_env->seal(); + + return napi_ok; +} + +napi_status NAPI_CDECL napi_destroy_environment(napi_env env, int* exit_code) { + CHECK_ARG(env, env); + node_napi_env node_env = reinterpret_cast(env); + + int r = node::SpinEventLoop(node_env->node_env()).FromMaybe(1); + if (exit_code != nullptr) *exit_code = r; + node::Stop(node_env->node_env()); + + auto emb_env = reinterpret_cast( + node_env->node_env()->get_embedded()); + node_env->node_env()->set_embedded(nullptr); + // This deletes the uniq_ptr to node::CommonEnvironmentSetup + // and the v8::locker + delete emb_env; + + return napi_ok; +} + +napi_status NAPI_CDECL napi_run_environment(napi_env env) { + CHECK_ARG(env, env); + node_napi_env node_env = reinterpret_cast(env); + + if (node::SpinEventLoopWithoutCleanup(node_env->node_env()).IsNothing()) + return napi_closing; + + return napi_ok; +} + +static void napi_promise_error_handler( + const v8::FunctionCallbackInfo& info) { + return; +} + +napi_status napi_await_promise(napi_env env, + napi_value promise, + napi_value* result) { + NAPI_PREAMBLE(env); + CHECK_ARG(env, result); + + v8::EscapableHandleScope scope(env->isolate); + node_napi_env node_env = reinterpret_cast(env); + + 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(), napi_promise_error_handler, rejected) + .ToLocalChecked(); + + if (promise_object->Catch(env->context(), err_handler).IsEmpty()) + return napi_pending_exception; + + if (node::SpinEventLoopWithoutCleanup( + node_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; +} + napi_status NAPI_CDECL napi_create_function(napi_env env, const char* utf8name, size_t length, diff --git a/src/node.h b/src/node.h index b041a20318145b..b68a4abad2bbe2 100644 --- a/src/node.h +++ b/src/node.h @@ -860,14 +860,26 @@ NODE_EXTERN struct uv_loop_s* GetCurrentEventLoop(v8::Isolate* isolate); // Runs the main loop for a given Environment. This roughly performs the // following steps: -// 1. Call uv_run() on the event loop until it is drained. +// 1. Call uv_run() on the event loop until it is drained or the optional +// condition returns false. // 2. Call platform->DrainTasks() on the associated platform/isolate. // 3. If the event loop is alive again, go to Step 1. -// 4. Call EmitProcessBeforeExit(). -// 5. If the event loop is alive again, go to Step 1. -// 6. Call EmitProcessExit() and forward the return value. +// Returns false if the environment died and true if it can be reused. +// This function only works if `env` has an associated `MultiIsolatePlatform`. +NODE_EXTERN v8::Maybe SpinEventLoopWithoutCleanup( + Environment* env, const std::function& condition); +NODE_EXTERN v8::Maybe SpinEventLoopWithoutCleanup(Environment* env); + +// Runs the main loop for a given Environment and performs environment +// shutdown when the loop exits. This roughly performs the +// following steps: +// 1. Call SpinEventLoopWithoutCleanup() +// 2. Call EmitProcessBeforeExit(). +// 3. If the event loop is alive again, go to Step 1. +// 4. Call EmitProcessExit() and forward the return value. // If at any point node::Stop() is called, the function will attempt to return -// as soon as possible, returning an empty `Maybe`. +// as soon as possible, returning an empty `Maybe`. Ohterwise it will return +// a reference to the exit value. // This function only works if `env` has an associated `MultiIsolatePlatform`. NODE_EXTERN v8::Maybe SpinEventLoop(Environment* env); diff --git a/src/node_api.cc b/src/node_api.cc index 5b5f6a55a0fc93..2624489e3f9631 100644 --- a/src/node_api.cc +++ b/src/node_api.cc @@ -4,6 +4,7 @@ #include "js_native_api_v8.h" #include "memory_tracker-inl.h" #include "node_api.h" +#include "node_api_embedding.h" #include "node_api_internals.h" #include "node_binding.h" #include "node_buffer.h" diff --git a/src/node_api_embedding.h b/src/node_api_embedding.h new file mode 100644 index 00000000000000..bd202432c3515b --- /dev/null +++ b/src/node_api_embedding.h @@ -0,0 +1,47 @@ +#ifndef SRC_NODE_API_EMBEDDING_H_ +#define SRC_NODE_API_EMBEDDING_H_ + +#include "js_native_api.h" +#include "js_native_api_types.h" + +#ifdef __cplusplus +#define EXTERN_C_START extern "C" { +#define EXTERN_C_END } +#else +#define EXTERN_C_START +#define EXTERN_C_END +#endif + +EXTERN_C_START + +typedef void (*napi_error_message_handler)(const char* msg); + +NAPI_EXTERN napi_status NAPI_CDECL +napi_create_platform(int argc, + char** argv, + int exec_argc, + char** exec_argv, + napi_error_message_handler err_handler, + napi_platform* result); + +NAPI_EXTERN napi_status NAPI_CDECL +napi_destroy_platform(napi_platform platform); + +NAPI_EXTERN napi_status NAPI_CDECL +napi_create_environment(napi_platform platform, + napi_error_message_handler err_handler, + const char* main_script, + napi_env* result); + +NAPI_EXTERN napi_status NAPI_CDECL napi_run_environment(napi_env env); + +NAPI_EXTERN napi_status NAPI_CDECL napi_await_promise(napi_env env, + napi_value promise, + napi_value* result); + +NAPI_EXTERN napi_status NAPI_CDECL napi_destroy_environment(napi_env env, + int* exit_code); + +EXTERN_C_END + +#endif // SRC_NODE_API_EMBEDDING_H_ diff --git a/src/node_builtins.cc b/src/node_builtins.cc index bbb63df7899d4b..4b0c0bd72f6a25 100644 --- a/src/node_builtins.cc +++ b/src/node_builtins.cc @@ -409,6 +409,7 @@ MaybeLocal BuiltinLoader::LookupAndCompile(Local context, FIXED_ONE_BYTE_STRING(isolate, "require"), FIXED_ONE_BYTE_STRING(isolate, "internalBinding"), FIXED_ONE_BYTE_STRING(isolate, "primordials"), + FIXED_ONE_BYTE_STRING(isolate, "path") }; } else { // others: exports, require, module, process, internalBinding, primordials @@ -456,12 +457,22 @@ MaybeLocal BuiltinLoader::CompileAndCall(Local context, strncmp(id, "internal/bootstrap/", strlen("internal/bootstrap/")) == 0) { + auto path = String::NewFromUtf8(isolate, realm->env()->exec_path().c_str()) + .ToLocalChecked(); // internal/main/*, internal/bootstrap/*: process, require, - // internalBinding, primordials + // internalBinding, primordials, + // path arguments = {realm->process_object(), realm->builtin_module_require(), realm->internal_binding_loader(), - realm->primordials()}; + realm->primordials(), + path}; + } else if (strncmp(id, "embedder_main_", strlen("embedder_main_")) == 0) { + // Synthetic embedder main scripts from LoadEnvironment(): process, require + arguments = { + realm->process_object(), + realm->builtin_module_require(), + }; } else { // This should be invoked with the other CompileAndCall() methods, as // we are unable to generate the arguments. 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/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/napi_embedding.c b/test/embedding/napi_embedding.c new file mode 100644 index 00000000000000..6c58d9c877c762 --- /dev/null +++ b/test/embedding/napi_embedding.c @@ -0,0 +1,256 @@ +#define NAPI_EXPERIMENTAL +#include +#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 int RunNodeInstance(napi_platform platform); + +const char* main_script = + "globalThis.embedVars = { nön_ascıı: '🏳️‍🌈' };"; + +#define CHECK(test, msg) \ + if (test != napi_ok) { \ + fprintf(stderr, "%s\n", msg); \ + goto fail; \ + } + +int main(int argc, char** argv) { + napi_platform platform; + + CHECK(napi_create_platform(argc, argv, 0, NULL, NULL, &platform), + "Failed creating the platform"); + + int exit_code = RunNodeInstance(platform); + + CHECK(napi_destroy_platform(platform), "Failed destroying the platform"); + + return exit_code; +fail: + return -1; +} + +int callMe(napi_env env) { + napi_handle_scope scope; + napi_value global; + napi_value cb; + napi_value key; + + napi_open_handle_scope(env, &scope); + + CHECK(napi_get_global(env, &global), "Failed accessing the global object"); + + CHECK(napi_create_string_utf8(env, "callMe", strlen("callMe"), &key), + "create string"); + + CHECK(napi_get_property(env, global, key, &cb), + "Failed accessing the global object"); + + napi_valuetype cb_type; + CHECK(napi_typeof(env, cb, &cb_type), "Failed accessing the global object"); + + if (cb_type == napi_function) { + napi_value undef; + napi_get_undefined(env, &undef); + napi_value arg; + napi_create_string_utf8(env, "called", strlen("called"), &arg); + napi_value result; + napi_call_function(env, undef, cb, 1, &arg, &result); + + char buf[32]; + size_t len; + napi_get_value_string_utf8(env, result, buf, 32, &len); + if (strncmp(buf, "called you", strlen("called you"))) { + fprintf(stderr, "Invalid value received: %s\n", buf); + goto fail; + } + printf("%s", buf); + } else if (cb_type != napi_undefined) { + fprintf(stderr, "Invalid callMe value\n"); + goto fail; + } + + napi_value object; + CHECK(napi_create_object(env, &object), "Failed creating an object\n"); + + napi_close_handle_scope(env, scope); + return 0; + +fail: + napi_close_handle_scope(env, scope); + return -1; +} + +char callback_buf[32]; +size_t callback_buf_len; +napi_value c_cb(napi_env env, napi_callback_info info) { + napi_handle_scope scope; + size_t argc = 1; + napi_value arg; + napi_value undef; + + napi_open_handle_scope(env, &scope); + napi_get_cb_info(env, info, &argc, &arg, NULL, NULL); + + napi_get_value_string_utf8(env, arg, callback_buf, 32, &callback_buf_len); + napi_get_undefined(env, &undef); + napi_close_handle_scope(env, scope); + return undef; +} + +int waitMe(napi_env env) { + napi_handle_scope scope; + napi_value global; + napi_value cb; + napi_value key; + + napi_open_handle_scope(env, &scope); + + CHECK(napi_get_global(env, &global), "Failed accessing the global object"); + + napi_create_string_utf8(env, "waitMe", strlen("waitMe"), &key); + + CHECK(napi_get_property(env, global, key, &cb), + "Failed accessing the global object"); + + napi_valuetype cb_type; + CHECK(napi_typeof(env, cb, &cb_type), "Failed accessing the global object"); + + if (cb_type == napi_function) { + napi_value undef; + napi_get_undefined(env, &undef); + napi_value args[2]; + napi_create_string_utf8(env, "waited", strlen("waited"), &args[0]); + CHECK(napi_create_function( + env, "wait_cb", strlen("wait_cb"), c_cb, NULL, &args[1]), + "Failed creating function"); + + napi_value result; + memset(callback_buf, 0, 32); + napi_call_function(env, undef, cb, 2, args, &result); + if (!strncmp(callback_buf, "waited you", strlen("waited you"))) { + fprintf(stderr, "Anachronism detected: %s\n", callback_buf); + goto fail; + } + + CHECK(napi_run_environment(env), "Failed spinning the event loop"); + + if (strncmp(callback_buf, "waited you", strlen("waited you"))) { + fprintf(stderr, "Invalid value received: %s\n", callback_buf); + goto fail; + } + printf("%s", callback_buf); + } else if (cb_type != napi_undefined) { + fprintf(stderr, "Invalid waitMe value\n"); + goto fail; + } + + napi_close_handle_scope(env, scope); + return 0; + +fail: + napi_close_handle_scope(env, scope); + return -1; +} + +int waitMeWithCheese(napi_env env) { + napi_handle_scope scope; + napi_value global; + napi_value cb; + napi_value key; + + napi_open_handle_scope(env, &scope); + + CHECK(napi_get_global(env, &global), "Failed accessing the global object"); + + napi_create_string_utf8(env, "waitPromise", strlen("waitPromise"), &key); + + CHECK(napi_get_property(env, global, key, &cb), + "Failed accessing the global object"); + + napi_valuetype cb_type; + CHECK(napi_typeof(env, cb, &cb_type), "Failed accessing the global object"); + + if (cb_type == napi_function) { + napi_value undef; + napi_get_undefined(env, &undef); + napi_value arg; + bool result_type; + + napi_create_string_utf8(env, "waited", strlen("waited"), &arg); + + memset(callback_buf, 0, 32); + napi_value promise; + napi_value result; + CHECK(napi_call_function(env, undef, cb, 1, &arg, &promise), + "Failed evaluating the function"); + + if (!strncmp( + callback_buf, "waited with cheese", strlen("waited with cheese"))) { + fprintf(stderr, "Anachronism detected: %s\n", callback_buf); + goto fail; + } + + CHECK(napi_is_promise(env, promise, &result_type), + "Failed evaluating the result"); + + if (!result_type) { + fprintf(stderr, "Result is not a Promise\n"); + goto fail; + } + + napi_status r = napi_await_promise(env, promise, &result); + if (r != napi_ok && r != napi_pending_exception) { + fprintf(stderr, "Failed awaiting promise: %d\n", r); + goto fail; + } + + const char* expected; + if (r == napi_ok) + expected = "waited with cheese"; + else + expected = "waited without cheese"; + + napi_get_value_string_utf8( + env, result, callback_buf, 32, &callback_buf_len); + if (strncmp(callback_buf, expected, strlen(expected))) { + fprintf(stderr, "Invalid value received: %s\n", callback_buf); + goto fail; + } + printf("%s", callback_buf); + } else if (cb_type != napi_undefined) { + fprintf(stderr, "Invalid waitPromise value\n"); + goto fail; + } + + napi_close_handle_scope(env, scope); + return 0; + +fail: + napi_close_handle_scope(env, scope); + return -1; +} + +int RunNodeInstance(napi_platform platform) { + napi_env env; + int exit_code; + + CHECK(napi_create_environment(platform, NULL, main_script, &env), + "Failed running JS"); + + if (callMe(env) != 0) exit_code = -1; + if (waitMe(env) != 0) exit_code = -1; + if (waitMeWithCheese(env) != 0) exit_code = -1; + + CHECK(napi_destroy_environment(env, &exit_code), "napi_destroy_environment"); + + return exit_code; + +fail: + return -1; +} diff --git a/test/embedding/napi_modules b/test/embedding/napi_modules new file mode 100755 index 0000000000000000000000000000000000000000..3c1b5f742668b151ed2cc69e83ff5929ee157f83 GIT binary patch literal 21728 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 literal 0 HcmV?d00001 diff --git a/test/embedding/napi_modules.c b/test/embedding/napi_modules.c new file mode 100644 index 00000000000000..e3f855aa0574a0 --- /dev/null +++ b/test/embedding/napi_modules.c @@ -0,0 +1,81 @@ +#include +#include +#define NAPI_EXPERIMENTAL +#include +#include + +#define CHECK(op, msg) \ + if (op != napi_ok) { \ + fprintf(stderr, "Failed: %s\n", msg); \ + return -1; \ + } + +int main(int argc, char* argv[]) { + napi_platform platform; + + if (argc < 3) { + fprintf(stderr, "napi_modules \n"); + return -2; + } + + CHECK(napi_create_platform(0, NULL, 0, NULL, NULL, &platform), + "Failed creating the platform"); + + napi_env env; + CHECK(napi_create_environment(platform, NULL, NULL, &env), + "Failed running JS"); + + napi_handle_scope scope; + CHECK(napi_open_handle_scope(env, &scope), "Failed creating a scope"); + + napi_value global, import_name, require_name, import, require, cjs, es6, + value; + CHECK(napi_get_global(env, &global), "napi_get_global"); + CHECK(napi_create_string_utf8(env, "import", strlen("import"), &import_name), + "create_string"); + CHECK( + napi_create_string_utf8(env, "require", strlen("require"), &require_name), + "create_string"); + CHECK(napi_get_property(env, global, import_name, &import), "import"); + CHECK(napi_get_property(env, global, require_name, &require), "require"); + + CHECK(napi_create_string_utf8(env, argv[1], strlen(argv[1]), &cjs), + "create_string"); + CHECK(napi_create_string_utf8(env, argv[2], strlen(argv[2]), &es6), + "create_string"); + CHECK(napi_create_string_utf8(env, "value", strlen("value"), &value), + "create_string"); + + 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), + "import"); + napi_await_promise(env, es6_promise, &es6_module); + + CHECK(napi_get_property(env, es6_module, value, &es6_result), "value"); + CHECK(napi_get_value_string_utf8( + env, es6_result, buffer, sizeof(buffer), &bufferlen), + "string"); + if (strncmp(buffer, "genuine", bufferlen)) { + fprintf(stderr, "Unexpected value: %s\n", buffer); + return -1; + } + + CHECK(napi_call_function(env, global, require, 1, &cjs, &cjs_module), + "require"); + CHECK(napi_get_property(env, cjs_module, value, &cjs_result), "value"); + CHECK(napi_get_value_string_utf8( + env, cjs_result, buffer, sizeof(buffer), &bufferlen), + "string"); + if (strncmp(buffer, "original", bufferlen)) { + fprintf(stderr, "Unexpected value: %s\n", buffer); + return -1; + } + + CHECK(napi_close_handle_scope(env, scope), "Failed destroying handle scope"); + CHECK(napi_destroy_environment(env, NULL), "destroy"); + CHECK(napi_destroy_platform(platform), "Failed destroying the platform"); + return 0; +} diff --git a/test/embedding/test-napi-embedding.js b/test/embedding/test-napi-embedding.js new file mode 100644 index 00000000000000..f854f25b230719 --- /dev/null +++ b/test/embedding/test-napi-embedding.js @@ -0,0 +1,75 @@ +'use strict'; +const common = require('../common'); +const fixtures = require('../common/fixtures'); +const assert = require('assert'); +const child_process = require('child_process'); +const path = require('path'); + +common.allowGlobals(global.require); +common.allowGlobals(global.embedVars); +common.allowGlobals(global.import); +common.allowGlobals(global.module); +let binary = `out/${common.buildType}/napi_embedding`; +if (common.isWindows) { + binary += '.exe'; +} +binary = path.resolve(__dirname, '..', '..', binary); + +assert.strictEqual( + child_process.spawnSync(binary, ['console.log(42)']) + .stdout.toString().trim(), + '42'); + +assert.strictEqual( + child_process.spawnSync(binary, ['console.log(embedVars.nön_ascıı)']) + .stdout.toString().trim(), + '🏳️‍🌈'); + +assert.strictEqual( + child_process.spawnSync(binary, ['console.log(42)']) + .stdout.toString().trim(), + '42'); + +assert.strictEqual( + child_process.spawnSync(binary, ['throw new Error()']).status, + 1); + +assert.strictEqual( + child_process.spawnSync(binary, ['process.exitCode = 8']).status, + 8); + + +const fixturePath = JSON.stringify(fixtures.path('exit.js')); +assert.strictEqual( + child_process.spawnSync(binary, [`require(${fixturePath})`, 92]).status, + 92); + +assert.strictEqual( + child_process.spawnSync(binary, ['function callMe(text) { return text + " you"; }']) + .stdout.toString().trim(), + 'called you'); + +assert.strictEqual( + child_process.spawnSync(binary, ['function waitMe(text, cb) { setTimeout(() => cb(text + " you"), 1); }']) + .stdout.toString().trim(), + 'waited you'); + +assert.strictEqual( + child_process.spawnSync(binary, + ['function waitPromise(text)' + + '{ return new Promise((res) => setTimeout(() => res(text + " with cheese"), 1)); }']) + .stdout.toString().trim(), + 'waited with cheese'); + +assert.strictEqual( + child_process.spawnSync(binary, + ['function waitPromise(text)' + + '{ return new Promise((res, rej) => setTimeout(() => rej(text + " without cheese"), 1)); }']) + .stdout.toString().trim(), + 'waited without cheese'); + +assert.match( + child_process.spawnSync(binary, + ['0syntax_error']) + .stderr.toString().trim(), + /SyntaxError: Invalid or unexpected token/); diff --git a/test/parallel/test-http-client-response-timeout.js b/test/parallel/test-http-client-response-timeout.js index 7e44d83a831143..6f9b490e551e66 100644 --- a/test/parallel/test-http-client-response-timeout.js +++ b/test/parallel/test-http-client-response-timeout.js @@ -9,6 +9,6 @@ server.listen(common.mustCall(() => { http.get({ port: server.address().port }, common.mustCall((res) => { res.on('timeout', common.mustCall(() => req.destroy())); res.setTimeout(1); - server.close(); + setTimeout(() => server.close(), 2); })); })); diff --git a/test/sequential/test-timers-block-eventloop.js b/test/sequential/test-timers-block-eventloop.js index 6118695c9235a2..ad8db215048f55 100644 --- a/test/sequential/test-timers-block-eventloop.js +++ b/test/sequential/test-timers-block-eventloop.js @@ -7,6 +7,8 @@ const { sleep } = require('internal/util'); let called = false; const t1 = setInterval(() => { + // Temporarily disable this test until there is a solution for + // https://github.com/libuv/libuv/issues/3686 assert(!called); called = true; setImmediate(common.mustCall(() => { From 414c501e9c35e92b91fdbc8beb360d88811f1e73 Mon Sep 17 00:00:00 2001 From: Momtchil Momtchev Date: Wed, 1 Feb 2023 14:46:09 +0100 Subject: [PATCH 02/16] new linter rules --- lib/internal/bootstrap/switches/is_embedded_env.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/internal/bootstrap/switches/is_embedded_env.js b/lib/internal/bootstrap/switches/is_embedded_env.js index 491fff0dcb2192..2edd8a056777f4 100644 --- a/lib/internal/bootstrap/switches/is_embedded_env.js +++ b/lib/internal/bootstrap/switches/is_embedded_env.js @@ -8,7 +8,7 @@ /* global path, primordials */ -const { globalThis, ObjectCreate } = primordials; +const { globalThis } = primordials; const CJSLoader = require('internal/modules/cjs/loader'); const ESMLoader = require('internal/modules/esm/loader').ESMLoader; @@ -17,5 +17,5 @@ globalThis.require = require('module').createRequire(path); const internalLoader = new ESMLoader(); const parent_path = require('url').pathToFileURL(path); -globalThis.import = (mod) => internalLoader.import(mod, parent_path, ObjectCreate(null)); +globalThis.import = (mod) => internalLoader.import(mod, parent_path, { __proto__: null }); globalThis.import.meta = { url: parent_path }; From 1fb6c8e7e21b10d0f272ff0155a4d9fec08e9948 Mon Sep 17 00:00:00 2001 From: Momtchil Momtchev Date: Wed, 1 Feb 2023 15:22:01 +0100 Subject: [PATCH 03/16] remove unintented change --- src/js_native_api_v8.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/js_native_api_v8.cc b/src/js_native_api_v8.cc index 0640a99d22582d..36e162f0b268f3 100644 --- a/src/js_native_api_v8.cc +++ b/src/js_native_api_v8.cc @@ -304,10 +304,10 @@ class EmbeddedEnvironment : public node::EmbeddedEnvironment { class HandleScopeWrapper { public: - explicit HandleScopeWrapper(v8::Isolate* isolate) : scope_(isolate) {} + explicit HandleScopeWrapper(v8::Isolate* isolate) : scope(isolate) {} private: - v8::HandleScope scope_; + v8::HandleScope scope; }; // In node v0.10 version of v8, there is no EscapableHandleScope and the From bd223ae9dec2441af25bb897f82ff1872a69f807 Mon Sep 17 00:00:00 2001 From: Momtchil Momtchev Date: Fri, 3 Feb 2023 19:30:37 +0100 Subject: [PATCH 04/16] restore require/import in main_script --- src/js_native_api_v8.cc | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/js_native_api_v8.cc b/src/js_native_api_v8.cc index 36e162f0b268f3..04e942a3ac0436 100644 --- a/src/js_native_api_v8.cc +++ b/src/js_native_api_v8.cc @@ -1058,12 +1058,20 @@ napi_create_environment(napi_platform platform, auto env = emb_env->setup()->env(); if (main_script == nullptr) main_script = ""; - auto ret = node::LoadEnvironment(env, main_script); - if (ret.IsEmpty()) return napi_pending_exception; - - node::Realm* realm = env->principal_realm(); - ret = - realm->ExecuteBootstrapper("internal/bootstrap/switches/is_embedded_env"); + auto ret = node::LoadEnvironment( + env, + [main_script, env](const node::StartExecutionCallbackInfo& info) + -> v8::MaybeLocal { + node::Realm* realm = env->principal_realm(); + auto ret = realm->ExecuteBootstrapper( + "internal/bootstrap/switches/is_embedded_env"); + if (ret.IsEmpty()) return ret; + + std::string name = + "embedder_main_napi_" + std::to_string(env->thread_id()); + env->builtin_loader()->Add(name.c_str(), main_script); + return realm->ExecuteBootstrapper(name.c_str()); + }); if (ret.IsEmpty()) return napi_pending_exception; emb_env->seal(); From 890bfcbb14d696da849bf2b225716f77556c7a3c Mon Sep 17 00:00:00 2001 From: Momtchil Momtchev Date: Thu, 6 Apr 2023 10:41:09 +0200 Subject: [PATCH 05/16] rectify napi_destroy_platform --- doc/api/n-api.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/doc/api/n-api.md b/doc/api/n-api.md index 318ce864ee506f..3762fbd7724025 100644 --- a/doc/api/n-api.md +++ b/doc/api/n-api.md @@ -6623,11 +6623,10 @@ added: REPLACEME > Stability: 1 - Experimental ```c -napi_status napi_destroy_platform(napi_platform platform, int *exit_code); +napi_status napi_destroy_platform(napi_platform platform); ``` * `[in] platform`: platform handle. -* `[out] exit_code`: if not NULL will receive the process exit code. Destroy the Node.js / V8 processes. From 23ddf964775073d8723bcf60e67a092743915c34 Mon Sep 17 00:00:00 2001 From: Momtchil Momtchev Date: Sat, 13 May 2023 00:50:54 +0200 Subject: [PATCH 06/16] catch up with main --- .../bootstrap/switches/is_embedded_env.js | 9 ++- src/js_native_api_v8.cc | 72 ++++++++++++++----- src/node_api_embedding.h | 17 +++-- 3 files changed, 68 insertions(+), 30 deletions(-) diff --git a/lib/internal/bootstrap/switches/is_embedded_env.js b/lib/internal/bootstrap/switches/is_embedded_env.js index 2edd8a056777f4..763a35809c923b 100644 --- a/lib/internal/bootstrap/switches/is_embedded_env.js +++ b/lib/internal/bootstrap/switches/is_embedded_env.js @@ -9,13 +9,12 @@ /* global path, primordials */ const { globalThis } = primordials; -const CJSLoader = require('internal/modules/cjs/loader'); -const ESMLoader = require('internal/modules/esm/loader').ESMLoader; +const cjsLoader = require('internal/modules/cjs/loader'); +const esmLoader = require('internal/process/esm_loader').esmLoader; -globalThis.module = new CJSLoader.Module(); +globalThis.module = new cjsLoader.Module(); globalThis.require = require('module').createRequire(path); -const internalLoader = new ESMLoader(); const parent_path = require('url').pathToFileURL(path); -globalThis.import = (mod) => internalLoader.import(mod, parent_path, { __proto__: null }); +globalThis.import = (mod) => esmLoader.import(mod, parent_path, { __proto__: null }); globalThis.import.meta = { url: parent_path }; diff --git a/src/js_native_api_v8.cc b/src/js_native_api_v8.cc index 04e942a3ac0436..7e20bc19fd7821 100644 --- a/src/js_native_api_v8.cc +++ b/src/js_native_api_v8.cc @@ -7,6 +7,7 @@ #include "js_native_api_v8.h" #include "node_api_embedding.h" #include "node_api_internals.h" +#include "simdutf.h" #include "util-inl.h" #define CHECK_MAYBE_NOTHING(env, maybe, status) \ @@ -266,18 +267,24 @@ struct PlatformWrapper { explicit PlatformWrapper(int argc, char** argv, int exec_argc, - char** exec_argv) - : args(argv, argv + argc), exec_args(exec_argv, exec_argv + exec_argc) {} + char** exec_argv, + int32_t _api_version) + : args(argv, argv + argc), + exec_args(exec_argv, exec_argv + exec_argc), + api_version(_api_version) {} std::unique_ptr platform; std::vector args; std::vector exec_args; + int32_t api_version; }; class EmbeddedEnvironment : public node::EmbeddedEnvironment { public: explicit EmbeddedEnvironment( - std::unique_ptr&& setup) - : setup_(std::move(setup)), + std::unique_ptr&& setup, + const std::shared_ptr& main_resource) + : main_resource_(main_resource), + setup_(std::move(setup)), locker_(setup_->isolate()), isolate_scope_(setup_->isolate()), handle_scope_(setup_->isolate()), @@ -291,6 +298,11 @@ class EmbeddedEnvironment : public node::EmbeddedEnvironment { } private: + // The pointer to the UTF-16 main script convertible to V8 UnionBytes resource + // This must be constructed first and destroyed last because the isolate + // references it + std::shared_ptr main_resource_; + std::unique_ptr setup_; v8::Locker locker_; v8::Isolate::Scope isolate_scope_; @@ -976,17 +988,18 @@ napi_status NAPI_CDECL napi_get_last_error_info( } napi_status NAPI_CDECL -napi_create_platform(int argc, - char** argv, - int exec_argc, - char** exec_argv, - napi_error_message_handler err_handler, - napi_platform* result) { +napi_create_platform_version(int argc, + char** argv, + int exec_argc, + char** exec_argv, + napi_error_message_handler err_handler, + napi_platform* result, + int32_t api_version) { argv = uv_setup_args(argc, argv); std::vector errors_vec; - v8impl::PlatformWrapper* platform = - new v8impl::PlatformWrapper(argc, argv, exec_argc, exec_argv); + v8impl::PlatformWrapper* platform = new v8impl::PlatformWrapper( + argc, argv, exec_argc, exec_argv, api_version); if (platform->args.size() < 1) platform->args.push_back("libnode"); int exit_code = node::InitializeNodeWithArgs( @@ -1043,12 +1056,29 @@ napi_create_environment(napi_platform platform, if (setup == nullptr) { return napi_generic_failure; } - auto emb_env = new v8impl::EmbeddedEnvironment(std::move(setup)); + + std::shared_ptr main_resource = nullptr; + if (main_script != nullptr) { + // We convert the user-supplied main_script to a UTF-16 resource + // and we store its shared_ptr in the environment + size_t u8_length = strlen(main_script); + size_t expected_u16_length = + simdutf::utf16_length_from_utf8(main_script, u8_length); + auto out = std::make_shared>(expected_u16_length); + size_t u16_length = simdutf::convert_utf8_to_utf16( + main_script, u8_length, reinterpret_cast(out->data())); + out->resize(u16_length); + main_resource = std::make_shared( + out->data(), out->size(), out); + } + + auto emb_env = + new v8impl::EmbeddedEnvironment(std::move(setup), main_resource); std::string filename = wrapper->args.size() > 1 ? wrapper->args[1] : ""; - auto env__ = new node_napi_env__(emb_env->setup()->context(), - filename, NODE_API_DEFAULT_MODULE_API_VERSION); + auto env__ = new node_napi_env__( + emb_env->setup()->context(), filename, wrapper->api_version); emb_env->setup()->env()->set_embedded(emb_env); env__->node_env()->AddCleanupHook( [](void* arg) { static_cast(arg)->Unref(); }, @@ -1056,11 +1086,11 @@ napi_create_environment(napi_platform platform, *result = env__; auto env = emb_env->setup()->env(); - if (main_script == nullptr) main_script = ""; auto ret = node::LoadEnvironment( env, - [main_script, env](const node::StartExecutionCallbackInfo& info) + [env, resource = main_resource.get()]( + const node::StartExecutionCallbackInfo& info) -> v8::MaybeLocal { node::Realm* realm = env->principal_realm(); auto ret = realm->ExecuteBootstrapper( @@ -1069,8 +1099,12 @@ napi_create_environment(napi_platform platform, std::string name = "embedder_main_napi_" + std::to_string(env->thread_id()); - env->builtin_loader()->Add(name.c_str(), main_script); - return realm->ExecuteBootstrapper(name.c_str()); + if (resource != nullptr) { + env->builtin_loader()->Add(name.c_str(), node::UnionBytes(resource)); + return realm->ExecuteBootstrapper(name.c_str()); + } else { + return v8::Undefined(env->isolate()); + } }); if (ret.IsEmpty()) return napi_pending_exception; diff --git a/src/node_api_embedding.h b/src/node_api_embedding.h index bd202432c3515b..7730ee92aab23f 100644 --- a/src/node_api_embedding.h +++ b/src/node_api_embedding.h @@ -17,12 +17,17 @@ EXTERN_C_START typedef void (*napi_error_message_handler)(const char* msg); NAPI_EXTERN napi_status NAPI_CDECL -napi_create_platform(int argc, - char** argv, - int exec_argc, - char** exec_argv, - napi_error_message_handler err_handler, - napi_platform* result); +napi_create_platform_version(int argc, + char** argv, + int exec_argc, + char** exec_argv, + napi_error_message_handler err_handler, + napi_platform* result, + int32_t api_version); +#define napi_create_platform( \ + argc, argv, exec_args, exec_argv, err_handler, result) \ + napi_create_platform_version( \ + argc, argv, exec_args, exec_argv, err_handler, result, NAPI_VERSION) NAPI_EXTERN napi_status NAPI_CDECL napi_destroy_platform(napi_platform platform); From 213aea4b761afc79bbd7fbb328fa01d02d753c97 Mon Sep 17 00:00:00 2001 From: Momtchil Momtchev Date: Sat, 13 May 2023 08:19:27 +0200 Subject: [PATCH 07/16] the linter was not happy with this indentation --- src/node_builtins.cc | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/node_builtins.cc b/src/node_builtins.cc index 4b0c0bd72f6a25..5403c8a615cef1 100644 --- a/src/node_builtins.cc +++ b/src/node_builtins.cc @@ -404,13 +404,11 @@ MaybeLocal BuiltinLoader::LookupAndCompile(Local context, strlen("internal/bootstrap/")) == 0) { // internal/main/*, internal/bootstrap/*: process, require, // internalBinding, primordials - parameters = { - FIXED_ONE_BYTE_STRING(isolate, "process"), - FIXED_ONE_BYTE_STRING(isolate, "require"), - FIXED_ONE_BYTE_STRING(isolate, "internalBinding"), - FIXED_ONE_BYTE_STRING(isolate, "primordials"), - FIXED_ONE_BYTE_STRING(isolate, "path") - }; + parameters = {FIXED_ONE_BYTE_STRING(isolate, "process"), + FIXED_ONE_BYTE_STRING(isolate, "require"), + FIXED_ONE_BYTE_STRING(isolate, "internalBinding"), + FIXED_ONE_BYTE_STRING(isolate, "primordials"), + FIXED_ONE_BYTE_STRING(isolate, "path")}; } else { // others: exports, require, module, process, internalBinding, primordials parameters = { From fc342034330452b863076036b3460cc22b2980bd Mon Sep 17 00:00:00 2001 From: Momtchil Momtchev Date: Fri, 28 Jul 2023 18:15:35 +0200 Subject: [PATCH 08/16] the underlying libuv issue has been resolved --- test/sequential/test-timers-block-eventloop.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/sequential/test-timers-block-eventloop.js b/test/sequential/test-timers-block-eventloop.js index ad8db215048f55..6118695c9235a2 100644 --- a/test/sequential/test-timers-block-eventloop.js +++ b/test/sequential/test-timers-block-eventloop.js @@ -7,8 +7,6 @@ const { sleep } = require('internal/util'); let called = false; const t1 = setInterval(() => { - // Temporarily disable this test until there is a solution for - // https://github.com/libuv/libuv/issues/3686 assert(!called); called = true; setImmediate(common.mustCall(() => { From a4b2b90c137b4ddb69302bd97c3c514c57dba436 Mon Sep 17 00:00:00 2001 From: Momtchil Momtchev Date: Fri, 28 Jul 2023 18:16:39 +0200 Subject: [PATCH 09/16] remove the deps/uv patch --- deps/uv/src/unix/core.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/deps/uv/src/unix/core.c b/deps/uv/src/unix/core.c index bcb72a601f38b8..965e7f775250cf 100644 --- a/deps/uv/src/unix/core.c +++ b/deps/uv/src/unix/core.c @@ -459,8 +459,6 @@ int uv_run(uv_loop_t* loop, uv_run_mode mode) { */ uv__metrics_update_idle_time(loop); - uv__run_timers(loop); - uv__run_check(loop); uv__run_closing_handles(loop); From 5220a89eba4a63165e4dac173e0e5ed701db162a Mon Sep 17 00:00:00 2001 From: Momtchil Momtchev Date: Fri, 4 Aug 2023 23:27:32 +0200 Subject: [PATCH 10/16] remove the custom main script arg and use execPath --- lib/internal/bootstrap/switches/is_embedded_env.js | 6 +++--- src/node_builtins.cc | 9 ++------- test/embedding/napi_modules.c | 2 +- 3 files changed, 6 insertions(+), 11 deletions(-) diff --git a/lib/internal/bootstrap/switches/is_embedded_env.js b/lib/internal/bootstrap/switches/is_embedded_env.js index 763a35809c923b..eb9003cff54906 100644 --- a/lib/internal/bootstrap/switches/is_embedded_env.js +++ b/lib/internal/bootstrap/switches/is_embedded_env.js @@ -6,15 +6,15 @@ // Set up globalThis.require and globalThis.import so that they can // be easily accessed from C/C++ -/* global path, primordials */ +/* global primordials */ const { globalThis } = primordials; const cjsLoader = require('internal/modules/cjs/loader'); const esmLoader = require('internal/process/esm_loader').esmLoader; globalThis.module = new cjsLoader.Module(); -globalThis.require = require('module').createRequire(path); +globalThis.require = require('module').createRequire(process.execPath); -const parent_path = require('url').pathToFileURL(path); +const parent_path = require('url').pathToFileURL(process.execPath); globalThis.import = (mod) => esmLoader.import(mod, parent_path, { __proto__: null }); globalThis.import.meta = { url: parent_path }; diff --git a/src/node_builtins.cc b/src/node_builtins.cc index 5403c8a615cef1..30c7a346af1afe 100644 --- a/src/node_builtins.cc +++ b/src/node_builtins.cc @@ -407,8 +407,7 @@ MaybeLocal BuiltinLoader::LookupAndCompile(Local context, parameters = {FIXED_ONE_BYTE_STRING(isolate, "process"), FIXED_ONE_BYTE_STRING(isolate, "require"), FIXED_ONE_BYTE_STRING(isolate, "internalBinding"), - FIXED_ONE_BYTE_STRING(isolate, "primordials"), - FIXED_ONE_BYTE_STRING(isolate, "path")}; + FIXED_ONE_BYTE_STRING(isolate, "primordials")}; } else { // others: exports, require, module, process, internalBinding, primordials parameters = { @@ -455,16 +454,12 @@ MaybeLocal BuiltinLoader::CompileAndCall(Local context, strncmp(id, "internal/bootstrap/", strlen("internal/bootstrap/")) == 0) { - auto path = String::NewFromUtf8(isolate, realm->env()->exec_path().c_str()) - .ToLocalChecked(); // internal/main/*, internal/bootstrap/*: process, require, // internalBinding, primordials, - // path arguments = {realm->process_object(), realm->builtin_module_require(), realm->internal_binding_loader(), - realm->primordials(), - path}; + realm->primordials()}; } else if (strncmp(id, "embedder_main_", strlen("embedder_main_")) == 0) { // Synthetic embedder main scripts from LoadEnvironment(): process, require arguments = { diff --git a/test/embedding/napi_modules.c b/test/embedding/napi_modules.c index e3f855aa0574a0..a1a6dd6dc57ef4 100644 --- a/test/embedding/napi_modules.c +++ b/test/embedding/napi_modules.c @@ -52,7 +52,7 @@ int main(int argc, char* argv[]) { CHECK(napi_call_function(env, global, import, 1, &es6, &es6_promise), "import"); - napi_await_promise(env, es6_promise, &es6_module); + CHECK(napi_await_promise(env, es6_promise, &es6_module), "await"); CHECK(napi_get_property(env, es6_module, value, &es6_result), "value"); CHECK(napi_get_value_string_utf8( From a0b3b25ea9a51c61856327150954b29f0081e24a Mon Sep 17 00:00:00 2001 From: Momtchil Momtchev Date: Fri, 11 Aug 2023 18:38:27 +0200 Subject: [PATCH 11/16] Update lib/internal/bootstrap/switches/is_embedded_env.js Co-authored-by: Gabriel Schulhof --- lib/internal/bootstrap/switches/is_embedded_env.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/internal/bootstrap/switches/is_embedded_env.js b/lib/internal/bootstrap/switches/is_embedded_env.js index eb9003cff54906..004a424f8537de 100644 --- a/lib/internal/bootstrap/switches/is_embedded_env.js +++ b/lib/internal/bootstrap/switches/is_embedded_env.js @@ -1,7 +1,7 @@ 'use strict'; // This is the bootstrapping code used when creating a new environment -// through N-API +// through Node-API // Set up globalThis.require and globalThis.import so that they can // be easily accessed from C/C++ From ccd228be0be2d22bbc5910161943b0b89c52f87c Mon Sep 17 00:00:00 2001 From: Momtchil Momtchev Date: Tue, 5 Sep 2023 12:54:30 +0200 Subject: [PATCH 12/16] adapt to the new embedding API --- doc/api/embedding.md | 4 +- doc/api/n-api.md | 7 ++-- src/js_native_api_v8.cc | 73 ++++++++++++++------------------- src/node_api_embedding.h | 16 +++----- test/embedding/napi_embedding.c | 7 ++-- test/embedding/napi_modules.c | 4 +- 6 files changed, 46 insertions(+), 65 deletions(-) diff --git a/doc/api/embedding.md b/doc/api/embedding.md index 14623874db1f88..4d6b737b9953be 100644 --- a/doc/api/embedding.md +++ b/doc/api/embedding.md @@ -180,13 +180,13 @@ An example can be found [in the Node.js source tree][napi_embedding.c]. napi_env env; const char *main_script = "console.log('hello world')"; - if (napi_create_platform(0, NULL, 0, NULL, NULL, &platform) != napi_ok) { + 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}, &env) != napi_ok) { + (napi_stdio){NULL, NULL, NULL}, NAPI_VERSION, &env) != napi_ok) { fprintf(stderr, "Failed running JS\n"); return -1; } diff --git a/doc/api/n-api.md b/doc/api/n-api.md index 3762fbd7724025..6f0d244498a80f 100644 --- a/doc/api/n-api.md +++ b/doc/api/n-api.md @@ -6594,16 +6594,12 @@ added: REPLACEME ```c napi_status napi_create_platform(int argc, char** argv, - int exec_argc, - char** exec_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] exec_argc`: Node.js CLI options count. -* `[in] exec_argv`: Node.js CLI options. * `[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. @@ -6642,6 +6638,7 @@ added: REPLACEME 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); ``` @@ -6658,6 +6655,8 @@ napi_status napi_create_environment(napi_platform platform, 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 diff --git a/src/js_native_api_v8.cc b/src/js_native_api_v8.cc index 7e20bc19fd7821..c346210517c1cf 100644 --- a/src/js_native_api_v8.cc +++ b/src/js_native_api_v8.cc @@ -263,21 +263,6 @@ inline v8impl::Persistent* NodePersistentFromJsDeferred( return reinterpret_cast*>(local); } -struct PlatformWrapper { - explicit PlatformWrapper(int argc, - char** argv, - int exec_argc, - char** exec_argv, - int32_t _api_version) - : args(argv, argv + argc), - exec_args(exec_argv, exec_argv + exec_argc), - api_version(_api_version) {} - std::unique_ptr platform; - std::vector args; - std::vector exec_args; - int32_t api_version; -}; - class EmbeddedEnvironment : public node::EmbeddedEnvironment { public: explicit EmbeddedEnvironment( @@ -988,49 +973,48 @@ napi_status NAPI_CDECL napi_get_last_error_info( } napi_status NAPI_CDECL -napi_create_platform_version(int argc, - char** argv, - int exec_argc, - char** exec_argv, - napi_error_message_handler err_handler, - napi_platform* result, - int32_t api_version) { +napi_create_platform(int argc, + char** argv, + napi_error_message_handler err_handler, + napi_platform* result) { argv = uv_setup_args(argc, argv); - std::vector errors_vec; - - v8impl::PlatformWrapper* platform = new v8impl::PlatformWrapper( - argc, argv, exec_argc, exec_argv, api_version); - if (platform->args.size() < 1) platform->args.push_back("libnode"); + std::vector args(argv, argv + argc); + if (args.size() < 1) args.push_back("libnode"); - int exit_code = node::InitializeNodeWithArgs( - &platform->args, &platform->exec_args, &errors_vec); + std::unique_ptr node_platform = + node::InitializeOncePerProcess( + args, + {node::ProcessInitializationFlags::kNoInitializeV8, + node::ProcessInitializationFlags::kNoInitializeNodeV8Platform}); - for (const std::string& error : errors_vec) { + for (const std::string& error : node_platform->errors()) { if (err_handler != nullptr) { err_handler(error.c_str()); } else { fprintf(stderr, "%s\n", error.c_str()); } } - - if (exit_code != 0) { + if (node_platform->early_return() != 0) { return napi_generic_failure; } auto thread_pool_size = node::per_process::cli_options->v8_thread_pool_size; - platform->platform = node::MultiIsolatePlatform::Create(thread_pool_size); - v8::V8::InitializePlatform(platform->platform.get()); + std::unique_ptr v8_platform = + node::MultiIsolatePlatform::Create(thread_pool_size); + v8::V8::InitializePlatform(v8_platform.get()); v8::V8::Initialize(); - *result = reinterpret_cast(platform); + reinterpret_cast(node_platform.get()) + ->platform_ = v8_platform.release(); + *result = reinterpret_cast(node_platform.release()); return napi_ok; } napi_status NAPI_CDECL napi_destroy_platform(napi_platform platform) { - auto wrapper = reinterpret_cast(platform); + auto wrapper = reinterpret_cast(platform); v8::V8::Dispose(); v8::V8::DisposePlatform(); - - // The node::CommonEnvironmentSetup::Create uniq_ptr is destroyed here + node::TearDownOncePerProcess(); + delete wrapper->platform(); delete wrapper; return napi_ok; } @@ -1039,12 +1023,13 @@ napi_status NAPI_CDECL napi_create_environment(napi_platform platform, napi_error_message_handler err_handler, const char* main_script, + int32_t api_version, napi_env* result) { - auto wrapper = reinterpret_cast(platform); + auto wrapper = reinterpret_cast(platform); std::vector errors_vec; auto setup = node::CommonEnvironmentSetup::Create( - wrapper->platform.get(), &errors_vec, wrapper->args, wrapper->exec_args); + wrapper->platform(), &errors_vec, wrapper->args(), wrapper->exec_args()); for (const std::string& error : errors_vec) { if (err_handler != nullptr) { @@ -1076,9 +1061,9 @@ napi_create_environment(napi_platform platform, new v8impl::EmbeddedEnvironment(std::move(setup), main_resource); std::string filename = - wrapper->args.size() > 1 ? wrapper->args[1] : ""; - auto env__ = new node_napi_env__( - emb_env->setup()->context(), filename, wrapper->api_version); + wrapper->args().size() > 1 ? wrapper->args()[1] : ""; + auto env__ = + new node_napi_env__(emb_env->setup()->context(), filename, api_version); emb_env->setup()->env()->set_embedded(emb_env); env__->node_env()->AddCleanupHook( [](void* arg) { static_cast(arg)->Unref(); }, @@ -1128,6 +1113,8 @@ napi_status NAPI_CDECL napi_destroy_environment(napi_env env, int* exit_code) { // and the v8::locker delete emb_env; + cppgc::ShutdownProcess(); + return napi_ok; } diff --git a/src/node_api_embedding.h b/src/node_api_embedding.h index 7730ee92aab23f..4550ce958a3d19 100644 --- a/src/node_api_embedding.h +++ b/src/node_api_embedding.h @@ -17,17 +17,10 @@ EXTERN_C_START typedef void (*napi_error_message_handler)(const char* msg); NAPI_EXTERN napi_status NAPI_CDECL -napi_create_platform_version(int argc, - char** argv, - int exec_argc, - char** exec_argv, - napi_error_message_handler err_handler, - napi_platform* result, - int32_t api_version); -#define napi_create_platform( \ - argc, argv, exec_args, exec_argv, err_handler, result) \ - napi_create_platform_version( \ - argc, argv, exec_args, exec_argv, err_handler, result, NAPI_VERSION) +napi_create_platform(int argc, + char** argv, + napi_error_message_handler err_handler, + napi_platform* result); NAPI_EXTERN napi_status NAPI_CDECL napi_destroy_platform(napi_platform platform); @@ -36,6 +29,7 @@ NAPI_EXTERN napi_status NAPI_CDECL napi_create_environment(napi_platform platform, napi_error_message_handler err_handler, const char* main_script, + int32_t api_version, napi_env* result); NAPI_EXTERN napi_status NAPI_CDECL napi_run_environment(napi_env env); diff --git a/test/embedding/napi_embedding.c b/test/embedding/napi_embedding.c index 6c58d9c877c762..618f8d53b46505 100644 --- a/test/embedding/napi_embedding.c +++ b/test/embedding/napi_embedding.c @@ -23,7 +23,7 @@ const char* main_script = int main(int argc, char** argv) { napi_platform platform; - CHECK(napi_create_platform(argc, argv, 0, NULL, NULL, &platform), + CHECK(napi_create_platform(argc, argv, NULL, &platform), "Failed creating the platform"); int exit_code = RunNodeInstance(platform); @@ -240,8 +240,9 @@ int RunNodeInstance(napi_platform platform) { napi_env env; int exit_code; - CHECK(napi_create_environment(platform, NULL, main_script, &env), - "Failed running JS"); + CHECK( + napi_create_environment(platform, NULL, main_script, NAPI_VERSION, &env), + "Failed running JS"); if (callMe(env) != 0) exit_code = -1; if (waitMe(env) != 0) exit_code = -1; diff --git a/test/embedding/napi_modules.c b/test/embedding/napi_modules.c index a1a6dd6dc57ef4..840c22dc38587f 100644 --- a/test/embedding/napi_modules.c +++ b/test/embedding/napi_modules.c @@ -18,11 +18,11 @@ int main(int argc, char* argv[]) { return -2; } - CHECK(napi_create_platform(0, NULL, 0, NULL, NULL, &platform), + CHECK(napi_create_platform(0, NULL, NULL, &platform), "Failed creating the platform"); napi_env env; - CHECK(napi_create_environment(platform, NULL, NULL, &env), + CHECK(napi_create_environment(platform, NULL, NULL, NAPI_VERSION, &env), "Failed running JS"); napi_handle_scope scope; From 7a566b7c210406a9f032e35f9a48b61a52ec3559 Mon Sep 17 00:00:00 2001 From: Vladimir Morozov Date: Fri, 8 Mar 2024 07:11:05 -0800 Subject: [PATCH 13/16] move code to node_api_embedding.cc --- node.gyp | 1 + src/js_native_api_types.h | 1 - src/js_native_api_v8.cc | 239 ----------------------------------- src/node_api_embedding.cc | 254 ++++++++++++++++++++++++++++++++++++++ src/node_api_embedding.h | 2 + 5 files changed, 257 insertions(+), 240 deletions(-) create mode 100644 src/node_api_embedding.cc diff --git a/node.gyp b/node.gyp index 81f88be98d2df3..ccfaed1814a239 100644 --- a/node.gyp +++ b/node.gyp @@ -94,6 +94,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', diff --git a/src/js_native_api_types.h b/src/js_native_api_types.h index c8a37a1286c257..7cb5b080cc377a 100644 --- a/src/js_native_api_types.h +++ b/src/js_native_api_types.h @@ -57,7 +57,6 @@ typedef struct napi_handle_scope__* napi_handle_scope; typedef struct napi_escapable_handle_scope__* napi_escapable_handle_scope; typedef struct napi_callback_info__* napi_callback_info; typedef struct napi_deferred__* napi_deferred; -typedef struct napi_platform__* napi_platform; typedef enum { napi_default = 0, diff --git a/src/js_native_api_v8.cc b/src/js_native_api_v8.cc index c346210517c1cf..3c5dc70330cd7f 100644 --- a/src/js_native_api_v8.cc +++ b/src/js_native_api_v8.cc @@ -5,9 +5,6 @@ #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" #define CHECK_MAYBE_NOTHING(env, maybe, status) \ @@ -263,42 +260,6 @@ inline v8impl::Persistent* NodePersistentFromJsDeferred( return reinterpret_cast*>(local); } -class EmbeddedEnvironment : public node::EmbeddedEnvironment { - public: - explicit EmbeddedEnvironment( - std::unique_ptr&& setup, - const std::shared_ptr& main_resource) - : main_resource_(main_resource), - setup_(std::move(setup)), - locker_(setup_->isolate()), - isolate_scope_(setup_->isolate()), - handle_scope_(setup_->isolate()), - context_scope_(setup_->context()), - seal_scope_(nullptr) {} - - inline node::CommonEnvironmentSetup* setup() { return setup_.get(); } - inline void seal() { - seal_scope_ = - std::make_unique(setup_->isolate()); - } - - private: - // The pointer to the UTF-16 main script convertible to V8 UnionBytes resource - // This must be constructed first and destroyed last because the isolate - // references it - std::shared_ptr main_resource_; - - std::unique_ptr setup_; - v8::Locker locker_; - v8::Isolate::Scope isolate_scope_; - v8::HandleScope handle_scope_; - v8::Context::Scope context_scope_; - // As this handle scope will remain open for the lifetime - // of the environment, we seal it to prevent it from - // becoming everyone's favorite trash bin - std::unique_ptr seal_scope_; -}; - class HandleScopeWrapper { public: explicit HandleScopeWrapper(v8::Isolate* isolate) : scope(isolate) {} @@ -972,206 +933,6 @@ napi_status NAPI_CDECL napi_get_last_error_info( return napi_ok; } -napi_status NAPI_CDECL -napi_create_platform(int argc, - char** argv, - napi_error_message_handler err_handler, - napi_platform* result) { - argv = uv_setup_args(argc, argv); - std::vector args(argv, argv + argc); - if (args.size() < 1) args.push_back("libnode"); - - std::unique_ptr node_platform = - node::InitializeOncePerProcess( - args, - {node::ProcessInitializationFlags::kNoInitializeV8, - node::ProcessInitializationFlags::kNoInitializeNodeV8Platform}); - - for (const std::string& error : node_platform->errors()) { - if (err_handler != nullptr) { - err_handler(error.c_str()); - } else { - fprintf(stderr, "%s\n", error.c_str()); - } - } - if (node_platform->early_return() != 0) { - return napi_generic_failure; - } - - auto thread_pool_size = node::per_process::cli_options->v8_thread_pool_size; - std::unique_ptr v8_platform = - node::MultiIsolatePlatform::Create(thread_pool_size); - v8::V8::InitializePlatform(v8_platform.get()); - v8::V8::Initialize(); - reinterpret_cast(node_platform.get()) - ->platform_ = v8_platform.release(); - *result = reinterpret_cast(node_platform.release()); - return napi_ok; -} - -napi_status NAPI_CDECL napi_destroy_platform(napi_platform platform) { - auto wrapper = reinterpret_cast(platform); - v8::V8::Dispose(); - v8::V8::DisposePlatform(); - node::TearDownOncePerProcess(); - delete wrapper->platform(); - delete wrapper; - return napi_ok; -} - -napi_status NAPI_CDECL -napi_create_environment(napi_platform platform, - napi_error_message_handler err_handler, - const char* main_script, - int32_t api_version, - napi_env* result) { - auto wrapper = reinterpret_cast(platform); - std::vector errors_vec; - - auto setup = node::CommonEnvironmentSetup::Create( - wrapper->platform(), &errors_vec, wrapper->args(), wrapper->exec_args()); - - for (const std::string& error : errors_vec) { - if (err_handler != nullptr) { - err_handler(error.c_str()); - } else { - fprintf(stderr, "%s\n", error.c_str()); - } - } - if (setup == nullptr) { - return napi_generic_failure; - } - - std::shared_ptr main_resource = nullptr; - if (main_script != nullptr) { - // We convert the user-supplied main_script to a UTF-16 resource - // and we store its shared_ptr in the environment - size_t u8_length = strlen(main_script); - size_t expected_u16_length = - simdutf::utf16_length_from_utf8(main_script, u8_length); - auto out = std::make_shared>(expected_u16_length); - size_t u16_length = simdutf::convert_utf8_to_utf16( - main_script, u8_length, reinterpret_cast(out->data())); - out->resize(u16_length); - main_resource = std::make_shared( - out->data(), out->size(), out); - } - - auto emb_env = - new v8impl::EmbeddedEnvironment(std::move(setup), main_resource); - - std::string filename = - wrapper->args().size() > 1 ? wrapper->args()[1] : ""; - auto env__ = - new node_napi_env__(emb_env->setup()->context(), filename, api_version); - emb_env->setup()->env()->set_embedded(emb_env); - env__->node_env()->AddCleanupHook( - [](void* arg) { static_cast(arg)->Unref(); }, - static_cast(env__)); - *result = env__; - - auto env = emb_env->setup()->env(); - - auto ret = node::LoadEnvironment( - env, - [env, resource = main_resource.get()]( - const node::StartExecutionCallbackInfo& info) - -> v8::MaybeLocal { - node::Realm* realm = env->principal_realm(); - auto ret = realm->ExecuteBootstrapper( - "internal/bootstrap/switches/is_embedded_env"); - if (ret.IsEmpty()) return ret; - - std::string name = - "embedder_main_napi_" + std::to_string(env->thread_id()); - if (resource != nullptr) { - env->builtin_loader()->Add(name.c_str(), node::UnionBytes(resource)); - return realm->ExecuteBootstrapper(name.c_str()); - } else { - return v8::Undefined(env->isolate()); - } - }); - if (ret.IsEmpty()) return napi_pending_exception; - - emb_env->seal(); - - return napi_ok; -} - -napi_status NAPI_CDECL napi_destroy_environment(napi_env env, int* exit_code) { - CHECK_ARG(env, env); - node_napi_env node_env = reinterpret_cast(env); - - int r = node::SpinEventLoop(node_env->node_env()).FromMaybe(1); - if (exit_code != nullptr) *exit_code = r; - node::Stop(node_env->node_env()); - - auto emb_env = reinterpret_cast( - node_env->node_env()->get_embedded()); - node_env->node_env()->set_embedded(nullptr); - // This deletes the uniq_ptr to node::CommonEnvironmentSetup - // and the v8::locker - delete emb_env; - - cppgc::ShutdownProcess(); - - return napi_ok; -} - -napi_status NAPI_CDECL napi_run_environment(napi_env env) { - CHECK_ARG(env, env); - node_napi_env node_env = reinterpret_cast(env); - - if (node::SpinEventLoopWithoutCleanup(node_env->node_env()).IsNothing()) - return napi_closing; - - return napi_ok; -} - -static void napi_promise_error_handler( - const v8::FunctionCallbackInfo& info) { - return; -} - -napi_status napi_await_promise(napi_env env, - napi_value promise, - napi_value* result) { - NAPI_PREAMBLE(env); - CHECK_ARG(env, result); - - v8::EscapableHandleScope scope(env->isolate); - node_napi_env node_env = reinterpret_cast(env); - - 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(), napi_promise_error_handler, rejected) - .ToLocalChecked(); - - if (promise_object->Catch(env->context(), err_handler).IsEmpty()) - return napi_pending_exception; - - if (node::SpinEventLoopWithoutCleanup( - node_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; -} - napi_status NAPI_CDECL napi_create_function(napi_env env, const char* utf8name, size_t length, diff --git a/src/node_api_embedding.cc b/src/node_api_embedding.cc new file mode 100644 index 00000000000000..0c5e4c3c59379a --- /dev/null +++ b/src/node_api_embedding.cc @@ -0,0 +1,254 @@ +#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 v8impl { +namespace { + +class EmbeddedEnvironment : public node::EmbeddedEnvironment { + public: + explicit EmbeddedEnvironment( + std::unique_ptr&& setup, + const std::shared_ptr& main_resource) + : main_resource_(main_resource), + setup_(std::move(setup)), + locker_(setup_->isolate()), + isolate_scope_(setup_->isolate()), + handle_scope_(setup_->isolate()), + context_scope_(setup_->context()), + seal_scope_(nullptr) {} + + inline node::CommonEnvironmentSetup* setup() { return setup_.get(); } + inline void seal() { + seal_scope_ = + std::make_unique(setup_->isolate()); + } + + private: + // The pointer to the UTF-16 main script convertible to V8 UnionBytes resource + // This must be constructed first and destroyed last because the isolate + // references it + std::shared_ptr main_resource_; + + std::unique_ptr setup_; + v8::Locker locker_; + v8::Isolate::Scope isolate_scope_; + v8::HandleScope handle_scope_; + v8::Context::Scope context_scope_; + // As this handle scope will remain open for the lifetime + // of the environment, we seal it to prevent it from + // becoming everyone's favorite trash bin + std::unique_ptr seal_scope_; +}; + +} // end of anonymous namespace +} // end of namespace v8impl + +napi_status NAPI_CDECL +napi_create_platform(int argc, + char** argv, + napi_error_message_handler err_handler, + napi_platform* result) { + argv = uv_setup_args(argc, argv); + std::vector args(argv, argv + argc); + if (args.size() < 1) args.push_back("libnode"); + + std::unique_ptr node_platform = + node::InitializeOncePerProcess( + args, + {node::ProcessInitializationFlags::kNoInitializeV8, + node::ProcessInitializationFlags::kNoInitializeNodeV8Platform}); + + for (const std::string& error : node_platform->errors()) { + if (err_handler != nullptr) { + err_handler(error.c_str()); + } else { + fprintf(stderr, "%s\n", error.c_str()); + } + } + if (node_platform->early_return() != 0) { + return napi_generic_failure; + } + + int thread_pool_size = + static_cast(node::per_process::cli_options->v8_thread_pool_size); + std::unique_ptr v8_platform = + node::MultiIsolatePlatform::Create(thread_pool_size); + v8::V8::InitializePlatform(v8_platform.get()); + v8::V8::Initialize(); + reinterpret_cast(node_platform.get()) + ->platform_ = v8_platform.release(); + *result = reinterpret_cast(node_platform.release()); + return napi_ok; +} + +napi_status NAPI_CDECL napi_destroy_platform(napi_platform platform) { + auto wrapper = reinterpret_cast(platform); + v8::V8::Dispose(); + v8::V8::DisposePlatform(); + node::TearDownOncePerProcess(); + delete wrapper->platform(); + delete wrapper; + return napi_ok; +} + +napi_status NAPI_CDECL +napi_create_environment(napi_platform platform, + napi_error_message_handler err_handler, + const char* main_script, + int32_t api_version, + napi_env* result) { + auto wrapper = reinterpret_cast(platform); + std::vector errors_vec; + + auto setup = node::CommonEnvironmentSetup::Create( + wrapper->platform(), &errors_vec, wrapper->args(), wrapper->exec_args()); + + for (const std::string& error : errors_vec) { + if (err_handler != nullptr) { + err_handler(error.c_str()); + } else { + fprintf(stderr, "%s\n", error.c_str()); + } + } + if (setup == nullptr) { + return napi_generic_failure; + } + + std::shared_ptr main_resource = nullptr; + if (main_script != nullptr) { + // We convert the user-supplied main_script to a UTF-16 resource + // and we store its shared_ptr in the environment + size_t u8_length = strlen(main_script); + size_t expected_u16_length = + simdutf::utf16_length_from_utf8(main_script, u8_length); + auto out = std::make_shared>(expected_u16_length); + size_t u16_length = simdutf::convert_utf8_to_utf16( + main_script, u8_length, reinterpret_cast(out->data())); + out->resize(u16_length); + main_resource = std::make_shared( + out->data(), out->size(), out); + } + + auto emb_env = + new v8impl::EmbeddedEnvironment(std::move(setup), main_resource); + + std::string filename = + wrapper->args().size() > 1 ? wrapper->args()[1] : ""; + auto env__ = + new node_napi_env__(emb_env->setup()->context(), filename, api_version); + emb_env->setup()->env()->set_embedded(emb_env); + env__->node_env()->AddCleanupHook( + [](void* arg) { static_cast(arg)->Unref(); }, + static_cast(env__)); + *result = env__; + + auto env = emb_env->setup()->env(); + + auto ret = node::LoadEnvironment( + env, + [env, resource = main_resource.get()]( + const node::StartExecutionCallbackInfo& info) + -> v8::MaybeLocal { + node::Realm* realm = env->principal_realm(); + auto ret = realm->ExecuteBootstrapper( + "internal/bootstrap/switches/is_embedded_env"); + if (ret.IsEmpty()) return ret; + + std::string name = + "embedder_main_napi_" + std::to_string(env->thread_id()); + if (resource != nullptr) { + env->builtin_loader()->Add(name.c_str(), node::UnionBytes(resource)); + return realm->ExecuteBootstrapper(name.c_str()); + } else { + return v8::Undefined(env->isolate()); + } + }); + if (ret.IsEmpty()) return napi_pending_exception; + + emb_env->seal(); + + return napi_ok; +} + +napi_status NAPI_CDECL napi_destroy_environment(napi_env env, int* exit_code) { + CHECK_ARG(env, env); + node_napi_env node_env = reinterpret_cast(env); + + int r = node::SpinEventLoop(node_env->node_env()).FromMaybe(1); + if (exit_code != nullptr) *exit_code = r; + node::Stop(node_env->node_env()); + + auto emb_env = reinterpret_cast( + node_env->node_env()->get_embedded()); + node_env->node_env()->set_embedded(nullptr); + // This deletes the uniq_ptr to node::CommonEnvironmentSetup + // and the v8::locker + delete emb_env; + + cppgc::ShutdownProcess(); + + return napi_ok; +} + +napi_status NAPI_CDECL napi_run_environment(napi_env env) { + CHECK_ARG(env, env); + node_napi_env node_env = reinterpret_cast(env); + + if (node::SpinEventLoopWithoutCleanup(node_env->node_env()).IsNothing()) + return napi_closing; + + return napi_ok; +} + +static void napi_promise_error_handler( + const v8::FunctionCallbackInfo& info) { + return; +} + +napi_status napi_await_promise(napi_env env, + napi_value promise, + napi_value* result) { + NAPI_PREAMBLE(env); + CHECK_ARG(env, result); + + v8::EscapableHandleScope scope(env->isolate); + node_napi_env node_env = reinterpret_cast(env); + + 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(), napi_promise_error_handler, rejected) + .ToLocalChecked(); + + if (promise_object->Catch(env->context(), err_handler).IsEmpty()) + return napi_pending_exception; + + if (node::SpinEventLoopWithoutCleanup( + node_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 index 4550ce958a3d19..2d2afcab8749a3 100644 --- a/src/node_api_embedding.h +++ b/src/node_api_embedding.h @@ -12,6 +12,8 @@ #define EXTERN_C_END #endif +typedef struct napi_platform__* napi_platform; + EXTERN_C_START typedef void (*napi_error_message_handler)(const char* msg); From b7adf10a4a990f2375672f7d0c883f483c6efd93 Mon Sep 17 00:00:00 2001 From: Vladimir Morozov Date: Fri, 8 Mar 2024 15:39:31 -0800 Subject: [PATCH 14/16] comment typo fix --- src/node.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/node.h b/src/node.h index b68a4abad2bbe2..473e6b62ded6c3 100644 --- a/src/node.h +++ b/src/node.h @@ -878,7 +878,7 @@ NODE_EXTERN v8::Maybe SpinEventLoopWithoutCleanup(Environment* env); // 3. If the event loop is alive again, go to Step 1. // 4. Call EmitProcessExit() and forward the return value. // If at any point node::Stop() is called, the function will attempt to return -// as soon as possible, returning an empty `Maybe`. Ohterwise it will return +// as soon as possible, returning an empty `Maybe`. Otherwise it will return // a reference to the exit value. // This function only works if `env` has an associated `MultiIsolatePlatform`. NODE_EXTERN v8::Maybe SpinEventLoop(Environment* env); From ebe1c55b4906a028b6ae7fce12748671f8946bf1 Mon Sep 17 00:00:00 2001 From: Vladimir Morozov Date: Fri, 8 Mar 2024 16:15:30 -0800 Subject: [PATCH 15/16] delete compiled binary file from test --- test/embedding/napi_modules | Bin 21728 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100755 test/embedding/napi_modules diff --git a/test/embedding/napi_modules b/test/embedding/napi_modules deleted file mode 100755 index 3c1b5f742668b151ed2cc69e83ff5929ee157f83..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 21728 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 From fc3f0adb4d66d5ca5b30b465e2a76ff63feb4cda Mon Sep 17 00:00:00 2001 From: Vladimir Morozov Date: Fri, 5 Apr 2024 13:09:06 -0700 Subject: [PATCH 16/16] Remove ShutdownProcess from napi_destroy_environment Implemented by @jasongin --- src/node_api_embedding.cc | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/node_api_embedding.cc b/src/node_api_embedding.cc index 0c5e4c3c59379a..5e4b1ede5fa840 100644 --- a/src/node_api_embedding.cc +++ b/src/node_api_embedding.cc @@ -194,8 +194,6 @@ napi_status NAPI_CDECL napi_destroy_environment(napi_env env, int* exit_code) { // and the v8::locker delete emb_env; - cppgc::ShutdownProcess(); - return napi_ok; }