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>
This commit is contained in:
Anna Henningsen 2023-01-24 21:35:52 +01:00 committed by Node.js GitHub Bot
parent 02fad4f40a
commit bfadee5e68
13 changed files with 310 additions and 200 deletions

View File

@ -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(() => {

View File

@ -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) {}

View File

@ -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];

View File

@ -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.

View File

@ -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({}); });
StartExecutionCallbackInfo info = {
return StartExecution(env, "internal/main/mksnapshot");
}
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

View File

@ -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(),

View File

@ -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;

View File

@ -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,

View File

@ -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);

View File

@ -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

View File

@ -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,57 +1129,11 @@ ExitCode SnapshotBuilder::Generate(SnapshotData* out,
}
});
// The default context with only things created by V8.
Local<Context> default_context = Context::New(isolate);
// The context used by the vm module.
Local<Context> vm_context;
{
Local<ObjectTemplate> global_template =
main_instance->isolate_data()->contextify_global_template();
CHECK(!global_template.IsEmpty());
if (!contextify::ContextifyContext::CreateV8Context(
isolate, global_template, nullptr, nullptr)
.ToLocal(&vm_context)) {
return ExitCode::kStartupSnapshotFailure;
}
}
// The Node.js-specific context with primodials, can be used by workers
// TODO(joyeecheung): investigate if this can be used by vm contexts
// without breaking compatibility.
Local<Context> base_context = NewContext(isolate);
if (base_context.IsEmpty()) {
return ExitCode::kBootstrapFailure;
}
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);
Context::Scope context_scope(setup->context());
Environment* env = setup->env();
// 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
@ -1223,6 +1155,52 @@ ExitCode SnapshotBuilder::Generate(SnapshotData* out,
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);
// The context used by the vm module.
Local<Context> vm_context;
{
Local<ObjectTemplate> global_template =
setup->isolate_data()->contextify_global_template();
CHECK(!global_template.IsEmpty());
if (!contextify::ContextifyContext::CreateV8Context(
isolate, global_template, nullptr, nullptr)
.ToLocal(&vm_context)) {
return ExitCode::kStartupSnapshotFailure;
}
}
// The Node.js-specific context with primodials, can be used by workers
// TODO(joyeecheung): investigate if this can be used by vm contexts
// without breaking compatibility.
Local<Context> base_context = NewContext(isolate);
if (base_context.IsEmpty()) {
return ExitCode::kBootstrapFailure;
}
ResetContextSettingsBeforeSnapshot(base_context);
{
Context::Scope context_scope(main_context);
if (per_process::enabled_debug_list.enabled(DebugCategory::MKSNAPSHOT)) {
env->ForEachRealm([](Realm* realm) { realm->PrintInfoForSnapshot(); });
@ -1230,9 +1208,8 @@ ExitCode SnapshotBuilder::Generate(SnapshotData* out,
}
// 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);

View File

@ -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);
node::Stop(env);
}
if (snapshot_arg_it < args.end() - 1 &&
snapshot_build_mode_it != args.end()) {
snapshot = setup->CreateSnapshot();
assert(snapshot);
FILE* fp = fopen((snapshot_arg_it + 1)->c_str(), "w");
assert(fp != nullptr);
snapshot->ToFile(fp);
fclose(fp);
}
node::Stop(env);
return exit_code;
}

View File

@ -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);