From 33b170ddfeaadb00a0a2809663f19e5f99099753 Mon Sep 17 00:00:00 2001 From: Momtchil Momtchev Date: Mon, 10 Oct 2022 22:14:37 +0200 Subject: [PATCH] node-api: add libnode support --- Makefile | 4 + deps/uv/src/unix/core.c | 16 +- doc/api/embedding.md | 39 +++ doc/api/n-api.md | 130 +++++++++ .../bootstrap/switches/is_embedded_env.js | 21 ++ node.gyp | 104 +++++++ src/api/embed_helpers.cc | 85 ++++-- src/env-inl.h | 11 + src/env.cc | 3 +- src/env.h | 14 + src/js_native_api.h | 28 ++ src/js_native_api_types.h | 1 + src/js_native_api_v8.cc | 217 ++++++++++++++- src/node.h | 22 +- src/node_api.cc | 1 + src/node_api_internals.h | 1 + src/node_binding.h | 1 + src/node_builtins.cc | 22 +- test/embedding/.eslintrc.yaml | 3 + test/embedding/cjs.cjs | 3 + test/embedding/es6.mjs | 1 + test/embedding/napi_embedding.c | 260 ++++++++++++++++++ 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 + 27 files changed, 1096 insertions(+), 51 deletions(-) create mode 100644 lib/internal/bootstrap/switches/is_embedded_env.js 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 05fdc1509844ad..a88c7de5c50fef 100644 --- a/Makefile +++ b/Makefile @@ -286,6 +286,8 @@ coverage-report-js: cctest: all @out/$(BUILDTYPE)/$@ --gtest_filter=$(GTEST_FILTER) @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: @@ -551,6 +553,8 @@ test-ci: | clear-stalled bench-addons-build build-addons build-js-native-api-tes --mode=$(BUILDTYPE_LOWER) --flaky-tests=$(FLAKY_TESTS) \ $(TEST_CI_ARGS) $(CI_JS_SUITES) $(CI_NATIVE_SUITES) $(CI_DOC) 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 71e9c525c4a77b..f3c009de2b52db 100644 --- a/deps/uv/src/unix/core.c +++ b/deps/uv/src/unix/core.c @@ -377,7 +377,6 @@ int uv_run(uv_loop_t* loop, uv_run_mode mode) { while (r != 0 && loop->stop_flag == 0) { uv__update_time(loop); - uv__run_timers(loop); ran_pending = uv__run_pending(loop); uv__run_idle(loop); uv__run_prepare(loop); @@ -395,22 +394,11 @@ 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); - if (mode == UV_RUN_ONCE) { - /* UV_RUN_ONCE implies forward progress: at least one callback must have - * been invoked when it returns. uv__io_poll() can return without doing - * I/O (meaning: no callbacks) when its timeout expires - which means we - * have pending timers that satisfy the forward progress constraint. - * - * UV_RUN_NOWAIT makes no guarantees about progress so it's omitted from - * the check. - */ - uv__update_time(loop); - uv__run_timers(loop); - } - r = uv__loop_alive(loop); if (mode == UV_RUN_ONCE || mode == UV_RUN_NOWAIT) break; diff --git a/doc/api/embedding.md b/doc/api/embedding.md index 9f831b342c2705..41bddb5713e4e8 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, 0, &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 3a2b9bceb2ab64..344288034b0120 100644 --- a/doc/api/n-api.md +++ b/doc/api/n-api.md @@ -6267,6 +6267,136 @@ 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, + char*** errors, + int thread_pool_size, + 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] errors`: If different than NULL, will receive an array of + strings that must be freed. +* `[in] thread_pool_size`: Thread pool size, 0 for automatic. +* `[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, + char*** errors, + const char* main_script, + napi_env* result); +``` + +* `[in] platform`: platform handle. +* `[in] errors`: If different than NULL, will receive an array of strings + that must be freed. +* `[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); +``` + +### `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. + +### `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 16b7515f003854..9c6ea545649e5a 100644 --- a/node.gyp +++ b/node.gyp @@ -1108,6 +1108,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 f0f92c690eb63c..c7c4de4806795d 100644 --- a/src/api/embed_helpers.cc +++ b/src/api/embed_helpers.cc @@ -1,6 +1,6 @@ -#include "node.h" -#include "env-inl.h" #include "debug_utils-inl.h" +#include "env-inl.h" +#include "node.h" using v8::Context; using v8::Function; @@ -16,7 +16,16 @@ using v8::SealHandleScope; 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); @@ -29,23 +38,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 (env->is_stopping()) return Nothing(); + + env->set_trace_sync_io(env->options()->trace_sync_io); + { + bool more; - if (EmitProcessBeforeExit(env).IsNothing()) + do { + if (SpinEventLoopWithoutCleanupInternal(env, AlwaysTrue).IsNothing()) break; + if (EmitProcessBeforeExit(env).IsNothing()) break; + { HandleScope handle_scope(isolate); if (env->RunSnapshotSerializeCallback().IsEmpty()) { @@ -53,16 +93,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. @@ -164,6 +200,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 4446b6057d5977..e29f3de2ca2de4 100644 --- a/src/env-inl.h +++ b/src/env-inl.h @@ -630,6 +630,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; @@ -805,6 +809,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.cc b/src/env.cc index d5552ce9133519..659cd38d018f3a 100644 --- a/src/env.cc +++ b/src/env.cc @@ -657,7 +657,8 @@ Environment::Environment(IsolateData* isolate_data, flags_(flags), thread_id_(thread_id.id == static_cast(-1) ? AllocateEnvironmentThreadId().id - : thread_id.id) { + : thread_id.id), + embedded_(nullptr) { // We'll be creating new objects so make sure we've entered the context. HandleScope handle_scope(isolate); diff --git a/src/env.h b/src/env.h index 9e72e5541c3a9b..8c6d2f1300c262 100644 --- a/src/env.h +++ b/src/env.h @@ -582,11 +582,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; @@ -815,6 +822,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; @@ -1017,6 +1025,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(); @@ -1195,6 +1206,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.h b/src/js_native_api.h index 220d140d4bfe9a..c8d64f31003d20 100644 --- a/src/js_native_api.h +++ b/src/js_native_api.h @@ -102,6 +102,34 @@ node_api_symbol_for(napi_env env, size_t length, napi_value* result); #endif // NAPI_EXPERIMENTAL + +#ifdef NAPI_EMBEDDING +NAPI_EXTERN napi_status NAPI_CDECL napi_create_platform(int argc, + char** argv, + int exec_argc, + char** exec_argv, + char*** errors, + int thread_pool_size, + 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, + char*** errors, + 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); +#endif // NAPI_EMBEDDING NAPI_EXTERN napi_status NAPI_CDECL napi_create_function(napi_env env, const char* utf8name, size_t length, diff --git a/src/js_native_api_types.h b/src/js_native_api_types.h index 376930ba4a3220..51017e520bd491 100644 --- a/src/js_native_api_types.h +++ b/src/js_native_api_types.h @@ -28,6 +28,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 58567c5e44a9e7..a5c12174bc9acc 100644 --- a/src/js_native_api_v8.cc +++ b/src/js_native_api_v8.cc @@ -2,9 +2,11 @@ #include // INT_MAX #include #define NAPI_EXPERIMENTAL +#define NAPI_EMBEDDING #include "env-inl.h" #include "js_native_api.h" #include "js_native_api_v8.h" +#include "node_api_internals.h" #include "util-inl.h" #define CHECK_MAYBE_NOTHING(env, maybe, status) \ @@ -110,12 +112,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 @@ -773,6 +815,177 @@ napi_status NAPI_CDECL napi_get_last_error_info( return napi_ok; } +#define HANDLE_ERRORS_VECTOR(errors, vec) \ + { \ + if (errors == nullptr) { \ + for (const std::string& error : vec) \ + fprintf(stderr, "%s\n", error.c_str()); \ + } else { \ + *errors = node::Malloc(vec.size() + 1); \ + if (errors == nullptr) return napi_generic_failure; \ + char** cur_error = *errors; \ + for (const std::string& error : vec) { \ + *(cur_error++) = strdup(error.c_str()); \ + } \ + *cur_error = nullptr; \ + } \ + } + +napi_status NAPI_CDECL napi_create_platform(int argc, + char** argv, + int exec_argc, + char** exec_argv, + char*** errors, + int thread_pool_size, + 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); + + HANDLE_ERRORS_VECTOR(errors, errors_vec); + + if (exit_code != 0) { + return napi_generic_failure; + } + + if (thread_pool_size <= 0) + 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, + char*** errors, + 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); + if (setup == nullptr) { + HANDLE_ERRORS_VECTOR(errors, errors_vec); + 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); + emb_env->setup()->env()->set_embedded(emb_env); + env__->node_env()->AddCleanupHook( + [](void* arg) { static_cast(arg)->Unref(); }, + static_cast(env__)); + + auto env = emb_env->setup()->env(); + if (main_script == nullptr) main_script = ""; + + node::Realm* realm = env->principal_realm(); + auto ret = + realm->ExecuteBootstrapper("internal/bootstrap/switches/is_embedded_env"); + if (ret.IsEmpty()) return napi_pending_exception; + + ret = node::LoadEnvironment(env, main_script); + if (ret.IsEmpty()) return napi_pending_exception; + + *result = env__; + 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 aea359f23da1bf..d006e51a9cd3a1 100644 --- a/src/node.h +++ b/src/node.h @@ -687,14 +687,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 48b94a7c12873c..1bc79bfa77a219 100644 --- a/src/node_api.cc +++ b/src/node_api.cc @@ -1,6 +1,7 @@ #include "async_wrap-inl.h" #include "env-inl.h" #define NAPI_EXPERIMENTAL +#define NAPI_EMBEDDING #include "js_native_api_v8.h" #include "memory_tracker-inl.h" #include "node_api.h" diff --git a/src/node_api_internals.h b/src/node_api_internals.h index de5d9dc0804367..199995453e138a 100644 --- a/src/node_api_internals.h +++ b/src/node_api_internals.h @@ -3,6 +3,7 @@ #include "v8.h" #define NAPI_EXPERIMENTAL +#define NAPI_EMBEDDING #include "env-inl.h" #include "js_native_api_v8.h" #include "node_api.h" diff --git a/src/node_binding.h b/src/node_binding.h index 5bced5b41431dc..0158623369763b 100644 --- a/src/node_binding.h +++ b/src/node_binding.h @@ -9,6 +9,7 @@ #include "node.h" #define NAPI_EXPERIMENTAL +#define NAPI_EMBEDDING #include "node_api.h" #include "uv.h" diff --git a/src/node_builtins.cc b/src/node_builtins.cc index e23bda89960996..99bd601456a816 100644 --- a/src/node_builtins.cc +++ b/src/node_builtins.cc @@ -358,13 +358,13 @@ MaybeLocal BuiltinLoader::LookupAndCompile( "internal/bootstrap/", 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"), - }; + // internalBinding, primordials, + // 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 if (strncmp(id, "embedder_main_", strlen("embedder_main_")) == 0) { // Synthetic embedder main scripts from LoadEnvironment(): process, require parameters = { @@ -420,12 +420,16 @@ 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 = { 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..8a3a9bcec22557 --- /dev/null +++ b/test/embedding/napi_embedding.c @@ -0,0 +1,260 @@ +#define NAPI_EXPERIMENTAL +#define NAPI_EMBEDDING +#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 = + "const publicRequire =" + " require('module').createRequire(process.cwd() + '/');" + "globalThis.require = publicRequire;" + "globalThis.embedVars = { nön_ascıı: '🏳️‍🌈' };" + "require('vm').runInThisContext(process.argv[1]);"; + +#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, 0, &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..0d407805d486da --- /dev/null +++ b/test/embedding/napi_modules.c @@ -0,0 +1,81 @@ +#include +#include +#define NAPI_EXPERIMENTAL +#define NAPI_EMBEDDING +#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, 0, &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(() => {