summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAnna Henningsen <anna.henningsen@mongodb.com>2023-01-24 21:35:52 +0100
committerNode.js GitHub Bot <github-bot@iojs.org>2023-02-03 20:48:24 +0000
commitbfadee5e6843e4116be8d5ecfb8f142fc82853f1 (patch)
treef623300e698201c879635c8cb3d1126e4b23b5f7
parent02fad4f40a10320406f22cd38c4994bd5fed0289 (diff)
downloadnode-new-bfadee5e6843e4116be8d5ecfb8f142fc82853f1.tar.gz
src: allow snapshotting from the embedder API
PR-URL: https://github.com/nodejs/node/pull/45888 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com>
-rw-r--r--lib/internal/main/mksnapshot.js27
-rw-r--r--src/api/embed_helpers.cc96
-rw-r--r--src/env-inl.h10
-rw-r--r--src/env.h4
-rw-r--r--src/node.cc17
-rw-r--r--src/node.h44
-rw-r--r--src/node_main_instance.cc35
-rw-r--r--src/node_main_instance.h27
-rw-r--r--src/node_options.cc3
-rw-r--r--src/node_snapshot_builder.h7
-rw-r--r--src/node_snapshotable.cc171
-rw-r--r--test/embedding/embedtest.cc38
-rw-r--r--test/embedding/test-embedding.js25
13 files changed, 307 insertions, 197 deletions
diff --git a/lib/internal/main/mksnapshot.js b/lib/internal/main/mksnapshot.js
index d7ad932411..6607d34138 100644
--- a/lib/internal/main/mksnapshot.js
+++ b/lib/internal/main/mksnapshot.js
@@ -12,6 +12,7 @@ const {
const binding = internalBinding('mksnapshot');
const { BuiltinModule } = require('internal/bootstrap/loaders');
const {
+ getEmbedderEntryFunction,
compileSerializeMain,
} = binding;
@@ -119,14 +120,21 @@ function main() {
prepareMainThreadExecution
} = require('internal/process/pre_execution');
- prepareMainThreadExecution(true, false);
+ let serializeMainFunction = getEmbedderEntryFunction();
+ const serializeMainArgs = [requireForUserSnapshot];
- const file = process.argv[1];
- const path = require('path');
- const filename = path.resolve(file);
- const dirname = path.dirname(filename);
- const source = readFileSync(file, 'utf-8');
- const serializeMainFunction = compileSerializeMain(filename, source);
+ if (serializeMainFunction) { // embedded case
+ prepareMainThreadExecution(false, false);
+ } else {
+ prepareMainThreadExecution(true, false);
+ const file = process.argv[1];
+ const path = require('path');
+ const filename = path.resolve(file);
+ const dirname = path.dirname(filename);
+ const source = readFileSync(file, 'utf-8');
+ serializeMainFunction = compileSerializeMain(filename, source);
+ serializeMainArgs.push(filename, dirname);
+ }
const {
initializeCallbacks,
@@ -146,10 +154,9 @@ function main() {
if (getOptionValue('--inspect-brk')) {
internalBinding('inspector').callAndPauseOnStart(
- serializeMainFunction, undefined,
- requireForUserSnapshot, filename, dirname);
+ serializeMainFunction, undefined, ...serializeMainArgs);
} else {
- serializeMainFunction(requireForUserSnapshot, filename, dirname);
+ serializeMainFunction(...serializeMainArgs);
}
addSerializeCallback(() => {
diff --git a/src/api/embed_helpers.cc b/src/api/embed_helpers.cc
index 3944890e44..02c4fdf1b5 100644
--- a/src/api/embed_helpers.cc
+++ b/src/api/embed_helpers.cc
@@ -14,6 +14,7 @@ using v8::Locker;
using v8::Maybe;
using v8::Nothing;
using v8::SealHandleScope;
+using v8::SnapshotCreator;
namespace node {
@@ -78,16 +79,18 @@ struct CommonEnvironmentSetup::Impl {
MultiIsolatePlatform* platform = nullptr;
uv_loop_t loop;
std::shared_ptr<ArrayBufferAllocator> allocator;
+ std::optional<SnapshotCreator> snapshot_creator;
Isolate* isolate = nullptr;
DeleteFnPtr<IsolateData, FreeIsolateData> isolate_data;
DeleteFnPtr<Environment, FreeEnvironment> env;
- Global<Context> context;
+ Global<Context> main_context;
};
CommonEnvironmentSetup::CommonEnvironmentSetup(
MultiIsolatePlatform* platform,
std::vector<std::string>* errors,
const EmbedderSnapshotData* snapshot_data,
+ uint32_t flags,
std::function<Environment*(const CommonEnvironmentSetup*)> make_env)
: impl_(new Impl()) {
CHECK_NOT_NULL(platform);
@@ -105,28 +108,43 @@ CommonEnvironmentSetup::CommonEnvironmentSetup(
}
loop->data = this;
- impl_->allocator = ArrayBufferAllocator::Create();
- impl_->isolate =
- NewIsolate(impl_->allocator, &impl_->loop, platform, snapshot_data);
- Isolate* isolate = impl_->isolate;
+ Isolate* isolate;
+ if (flags & Flags::kIsForSnapshotting) {
+ const std::vector<intptr_t>& external_references =
+ SnapshotBuilder::CollectExternalReferences();
+ isolate = impl_->isolate = Isolate::Allocate();
+ // Must be done before the SnapshotCreator creation so that the
+ // memory reducer can be initialized.
+ platform->RegisterIsolate(isolate, loop);
+ impl_->snapshot_creator.emplace(isolate, external_references.data());
+ isolate->SetCaptureStackTraceForUncaughtExceptions(
+ true, 10, v8::StackTrace::StackTraceOptions::kDetailed);
+ SetIsolateMiscHandlers(isolate, {});
+ } else {
+ impl_->allocator = ArrayBufferAllocator::Create();
+ isolate = impl_->isolate =
+ NewIsolate(impl_->allocator, &impl_->loop, platform, snapshot_data);
+ }
{
Locker locker(isolate);
Isolate::Scope isolate_scope(isolate);
impl_->isolate_data.reset(CreateIsolateData(
isolate, loop, platform, impl_->allocator.get(), snapshot_data));
+ impl_->isolate_data->options()->build_snapshot =
+ impl_->snapshot_creator.has_value();
HandleScope handle_scope(isolate);
if (snapshot_data) {
impl_->env.reset(make_env(this));
if (impl_->env) {
- impl_->context.Reset(isolate, impl_->env->context());
+ impl_->main_context.Reset(isolate, impl_->env->context());
}
return;
}
Local<Context> context = NewContext(isolate);
- impl_->context.Reset(isolate, context);
+ impl_->main_context.Reset(isolate, context);
if (context.IsEmpty()) {
errors->push_back("Failed to initialize V8 Context");
return;
@@ -141,7 +159,37 @@ CommonEnvironmentSetup::CommonEnvironmentSetup(
MultiIsolatePlatform* platform,
std::vector<std::string>* errors,
std::function<Environment*(const CommonEnvironmentSetup*)> make_env)
- : CommonEnvironmentSetup(platform, errors, nullptr, make_env) {}
+ : CommonEnvironmentSetup(platform, errors, nullptr, false, make_env) {}
+
+std::unique_ptr<CommonEnvironmentSetup>
+CommonEnvironmentSetup::CreateForSnapshotting(
+ MultiIsolatePlatform* platform,
+ std::vector<std::string>* errors,
+ const std::vector<std::string>& args,
+ const std::vector<std::string>& exec_args) {
+ // It's not guaranteed that a context that goes through
+ // v8_inspector::V8Inspector::contextCreated() is runtime-independent,
+ // so do not start the inspector on the main context when building
+ // the default snapshot.
+ uint64_t env_flags =
+ EnvironmentFlags::kDefaultFlags | EnvironmentFlags::kNoCreateInspector;
+
+ auto ret = std::unique_ptr<CommonEnvironmentSetup>(new CommonEnvironmentSetup(
+ platform,
+ errors,
+ nullptr,
+ true,
+ [&](const CommonEnvironmentSetup* setup) -> Environment* {
+ return CreateEnvironment(
+ setup->isolate_data(),
+ setup->context(),
+ args,
+ exec_args,
+ static_cast<EnvironmentFlags::Flags>(env_flags));
+ }));
+ if (!errors->empty()) ret.reset();
+ return ret;
+}
CommonEnvironmentSetup::~CommonEnvironmentSetup() {
if (impl_->isolate != nullptr) {
@@ -150,7 +198,7 @@ CommonEnvironmentSetup::~CommonEnvironmentSetup() {
Locker locker(isolate);
Isolate::Scope isolate_scope(isolate);
- impl_->context.Reset();
+ impl_->main_context.Reset();
impl_->env.reset();
impl_->isolate_data.reset();
}
@@ -160,7 +208,10 @@ CommonEnvironmentSetup::~CommonEnvironmentSetup() {
*static_cast<bool*>(data) = true;
}, &platform_finished);
impl_->platform->UnregisterIsolate(isolate);
- isolate->Dispose();
+ if (impl_->snapshot_creator.has_value())
+ impl_->snapshot_creator.reset();
+ else
+ isolate->Dispose();
// Wait until the platform has cleaned up all relevant resources.
while (!platform_finished)
@@ -173,6 +224,21 @@ CommonEnvironmentSetup::~CommonEnvironmentSetup() {
delete impl_;
}
+EmbedderSnapshotData::Pointer CommonEnvironmentSetup::CreateSnapshot() {
+ CHECK_NOT_NULL(snapshot_creator());
+ SnapshotData* snapshot_data = new SnapshotData();
+ EmbedderSnapshotData::Pointer result{
+ new EmbedderSnapshotData(snapshot_data, true)};
+
+ auto exit_code = SnapshotBuilder::CreateSnapshot(
+ snapshot_data,
+ this,
+ static_cast<uint8_t>(SnapshotMetadata::Type::kFullyCustomized));
+ if (exit_code != ExitCode::kNoFailure) return {};
+
+ return result;
+}
+
Maybe<int> SpinEventLoop(Environment* env) {
Maybe<ExitCode> result = SpinEventLoopInternal(env);
if (result.IsNothing()) {
@@ -203,7 +269,11 @@ Environment* CommonEnvironmentSetup::env() const {
}
v8::Local<v8::Context> CommonEnvironmentSetup::context() const {
- return impl_->context.Get(impl_->isolate);
+ return impl_->main_context.Get(impl_->isolate);
+}
+
+v8::SnapshotCreator* CommonEnvironmentSetup::snapshot_creator() {
+ return impl_->snapshot_creator ? &impl_->snapshot_creator.value() : nullptr;
}
void EmbedderSnapshotData::DeleteSnapshotData::operator()(
@@ -232,6 +302,10 @@ EmbedderSnapshotData::Pointer EmbedderSnapshotData::FromFile(FILE* in) {
return result;
}
+void EmbedderSnapshotData::ToFile(FILE* out) const {
+ impl_->ToBlob(out);
+}
+
EmbedderSnapshotData::EmbedderSnapshotData(const SnapshotData* impl,
bool owns_impl)
: impl_(impl), owns_impl_(owns_impl) {}
diff --git a/src/env-inl.h b/src/env-inl.h
index 98aa46142d..0ca495bd58 100644
--- a/src/env-inl.h
+++ b/src/env-inl.h
@@ -444,6 +444,16 @@ inline builtins::BuiltinLoader* Environment::builtin_loader() {
return &builtin_loader_;
}
+inline const StartExecutionCallback&
+Environment::embedder_mksnapshot_entry_point() const {
+ return embedder_mksnapshot_entry_point_;
+}
+
+inline void Environment::set_embedder_mksnapshot_entry_point(
+ StartExecutionCallback&& fn) {
+ embedder_mksnapshot_entry_point_ = std::move(fn);
+}
+
inline double Environment::new_async_id() {
async_hooks()->async_id_fields()[AsyncHooks::kAsyncIdCounter] += 1;
return async_hooks()->async_id_fields()[AsyncHooks::kAsyncIdCounter];
diff --git a/src/env.h b/src/env.h
index b18e4f2cb2..0d43237832 100644
--- a/src/env.h
+++ b/src/env.h
@@ -961,6 +961,9 @@ class Environment : public MemoryRetainer {
#endif // HAVE_INSPECTOR
+ inline const StartExecutionCallback& embedder_mksnapshot_entry_point() const;
+ inline void set_embedder_mksnapshot_entry_point(StartExecutionCallback&& fn);
+
inline void set_process_exit_handler(
std::function<void(Environment*, ExitCode)>&& handler);
@@ -1144,6 +1147,7 @@ class Environment : public MemoryRetainer {
std::unique_ptr<Realm> principal_realm_ = nullptr;
builtins::BuiltinLoader builtin_loader_;
+ StartExecutionCallback embedder_mksnapshot_entry_point_;
// Used by allocate_managed_buffer() and release_managed_buffer() to keep
// track of the BackingStore for a given pointer.
diff --git a/src/node.cc b/src/node.cc
index f4402482ca..ed6b1c7a1a 100644
--- a/src/node.cc
+++ b/src/node.cc
@@ -276,14 +276,21 @@ MaybeLocal<Value> StartExecution(Environment* env, StartExecutionCallback cb) {
if (cb != nullptr) {
EscapableHandleScope scope(env->isolate());
- if (StartExecution(env, "internal/main/environment").IsEmpty()) return {};
+ if (env->isolate_data()->options()->build_snapshot) {
+ // TODO(addaleax): pass the callback to the main script more directly,
+ // e.g. by making StartExecution(env, builtin) parametrizable
+ env->set_embedder_mksnapshot_entry_point(std::move(cb));
+ auto reset_entry_point =
+ OnScopeLeave([&]() { env->set_embedder_mksnapshot_entry_point({}); });
+
+ return StartExecution(env, "internal/main/mksnapshot");
+ }
- StartExecutionCallbackInfo info = {
+ if (StartExecution(env, "internal/main/environment").IsEmpty()) return {};
+ return scope.EscapeMaybe(cb({
env->process_object(),
env->builtin_module_require(),
- };
-
- return scope.EscapeMaybe(cb(info));
+ }));
}
// TODO(joyeecheung): move these conditions into JS land and let the
diff --git a/src/node.h b/src/node.h
index 5ae440a59f..15c8b1ba3c 100644
--- a/src/node.h
+++ b/src/node.h
@@ -511,11 +511,16 @@ class EmbedderSnapshotData {
static Pointer BuiltinSnapshotData();
// Return an EmbedderSnapshotData object that is based on an input file.
- // Calling this method will not consume but not close the FILE* handle.
+ // Calling this method will consume but not close the FILE* handle.
// The FILE* handle can be closed immediately following this call.
// If the snapshot is invalid, this returns an empty pointer.
static Pointer FromFile(FILE* in);
+ // Write this EmbedderSnapshotData object to an output file.
+ // Calling this method will not close the FILE* handle.
+ // The FILE* handle can be closed immediately following this call.
+ void ToFile(FILE* out) const;
+
// Returns whether custom snapshots can be used. Currently, this means
// that V8 was configured without the shared-readonly-heap feature.
static bool CanUseCustomSnapshotPerIsolate();
@@ -532,6 +537,7 @@ class EmbedderSnapshotData {
const SnapshotData* impl_;
bool owns_impl_;
friend struct SnapshotData;
+ friend class CommonEnvironmentSetup;
};
// Overriding IsolateSettings may produce unexpected behavior
@@ -817,13 +823,35 @@ class NODE_EXTERN CommonEnvironmentSetup {
std::vector<std::string>* errors,
EnvironmentArgs&&... env_args);
template <typename... EnvironmentArgs>
- static std::unique_ptr<CommonEnvironmentSetup> CreateWithSnapshot(
+ static std::unique_ptr<CommonEnvironmentSetup> CreateFromSnapshot(
MultiIsolatePlatform* platform,
std::vector<std::string>* errors,
const EmbedderSnapshotData* snapshot_data,
EnvironmentArgs&&... env_args);
+ // Create an embedding setup which will be used for creating a snapshot
+ // using CreateSnapshot().
+ //
+ // This will create and attach a v8::SnapshotCreator to this instance,
+ // and the same restrictions apply to this instance that also apply to
+ // other V8 snapshotting environments.
+ // Not all Node.js APIs are supported in this case. Currently, there is
+ // no support for native/host objects other than Node.js builtins
+ // in the snapshot.
+ //
+ // Snapshots are an *experimental* feature. In particular, the embedder API
+ // exposed through this class is subject to change or removal between Node.js
+ // versions, including possible API and ABI breakage.
+ static std::unique_ptr<CommonEnvironmentSetup> CreateForSnapshotting(
+ MultiIsolatePlatform* platform,
+ std::vector<std::string>* errors,
+ const std::vector<std::string>& args = {},
+ const std::vector<std::string>& exec_args = {});
+ EmbedderSnapshotData::Pointer CreateSnapshot();
+
struct uv_loop_s* event_loop() const;
+ v8::SnapshotCreator* snapshot_creator();
+ // Empty for snapshotting environments.
std::shared_ptr<ArrayBufferAllocator> array_buffer_allocator() const;
v8::Isolate* isolate() const;
IsolateData* isolate_data() const;
@@ -836,8 +864,14 @@ class NODE_EXTERN CommonEnvironmentSetup {
CommonEnvironmentSetup& operator=(CommonEnvironmentSetup&&) = delete;
private:
+ enum Flags : uint32_t {
+ kNoFlags = 0,
+ kIsForSnapshotting = 1,
+ };
+
struct Impl;
Impl* impl_;
+
CommonEnvironmentSetup(
MultiIsolatePlatform*,
std::vector<std::string>*,
@@ -846,6 +880,7 @@ class NODE_EXTERN CommonEnvironmentSetup {
MultiIsolatePlatform*,
std::vector<std::string>*,
const EmbedderSnapshotData*,
+ uint32_t flags,
std::function<Environment*(const CommonEnvironmentSetup*)>);
};
@@ -865,11 +900,11 @@ std::unique_ptr<CommonEnvironmentSetup> CommonEnvironmentSetup::Create(
if (!errors->empty()) ret.reset();
return ret;
}
-// Implementation for ::CreateWithSnapshot -- the ::Create() method
+// Implementation for ::CreateFromSnapshot -- the ::Create() method
// could call this with a nullptr snapshot_data in a major version.
template <typename... EnvironmentArgs>
std::unique_ptr<CommonEnvironmentSetup>
-CommonEnvironmentSetup::CreateWithSnapshot(
+CommonEnvironmentSetup::CreateFromSnapshot(
MultiIsolatePlatform* platform,
std::vector<std::string>* errors,
const EmbedderSnapshotData* snapshot_data,
@@ -878,6 +913,7 @@ CommonEnvironmentSetup::CreateWithSnapshot(
platform,
errors,
snapshot_data,
+ Flags::kNoFlags,
[&](const CommonEnvironmentSetup* setup) -> Environment* {
return CreateEnvironment(setup->isolate_data(),
setup->context(),
diff --git a/src/node_main_instance.cc b/src/node_main_instance.cc
index 81804656c5..5a4d127ffe 100644
--- a/src/node_main_instance.cc
+++ b/src/node_main_instance.cc
@@ -29,34 +29,6 @@ using v8::Isolate;
using v8::Local;
using v8::Locker;
-NodeMainInstance::NodeMainInstance(Isolate* isolate,
- uv_loop_t* event_loop,
- MultiIsolatePlatform* platform,
- const std::vector<std::string>& args,
- const std::vector<std::string>& exec_args)
- : args_(args),
- exec_args_(exec_args),
- array_buffer_allocator_(nullptr),
- isolate_(isolate),
- platform_(platform),
- isolate_data_(nullptr),
- snapshot_data_(nullptr) {
- isolate_data_ =
- std::make_unique<IsolateData>(isolate_, event_loop, platform, nullptr);
-
- SetIsolateMiscHandlers(isolate_, {});
-}
-
-std::unique_ptr<NodeMainInstance> NodeMainInstance::Create(
- Isolate* isolate,
- uv_loop_t* event_loop,
- MultiIsolatePlatform* platform,
- const std::vector<std::string>& args,
- const std::vector<std::string>& exec_args) {
- return std::unique_ptr<NodeMainInstance>(
- new NodeMainInstance(isolate, event_loop, platform, args, exec_args));
-}
-
NodeMainInstance::NodeMainInstance(const SnapshotData* snapshot_data,
uv_loop_t* event_loop,
MultiIsolatePlatform* platform,
@@ -88,13 +60,6 @@ NodeMainInstance::NodeMainInstance(const SnapshotData* snapshot_data,
isolate_params_->constraints.max_young_generation_size_in_bytes();
}
-void NodeMainInstance::Dispose() {
- // This should only be called on a main instance that does not own its
- // isolate.
- CHECK_NULL(isolate_params_);
- platform_->DrainTasks(isolate_);
-}
-
NodeMainInstance::~NodeMainInstance() {
if (isolate_params_ == nullptr) {
return;
diff --git a/src/node_main_instance.h b/src/node_main_instance.h
index 43b6a8d64e..2752ff4d0e 100644
--- a/src/node_main_instance.h
+++ b/src/node_main_instance.h
@@ -22,33 +22,6 @@ struct SnapshotData;
// We may be able to create an abstract class to reuse some of the routines.
class NodeMainInstance {
public:
- // To create a main instance that does not own the isolate,
- // The caller needs to do:
- //
- // Isolate* isolate = Isolate::Allocate();
- // platform->RegisterIsolate(isolate, loop);
- // isolate->Initialize(...);
- // isolate->Enter();
- // std::unique_ptr<NodeMainInstance> main_instance =
- // NodeMainInstance::Create(isolate, loop, args, exec_args);
- //
- // When tearing it down:
- //
- // main_instance->Cleanup(); // While the isolate is entered
- // isolate->Exit();
- // isolate->Dispose();
- // platform->UnregisterIsolate(isolate);
- //
- // After calling Dispose() the main_instance is no longer accessible.
- static std::unique_ptr<NodeMainInstance> Create(
- v8::Isolate* isolate,
- uv_loop_t* event_loop,
- MultiIsolatePlatform* platform,
- const std::vector<std::string>& args,
- const std::vector<std::string>& exec_args);
-
- void Dispose();
-
// Create a main instance that owns the isolate
NodeMainInstance(const SnapshotData* snapshot_data,
uv_loop_t* event_loop,
diff --git a/src/node_options.cc b/src/node_options.cc
index e9c5ac2b19..c1f97a5d92 100644
--- a/src/node_options.cc
+++ b/src/node_options.cc
@@ -778,8 +778,7 @@ PerIsolateOptionsParser::PerIsolateOptionsParser(
Implies("--harmony-shadow-realm", "--experimental-shadow-realm");
ImpliesNot("--no-harmony-shadow-realm", "--experimental-shadow-realm");
AddOption("--build-snapshot",
- "Generate a snapshot blob when the process exits."
- " Currently only supported in the node_mksnapshot binary.",
+ "Generate a snapshot blob when the process exits.",
&PerIsolateOptions::build_snapshot,
kDisallowedInEnvvar);
diff --git a/src/node_snapshot_builder.h b/src/node_snapshot_builder.h
index 3367cd3d7a..f8cd900b2b 100644
--- a/src/node_snapshot_builder.h
+++ b/src/node_snapshot_builder.h
@@ -31,9 +31,14 @@ class NODE_EXTERN_PRIVATE SnapshotBuilder {
static void InitializeIsolateParams(const SnapshotData* data,
v8::Isolate::CreateParams* params);
- private:
static const std::vector<intptr_t>& CollectExternalReferences();
+ static ExitCode CreateSnapshot(
+ SnapshotData* out,
+ CommonEnvironmentSetup* setup,
+ /*SnapshotMetadata::Type*/ uint8_t snapshot_type);
+
+ private:
static std::unique_ptr<ExternalReferenceRegistry> registry_;
};
} // namespace node
diff --git a/src/node_snapshotable.cc b/src/node_snapshotable.cc
index a9f729f80e..aa66eb3312 100644
--- a/src/node_snapshotable.cc
+++ b/src/node_snapshotable.cc
@@ -33,6 +33,7 @@ using v8::FunctionCallbackInfo;
using v8::HandleScope;
using v8::Isolate;
using v8::Local;
+using v8::MaybeLocal;
using v8::Object;
using v8::ObjectTemplate;
using v8::ScriptCompiler;
@@ -1101,38 +1102,15 @@ void SnapshotBuilder::InitializeIsolateParams(const SnapshotData* data,
ExitCode SnapshotBuilder::Generate(SnapshotData* out,
const std::vector<std::string> args,
const std::vector<std::string> exec_args) {
- const std::vector<intptr_t>& external_references =
- CollectExternalReferences();
- Isolate* isolate = Isolate::Allocate();
- // Must be done before the SnapshotCreator creation so that the
- // memory reducer can be initialized.
- per_process::v8_platform.Platform()->RegisterIsolate(isolate,
- uv_default_loop());
-
- SnapshotCreator creator(isolate, external_references.data());
-
- isolate->SetCaptureStackTraceForUncaughtExceptions(
- true, 10, v8::StackTrace::StackTraceOptions::kDetailed);
-
- Environment* env = nullptr;
- std::unique_ptr<NodeMainInstance> main_instance =
- NodeMainInstance::Create(isolate,
- uv_default_loop(),
- per_process::v8_platform.Platform(),
- args,
- exec_args);
-
- // The cleanups should be done in case of an early exit due to errors.
- auto cleanup = OnScopeLeave([&]() {
- // Must be done while the snapshot creator isolate is entered i.e. the
- // creator is still alive. The snapshot creator destructor will destroy
- // the isolate.
- if (env != nullptr) {
- FreeEnvironment(env);
- }
- main_instance->Dispose();
- per_process::v8_platform.Platform()->UnregisterIsolate(isolate);
- });
+ std::vector<std::string> errors;
+ auto setup = CommonEnvironmentSetup::CreateForSnapshotting(
+ per_process::v8_platform.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 ExitCode::kBootstrapFailure;
+ }
+ Isolate* isolate = setup->isolate();
// It's only possible to be kDefault in node_mksnapshot.
SnapshotMetadata::Type snapshot_type =
@@ -1151,6 +1129,51 @@ ExitCode SnapshotBuilder::Generate(SnapshotData* out,
}
});
+ // Initialize the main instance context.
+ {
+ Context::Scope context_scope(setup->context());
+ Environment* env = setup->env();
+
+ // If --build-snapshot is true, lib/internal/main/mksnapshot.js would be
+ // loaded via LoadEnvironment() to execute process.argv[1] as the entry
+ // point (we currently only support this kind of entry point, but we
+ // could also explore snapshotting other kinds of execution modes
+ // in the future).
+ if (snapshot_type == SnapshotMetadata::Type::kFullyCustomized) {
+#if HAVE_INSPECTOR
+ env->InitializeInspector({});
+#endif
+ if (LoadEnvironment(env, StartExecutionCallback{}).IsEmpty()) {
+ return ExitCode::kGenericUserError;
+ }
+ // FIXME(joyeecheung): right now running the loop in the snapshot
+ // builder seems to introduces inconsistencies in JS land that need to
+ // be synchronized again after snapshot restoration.
+ ExitCode exit_code =
+ SpinEventLoopInternal(env).FromMaybe(ExitCode::kGenericUserError);
+ if (exit_code != ExitCode::kNoFailure) {
+ return exit_code;
+ }
+ }
+ }
+ }
+
+ return CreateSnapshot(out, setup.get(), static_cast<uint8_t>(snapshot_type));
+}
+
+ExitCode SnapshotBuilder::CreateSnapshot(SnapshotData* out,
+ CommonEnvironmentSetup* setup,
+ uint8_t snapshot_type_u8) {
+ SnapshotMetadata::Type snapshot_type =
+ static_cast<SnapshotMetadata::Type>(snapshot_type_u8);
+ Isolate* isolate = setup->isolate();
+ Environment* env = setup->env();
+ SnapshotCreator* creator = setup->snapshot_creator();
+
+ {
+ HandleScope scope(isolate);
+ Local<Context> main_context = setup->context();
+
// The default context with only things created by V8.
Local<Context> default_context = Context::New(isolate);
@@ -1158,7 +1181,7 @@ ExitCode SnapshotBuilder::Generate(SnapshotData* out,
Local<Context> vm_context;
{
Local<ObjectTemplate> global_template =
- main_instance->isolate_data()->contextify_global_template();
+ setup->isolate_data()->contextify_global_template();
CHECK(!global_template.IsEmpty());
if (!contextify::ContextifyContext::CreateV8Context(
isolate, global_template, nullptr, nullptr)
@@ -1176,63 +1199,17 @@ ExitCode SnapshotBuilder::Generate(SnapshotData* out,
}
ResetContextSettingsBeforeSnapshot(base_context);
- Local<Context> main_context = NewContext(isolate);
- if (main_context.IsEmpty()) {
- return ExitCode::kBootstrapFailure;
- }
- // Initialize the main instance context.
{
Context::Scope context_scope(main_context);
- // Create the environment.
- // It's not guaranteed that a context that goes through
- // v8_inspector::V8Inspector::contextCreated() is runtime-independent,
- // so do not start the inspector on the main context when building
- // the default snapshot.
- uint64_t env_flags = EnvironmentFlags::kDefaultFlags |
- EnvironmentFlags::kNoCreateInspector;
-
- env = CreateEnvironment(main_instance->isolate_data(),
- main_context,
- args,
- exec_args,
- static_cast<EnvironmentFlags::Flags>(env_flags));
-
- // This already ran scripts in lib/internal/bootstrap/, if it fails return
- if (env == nullptr) {
- return ExitCode::kBootstrapFailure;
- }
- // If --build-snapshot is true, lib/internal/main/mksnapshot.js would be
- // loaded via LoadEnvironment() to execute process.argv[1] as the entry
- // point (we currently only support this kind of entry point, but we
- // could also explore snapshotting other kinds of execution modes
- // in the future).
- if (snapshot_type == SnapshotMetadata::Type::kFullyCustomized) {
-#if HAVE_INSPECTOR
- env->InitializeInspector({});
-#endif
- if (LoadEnvironment(env, StartExecutionCallback{}).IsEmpty()) {
- return ExitCode::kGenericUserError;
- }
- // FIXME(joyeecheung): right now running the loop in the snapshot
- // builder seems to introduces inconsistencies in JS land that need to
- // be synchronized again after snapshot restoration.
- ExitCode exit_code =
- SpinEventLoopInternal(env).FromMaybe(ExitCode::kGenericUserError);
- if (exit_code != ExitCode::kNoFailure) {
- return exit_code;
- }
- }
-
if (per_process::enabled_debug_list.enabled(DebugCategory::MKSNAPSHOT)) {
env->ForEachRealm([](Realm* realm) { realm->PrintInfoForSnapshot(); });
printf("Environment = %p\n", env);
}
// Serialize the native states
- out->isolate_data_info =
- main_instance->isolate_data()->Serialize(&creator);
- out->env_info = env->Serialize(&creator);
+ out->isolate_data_info = setup->isolate_data()->Serialize(creator);
+ out->env_info = env->Serialize(creator);
#ifdef NODE_USE_NODE_CODE_CACHE
// Regenerate all the code cache.
@@ -1255,19 +1232,19 @@ ExitCode SnapshotBuilder::Generate(SnapshotData* out,
// Global handles to the contexts can't be disposed before the
// blob is created. So initialize all the contexts before adding them.
// TODO(joyeecheung): figure out how to remove this restriction.
- creator.SetDefaultContext(default_context);
- size_t index = creator.AddContext(vm_context);
+ creator->SetDefaultContext(default_context);
+ size_t index = creator->AddContext(vm_context);
CHECK_EQ(index, SnapshotData::kNodeVMContextIndex);
- index = creator.AddContext(base_context);
+ index = creator->AddContext(base_context);
CHECK_EQ(index, SnapshotData::kNodeBaseContextIndex);
- index = creator.AddContext(main_context,
- {SerializeNodeContextInternalFields, env});
+ index = creator->AddContext(main_context,
+ {SerializeNodeContextInternalFields, env});
CHECK_EQ(index, SnapshotData::kNodeMainContextIndex);
}
// Must be out of HandleScope
out->v8_snapshot_blob_data =
- creator.CreateBlob(SnapshotCreator::FunctionCodeHandling::kKeep);
+ creator->CreateBlob(SnapshotCreator::FunctionCodeHandling::kKeep);
// We must be able to rehash the blob when we restore it or otherwise
// the hash seed would be fixed by V8, introducing a vulnerability.
@@ -1468,6 +1445,23 @@ void SerializeSnapshotableObjects(Realm* realm,
namespace mksnapshot {
+void GetEmbedderEntryFunction(const FunctionCallbackInfo<Value>& args) {
+ Environment* env = Environment::GetCurrent(args);
+ Isolate* isolate = env->isolate();
+ if (!env->embedder_mksnapshot_entry_point()) return;
+ MaybeLocal<Function> jsfn =
+ Function::New(isolate->GetCurrentContext(),
+ [](const FunctionCallbackInfo<Value>& args) {
+ Environment* env = Environment::GetCurrent(args);
+ Local<Value> require_fn = args[0];
+ CHECK(require_fn->IsFunction());
+ CHECK(env->embedder_mksnapshot_entry_point());
+ env->embedder_mksnapshot_entry_point()(
+ {env->process_object(), require_fn.As<Function>()});
+ });
+ if (!jsfn.IsEmpty()) args.GetReturnValue().Set(jsfn.ToLocalChecked());
+}
+
void CompileSerializeMain(const FunctionCallbackInfo<Value>& args) {
CHECK(args[0]->IsString());
Local<String> filename = args[0].As<String>();
@@ -1521,6 +1515,8 @@ void Initialize(Local<Object> target,
Local<Value> unused,
Local<Context> context,
void* priv) {
+ SetMethod(
+ context, target, "getEmbedderEntryFunction", GetEmbedderEntryFunction);
SetMethod(context, target, "compileSerializeMain", CompileSerializeMain);
SetMethod(context, target, "setSerializeCallback", SetSerializeCallback);
SetMethod(context, target, "setDeserializeCallback", SetDeserializeCallback);
@@ -1531,6 +1527,7 @@ void Initialize(Local<Object> target,
}
void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
+ registry->Register(GetEmbedderEntryFunction);
registry->Register(CompileSerializeMain);
registry->Register(SetSerializeCallback);
registry->Register(SetDeserializeCallback);
diff --git a/test/embedding/embedtest.cc b/test/embedding/embedtest.cc
index ea3af21731..ef07056d5b 100644
--- a/test/embedding/embedtest.cc
+++ b/test/embedding/embedtest.cc
@@ -1,3 +1,6 @@
+#ifdef NDEBUG
+#undef NDEBUG
+#endif
#include "node.h"
#include "uv.h"
#include <assert.h>
@@ -58,9 +61,12 @@ int RunNodeInstance(MultiIsolatePlatform* platform,
int exit_code = 0;
node::EmbedderSnapshotData::Pointer snapshot;
+ auto snapshot_build_mode_it =
+ std::find(args.begin(), args.end(), "--embedder-snapshot-create");
auto snapshot_arg_it =
std::find(args.begin(), args.end(), "--embedder-snapshot-blob");
- if (snapshot_arg_it < args.end() - 1) {
+ if (snapshot_arg_it < args.end() - 1 &&
+ snapshot_build_mode_it == args.end()) {
FILE* fp = fopen((snapshot_arg_it + 1)->c_str(), "r");
assert(fp != nullptr);
snapshot = node::EmbedderSnapshotData::FromFile(fp);
@@ -69,9 +75,11 @@ int RunNodeInstance(MultiIsolatePlatform* platform,
std::vector<std::string> errors;
std::unique_ptr<CommonEnvironmentSetup> setup =
- snapshot
- ? CommonEnvironmentSetup::CreateWithSnapshot(
- platform, &errors, snapshot.get(), args, exec_args)
+ snapshot ? CommonEnvironmentSetup::CreateFromSnapshot(
+ platform, &errors, snapshot.get(), args, exec_args)
+ : snapshot_build_mode_it != args.end()
+ ? CommonEnvironmentSetup::CreateForSnapshotting(
+ platform, &errors, args, exec_args)
: CommonEnvironmentSetup::Create(platform, &errors, args, exec_args);
if (!setup) {
for (const std::string& err : errors)
@@ -94,9 +102,12 @@ int RunNodeInstance(MultiIsolatePlatform* platform,
} else {
loadenv_ret = node::LoadEnvironment(
env,
- "const publicRequire ="
- " require('module').createRequire(process.cwd() + '/');"
- "globalThis.require = publicRequire;"
+ // Snapshots do not support userland require()s (yet)
+ "if (!require('v8').startupSnapshot.isBuildingSnapshot()) {"
+ " const publicRequire ="
+ " require('module').createRequire(process.cwd() + '/');"
+ " globalThis.require = publicRequire;"
+ "} else globalThis.require = require;"
"globalThis.embedVars = { nön_ascıı: '🏳️‍🌈' };"
"require('vm').runInThisContext(process.argv[1]);");
}
@@ -105,9 +116,20 @@ int RunNodeInstance(MultiIsolatePlatform* platform,
return 1;
exit_code = node::SpinEventLoop(env).FromMaybe(1);
+ }
+
+ if (snapshot_arg_it < args.end() - 1 &&
+ snapshot_build_mode_it != args.end()) {
+ snapshot = setup->CreateSnapshot();
+ assert(snapshot);
- node::Stop(env);
+ FILE* fp = fopen((snapshot_arg_it + 1)->c_str(), "w");
+ assert(fp != nullptr);
+ snapshot->ToFile(fp);
+ fclose(fp);
}
+ node::Stop(env);
+
return exit_code;
}
diff --git a/test/embedding/test-embedding.js b/test/embedding/test-embedding.js
index c21c39b28e..197158e78f 100644
--- a/test/embedding/test-embedding.js
+++ b/test/embedding/test-embedding.js
@@ -20,7 +20,6 @@ function resolveBuiltBinary(bin) {
}
const binary = resolveBuiltBinary('embedtest');
-const standaloneNodeBinary = resolveBuiltBinary('node');
assert.strictEqual(
child_process.spawnSync(binary, ['console.log(42)'])
@@ -51,22 +50,30 @@ assert.strictEqual(
child_process.spawnSync(binary, [`require(${fixturePath})`, 92]).status,
92);
+function getReadFileCodeForPath(path) {
+ return `(require("fs").readFileSync(${JSON.stringify(path)}, "utf8"))`;
+}
+
// Basic snapshot support
{
+ // readSync + eval since snapshots don't support userland require() (yet)
const snapshotFixture = fixtures.path('snapshot', 'echo-args.js');
const blobPath = path.join(tmpdir.path, 'embedder-snapshot.blob');
- const buildSnapshotArgs = [snapshotFixture, 'arg1', 'arg2'];
+ const buildSnapshotArgs = [
+ `eval(${getReadFileCodeForPath(snapshotFixture)})`, 'arg1', 'arg2',
+ '--embedder-snapshot-blob', blobPath, '--embedder-snapshot-create',
+ ];
const runEmbeddedArgs = ['--embedder-snapshot-blob', blobPath, 'arg3', 'arg4'];
fs.rmSync(blobPath, { force: true });
- assert.strictEqual(child_process.spawnSync(standaloneNodeBinary, [
- '--snapshot-blob', blobPath, '--build-snapshot', ...buildSnapshotArgs,
+ assert.strictEqual(child_process.spawnSync(binary, [
+ '--', ...buildSnapshotArgs,
], {
cwd: tmpdir.path,
}).status, 0);
const spawnResult = child_process.spawnSync(binary, ['--', ...runEmbeddedArgs]);
assert.deepStrictEqual(JSON.parse(spawnResult.stdout), {
- originalArgv: [standaloneNodeBinary, ...buildSnapshotArgs],
+ originalArgv: [binary, ...buildSnapshotArgs],
currentArgv: [binary, ...runEmbeddedArgs],
});
}
@@ -75,10 +82,14 @@ assert.strictEqual(
{
const snapshotFixture = fixtures.path('snapshot', 'create-worker-and-vm.js');
const blobPath = path.join(tmpdir.path, 'embedder-snapshot.blob');
+ const buildSnapshotArgs = [
+ `eval(${getReadFileCodeForPath(snapshotFixture)})`,
+ '--embedder-snapshot-blob', blobPath, '--embedder-snapshot-create',
+ ];
fs.rmSync(blobPath, { force: true });
- assert.strictEqual(child_process.spawnSync(standaloneNodeBinary, [
- '--snapshot-blob', blobPath, '--build-snapshot', snapshotFixture,
+ assert.strictEqual(child_process.spawnSync(binary, [
+ '--', ...buildSnapshotArgs,
], {
cwd: tmpdir.path,
}).status, 0);