Skip to content

Commit f7d658a

Browse files
authoredMay 5, 2022
bootstrap: use a context snapshotted with primordials in workers
PR-URL: nodejs#42867 Reviewed-By: Chengzhong Wu <[email protected]> Reviewed-By: Antoine du Hamel <[email protected]>
1 parent ffa1f84 commit f7d658a

7 files changed

+111
-73
lines changed
 

‎src/node_main_instance.cc

+1-1
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ NodeMainInstance::CreateMainEnvironment(int* exit_code) {
183183
EnvironmentFlags::kDefaultFlags,
184184
{}));
185185
context = Context::FromSnapshot(isolate_,
186-
kNodeContextIndex,
186+
SnapshotBuilder::kNodeMainContextIndex,
187187
{DeserializeNodeInternalFields, env.get()})
188188
.ToLocalChecked();
189189

‎src/node_main_instance.h

-1
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,6 @@ class NodeMainInstance {
6565
DeleteFnPtr<Environment, FreeEnvironment> CreateMainEnvironment(
6666
int* exit_code);
6767

68-
static const size_t kNodeContextIndex = 0;
6968
NodeMainInstance(const NodeMainInstance&) = delete;
7069
NodeMainInstance& operator=(const NodeMainInstance&) = delete;
7170
NodeMainInstance(NodeMainInstance&&) = delete;

‎src/node_snapshot_builder.h

+3
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ class SnapshotBuilder {
2929
static void InitializeIsolateParams(const SnapshotData* data,
3030
v8::Isolate::CreateParams* params);
3131

32+
static const size_t kNodeBaseContextIndex = 0;
33+
static const size_t kNodeMainContextIndex = kNodeBaseContextIndex + 1;
34+
3235
private:
3336
// Used to synchronize access to the snapshot data
3437
static Mutex snapshot_data_mutex_;

‎src/node_snapshotable.cc

+74-57
Original file line numberDiff line numberDiff line change
@@ -129,84 +129,101 @@ void SnapshotBuilder::Generate(SnapshotData* out,
129129
per_process::v8_platform.Platform(),
130130
args,
131131
exec_args);
132+
out->isolate_data_indices =
133+
main_instance->isolate_data()->Serialize(&creator);
132134

133135
HandleScope scope(isolate);
136+
137+
// The default context with only things created by V8.
134138
creator.SetDefaultContext(Context::New(isolate));
135-
out->isolate_data_indices =
136-
main_instance->isolate_data()->Serialize(&creator);
137139

138-
// Run the per-context scripts
139-
Local<Context> context;
140-
{
140+
auto CreateBaseContext = [&]() {
141141
TryCatch bootstrapCatch(isolate);
142-
context = NewContext(isolate);
142+
// Run the per-context scripts.
143+
Local<Context> base_context = NewContext(isolate);
143144
if (bootstrapCatch.HasCaught()) {
144-
PrintCaughtException(isolate, context, bootstrapCatch);
145+
PrintCaughtException(isolate, base_context, bootstrapCatch);
145146
abort();
146147
}
148+
return base_context;
149+
};
150+
151+
// The Node.js-specific context with primodials, can be used by workers
152+
// TODO(joyeecheung): investigate if this can be used by vm contexts
153+
// without breaking compatibility.
154+
{
155+
size_t index = creator.AddContext(CreateBaseContext());
156+
CHECK_EQ(index, SnapshotBuilder::kNodeBaseContextIndex);
147157
}
148-
Context::Scope context_scope(context);
149-
150-
// Create the environment
151-
env = new Environment(main_instance->isolate_data(),
152-
context,
153-
args,
154-
exec_args,
155-
nullptr,
156-
node::EnvironmentFlags::kDefaultFlags,
157-
{});
158-
159-
// Run scripts in lib/internal/bootstrap/
158+
159+
// The main instance context.
160160
{
161+
Local<Context> main_context = CreateBaseContext();
162+
Context::Scope context_scope(main_context);
161163
TryCatch bootstrapCatch(isolate);
164+
165+
// Create the environment.
166+
env = new Environment(main_instance->isolate_data(),
167+
main_context,
168+
args,
169+
exec_args,
170+
nullptr,
171+
node::EnvironmentFlags::kDefaultFlags,
172+
{});
173+
174+
// Run scripts in lib/internal/bootstrap/
162175
MaybeLocal<Value> result = env->RunBootstrapping();
163176
if (bootstrapCatch.HasCaught()) {
164-
PrintCaughtException(isolate, context, bootstrapCatch);
177+
// TODO(joyeecheung): fail by exiting with a non-zero exit code.
178+
PrintCaughtException(isolate, main_context, bootstrapCatch);
179+
abort();
165180
}
166181
result.ToLocalChecked();
167-
}
168-
169-
// If --build-snapshot is true, lib/internal/main/mksnapshot.js would be
170-
// loaded via LoadEnvironment() to execute process.argv[1] as the entry
171-
// point (we currently only support this kind of entry point, but we
172-
// could also explore snapshotting other kinds of execution modes
173-
// in the future).
174-
if (per_process::cli_options->build_snapshot) {
182+
// If --build-snapshot is true, lib/internal/main/mksnapshot.js would be
183+
// loaded via LoadEnvironment() to execute process.argv[1] as the entry
184+
// point (we currently only support this kind of entry point, but we
185+
// could also explore snapshotting other kinds of execution modes
186+
// in the future).
187+
if (per_process::cli_options->build_snapshot) {
175188
#if HAVE_INSPECTOR
176-
env->InitializeInspector({});
189+
env->InitializeInspector({});
177190
#endif
178-
TryCatch bootstrapCatch(isolate);
179-
// TODO(joyeecheung): we could use the result for something special,
180-
// like setting up initializers that should be invoked at snapshot
181-
// dehydration.
182-
MaybeLocal<Value> result =
183-
LoadEnvironment(env, StartExecutionCallback{});
184-
if (bootstrapCatch.HasCaught()) {
185-
PrintCaughtException(isolate, context, bootstrapCatch);
191+
// TODO(joyeecheung): we could use the result for something special,
192+
// like setting up initializers that should be invoked at snapshot
193+
// dehydration.
194+
MaybeLocal<Value> result =
195+
LoadEnvironment(env, StartExecutionCallback{});
196+
if (bootstrapCatch.HasCaught()) {
197+
// TODO(joyeecheung): fail by exiting with a non-zero exit code.
198+
PrintCaughtException(isolate, main_context, bootstrapCatch);
199+
abort();
200+
}
201+
result.ToLocalChecked();
202+
// FIXME(joyeecheung): right now running the loop in the snapshot
203+
// builder seems to introduces inconsistencies in JS land that need to
204+
// be synchronized again after snapshot restoration.
205+
int exit_code = SpinEventLoop(env).FromMaybe(1);
206+
CHECK_EQ(exit_code, 0);
207+
if (bootstrapCatch.HasCaught()) {
208+
// TODO(joyeecheung): fail by exiting with a non-zero exit code.
209+
PrintCaughtException(isolate, main_context, bootstrapCatch);
210+
abort();
211+
}
186212
}
187-
result.ToLocalChecked();
188-
// FIXME(joyeecheung): right now running the loop in the snapshot
189-
// builder seems to introduces inconsistencies in JS land that need to
190-
// be synchronized again after snapshot restoration.
191-
int exit_code = SpinEventLoop(env).FromMaybe(1);
192-
CHECK_EQ(exit_code, 0);
193-
if (bootstrapCatch.HasCaught()) {
194-
PrintCaughtException(isolate, context, bootstrapCatch);
195-
abort();
213+
214+
if (per_process::enabled_debug_list.enabled(
215+
DebugCategory::MKSNAPSHOT)) {
216+
env->PrintAllBaseObjects();
217+
printf("Environment = %p\n", env);
196218
}
197-
}
198219

199-
if (per_process::enabled_debug_list.enabled(DebugCategory::MKSNAPSHOT)) {
200-
env->PrintAllBaseObjects();
201-
printf("Environment = %p\n", env);
220+
// Serialize the native states
221+
out->env_info = env->Serialize(&creator);
222+
// Serialize the context
223+
size_t index = creator.AddContext(
224+
main_context, {SerializeNodeContextInternalFields, env});
225+
CHECK_EQ(index, SnapshotBuilder::kNodeMainContextIndex);
202226
}
203-
204-
// Serialize the native states
205-
out->env_info = env->Serialize(&creator);
206-
// Serialize the context
207-
size_t index = creator.AddContext(
208-
context, {SerializeNodeContextInternalFields, env});
209-
CHECK_EQ(index, NodeMainInstance::kNodeContextIndex);
210227
}
211228

212229
// Must be out of HandleScope

‎src/node_worker.cc

+24-11
Original file line numberDiff line numberDiff line change
@@ -50,13 +50,15 @@ Worker::Worker(Environment* env,
5050
const std::string& url,
5151
std::shared_ptr<PerIsolateOptions> per_isolate_opts,
5252
std::vector<std::string>&& exec_argv,
53-
std::shared_ptr<KVStore> env_vars)
53+
std::shared_ptr<KVStore> env_vars,
54+
const SnapshotData* snapshot_data)
5455
: AsyncWrap(env, wrap, AsyncWrap::PROVIDER_WORKER),
5556
per_isolate_opts_(per_isolate_opts),
5657
exec_argv_(exec_argv),
5758
platform_(env->isolate_data()->platform()),
5859
thread_id_(AllocateEnvironmentThreadId()),
59-
env_vars_(env_vars) {
60+
env_vars_(env_vars),
61+
snapshot_data_(snapshot_data) {
6062
Debug(this, "Creating new worker instance with thread id %llu",
6163
thread_id_.id);
6264

@@ -147,12 +149,8 @@ class WorkerThreadData {
147149
SetIsolateCreateParamsForNode(&params);
148150
params.array_buffer_allocator_shared = allocator;
149151

150-
bool use_node_snapshot = per_process::cli_options->node_snapshot;
151-
const SnapshotData* snapshot_data =
152-
use_node_snapshot ? SnapshotBuilder::GetEmbeddedSnapshotData()
153-
: nullptr;
154-
if (snapshot_data != nullptr) {
155-
SnapshotBuilder::InitializeIsolateParams(snapshot_data, &params);
152+
if (w->snapshot_data() != nullptr) {
153+
SnapshotBuilder::InitializeIsolateParams(w->snapshot_data(), &params);
156154
}
157155
w->UpdateResourceConstraints(&params.constraints);
158156

@@ -239,7 +237,7 @@ class WorkerThreadData {
239237
uv_loop_t loop_;
240238
bool loop_init_failed_ = true;
241239
DeleteFnPtr<IsolateData, FreeIsolateData> isolate_data_;
242-
240+
const SnapshotData* snapshot_data_ = nullptr;
243241
friend class Worker;
244242
};
245243

@@ -302,7 +300,17 @@ void Worker::Run() {
302300
// resource constraints, we need something in place to handle it,
303301
// though.
304302
TryCatch try_catch(isolate_);
305-
context = NewContext(isolate_);
303+
if (snapshot_data_ != nullptr) {
304+
context = Context::FromSnapshot(
305+
isolate_, SnapshotBuilder::kNodeBaseContextIndex)
306+
.ToLocalChecked();
307+
if (!context.IsEmpty() &&
308+
!InitializeContextRuntime(context).IsJust()) {
309+
context = Local<Context>();
310+
}
311+
} else {
312+
context = NewContext(isolate_);
313+
}
306314
if (context.IsEmpty()) {
307315
Exit(1, "ERR_WORKER_INIT_FAILED", "Failed to create new Context");
308316
return;
@@ -560,12 +568,17 @@ void Worker::New(const FunctionCallbackInfo<Value>& args) {
560568
exec_argv_out = env->exec_argv();
561569
}
562570

571+
bool use_node_snapshot = per_process::cli_options->node_snapshot;
572+
const SnapshotData* snapshot_data =
573+
use_node_snapshot ? SnapshotBuilder::GetEmbeddedSnapshotData() : nullptr;
574+
563575
Worker* worker = new Worker(env,
564576
args.This(),
565577
url,
566578
per_isolate_opts,
567579
std::move(exec_argv_out),
568-
env_vars);
580+
env_vars,
581+
snapshot_data);
569582

570583
CHECK(args[3]->IsFloat64Array());
571584
Local<Float64Array> limit_info = args[3].As<Float64Array>();

‎src/node_worker.h

+6-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
#include "uv.h"
1010

1111
namespace node {
12+
13+
struct SnapshotData;
1214
namespace worker {
1315

1416
class WorkerThreadData;
@@ -29,7 +31,8 @@ class Worker : public AsyncWrap {
2931
const std::string& url,
3032
std::shared_ptr<PerIsolateOptions> per_isolate_opts,
3133
std::vector<std::string>&& exec_argv,
32-
std::shared_ptr<KVStore> env_vars);
34+
std::shared_ptr<KVStore> env_vars,
35+
const SnapshotData* snapshot_data);
3336
~Worker() override;
3437

3538
// Run the worker. This is only called from the worker thread.
@@ -54,6 +57,7 @@ class Worker : public AsyncWrap {
5457
bool IsNotIndicativeOfMemoryLeakAtExit() const override;
5558

5659
bool is_stopped() const;
60+
const SnapshotData* snapshot_data() const { return snapshot_data_; }
5761

5862
static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
5963
static void CloneParentEnvVars(
@@ -126,6 +130,7 @@ class Worker : public AsyncWrap {
126130
// destroyed alongwith the worker thread.
127131
Environment* env_ = nullptr;
128132

133+
const SnapshotData* snapshot_data_ = nullptr;
129134
friend class WorkerThreadData;
130135
};
131136

‎test/parallel/test-worker-stack-overflow-stack-size.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ async function runWorker(options = {}) {
2323
});
2424

2525
const [ error ] = await once(worker, 'error');
26-
2726
if (!options.skipErrorCheck) {
2827
common.expectsError({
2928
constructor: RangeError,
@@ -56,7 +55,9 @@ async function runWorker(options = {}) {
5655
}
5756

5857
// Test that various low stack sizes result in an 'error' event.
59-
for (const stackSizeMb of [ 0.001, 0.01, 0.1, 0.2, 0.3, 0.5 ]) {
58+
// Currently the stack size needs to be at least 0.3MB for the worker to be
59+
// bootstrapped properly.
60+
for (const stackSizeMb of [ 0.3, 0.5, 1 ]) {
6061
await runWorker({ resourceLimits: { stackSizeMb }, skipErrorCheck: true });
6162
}
6263
})().then(common.mustCall());

0 commit comments

Comments
 (0)
Please sign in to comment.