mirror of
https://github.com/nodejs/node.git
synced 2025-04-29 22:40:57 +00:00
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:
parent
d523bfe2c4
commit
06bb6b42b3
@ -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
|
||||
|
@ -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(¶ms, event_loop, platform);
|
||||
return NewIsolate(¶ms,
|
||||
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(¶ms,
|
||||
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(¶ms, 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());
|
||||
}
|
||||
|
@ -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;
|
||||
|
41
src/env.cc
41
src/env.cc
@ -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>(
|
||||
|
15
src/env.h
15
src/env.h
@ -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();
|
||||
|
118
src/node.h
118
src/node.h
@ -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",
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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));
|
||||
|
@ -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(),
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
20
test/fixtures/snapshot/create-worker-and-vm.js
vendored
Normal file
20
test/fixtures/snapshot/create-worker-and-vm.js
vendored
Normal 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
12
test/fixtures/snapshot/echo-args.js
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
const {
|
||||
setDeserializeMainFunction,
|
||||
} = require('v8').startupSnapshot;
|
||||
|
||||
const originalArgv = [...process.argv];
|
||||
|
||||
setDeserializeMainFunction(() => {
|
||||
console.log(JSON.stringify({
|
||||
currentArgv: process.argv,
|
||||
originalArgv
|
||||
}));
|
||||
});
|
Loading…
Reference in New Issue
Block a user