Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/nodejs/node.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/api/embedding.md105
-rw-r--r--node.gyp1
-rw-r--r--src/api/embed_helpers.cc169
-rw-r--r--src/node.h67
-rw-r--r--src/node_main_instance.cc33
-rw-r--r--src/node_worker.cc40
-rw-r--r--test/embedding/embedtest.cc80
7 files changed, 278 insertions, 217 deletions
diff --git a/doc/api/embedding.md b/doc/api/embedding.md
index f38d5a7cabc..82c87f5890f 100644
--- a/doc/api/embedding.md
+++ b/doc/api/embedding.md
@@ -67,6 +67,13 @@ int main(int argc, char** argv) {
```
### Per-instance state
+<!-- YAML
+changes:
+ - version: REPLACEME
+ pr-url: https://github.com/nodejs/node/pull/35597
+ description:
+ The `CommonEnvironmentSetup` and `SpinEventLoop` utilities were added.
+-->
Node.js has a concept of a “Node.js instance”, that is commonly being referred
to as `node::Environment`. Each `node::Environment` is associated with:
@@ -99,52 +106,26 @@ int RunNodeInstance(MultiIsolatePlatform* platform,
const std::vector<std::string>& args,
const std::vector<std::string>& exec_args) {
int exit_code = 0;
- // Set up a libuv event loop.
- uv_loop_t loop;
- int ret = uv_loop_init(&loop);
- if (ret != 0) {
- fprintf(stderr, "%s: Failed to initialize loop: %s\n",
- args[0].c_str(),
- uv_err_name(ret));
- return 1;
- }
- std::shared_ptr<ArrayBufferAllocator> allocator =
- ArrayBufferAllocator::Create();
-
- Isolate* isolate = NewIsolate(allocator, &loop, platform);
- if (isolate == nullptr) {
- fprintf(stderr, "%s: Failed to initialize V8 Isolate\n", args[0].c_str());
+ // Setup up a libuv event loop, v8::Isolate, and Node.js Environment.
+ std::vector<std::string> errors;
+ std::unique_ptr<CommonEnvironmentSetup> setup =
+ CommonEnvironmentSetup::Create(platform, &errors, args, exec_args);
+ if (!setup) {
+ for (const std::string& err : errors)
+ fprintf(stderr, "%s: %s\n", args[0].c_str(), err.c_str());
return 1;
}
+ Isolate* isolate = setup->isolate();
+ Environment* env = setup->env();
+
{
Locker locker(isolate);
Isolate::Scope isolate_scope(isolate);
-
- // Create a node::IsolateData instance that will later be released using
- // node::FreeIsolateData().
- std::unique_ptr<IsolateData, decltype(&node::FreeIsolateData)> isolate_data(
- node::CreateIsolateData(isolate, &loop, platform, allocator.get()),
- node::FreeIsolateData);
-
- // Set up a new v8::Context.
- HandleScope handle_scope(isolate);
- Local<Context> context = node::NewContext(isolate);
- if (context.IsEmpty()) {
- fprintf(stderr, "%s: Failed to initialize V8 Context\n", args[0].c_str());
- return 1;
- }
-
// The v8::Context needs to be entered when node::CreateEnvironment() and
// node::LoadEnvironment() are being called.
- Context::Scope context_scope(context);
-
- // Create a node::Environment instance that will later be released using
- // node::FreeEnvironment().
- std::unique_ptr<Environment, decltype(&node::FreeEnvironment)> env(
- node::CreateEnvironment(isolate_data.get(), context, args, exec_args),
- node::FreeEnvironment);
+ Context::Scope context_scope(setup->context());
// Set up the Node.js instance for execution, and run code inside of it.
// There is also a variant that takes a callback and provides it with
@@ -156,7 +137,7 @@ int RunNodeInstance(MultiIsolatePlatform* platform,
// load files from the disk, and uses the standard CommonJS file loader
// instead of the internal-only `require` function.
MaybeLocal<Value> loadenv_ret = node::LoadEnvironment(
- env.get(),
+ env,
"const publicRequire ="
" require('module').createRequire(process.cwd() + '/');"
"globalThis.require = publicRequire;"
@@ -165,58 +146,14 @@ int RunNodeInstance(MultiIsolatePlatform* platform,
if (loadenv_ret.IsEmpty()) // There has been a JS exception.
return 1;
- {
- // SealHandleScope protects against handle leaks from callbacks.
- SealHandleScope seal(isolate);
- bool more;
- do {
- uv_run(&loop, UV_RUN_DEFAULT);
-
- // V8 tasks on background threads may end up scheduling new tasks in the
- // foreground, which in turn can keep the event loop going. For example,
- // WebAssembly.compile() may do so.
- platform->DrainTasks(isolate);
-
- // If there are new tasks, continue.
- more = uv_loop_alive(&loop);
- if (more) continue;
-
- // node::EmitProcessBeforeExit() is used to emit the 'beforeExit' event
- // on the `process` object.
- if (node::EmitProcessBeforeExit(env.get()).IsNothing())
- break;
-
- // 'beforeExit' can also schedule new work that keeps the event loop
- // running.
- more = uv_loop_alive(&loop);
- } while (more == true);
- }
-
- // node::EmitProcessExit() returns the current exit code.
- exit_code = node::EmitProcessExit(env.get()).FromMaybe(1);
+ exit_code = node::SpinEventLoop(env).FromMaybe(1);
// node::Stop() can be used to explicitly stop the event loop and keep
// further JavaScript from running. It can be called from any thread,
// and will act like worker.terminate() if called from another thread.
- node::Stop(env.get());
+ node::Stop(env);
}
- // Unregister the Isolate with the platform and add a listener that is called
- // when the Platform is done cleaning up any state it had associated with
- // the Isolate.
- bool platform_finished = false;
- platform->AddIsolateFinishedCallback(isolate, [](void* data) {
- *static_cast<bool*>(data) = true;
- }, &platform_finished);
- platform->UnregisterIsolate(isolate);
- isolate->Dispose();
-
- // Wait until the platform has cleaned up all relevant resources.
- while (!platform_finished)
- uv_run(&loop, UV_RUN_ONCE);
- int err = uv_loop_close(&loop);
- assert(err == 0);
-
return exit_code;
}
```
diff --git a/node.gyp b/node.gyp
index 264a2cb408e..a96162d1079 100644
--- a/node.gyp
+++ b/node.gyp
@@ -575,6 +575,7 @@
'sources': [
'src/api/async_resource.cc',
'src/api/callback.cc',
+ 'src/api/embed_helpers.cc',
'src/api/encoding.cc',
'src/api/environment.cc',
'src/api/exceptions.cc',
diff --git a/src/api/embed_helpers.cc b/src/api/embed_helpers.cc
new file mode 100644
index 00000000000..998d7507fc0
--- /dev/null
+++ b/src/api/embed_helpers.cc
@@ -0,0 +1,169 @@
+#include "node.h"
+#include "env-inl.h"
+#include "debug_utils-inl.h"
+
+using v8::Context;
+using v8::Global;
+using v8::HandleScope;
+using v8::Isolate;
+using v8::Local;
+using v8::Locker;
+using v8::Maybe;
+using v8::Nothing;
+using v8::SealHandleScope;
+
+namespace node {
+
+Maybe<int> SpinEventLoop(Environment* env) {
+ CHECK_NOT_NULL(env);
+ MultiIsolatePlatform* platform = GetMultiIsolatePlatform(env);
+ CHECK_NOT_NULL(platform);
+
+ HandleScope handle_scope(env->isolate());
+ Context::Scope context_scope(env->context());
+ SealHandleScope seal(env->isolate());
+
+ if (env->is_stopping()) return Nothing<int>();
+
+ env->set_trace_sync_io(env->options()->trace_sync_io);
+ {
+ bool more;
+ env->performance_state()->Mark(
+ node::performance::NODE_PERFORMANCE_MILESTONE_LOOP_START);
+ do {
+ if (env->is_stopping()) break;
+ uv_run(env->event_loop(), UV_RUN_DEFAULT);
+ if (env->is_stopping()) break;
+
+ platform->DrainTasks(env->isolate());
+
+ more = uv_loop_alive(env->event_loop());
+ if (more && !env->is_stopping()) continue;
+
+ if (EmitProcessBeforeExit(env).IsNothing())
+ break;
+
+ // Emit `beforeExit` if the loop became alive either after emitting
+ // event, or after running some callbacks.
+ more = uv_loop_alive(env->event_loop());
+ } while (more == true && !env->is_stopping());
+ env->performance_state()->Mark(
+ node::performance::NODE_PERFORMANCE_MILESTONE_LOOP_EXIT);
+ }
+ if (env->is_stopping()) return Nothing<int>();
+
+ env->set_trace_sync_io(false);
+ env->VerifyNoStrongBaseObjects();
+ return EmitProcessExit(env);
+}
+
+struct CommonEnvironmentSetup::Impl {
+ MultiIsolatePlatform* platform = nullptr;
+ uv_loop_t loop;
+ std::shared_ptr<ArrayBufferAllocator> allocator;
+ Isolate* isolate = nullptr;
+ DeleteFnPtr<IsolateData, FreeIsolateData> isolate_data;
+ DeleteFnPtr<Environment, FreeEnvironment> env;
+ Global<Context> context;
+};
+
+CommonEnvironmentSetup::CommonEnvironmentSetup(
+ MultiIsolatePlatform* platform,
+ std::vector<std::string>* errors,
+ std::function<Environment*(const CommonEnvironmentSetup*)> make_env)
+ : impl_(new Impl()) {
+ CHECK_NOT_NULL(platform);
+ CHECK_NOT_NULL(errors);
+
+ impl_->platform = platform;
+ uv_loop_t* loop = &impl_->loop;
+ // Use `data` to tell the destructor whether the loop was initialized or not.
+ loop->data = nullptr;
+ int ret = uv_loop_init(loop);
+ if (ret != 0) {
+ errors->push_back(
+ SPrintF("Failed to initialize loop: %s", uv_err_name(ret)));
+ return;
+ }
+ loop->data = this;
+
+ impl_->allocator = ArrayBufferAllocator::Create();
+ impl_->isolate = NewIsolate(impl_->allocator, &impl_->loop, platform);
+ Isolate* isolate = impl_->isolate;
+
+ {
+ Locker locker(isolate);
+ Isolate::Scope isolate_scope(isolate);
+ impl_->isolate_data.reset(CreateIsolateData(
+ isolate, loop, platform, impl_->allocator.get()));
+
+ HandleScope handle_scope(isolate);
+ Local<Context> context = NewContext(isolate);
+ impl_->context.Reset(isolate, context);
+ if (context.IsEmpty()) {
+ errors->push_back("Failed to initialize V8 Context");
+ return;
+ }
+
+ Context::Scope context_scope(context);
+ impl_->env.reset(make_env(this));
+ }
+}
+
+CommonEnvironmentSetup::~CommonEnvironmentSetup() {
+ if (impl_->isolate != nullptr) {
+ Isolate* isolate = impl_->isolate;
+ {
+ Locker locker(isolate);
+ Isolate::Scope isolate_scope(isolate);
+
+ impl_->context.Reset();
+ impl_->env.reset();
+ impl_->isolate_data.reset();
+ }
+
+ bool platform_finished = false;
+ impl_->platform->AddIsolateFinishedCallback(isolate, [](void* data) {
+ *static_cast<bool*>(data) = true;
+ }, &platform_finished);
+ impl_->platform->UnregisterIsolate(isolate);
+ isolate->Dispose();
+
+ // Wait until the platform has cleaned up all relevant resources.
+ while (!platform_finished)
+ uv_run(&impl_->loop, UV_RUN_ONCE);
+ }
+
+ if (impl_->isolate || impl_->loop.data != nullptr)
+ CheckedUvLoopClose(&impl_->loop);
+
+ delete impl_;
+}
+
+
+uv_loop_t* CommonEnvironmentSetup::event_loop() const {
+ return &impl_->loop;
+}
+
+std::shared_ptr<ArrayBufferAllocator>
+CommonEnvironmentSetup::array_buffer_allocator() const {
+ return impl_->allocator;
+}
+
+Isolate* CommonEnvironmentSetup::isolate() const {
+ return impl_->isolate;
+}
+
+IsolateData* CommonEnvironmentSetup::isolate_data() const {
+ return impl_->isolate_data.get();
+}
+
+Environment* CommonEnvironmentSetup::env() const {
+ return impl_->env.get();
+}
+
+v8::Local<v8::Context> CommonEnvironmentSetup::context() const {
+ return impl_->context.Get(impl_->isolate);
+}
+
+} // namespace node
diff --git a/src/node.h b/src/node.h
index f09c979a4be..6f93fc89e1f 100644
--- a/src/node.h
+++ b/src/node.h
@@ -553,6 +553,73 @@ NODE_EXTERN void RunAtExit(Environment* env);
// with a Node instance.
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.
+// 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.
+// If at any point node::Stop() is called, the function will attempt to return
+// as soon as possible, returning an empty `Maybe`.
+// This function only works if `env` has an associated `MultiIsolatePlatform`.
+NODE_EXTERN v8::Maybe<int> SpinEventLoop(Environment* env);
+
+class NODE_EXTERN CommonEnvironmentSetup {
+ public:
+ ~CommonEnvironmentSetup();
+
+ // Create a new CommonEnvironmentSetup, that is, a group of objects that
+ // together form the typical setup for a single Node.js Environment instance.
+ // If any error occurs, `*errors` will be populated and the returned pointer
+ // will be empty.
+ // env_args will be passed through as arguments to CreateEnvironment(), after
+ // `isolate_data` and `context`.
+ template <typename... EnvironmentArgs>
+ static std::unique_ptr<CommonEnvironmentSetup> Create(
+ MultiIsolatePlatform* platform,
+ std::vector<std::string>* errors,
+ EnvironmentArgs&&... env_args);
+
+ struct uv_loop_s* event_loop() const;
+ std::shared_ptr<ArrayBufferAllocator> array_buffer_allocator() const;
+ v8::Isolate* isolate() const;
+ IsolateData* isolate_data() const;
+ Environment* env() const;
+ v8::Local<v8::Context> context() const;
+
+ CommonEnvironmentSetup(const CommonEnvironmentSetup&) = delete;
+ CommonEnvironmentSetup& operator=(const CommonEnvironmentSetup&) = delete;
+ CommonEnvironmentSetup(CommonEnvironmentSetup&&) = delete;
+ CommonEnvironmentSetup& operator=(CommonEnvironmentSetup&&) = delete;
+
+ private:
+ struct Impl;
+ Impl* impl_;
+ CommonEnvironmentSetup(
+ MultiIsolatePlatform*,
+ std::vector<std::string>*,
+ std::function<Environment*(const CommonEnvironmentSetup*)>);
+};
+
+// Implementation for CommonEnvironmentSetup::Create
+template <typename... EnvironmentArgs>
+std::unique_ptr<CommonEnvironmentSetup> CommonEnvironmentSetup::Create(
+ MultiIsolatePlatform* platform,
+ std::vector<std::string>* errors,
+ EnvironmentArgs&&... env_args) {
+ auto ret = std::unique_ptr<CommonEnvironmentSetup>(new CommonEnvironmentSetup(
+ platform, errors,
+ [&](const CommonEnvironmentSetup* setup) -> Environment* {
+ return CreateEnvironment(
+ setup->isolate_data(), setup->context(),
+ std::forward<EnvironmentArgs>(env_args)...);
+ }));
+ if (!errors->empty()) ret.reset();
+ return ret;
+}
+
/* Converts a unixtime to V8 Date */
NODE_DEPRECATED("Use v8::Date::New() directly",
inline v8::Local<v8::Value> NODE_UNIXTIME_V8(double time) {
diff --git a/src/node_main_instance.cc b/src/node_main_instance.cc
index 597ce4c96e0..d406bf15444 100644
--- a/src/node_main_instance.cc
+++ b/src/node_main_instance.cc
@@ -23,7 +23,6 @@ using v8::Isolate;
using v8::Local;
using v8::Locker;
using v8::Object;
-using v8::SealHandleScope;
std::unique_ptr<ExternalReferenceRegistry> NodeMainInstance::registry_ =
nullptr;
@@ -140,37 +139,7 @@ int NodeMainInstance::Run(const EnvSerializeInfo* env_info) {
if (exit_code == 0) {
LoadEnvironment(env.get());
- env->set_trace_sync_io(env->options()->trace_sync_io);
-
- {
- SealHandleScope seal(isolate_);
- bool more;
- env->performance_state()->Mark(
- node::performance::NODE_PERFORMANCE_MILESTONE_LOOP_START);
- do {
- uv_run(env->event_loop(), UV_RUN_DEFAULT);
-
- per_process::v8_platform.DrainVMTasks(isolate_);
-
- more = uv_loop_alive(env->event_loop());
- if (more && !env->is_stopping()) continue;
-
- if (!uv_loop_alive(env->event_loop())) {
- if (EmitProcessBeforeExit(env.get()).IsNothing())
- break;
- }
-
- // Emit `beforeExit` if the loop became alive either after emitting
- // event, or after running some callbacks.
- more = uv_loop_alive(env->event_loop());
- } while (more == true && !env->is_stopping());
- env->performance_state()->Mark(
- node::performance::NODE_PERFORMANCE_MILESTONE_LOOP_EXIT);
- }
-
- env->set_trace_sync_io(false);
- if (!env->is_stopping()) env->VerifyNoStrongBaseObjects();
- exit_code = EmitProcessExit(env.get()).FromMaybe(1);
+ exit_code = SpinEventLoop(env.get()).FromMaybe(1);
}
ResetStdio();
diff --git a/src/node_worker.cc b/src/node_worker.cc
index cdaeefb7897..2006380cd41 100644
--- a/src/node_worker.cc
+++ b/src/node_worker.cc
@@ -27,6 +27,7 @@ using v8::Integer;
using v8::Isolate;
using v8::Local;
using v8::Locker;
+using v8::Maybe;
using v8::MaybeLocal;
using v8::Null;
using v8::Number;
@@ -332,45 +333,14 @@ void Worker::Run() {
Debug(this, "Loaded environment for worker %llu", thread_id_.id);
}
-
- if (is_stopped()) return;
- {
- SealHandleScope seal(isolate_);
- bool more;
- env_->performance_state()->Mark(
- node::performance::NODE_PERFORMANCE_MILESTONE_LOOP_START);
- do {
- if (is_stopped()) break;
- uv_run(&data.loop_, UV_RUN_DEFAULT);
- if (is_stopped()) break;
-
- platform_->DrainTasks(isolate_);
-
- more = uv_loop_alive(&data.loop_);
- if (more && !is_stopped()) continue;
-
- if (EmitProcessBeforeExit(env_.get()).IsNothing())
- break;
-
- // Emit `beforeExit` if the loop became alive either after emitting
- // event, or after running some callbacks.
- more = uv_loop_alive(&data.loop_);
- } while (more == true && !is_stopped());
- env_->performance_state()->Mark(
- node::performance::NODE_PERFORMANCE_MILESTONE_LOOP_EXIT);
- }
}
{
- int exit_code;
- bool stopped = is_stopped();
- if (!stopped) {
- env_->VerifyNoStrongBaseObjects();
- exit_code = EmitProcessExit(env_.get()).FromMaybe(1);
- }
+ Maybe<int> exit_code = SpinEventLoop(env_.get());
Mutex::ScopedLock lock(mutex_);
- if (exit_code_ == 0 && !stopped)
- exit_code_ = exit_code;
+ if (exit_code_ == 0 && exit_code.IsJust()) {
+ exit_code_ = exit_code.FromJust();
+ }
Debug(this, "Exiting thread for worker %llu with exit code %d",
thread_id_.id, exit_code_);
diff --git a/test/embedding/embedtest.cc b/test/embedding/embedtest.cc
index fece8924ad6..b0cf9d5f973 100644
--- a/test/embedding/embedtest.cc
+++ b/test/embedding/embedtest.cc
@@ -5,17 +5,14 @@
// 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.
-using node::ArrayBufferAllocator;
+using node::CommonEnvironmentSetup;
using node::Environment;
-using node::IsolateData;
using node::MultiIsolatePlatform;
using v8::Context;
using v8::HandleScope;
using v8::Isolate;
-using v8::Local;
using v8::Locker;
using v8::MaybeLocal;
-using v8::SealHandleScope;
using v8::V8;
using v8::Value;
@@ -51,46 +48,27 @@ int RunNodeInstance(MultiIsolatePlatform* platform,
const std::vector<std::string>& args,
const std::vector<std::string>& exec_args) {
int exit_code = 0;
- uv_loop_t loop;
- int ret = uv_loop_init(&loop);
- if (ret != 0) {
- fprintf(stderr, "%s: Failed to initialize loop: %s\n",
- args[0].c_str(),
- uv_err_name(ret));
- return 1;
- }
- std::shared_ptr<ArrayBufferAllocator> allocator =
- ArrayBufferAllocator::Create();
-
- Isolate* isolate = NewIsolate(allocator, &loop, platform);
- if (isolate == nullptr) {
- fprintf(stderr, "%s: Failed to initialize V8 Isolate\n", args[0].c_str());
+ std::vector<std::string> errors;
+ std::unique_ptr<CommonEnvironmentSetup> setup =
+ CommonEnvironmentSetup::Create(platform, &errors, args, exec_args);
+ if (!setup) {
+ for (const std::string& err : errors)
+ fprintf(stderr, "%s: %s\n", args[0].c_str(), err.c_str());
return 1;
}
+ Isolate* isolate = setup->isolate();
+ Environment* env = setup->env();
+
{
Locker locker(isolate);
Isolate::Scope isolate_scope(isolate);
-
- std::unique_ptr<IsolateData, decltype(&node::FreeIsolateData)> isolate_data(
- node::CreateIsolateData(isolate, &loop, platform, allocator.get()),
- node::FreeIsolateData);
-
HandleScope handle_scope(isolate);
- Local<Context> context = node::NewContext(isolate);
- if (context.IsEmpty()) {
- fprintf(stderr, "%s: Failed to initialize V8 Context\n", args[0].c_str());
- return 1;
- }
-
- Context::Scope context_scope(context);
- std::unique_ptr<Environment, decltype(&node::FreeEnvironment)> env(
- node::CreateEnvironment(isolate_data.get(), context, args, exec_args),
- node::FreeEnvironment);
+ Context::Scope context_scope(setup->context());
MaybeLocal<Value> loadenv_ret = node::LoadEnvironment(
- env.get(),
+ env,
"const publicRequire ="
" require('module').createRequire(process.cwd() + '/');"
"globalThis.require = publicRequire;"
@@ -100,40 +78,10 @@ int RunNodeInstance(MultiIsolatePlatform* platform,
if (loadenv_ret.IsEmpty()) // There has been a JS exception.
return 1;
- {
- SealHandleScope seal(isolate);
- bool more;
- do {
- uv_run(&loop, UV_RUN_DEFAULT);
-
- platform->DrainTasks(isolate);
- more = uv_loop_alive(&loop);
- if (more) continue;
-
- if (node::EmitProcessBeforeExit(env.get()).IsNothing())
- break;
+ exit_code = node::SpinEventLoop(env).FromMaybe(1);
- more = uv_loop_alive(&loop);
- } while (more == true);
- }
-
- exit_code = node::EmitProcessExit(env.get()).FromMaybe(1);
-
- node::Stop(env.get());
+ node::Stop(env);
}
- bool platform_finished = false;
- platform->AddIsolateFinishedCallback(isolate, [](void* data) {
- *static_cast<bool*>(data) = true;
- }, &platform_finished);
- platform->UnregisterIsolate(isolate);
- isolate->Dispose();
-
- // Wait until the platform has cleaned up all relevant resources.
- while (!platform_finished)
- uv_run(&loop, UV_RUN_ONCE);
- int err = uv_loop_close(&loop);
- assert(err == 0);
-
return exit_code;
}