Skip to content

Commit 25e069f

Browse files
addaleaxjoesepi
authored andcommitted
src: add embedding helpers to reduce boilerplate code
Provide helpers for a) spinning the event loop and b) setting up and tearing down the objects involved in a single Node.js instance, as they would typically be used. The former helper is also usable inside Node.js itself, for both Worker and main threads. PR-URL: nodejs#35597 Reviewed-By: Colin Ihrig <cjihrig@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Denys Otrishko <shishugi@gmail.com>
1 parent 8d03adb commit 25e069f

File tree

7 files changed

+278
-217
lines changed

7 files changed

+278
-217
lines changed

doc/api/embedding.md

+21-84
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,13 @@ int main(int argc, char** argv) {
6767
```
6868
6969
### Per-instance state
70+
<!-- YAML
71+
changes:
72+
- version: REPLACEME
73+
pr-url: https://github.com/nodejs/node/pull/35597
74+
description:
75+
The `CommonEnvironmentSetup` and `SpinEventLoop` utilities were added.
76+
-->
7077
7178
Node.js has a concept of a “Node.js instance”, that is commonly being referred
7279
to as `node::Environment`. Each `node::Environment` is associated with:
@@ -99,52 +106,26 @@ int RunNodeInstance(MultiIsolatePlatform* platform,
99106
const std::vector<std::string>& args,
100107
const std::vector<std::string>& exec_args) {
101108
int exit_code = 0;
102-
// Set up a libuv event loop.
103-
uv_loop_t loop;
104-
int ret = uv_loop_init(&loop);
105-
if (ret != 0) {
106-
fprintf(stderr, "%s: Failed to initialize loop: %s\n",
107-
args[0].c_str(),
108-
uv_err_name(ret));
109-
return 1;
110-
}
111109
112-
std::shared_ptr<ArrayBufferAllocator> allocator =
113-
ArrayBufferAllocator::Create();
114-
115-
Isolate* isolate = NewIsolate(allocator, &loop, platform);
116-
if (isolate == nullptr) {
117-
fprintf(stderr, "%s: Failed to initialize V8 Isolate\n", args[0].c_str());
110+
// Setup up a libuv event loop, v8::Isolate, and Node.js Environment.
111+
std::vector<std::string> errors;
112+
std::unique_ptr<CommonEnvironmentSetup> setup =
113+
CommonEnvironmentSetup::Create(platform, &errors, args, exec_args);
114+
if (!setup) {
115+
for (const std::string& err : errors)
116+
fprintf(stderr, "%s: %s\n", args[0].c_str(), err.c_str());
118117
return 1;
119118
}
120119
120+
Isolate* isolate = setup->isolate();
121+
Environment* env = setup->env();
122+
121123
{
122124
Locker locker(isolate);
123125
Isolate::Scope isolate_scope(isolate);
124-
125-
// Create a node::IsolateData instance that will later be released using
126-
// node::FreeIsolateData().
127-
std::unique_ptr<IsolateData, decltype(&node::FreeIsolateData)> isolate_data(
128-
node::CreateIsolateData(isolate, &loop, platform, allocator.get()),
129-
node::FreeIsolateData);
130-
131-
// Set up a new v8::Context.
132-
HandleScope handle_scope(isolate);
133-
Local<Context> context = node::NewContext(isolate);
134-
if (context.IsEmpty()) {
135-
fprintf(stderr, "%s: Failed to initialize V8 Context\n", args[0].c_str());
136-
return 1;
137-
}
138-
139126
// The v8::Context needs to be entered when node::CreateEnvironment() and
140127
// node::LoadEnvironment() are being called.
141-
Context::Scope context_scope(context);
142-
143-
// Create a node::Environment instance that will later be released using
144-
// node::FreeEnvironment().
145-
std::unique_ptr<Environment, decltype(&node::FreeEnvironment)> env(
146-
node::CreateEnvironment(isolate_data.get(), context, args, exec_args),
147-
node::FreeEnvironment);
128+
Context::Scope context_scope(setup->context());
148129
149130
// Set up the Node.js instance for execution, and run code inside of it.
150131
// There is also a variant that takes a callback and provides it with
@@ -156,7 +137,7 @@ int RunNodeInstance(MultiIsolatePlatform* platform,
156137
// load files from the disk, and uses the standard CommonJS file loader
157138
// instead of the internal-only `require` function.
158139
MaybeLocal<Value> loadenv_ret = node::LoadEnvironment(
159-
env.get(),
140+
env,
160141
"const publicRequire ="
161142
" require('module').createRequire(process.cwd() + '/');"
162143
"globalThis.require = publicRequire;"
@@ -165,58 +146,14 @@ int RunNodeInstance(MultiIsolatePlatform* platform,
165146
if (loadenv_ret.IsEmpty()) // There has been a JS exception.
166147
return 1;
167148
168-
{
169-
// SealHandleScope protects against handle leaks from callbacks.
170-
SealHandleScope seal(isolate);
171-
bool more;
172-
do {
173-
uv_run(&loop, UV_RUN_DEFAULT);
174-
175-
// V8 tasks on background threads may end up scheduling new tasks in the
176-
// foreground, which in turn can keep the event loop going. For example,
177-
// WebAssembly.compile() may do so.
178-
platform->DrainTasks(isolate);
179-
180-
// If there are new tasks, continue.
181-
more = uv_loop_alive(&loop);
182-
if (more) continue;
183-
184-
// node::EmitProcessBeforeExit() is used to emit the 'beforeExit' event
185-
// on the `process` object.
186-
if (node::EmitProcessBeforeExit(env.get()).IsNothing())
187-
break;
188-
189-
// 'beforeExit' can also schedule new work that keeps the event loop
190-
// running.
191-
more = uv_loop_alive(&loop);
192-
} while (more == true);
193-
}
194-
195-
// node::EmitProcessExit() returns the current exit code.
196-
exit_code = node::EmitProcessExit(env.get()).FromMaybe(1);
149+
exit_code = node::SpinEventLoop(env).FromMaybe(1);
197150
198151
// node::Stop() can be used to explicitly stop the event loop and keep
199152
// further JavaScript from running. It can be called from any thread,
200153
// and will act like worker.terminate() if called from another thread.
201-
node::Stop(env.get());
154+
node::Stop(env);
202155
}
203156
204-
// Unregister the Isolate with the platform and add a listener that is called
205-
// when the Platform is done cleaning up any state it had associated with
206-
// the Isolate.
207-
bool platform_finished = false;
208-
platform->AddIsolateFinishedCallback(isolate, [](void* data) {
209-
*static_cast<bool*>(data) = true;
210-
}, &platform_finished);
211-
platform->UnregisterIsolate(isolate);
212-
isolate->Dispose();
213-
214-
// Wait until the platform has cleaned up all relevant resources.
215-
while (!platform_finished)
216-
uv_run(&loop, UV_RUN_ONCE);
217-
int err = uv_loop_close(&loop);
218-
assert(err == 0);
219-
220157
return exit_code;
221158
}
222159
```

node.gyp

+1
Original file line numberDiff line numberDiff line change
@@ -576,6 +576,7 @@
576576
'sources': [
577577
'src/api/async_resource.cc',
578578
'src/api/callback.cc',
579+
'src/api/embed_helpers.cc',
579580
'src/api/encoding.cc',
580581
'src/api/environment.cc',
581582
'src/api/exceptions.cc',

src/api/embed_helpers.cc

+169
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
#include "node.h"
2+
#include "env-inl.h"
3+
#include "debug_utils-inl.h"
4+
5+
using v8::Context;
6+
using v8::Global;
7+
using v8::HandleScope;
8+
using v8::Isolate;
9+
using v8::Local;
10+
using v8::Locker;
11+
using v8::Maybe;
12+
using v8::Nothing;
13+
using v8::SealHandleScope;
14+
15+
namespace node {
16+
17+
Maybe<int> SpinEventLoop(Environment* env) {
18+
CHECK_NOT_NULL(env);
19+
MultiIsolatePlatform* platform = GetMultiIsolatePlatform(env);
20+
CHECK_NOT_NULL(platform);
21+
22+
HandleScope handle_scope(env->isolate());
23+
Context::Scope context_scope(env->context());
24+
SealHandleScope seal(env->isolate());
25+
26+
if (env->is_stopping()) return Nothing<int>();
27+
28+
env->set_trace_sync_io(env->options()->trace_sync_io);
29+
{
30+
bool more;
31+
env->performance_state()->Mark(
32+
node::performance::NODE_PERFORMANCE_MILESTONE_LOOP_START);
33+
do {
34+
if (env->is_stopping()) break;
35+
uv_run(env->event_loop(), UV_RUN_DEFAULT);
36+
if (env->is_stopping()) break;
37+
38+
platform->DrainTasks(env->isolate());
39+
40+
more = uv_loop_alive(env->event_loop());
41+
if (more && !env->is_stopping()) continue;
42+
43+
if (EmitProcessBeforeExit(env).IsNothing())
44+
break;
45+
46+
// Emit `beforeExit` if the loop became alive either after emitting
47+
// event, or after running some callbacks.
48+
more = uv_loop_alive(env->event_loop());
49+
} while (more == true && !env->is_stopping());
50+
env->performance_state()->Mark(
51+
node::performance::NODE_PERFORMANCE_MILESTONE_LOOP_EXIT);
52+
}
53+
if (env->is_stopping()) return Nothing<int>();
54+
55+
env->set_trace_sync_io(false);
56+
env->VerifyNoStrongBaseObjects();
57+
return EmitProcessExit(env);
58+
}
59+
60+
struct CommonEnvironmentSetup::Impl {
61+
MultiIsolatePlatform* platform = nullptr;
62+
uv_loop_t loop;
63+
std::shared_ptr<ArrayBufferAllocator> allocator;
64+
Isolate* isolate = nullptr;
65+
DeleteFnPtr<IsolateData, FreeIsolateData> isolate_data;
66+
DeleteFnPtr<Environment, FreeEnvironment> env;
67+
Global<Context> context;
68+
};
69+
70+
CommonEnvironmentSetup::CommonEnvironmentSetup(
71+
MultiIsolatePlatform* platform,
72+
std::vector<std::string>* errors,
73+
std::function<Environment*(const CommonEnvironmentSetup*)> make_env)
74+
: impl_(new Impl()) {
75+
CHECK_NOT_NULL(platform);
76+
CHECK_NOT_NULL(errors);
77+
78+
impl_->platform = platform;
79+
uv_loop_t* loop = &impl_->loop;
80+
// Use `data` to tell the destructor whether the loop was initialized or not.
81+
loop->data = nullptr;
82+
int ret = uv_loop_init(loop);
83+
if (ret != 0) {
84+
errors->push_back(
85+
SPrintF("Failed to initialize loop: %s", uv_err_name(ret)));
86+
return;
87+
}
88+
loop->data = this;
89+
90+
impl_->allocator = ArrayBufferAllocator::Create();
91+
impl_->isolate = NewIsolate(impl_->allocator, &impl_->loop, platform);
92+
Isolate* isolate = impl_->isolate;
93+
94+
{
95+
Locker locker(isolate);
96+
Isolate::Scope isolate_scope(isolate);
97+
impl_->isolate_data.reset(CreateIsolateData(
98+
isolate, loop, platform, impl_->allocator.get()));
99+
100+
HandleScope handle_scope(isolate);
101+
Local<Context> context = NewContext(isolate);
102+
impl_->context.Reset(isolate, context);
103+
if (context.IsEmpty()) {
104+
errors->push_back("Failed to initialize V8 Context");
105+
return;
106+
}
107+
108+
Context::Scope context_scope(context);
109+
impl_->env.reset(make_env(this));
110+
}
111+
}
112+
113+
CommonEnvironmentSetup::~CommonEnvironmentSetup() {
114+
if (impl_->isolate != nullptr) {
115+
Isolate* isolate = impl_->isolate;
116+
{
117+
Locker locker(isolate);
118+
Isolate::Scope isolate_scope(isolate);
119+
120+
impl_->context.Reset();
121+
impl_->env.reset();
122+
impl_->isolate_data.reset();
123+
}
124+
125+
bool platform_finished = false;
126+
impl_->platform->AddIsolateFinishedCallback(isolate, [](void* data) {
127+
*static_cast<bool*>(data) = true;
128+
}, &platform_finished);
129+
impl_->platform->UnregisterIsolate(isolate);
130+
isolate->Dispose();
131+
132+
// Wait until the platform has cleaned up all relevant resources.
133+
while (!platform_finished)
134+
uv_run(&impl_->loop, UV_RUN_ONCE);
135+
}
136+
137+
if (impl_->isolate || impl_->loop.data != nullptr)
138+
CheckedUvLoopClose(&impl_->loop);
139+
140+
delete impl_;
141+
}
142+
143+
144+
uv_loop_t* CommonEnvironmentSetup::event_loop() const {
145+
return &impl_->loop;
146+
}
147+
148+
std::shared_ptr<ArrayBufferAllocator>
149+
CommonEnvironmentSetup::array_buffer_allocator() const {
150+
return impl_->allocator;
151+
}
152+
153+
Isolate* CommonEnvironmentSetup::isolate() const {
154+
return impl_->isolate;
155+
}
156+
157+
IsolateData* CommonEnvironmentSetup::isolate_data() const {
158+
return impl_->isolate_data.get();
159+
}
160+
161+
Environment* CommonEnvironmentSetup::env() const {
162+
return impl_->env.get();
163+
}
164+
165+
v8::Local<v8::Context> CommonEnvironmentSetup::context() const {
166+
return impl_->context.Get(impl_->isolate);
167+
}
168+
169+
} // namespace node

src/node.h

+67
Original file line numberDiff line numberDiff line change
@@ -553,6 +553,73 @@ NODE_EXTERN void RunAtExit(Environment* env);
553553
// with a Node instance.
554554
NODE_EXTERN struct uv_loop_s* GetCurrentEventLoop(v8::Isolate* isolate);
555555

556+
// Runs the main loop for a given Environment. This roughly performs the
557+
// following steps:
558+
// 1. Call uv_run() on the event loop until it is drained.
559+
// 2. Call platform->DrainTasks() on the associated platform/isolate.
560+
// 3. If the event loop is alive again, go to Step 1.
561+
// 4. Call EmitProcessBeforeExit().
562+
// 5. If the event loop is alive again, go to Step 1.
563+
// 6. Call EmitProcessExit() and forward the return value.
564+
// If at any point node::Stop() is called, the function will attempt to return
565+
// as soon as possible, returning an empty `Maybe`.
566+
// This function only works if `env` has an associated `MultiIsolatePlatform`.
567+
NODE_EXTERN v8::Maybe<int> SpinEventLoop(Environment* env);
568+
569+
class NODE_EXTERN CommonEnvironmentSetup {
570+
public:
571+
~CommonEnvironmentSetup();
572+
573+
// Create a new CommonEnvironmentSetup, that is, a group of objects that
574+
// together form the typical setup for a single Node.js Environment instance.
575+
// If any error occurs, `*errors` will be populated and the returned pointer
576+
// will be empty.
577+
// env_args will be passed through as arguments to CreateEnvironment(), after
578+
// `isolate_data` and `context`.
579+
template <typename... EnvironmentArgs>
580+
static std::unique_ptr<CommonEnvironmentSetup> Create(
581+
MultiIsolatePlatform* platform,
582+
std::vector<std::string>* errors,
583+
EnvironmentArgs&&... env_args);
584+
585+
struct uv_loop_s* event_loop() const;
586+
std::shared_ptr<ArrayBufferAllocator> array_buffer_allocator() const;
587+
v8::Isolate* isolate() const;
588+
IsolateData* isolate_data() const;
589+
Environment* env() const;
590+
v8::Local<v8::Context> context() const;
591+
592+
CommonEnvironmentSetup(const CommonEnvironmentSetup&) = delete;
593+
CommonEnvironmentSetup& operator=(const CommonEnvironmentSetup&) = delete;
594+
CommonEnvironmentSetup(CommonEnvironmentSetup&&) = delete;
595+
CommonEnvironmentSetup& operator=(CommonEnvironmentSetup&&) = delete;
596+
597+
private:
598+
struct Impl;
599+
Impl* impl_;
600+
CommonEnvironmentSetup(
601+
MultiIsolatePlatform*,
602+
std::vector<std::string>*,
603+
std::function<Environment*(const CommonEnvironmentSetup*)>);
604+
};
605+
606+
// Implementation for CommonEnvironmentSetup::Create
607+
template <typename... EnvironmentArgs>
608+
std::unique_ptr<CommonEnvironmentSetup> CommonEnvironmentSetup::Create(
609+
MultiIsolatePlatform* platform,
610+
std::vector<std::string>* errors,
611+
EnvironmentArgs&&... env_args) {
612+
auto ret = std::unique_ptr<CommonEnvironmentSetup>(new CommonEnvironmentSetup(
613+
platform, errors,
614+
[&](const CommonEnvironmentSetup* setup) -> Environment* {
615+
return CreateEnvironment(
616+
setup->isolate_data(), setup->context(),
617+
std::forward<EnvironmentArgs>(env_args)...);
618+
}));
619+
if (!errors->empty()) ret.reset();
620+
return ret;
621+
}
622+
556623
/* Converts a unixtime to V8 Date */
557624
NODE_DEPRECATED("Use v8::Date::New() directly",
558625
inline v8::Local<v8::Value> NODE_UNIXTIME_V8(double time) {

0 commit comments

Comments
 (0)