src: add snapshot support for embedder API

Add experimental support for loading snapshots in the embedder API
by adding a public opaque wrapper for our `SnapshotData` struct and
allowing embedders to pass it to the relevant setup functions.
Where applicable, use these helpers to deduplicate existing code
in Node.js’s startup path.

This has shown a 40 % startup performance increase for a real-world
application, even with the somewhat limited current support for
built-in modules.

The documentation includes a note about no guarantees for API or
ABI stability for this feature while it is experimental.

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 2022-12-12 18:18:37 +01:00 committed by Node.js GitHub Bot
parent d523bfe2c4
commit 06bb6b42b3
15 changed files with 434 additions and 111 deletions

View File

@ -1,6 +1,7 @@
#include "node.h"
#include "env-inl.h"
#include "debug_utils-inl.h"
#include "env-inl.h"
#include "node.h"
#include "node_snapshot_builder.h"
using v8::Context;
using v8::Function;
@ -86,8 +87,9 @@ struct CommonEnvironmentSetup::Impl {
CommonEnvironmentSetup::CommonEnvironmentSetup(
MultiIsolatePlatform* platform,
std::vector<std::string>* errors,
const EmbedderSnapshotData* snapshot_data,
std::function<Environment*(const CommonEnvironmentSetup*)> make_env)
: impl_(new Impl()) {
: impl_(new Impl()) {
CHECK_NOT_NULL(platform);
CHECK_NOT_NULL(errors);
@ -104,16 +106,25 @@ CommonEnvironmentSetup::CommonEnvironmentSetup(
loop->data = this;
impl_->allocator = ArrayBufferAllocator::Create();
impl_->isolate = NewIsolate(impl_->allocator, &impl_->loop, platform);
impl_->isolate =
NewIsolate(impl_->allocator, &impl_->loop, platform, snapshot_data);
Isolate* isolate = impl_->isolate;
{
Locker locker(isolate);
Isolate::Scope isolate_scope(isolate);
impl_->isolate_data.reset(CreateIsolateData(
isolate, loop, platform, impl_->allocator.get()));
isolate, loop, platform, impl_->allocator.get(), snapshot_data));
HandleScope handle_scope(isolate);
if (snapshot_data) {
impl_->env.reset(make_env(this));
if (impl_->env) {
impl_->context.Reset(isolate, impl_->env->context());
}
return;
}
Local<Context> context = NewContext(isolate);
impl_->context.Reset(isolate, context);
if (context.IsEmpty()) {
@ -126,6 +137,12 @@ CommonEnvironmentSetup::CommonEnvironmentSetup(
}
}
CommonEnvironmentSetup::CommonEnvironmentSetup(
MultiIsolatePlatform* platform,
std::vector<std::string>* errors,
std::function<Environment*(const CommonEnvironmentSetup*)> make_env)
: CommonEnvironmentSetup(platform, errors, nullptr, make_env) {}
CommonEnvironmentSetup::~CommonEnvironmentSetup() {
if (impl_->isolate != nullptr) {
Isolate* isolate = impl_->isolate;
@ -189,4 +206,42 @@ v8::Local<v8::Context> CommonEnvironmentSetup::context() const {
return impl_->context.Get(impl_->isolate);
}
void EmbedderSnapshotData::DeleteSnapshotData::operator()(
const EmbedderSnapshotData* data) const {
CHECK_IMPLIES(data->owns_impl_, data->impl_);
if (data->owns_impl_ &&
data->impl_->data_ownership == SnapshotData::DataOwnership::kOwned) {
delete data->impl_;
}
delete data;
}
EmbedderSnapshotData::Pointer EmbedderSnapshotData::BuiltinSnapshotData() {
return EmbedderSnapshotData::Pointer{new EmbedderSnapshotData(
SnapshotBuilder::GetEmbeddedSnapshotData(), false)};
}
EmbedderSnapshotData::Pointer EmbedderSnapshotData::FromFile(FILE* in) {
SnapshotData* snapshot_data = new SnapshotData();
CHECK_EQ(snapshot_data->data_ownership, SnapshotData::DataOwnership::kOwned);
EmbedderSnapshotData::Pointer result{
new EmbedderSnapshotData(snapshot_data, true)};
if (!SnapshotData::FromBlob(snapshot_data, in)) {
return {};
}
return result;
}
EmbedderSnapshotData::EmbedderSnapshotData(const SnapshotData* impl,
bool owns_impl)
: impl_(impl), owns_impl_(owns_impl) {}
bool EmbedderSnapshotData::CanUseCustomSnapshotPerIsolate() {
#ifdef NODE_V8_SHARED_RO_HEAP
return false;
#else
return true;
#endif
}
} // namespace node

View File

@ -9,6 +9,7 @@
#include "node_platform.h"
#include "node_realm-inl.h"
#include "node_shadow_realm.h"
#include "node_snapshot_builder.h"
#include "node_v8_platform-inl.h"
#include "node_wasm_web_api.h"
#include "uv.h"
@ -315,9 +316,15 @@ void SetIsolateUpForNode(v8::Isolate* isolate) {
Isolate* NewIsolate(Isolate::CreateParams* params,
uv_loop_t* event_loop,
MultiIsolatePlatform* platform,
bool has_snapshot_data) {
const SnapshotData* snapshot_data,
const IsolateSettings& settings) {
Isolate* isolate = Isolate::Allocate();
if (isolate == nullptr) return nullptr;
if (snapshot_data != nullptr) {
SnapshotBuilder::InitializeIsolateParams(snapshot_data, params);
}
#ifdef NODE_V8_SHARED_RO_HEAP
{
// In shared-readonly-heap mode, V8 requires all snapshots used for
@ -336,12 +343,12 @@ Isolate* NewIsolate(Isolate::CreateParams* params,
SetIsolateCreateParamsForNode(params);
Isolate::Initialize(isolate, *params);
if (!has_snapshot_data) {
if (snapshot_data == nullptr) {
// If in deserialize mode, delay until after the deserialization is
// complete.
SetIsolateUpForNode(isolate);
SetIsolateUpForNode(isolate, settings);
} else {
SetIsolateMiscHandlers(isolate, {});
SetIsolateMiscHandlers(isolate, settings);
}
return isolate;
@ -349,25 +356,60 @@ Isolate* NewIsolate(Isolate::CreateParams* params,
Isolate* NewIsolate(ArrayBufferAllocator* allocator,
uv_loop_t* event_loop,
MultiIsolatePlatform* platform) {
MultiIsolatePlatform* platform,
const EmbedderSnapshotData* snapshot_data,
const IsolateSettings& settings) {
Isolate::CreateParams params;
if (allocator != nullptr) params.array_buffer_allocator = allocator;
return NewIsolate(&params, event_loop, platform);
return NewIsolate(&params,
event_loop,
platform,
SnapshotData::FromEmbedderWrapper(snapshot_data),
settings);
}
Isolate* NewIsolate(std::shared_ptr<ArrayBufferAllocator> allocator,
uv_loop_t* event_loop,
MultiIsolatePlatform* platform,
const EmbedderSnapshotData* snapshot_data,
const IsolateSettings& settings) {
Isolate::CreateParams params;
if (allocator) params.array_buffer_allocator_shared = allocator;
return NewIsolate(&params,
event_loop,
platform,
SnapshotData::FromEmbedderWrapper(snapshot_data),
settings);
}
Isolate* NewIsolate(ArrayBufferAllocator* allocator,
uv_loop_t* event_loop,
MultiIsolatePlatform* platform) {
return NewIsolate(allocator, event_loop, platform, nullptr);
}
Isolate* NewIsolate(std::shared_ptr<ArrayBufferAllocator> allocator,
uv_loop_t* event_loop,
MultiIsolatePlatform* platform) {
Isolate::CreateParams params;
if (allocator) params.array_buffer_allocator_shared = allocator;
return NewIsolate(&params, event_loop, platform);
return NewIsolate(allocator, event_loop, platform, nullptr);
}
IsolateData* CreateIsolateData(
Isolate* isolate,
uv_loop_t* loop,
MultiIsolatePlatform* platform,
ArrayBufferAllocator* allocator,
const EmbedderSnapshotData* embedder_snapshot_data) {
const SnapshotData* snapshot_data =
SnapshotData::FromEmbedderWrapper(embedder_snapshot_data);
return new IsolateData(isolate, loop, platform, allocator, snapshot_data);
}
IsolateData* CreateIsolateData(Isolate* isolate,
uv_loop_t* loop,
MultiIsolatePlatform* platform,
ArrayBufferAllocator* allocator) {
return new IsolateData(isolate, loop, platform, allocator);
return CreateIsolateData(isolate, loop, platform, allocator, nullptr);
}
void FreeIsolateData(IsolateData* isolate_data) {
@ -395,13 +437,45 @@ Environment* CreateEnvironment(
EnvironmentFlags::Flags flags,
ThreadId thread_id,
std::unique_ptr<InspectorParentHandle> inspector_parent_handle) {
Isolate* isolate = context->GetIsolate();
Isolate* isolate = isolate_data->isolate();
HandleScope handle_scope(isolate);
Context::Scope context_scope(context);
const bool use_snapshot = context.IsEmpty();
const EnvSerializeInfo* env_snapshot_info = nullptr;
if (use_snapshot) {
CHECK_NOT_NULL(isolate_data->snapshot_data());
env_snapshot_info = &isolate_data->snapshot_data()->env_info;
}
// TODO(addaleax): This is a much better place for parsing per-Environment
// options than the global parse call.
Environment* env = new Environment(
isolate_data, context, args, exec_args, nullptr, flags, thread_id);
Environment* env = new Environment(isolate_data,
isolate,
args,
exec_args,
env_snapshot_info,
flags,
thread_id);
CHECK_NOT_NULL(env);
if (use_snapshot) {
context = Context::FromSnapshot(isolate,
SnapshotData::kNodeMainContextIndex,
{DeserializeNodeInternalFields, env})
.ToLocalChecked();
CHECK(!context.IsEmpty());
Context::Scope context_scope(context);
if (InitializeContextRuntime(context).IsNothing()) {
FreeEnvironment(env);
return nullptr;
}
SetIsolateErrorHandlers(isolate, {});
}
Context::Scope context_scope(context);
env->InitializeMainContext(context, env_snapshot_info);
#if HAVE_INSPECTOR
if (env->should_create_inspector()) {
@ -415,7 +489,7 @@ Environment* CreateEnvironment(
}
#endif
if (env->principal_realm()->RunBootstrapping().IsEmpty()) {
if (!use_snapshot && env->principal_realm()->RunBootstrapping().IsEmpty()) {
FreeEnvironment(env);
return nullptr;
}
@ -500,6 +574,10 @@ ArrayBufferAllocator* GetArrayBufferAllocator(IsolateData* isolate_data) {
return isolate_data->node_allocator();
}
Local<Context> GetMainContext(Environment* env) {
return env->context();
}
MultiIsolatePlatform* GetMultiIsolatePlatform(Environment* env) {
return GetMultiIsolatePlatform(env->isolate_data());
}

View File

@ -69,6 +69,10 @@ inline MultiIsolatePlatform* IsolateData::platform() const {
return platform_;
}
inline const SnapshotData* IsolateData::snapshot_data() const {
return snapshot_data_;
}
inline void IsolateData::set_worker_context(worker::Worker* context) {
CHECK_NULL(worker_context_); // Should be set only once.
worker_context_ = context;

View File

@ -472,19 +472,20 @@ IsolateData::IsolateData(Isolate* isolate,
uv_loop_t* event_loop,
MultiIsolatePlatform* platform,
ArrayBufferAllocator* node_allocator,
const IsolateDataSerializeInfo* isolate_data_info)
const SnapshotData* snapshot_data)
: isolate_(isolate),
event_loop_(event_loop),
node_allocator_(node_allocator == nullptr ? nullptr
: node_allocator->GetImpl()),
platform_(platform) {
platform_(platform),
snapshot_data_(snapshot_data) {
options_.reset(
new PerIsolateOptions(*(per_process::cli_options->per_isolate)));
if (isolate_data_info == nullptr) {
if (snapshot_data == nullptr) {
CreateProperties();
} else {
DeserializeProperties(isolate_data_info);
DeserializeProperties(&snapshot_data->isolate_data_info);
}
}
@ -675,14 +676,23 @@ Environment::Environment(IsolateData* isolate_data,
thread_id_(thread_id.id == static_cast<uint64_t>(-1)
? AllocateEnvironmentThreadId().id
: thread_id.id) {
constexpr bool is_shared_ro_heap =
#ifdef NODE_V8_SHARED_RO_HEAP
if (!is_main_thread()) {
true;
#else
false;
#endif
if (is_shared_ro_heap && !is_main_thread()) {
// If this is a Worker thread and we are in shared-readonly-heap mode,
// we can always safely use the parent's Isolate's code cache.
CHECK_NOT_NULL(isolate_data->worker_context());
// TODO(addaleax): Adjust for the embedder API snapshot support changes
builtin_loader()->CopySourceAndCodeCacheReferenceFrom(
isolate_data->worker_context()->env()->builtin_loader());
} else if (isolate_data->snapshot_data() != nullptr) {
// ... otherwise, if a snapshot was provided, use its code cache.
builtin_loader()->RefreshCodeCache(
isolate_data->snapshot_data()->code_cache);
}
#endif
// We'll be creating new objects so make sure we've entered the context.
HandleScope handle_scope(isolate);
@ -747,23 +757,6 @@ Environment::Environment(IsolateData* isolate_data,
}
}
Environment::Environment(IsolateData* isolate_data,
Local<Context> context,
const std::vector<std::string>& args,
const std::vector<std::string>& exec_args,
const EnvSerializeInfo* env_info,
EnvironmentFlags::Flags flags,
ThreadId thread_id)
: Environment(isolate_data,
context->GetIsolate(),
args,
exec_args,
env_info,
flags,
thread_id) {
InitializeMainContext(context, env_info);
}
void Environment::InitializeMainContext(Local<Context> context,
const EnvSerializeInfo* env_info) {
principal_realm_ = std::make_unique<Realm>(

View File

@ -126,7 +126,7 @@ class NODE_EXTERN_PRIVATE IsolateData : public MemoryRetainer {
uv_loop_t* event_loop,
MultiIsolatePlatform* platform = nullptr,
ArrayBufferAllocator* node_allocator = nullptr,
const IsolateDataSerializeInfo* isolate_data_info = nullptr);
const SnapshotData* snapshot_data = nullptr);
SET_MEMORY_INFO_NAME(IsolateData)
SET_SELF_SIZE(IsolateData)
void MemoryInfo(MemoryTracker* tracker) const override;
@ -134,6 +134,7 @@ class NODE_EXTERN_PRIVATE IsolateData : public MemoryRetainer {
inline uv_loop_t* event_loop() const;
inline MultiIsolatePlatform* platform() const;
inline const SnapshotData* snapshot_data() const;
inline std::shared_ptr<PerIsolateOptions> options();
inline void set_options(std::shared_ptr<PerIsolateOptions> options);
@ -205,6 +206,7 @@ class NODE_EXTERN_PRIVATE IsolateData : public MemoryRetainer {
uv_loop_t* const event_loop_;
NodeArrayBufferAllocator* const node_allocator_;
MultiIsolatePlatform* platform_;
const SnapshotData* snapshot_data_;
std::shared_ptr<PerIsolateOptions> options_;
worker::Worker* worker_context_ = nullptr;
};
@ -520,6 +522,9 @@ struct SnapshotData {
// and the caller should not consume the snapshot data.
bool Check() const;
static bool FromBlob(SnapshotData* out, FILE* in);
static const SnapshotData* FromEmbedderWrapper(
const EmbedderSnapshotData* data);
EmbedderSnapshotData::Pointer AsEmbedderWrapper() const;
~SnapshotData();
};
@ -610,14 +615,6 @@ class Environment : public MemoryRetainer {
ThreadId thread_id);
void InitializeMainContext(v8::Local<v8::Context> context,
const EnvSerializeInfo* env_info);
// Create an Environment and initialize the provided principal context for it.
Environment(IsolateData* isolate_data,
v8::Local<v8::Context> context,
const std::vector<std::string>& args,
const std::vector<std::string>& exec_args,
const EnvSerializeInfo* env_info,
EnvironmentFlags::Flags flags,
ThreadId thread_id);
~Environment() override;
void InitializeLibuv();

View File

@ -127,6 +127,8 @@ struct napi_module;
// terminally confused when it's done in node_internals.h
namespace node {
struct SnapshotData;
namespace tracing {
class TracingController;
@ -473,6 +475,65 @@ struct IsolateSettings {
modify_code_generation_from_strings_callback = nullptr;
};
// Represents a startup snapshot blob, e.g. created by passing
// --node-snapshot-main=entry.js to the configure script at build time,
// or by running Node.js with the --build-snapshot option.
//
// If used, the snapshot *must* have been built with the same Node.js
// version and V8 flags as the version that is currently running, and will
// be rejected otherwise.
// The same EmbedderSnapshotData instance *must* be passed to both
// `NewIsolate()` and `CreateIsolateData()`. The first `Environment` instance
// should be created with an empty `context` argument and will then
// use the main context included in the snapshot blob. It can be retrieved
// using `GetMainContext()`. `LoadEnvironment` can receive an empty
// `StartExecutionCallback` in this case.
// If V8 was configured with the shared-readonly-heap option, it requires
// all snapshots used to create `Isolate` instances to be identical.
// This option *must* be unset by embedders who wish to use the startup
// feature during the build step by passing the --disable-shared-readonly-heap
// flag to the configure script.
//
// 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.
class EmbedderSnapshotData {
public:
struct DeleteSnapshotData {
void operator()(const EmbedderSnapshotData*) const;
};
using Pointer =
std::unique_ptr<const EmbedderSnapshotData, DeleteSnapshotData>;
// Return an EmbedderSnapshotData object that refers to the built-in
// snapshot of Node.js. This can have been configured through e.g.
// --node-snapshot-main=entry.js.
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.
// 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);
// Returns whether custom snapshots can be used. Currently, this means
// that V8 was configured without the shared-readonly-heap feature.
static bool CanUseCustomSnapshotPerIsolate();
EmbedderSnapshotData(const EmbedderSnapshotData&) = delete;
EmbedderSnapshotData& operator=(const EmbedderSnapshotData&) = delete;
EmbedderSnapshotData(EmbedderSnapshotData&&) = delete;
EmbedderSnapshotData& operator=(EmbedderSnapshotData&&) = delete;
protected:
EmbedderSnapshotData(const SnapshotData* impl, bool owns_impl);
private:
const SnapshotData* impl_;
bool owns_impl_;
friend struct SnapshotData;
};
// Overriding IsolateSettings may produce unexpected behavior
// in Node.js core functionality, so proceed at your own risk.
NODE_EXTERN void SetIsolateUpForNode(v8::Isolate* isolate,
@ -489,10 +550,23 @@ NODE_EXTERN void SetIsolateUpForNode(v8::Isolate* isolate);
NODE_EXTERN v8::Isolate* NewIsolate(ArrayBufferAllocator* allocator,
struct uv_loop_s* event_loop,
MultiIsolatePlatform* platform = nullptr);
// TODO(addaleax): Merge with the function definition above.
NODE_EXTERN v8::Isolate* NewIsolate(ArrayBufferAllocator* allocator,
struct uv_loop_s* event_loop,
MultiIsolatePlatform* platform,
const EmbedderSnapshotData* snapshot_data,
const IsolateSettings& settings = {});
NODE_EXTERN v8::Isolate* NewIsolate(
std::shared_ptr<ArrayBufferAllocator> allocator,
struct uv_loop_s* event_loop,
MultiIsolatePlatform* platform);
// TODO(addaleax): Merge with the function definition above.
NODE_EXTERN v8::Isolate* NewIsolate(
std::shared_ptr<ArrayBufferAllocator> allocator,
struct uv_loop_s* event_loop,
MultiIsolatePlatform* platform,
const EmbedderSnapshotData* snapshot_data,
const IsolateSettings& settings = {});
// Creates a new context with Node.js-specific tweaks.
NODE_EXTERN v8::Local<v8::Context> NewContext(
@ -512,6 +586,13 @@ NODE_EXTERN IsolateData* CreateIsolateData(
struct uv_loop_s* loop,
MultiIsolatePlatform* platform = nullptr,
ArrayBufferAllocator* allocator = nullptr);
// TODO(addaleax): Merge with the function definition above.
NODE_EXTERN IsolateData* CreateIsolateData(
v8::Isolate* isolate,
struct uv_loop_s* loop,
MultiIsolatePlatform* platform,
ArrayBufferAllocator* allocator,
const EmbedderSnapshotData* snapshot_data);
NODE_EXTERN void FreeIsolateData(IsolateData* isolate_data);
struct ThreadId {
@ -571,6 +652,8 @@ struct InspectorParentHandle {
// TODO(addaleax): Maybe move per-Environment options parsing here.
// Returns nullptr when the Environment cannot be created e.g. there are
// pending JavaScript exceptions.
// `context` may be empty if an `EmbedderSnapshotData` instance was provided
// to `NewIsolate()` and `CreateIsolateData()`.
NODE_EXTERN Environment* CreateEnvironment(
IsolateData* isolate_data,
v8::Local<v8::Context> context,
@ -624,6 +707,9 @@ NODE_EXTERN void DefaultProcessExitHandler(Environment* env, int exit_code);
NODE_EXTERN Environment* GetCurrentEnvironment(v8::Local<v8::Context> context);
NODE_EXTERN IsolateData* GetEnvironmentIsolateData(Environment* env);
NODE_EXTERN ArrayBufferAllocator* GetArrayBufferAllocator(IsolateData* data);
// This is mostly useful for Environment* instances that were created through
// a snapshot and have a main context that was read from that snapshot.
NODE_EXTERN v8::Local<v8::Context> GetMainContext(Environment* env);
NODE_EXTERN void OnFatalError(const char* location, const char* message);
NODE_EXTERN void PromiseRejectCallback(v8::PromiseRejectMessage message);
@ -730,6 +816,12 @@ class NODE_EXTERN CommonEnvironmentSetup {
MultiIsolatePlatform* platform,
std::vector<std::string>* errors,
EnvironmentArgs&&... env_args);
template <typename... EnvironmentArgs>
static std::unique_ptr<CommonEnvironmentSetup> CreateWithSnapshot(
MultiIsolatePlatform* platform,
std::vector<std::string>* errors,
const EmbedderSnapshotData* snapshot_data,
EnvironmentArgs&&... env_args);
struct uv_loop_s* event_loop() const;
std::shared_ptr<ArrayBufferAllocator> array_buffer_allocator() const;
@ -750,6 +842,11 @@ class NODE_EXTERN CommonEnvironmentSetup {
MultiIsolatePlatform*,
std::vector<std::string>*,
std::function<Environment*(const CommonEnvironmentSetup*)>);
CommonEnvironmentSetup(
MultiIsolatePlatform*,
std::vector<std::string>*,
const EmbedderSnapshotData*,
std::function<Environment*(const CommonEnvironmentSetup*)>);
};
// Implementation for CommonEnvironmentSetup::Create
@ -768,6 +865,27 @@ std::unique_ptr<CommonEnvironmentSetup> CommonEnvironmentSetup::Create(
if (!errors->empty()) ret.reset();
return ret;
}
// Implementation for ::CreateWithSnapshot -- the ::Create() method
// could call this with a nullptr snapshot_data in a major version.
template <typename... EnvironmentArgs>
std::unique_ptr<CommonEnvironmentSetup>
CommonEnvironmentSetup::CreateWithSnapshot(
MultiIsolatePlatform* platform,
std::vector<std::string>* errors,
const EmbedderSnapshotData* snapshot_data,
EnvironmentArgs&&... env_args) {
auto ret = std::unique_ptr<CommonEnvironmentSetup>(new CommonEnvironmentSetup(
platform,
errors,
snapshot_data,
[&](const CommonEnvironmentSetup* setup) -> Environment* {
return CreateEnvironment(setup->isolate_data(),
setup->context(),
std::forward<EnvironmentArgs>(env_args)...);
}));
if (!errors->empty()) ret.reset();
return ret;
}
/* Converts a unixtime to V8 Date */
NODE_DEPRECATED("Use v8::Date::New() directly",

View File

@ -117,9 +117,7 @@ BaseObjectPtr<ContextifyContext> ContextifyContext::New(
InitializeGlobalTemplates(env->isolate_data());
Local<ObjectTemplate> object_template = env->contextify_global_template();
DCHECK(!object_template.IsEmpty());
bool use_node_snapshot = per_process::cli_options->node_snapshot;
const SnapshotData* snapshot_data =
use_node_snapshot ? SnapshotBuilder::GetEmbeddedSnapshotData() : nullptr;
const SnapshotData* snapshot_data = env->isolate_data()->snapshot_data();
MicrotaskQueue* queue =
options.microtask_queue_wrap

View File

@ -304,7 +304,8 @@ void DefineZlibConstants(v8::Local<v8::Object> target);
v8::Isolate* NewIsolate(v8::Isolate::CreateParams* params,
uv_loop_t* event_loop,
MultiIsolatePlatform* platform,
bool has_snapshot_data = false);
const SnapshotData* snapshot_data = nullptr,
const IsolateSettings& settings = {});
// This overload automatically picks the right 'main_script_id' if no callback
// was provided by the embedder.
v8::MaybeLocal<v8::Value> StartExecution(Environment* env,

View File

@ -71,22 +71,18 @@ NodeMainInstance::NodeMainInstance(const SnapshotData* snapshot_data,
isolate_params_(std::make_unique<Isolate::CreateParams>()),
snapshot_data_(snapshot_data) {
isolate_params_->array_buffer_allocator = array_buffer_allocator_.get();
if (snapshot_data != nullptr) {
SnapshotBuilder::InitializeIsolateParams(snapshot_data,
isolate_params_.get());
}
isolate_ = NewIsolate(
isolate_params_.get(), event_loop, platform, snapshot_data != nullptr);
isolate_ =
NewIsolate(isolate_params_.get(), event_loop, platform, snapshot_data);
CHECK_NOT_NULL(isolate_);
// If the indexes are not nullptr, we are not deserializing
isolate_data_ = std::make_unique<IsolateData>(
isolate_,
event_loop,
platform,
array_buffer_allocator_.get(),
snapshot_data == nullptr ? nullptr : &(snapshot_data->isolate_data_info));
isolate_data_.reset(
CreateIsolateData(isolate_,
event_loop,
platform,
array_buffer_allocator_.get(),
snapshot_data->AsEmbedderWrapper().get()));
isolate_data_->max_young_gen_size =
isolate_params_->constraints.max_young_generation_size_in_bytes();
@ -152,33 +148,10 @@ NodeMainInstance::CreateMainEnvironment(ExitCode* exit_code) {
DeleteFnPtr<Environment, FreeEnvironment> env;
if (snapshot_data_ != nullptr) {
env.reset(new Environment(isolate_data_.get(),
isolate_,
args_,
exec_args_,
&(snapshot_data_->env_info),
EnvironmentFlags::kDefaultFlags,
{}));
#ifdef NODE_V8_SHARED_RO_HEAP
// TODO(addaleax): Do this as part of creating the Environment
// once we store the SnapshotData* itself on IsolateData.
env->builtin_loader()->RefreshCodeCache(snapshot_data_->code_cache);
#endif
context = Context::FromSnapshot(isolate_,
SnapshotData::kNodeMainContextIndex,
{DeserializeNodeInternalFields, env.get()})
.ToLocalChecked();
CHECK(!context.IsEmpty());
Context::Scope context_scope(context);
CHECK(InitializeContextRuntime(context).IsJust());
SetIsolateErrorHandlers(isolate_, {});
env->InitializeMainContext(context, &(snapshot_data_->env_info));
#if HAVE_INSPECTOR
env->InitializeInspector({});
#endif
env.reset(CreateEnvironment(isolate_data_.get(),
Local<Context>(), // read from snapshot
args_,
exec_args_));
#if HAVE_OPENSSL
crypto::InitCryptoOnce(isolate_);
#endif // HAVE_OPENSSL

View File

@ -862,6 +862,15 @@ void SnapshotData::ToBlob(FILE* out) const {
w.Debug("SnapshotData::ToBlob() Wrote %d bytes\n", written_total);
}
const SnapshotData* SnapshotData::FromEmbedderWrapper(
const EmbedderSnapshotData* data) {
return data != nullptr ? data->impl_ : nullptr;
}
EmbedderSnapshotData::Pointer SnapshotData::AsEmbedderWrapper() const {
return EmbedderSnapshotData::Pointer{new EmbedderSnapshotData(this, false)};
}
bool SnapshotData::FromBlob(SnapshotData* out, FILE* in) {
CHECK_EQ(ftell(in), 0);
int err = fseek(in, 0, SEEK_END);
@ -1082,6 +1091,8 @@ const std::vector<intptr_t>& SnapshotBuilder::CollectExternalReferences() {
void SnapshotBuilder::InitializeIsolateParams(const SnapshotData* data,
Isolate::CreateParams* params) {
CHECK_NULL(params->external_references);
CHECK_NULL(params->snapshot_blob);
params->external_references = CollectExternalReferences().data();
params->snapshot_blob =
const_cast<v8::StartupData*>(&(data->v8_snapshot_blob_data));

View File

@ -584,9 +584,7 @@ void Worker::New(const FunctionCallbackInfo<Value>& args) {
exec_argv_out = env->exec_argv();
}
bool use_node_snapshot = per_process::cli_options->node_snapshot;
const SnapshotData* snapshot_data =
use_node_snapshot ? SnapshotBuilder::GetEmbeddedSnapshotData() : nullptr;
const SnapshotData* snapshot_data = env->isolate_data()->snapshot_data();
Worker* worker = new Worker(env,
args.This(),

View File

@ -4,6 +4,8 @@
// Note: This file is being referred to from doc/api/embedding.md, and excerpts
// from it are included in the documentation. Try to keep these in sync.
// Snapshot support is not part of the embedder API docs yet due to its
// experimental nature, although it is of course documented in node.h.
using node::CommonEnvironmentSetup;
using node::Environment;
@ -55,9 +57,22 @@ int RunNodeInstance(MultiIsolatePlatform* platform,
const std::vector<std::string>& exec_args) {
int exit_code = 0;
node::EmbedderSnapshotData::Pointer snapshot;
auto snapshot_arg_it =
std::find(args.begin(), args.end(), "--embedder-snapshot-blob");
if (snapshot_arg_it < args.end() - 1) {
FILE* fp = fopen((snapshot_arg_it + 1)->c_str(), "r");
assert(fp != nullptr);
snapshot = node::EmbedderSnapshotData::FromFile(fp);
fclose(fp);
}
std::vector<std::string> errors;
std::unique_ptr<CommonEnvironmentSetup> setup =
CommonEnvironmentSetup::Create(platform, &errors, args, exec_args);
snapshot
? CommonEnvironmentSetup::CreateWithSnapshot(
platform, &errors, snapshot.get(), args, exec_args)
: CommonEnvironmentSetup::Create(platform, &errors, args, exec_args);
if (!setup) {
for (const std::string& err : errors)
fprintf(stderr, "%s: %s\n", args[0].c_str(), err.c_str());
@ -73,13 +88,18 @@ int RunNodeInstance(MultiIsolatePlatform* platform,
HandleScope handle_scope(isolate);
Context::Scope context_scope(setup->context());
MaybeLocal<Value> loadenv_ret = node::LoadEnvironment(
env,
"const publicRequire ="
" require('module').createRequire(process.cwd() + '/');"
"globalThis.require = publicRequire;"
"globalThis.embedVars = { nön_ascıı: '🏳️‍🌈' };"
"require('vm').runInThisContext(process.argv[1]);");
MaybeLocal<Value> loadenv_ret;
if (snapshot) {
loadenv_ret = node::LoadEnvironment(env, node::StartExecutionCallback{});
} else {
loadenv_ret = node::LoadEnvironment(
env,
"const publicRequire ="
" require('module').createRequire(process.cwd() + '/');"
"globalThis.require = publicRequire;"
"globalThis.embedVars = { nön_ascıı: '🏳️‍🌈' };"
"require('vm').runInThisContext(process.argv[1]);");
}
if (loadenv_ret.IsEmpty()) // There has been a JS exception.
return 1;

View File

@ -1,17 +1,26 @@
'use strict';
const common = require('../common');
const fixtures = require('../common/fixtures');
const tmpdir = require('../common/tmpdir');
const assert = require('assert');
const child_process = require('child_process');
const path = require('path');
const fs = require('fs');
tmpdir.refresh();
common.allowGlobals(global.require);
common.allowGlobals(global.embedVars);
let binary = `out/${common.buildType}/embedtest`;
if (common.isWindows) {
binary += '.exe';
function resolveBuiltBinary(bin) {
let binary = `out/${common.buildType}/${bin}`;
if (common.isWindows) {
binary += '.exe';
}
return path.resolve(__dirname, '..', '..', binary);
}
binary = path.resolve(__dirname, '..', '..', binary);
const binary = resolveBuiltBinary('embedtest');
const standaloneNodeBinary = resolveBuiltBinary('node');
assert.strictEqual(
child_process.spawnSync(binary, ['console.log(42)'])
@ -41,3 +50,39 @@ const fixturePath = JSON.stringify(fixtures.path('exit.js'));
assert.strictEqual(
child_process.spawnSync(binary, [`require(${fixturePath})`, 92]).status,
92);
// Basic snapshot support
{
const snapshotFixture = fixtures.path('snapshot', 'echo-args.js');
const blobPath = path.join(tmpdir.path, 'embedder-snapshot.blob');
const buildSnapshotArgs = [snapshotFixture, 'arg1', 'arg2'];
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,
], {
cwd: tmpdir.path,
}).status, 0);
const spawnResult = child_process.spawnSync(binary, ['--', ...runEmbeddedArgs]);
assert.deepStrictEqual(JSON.parse(spawnResult.stdout), {
originalArgv: [standaloneNodeBinary, ...buildSnapshotArgs],
currentArgv: [binary, ...runEmbeddedArgs],
});
}
// Create workers and vm contexts after deserialization
{
const snapshotFixture = fixtures.path('snapshot', 'create-worker-and-vm.js');
const blobPath = path.join(tmpdir.path, 'embedder-snapshot.blob');
fs.rmSync(blobPath, { force: true });
assert.strictEqual(child_process.spawnSync(standaloneNodeBinary, [
'--snapshot-blob', blobPath, '--build-snapshot', snapshotFixture,
], {
cwd: tmpdir.path,
}).status, 0);
assert.strictEqual(
child_process.spawnSync(binary, ['--', '--embedder-snapshot-blob', blobPath]).status,
0);
}

View File

@ -0,0 +1,20 @@
const {
setDeserializeMainFunction,
} = require('v8').startupSnapshot;
const assert = require('assert');
setDeserializeMainFunction(() => {
const vm = require('vm');
const { Worker } = require('worker_threads');
assert.strictEqual(vm.runInNewContext('21+21'), 42);
const worker = new Worker(
'require("worker_threads").parentPort.postMessage({value: 21 + 21})',
{ eval: true });
const messages = [];
worker.on('message', message => messages.push(message));
process.on('beforeExit', () => {
assert.deepStrictEqual(messages, [{value:42}]);
})
});

12
test/fixtures/snapshot/echo-args.js vendored Normal file
View File

@ -0,0 +1,12 @@
const {
setDeserializeMainFunction,
} = require('v8').startupSnapshot;
const originalArgv = [...process.argv];
setDeserializeMainFunction(() => {
console.log(JSON.stringify({
currentArgv: process.argv,
originalArgv
}));
});