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:
parent
02fad4f40a
commit
bfadee5e68
@ -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(() => {
|
||||
|
@ -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) {}
|
||||
|
@ -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];
|
||||
|
@ -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.
|
||||
|
17
src/node.cc
17
src/node.cc
@ -276,14 +276,21 @@ MaybeLocal<Value> StartExecution(Environment* env, StartExecutionCallback cb) {
|
||||
if (cb != nullptr) {
|
||||
EscapableHandleScope scope(env->isolate());
|
||||
|
||||
if (StartExecution(env, "internal/main/environment").IsEmpty()) return {};
|
||||
if (env->isolate_data()->options()->build_snapshot) {
|
||||
// TODO(addaleax): pass the callback to the main script more directly,
|
||||
// e.g. by making StartExecution(env, builtin) parametrizable
|
||||
env->set_embedder_mksnapshot_entry_point(std::move(cb));
|
||||
auto reset_entry_point =
|
||||
OnScopeLeave([&]() { env->set_embedder_mksnapshot_entry_point({}); });
|
||||
|
||||
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
|
||||
|
44
src/node.h
44
src/node.h
@ -511,11 +511,16 @@ class EmbedderSnapshotData {
|
||||
static Pointer BuiltinSnapshotData();
|
||||
|
||||
// Return an EmbedderSnapshotData object that is based on an input file.
|
||||
// Calling this method will not consume but not close the FILE* handle.
|
||||
// Calling this method will consume but not close the FILE* handle.
|
||||
// The FILE* handle can be closed immediately following this call.
|
||||
// If the snapshot is invalid, this returns an empty pointer.
|
||||
static Pointer FromFile(FILE* in);
|
||||
|
||||
// Write this EmbedderSnapshotData object to an output file.
|
||||
// Calling this method will not close the FILE* handle.
|
||||
// The FILE* handle can be closed immediately following this call.
|
||||
void ToFile(FILE* out) const;
|
||||
|
||||
// Returns whether custom snapshots can be used. Currently, this means
|
||||
// that V8 was configured without the shared-readonly-heap feature.
|
||||
static bool CanUseCustomSnapshotPerIsolate();
|
||||
@ -532,6 +537,7 @@ class EmbedderSnapshotData {
|
||||
const SnapshotData* impl_;
|
||||
bool owns_impl_;
|
||||
friend struct SnapshotData;
|
||||
friend class CommonEnvironmentSetup;
|
||||
};
|
||||
|
||||
// Overriding IsolateSettings may produce unexpected behavior
|
||||
@ -817,13 +823,35 @@ class NODE_EXTERN CommonEnvironmentSetup {
|
||||
std::vector<std::string>* errors,
|
||||
EnvironmentArgs&&... env_args);
|
||||
template <typename... EnvironmentArgs>
|
||||
static std::unique_ptr<CommonEnvironmentSetup> CreateWithSnapshot(
|
||||
static std::unique_ptr<CommonEnvironmentSetup> CreateFromSnapshot(
|
||||
MultiIsolatePlatform* platform,
|
||||
std::vector<std::string>* errors,
|
||||
const EmbedderSnapshotData* snapshot_data,
|
||||
EnvironmentArgs&&... env_args);
|
||||
|
||||
// Create an embedding setup which will be used for creating a snapshot
|
||||
// using CreateSnapshot().
|
||||
//
|
||||
// This will create and attach a v8::SnapshotCreator to this instance,
|
||||
// and the same restrictions apply to this instance that also apply to
|
||||
// other V8 snapshotting environments.
|
||||
// Not all Node.js APIs are supported in this case. Currently, there is
|
||||
// no support for native/host objects other than Node.js builtins
|
||||
// in the snapshot.
|
||||
//
|
||||
// Snapshots are an *experimental* feature. In particular, the embedder API
|
||||
// exposed through this class is subject to change or removal between Node.js
|
||||
// versions, including possible API and ABI breakage.
|
||||
static std::unique_ptr<CommonEnvironmentSetup> CreateForSnapshotting(
|
||||
MultiIsolatePlatform* platform,
|
||||
std::vector<std::string>* errors,
|
||||
const std::vector<std::string>& args = {},
|
||||
const std::vector<std::string>& exec_args = {});
|
||||
EmbedderSnapshotData::Pointer CreateSnapshot();
|
||||
|
||||
struct uv_loop_s* event_loop() const;
|
||||
v8::SnapshotCreator* snapshot_creator();
|
||||
// Empty for snapshotting environments.
|
||||
std::shared_ptr<ArrayBufferAllocator> array_buffer_allocator() const;
|
||||
v8::Isolate* isolate() const;
|
||||
IsolateData* isolate_data() const;
|
||||
@ -836,8 +864,14 @@ class NODE_EXTERN CommonEnvironmentSetup {
|
||||
CommonEnvironmentSetup& operator=(CommonEnvironmentSetup&&) = delete;
|
||||
|
||||
private:
|
||||
enum Flags : uint32_t {
|
||||
kNoFlags = 0,
|
||||
kIsForSnapshotting = 1,
|
||||
};
|
||||
|
||||
struct Impl;
|
||||
Impl* impl_;
|
||||
|
||||
CommonEnvironmentSetup(
|
||||
MultiIsolatePlatform*,
|
||||
std::vector<std::string>*,
|
||||
@ -846,6 +880,7 @@ class NODE_EXTERN CommonEnvironmentSetup {
|
||||
MultiIsolatePlatform*,
|
||||
std::vector<std::string>*,
|
||||
const EmbedderSnapshotData*,
|
||||
uint32_t flags,
|
||||
std::function<Environment*(const CommonEnvironmentSetup*)>);
|
||||
};
|
||||
|
||||
@ -865,11 +900,11 @@ std::unique_ptr<CommonEnvironmentSetup> CommonEnvironmentSetup::Create(
|
||||
if (!errors->empty()) ret.reset();
|
||||
return ret;
|
||||
}
|
||||
// Implementation for ::CreateWithSnapshot -- the ::Create() method
|
||||
// Implementation for ::CreateFromSnapshot -- the ::Create() method
|
||||
// could call this with a nullptr snapshot_data in a major version.
|
||||
template <typename... EnvironmentArgs>
|
||||
std::unique_ptr<CommonEnvironmentSetup>
|
||||
CommonEnvironmentSetup::CreateWithSnapshot(
|
||||
CommonEnvironmentSetup::CreateFromSnapshot(
|
||||
MultiIsolatePlatform* platform,
|
||||
std::vector<std::string>* errors,
|
||||
const EmbedderSnapshotData* snapshot_data,
|
||||
@ -878,6 +913,7 @@ CommonEnvironmentSetup::CreateWithSnapshot(
|
||||
platform,
|
||||
errors,
|
||||
snapshot_data,
|
||||
Flags::kNoFlags,
|
||||
[&](const CommonEnvironmentSetup* setup) -> Environment* {
|
||||
return CreateEnvironment(setup->isolate_data(),
|
||||
setup->context(),
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
Loading…
x
Reference in New Issue
Block a user