Skip to content

Commit

Permalink
src: allow snapshotting from the embedder API
Browse files Browse the repository at this point in the history
PR-URL: #45888
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com>
  • Loading branch information
addaleax authored and nodejs-github-bot committed Feb 3, 2023
1 parent 02fad4f commit bfadee5
Show file tree
Hide file tree
Showing 13 changed files with 307 additions and 197 deletions.
27 changes: 17 additions & 10 deletions lib/internal/main/mksnapshot.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const {
const binding = internalBinding('mksnapshot');
const { BuiltinModule } = require('internal/bootstrap/loaders');
const {
getEmbedderEntryFunction,
compileSerializeMain,
} = binding;

Expand Down Expand Up @@ -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,
Expand All @@ -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(() => {
Expand Down
96 changes: 85 additions & 11 deletions src/api/embed_helpers.cc
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ using v8::Locker;
using v8::Maybe;
using v8::Nothing;
using v8::SealHandleScope;
using v8::SnapshotCreator;

namespace node {

Expand Down Expand Up @@ -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);
Expand All @@ -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;
Expand All @@ -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) {
Expand All @@ -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();
}
Expand All @@ -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)
Expand All @@ -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()) {
Expand Down Expand Up @@ -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()(
Expand Down Expand Up @@ -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) {}
Expand Down
10 changes: 10 additions & 0 deletions src/env-inl.h
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand Down
4 changes: 4 additions & 0 deletions src/env.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -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.
Expand Down
17 changes: 12 additions & 5 deletions src/node.cc
Original file line number Diff line number Diff line change
Expand Up @@ -276,14 +276,21 @@ MaybeLocal<Value> StartExecution(Environment* env, StartExecutionCallback cb) {
if (cb != nullptr) {
EscapableHandleScope scope(env->isolate());

if (StartExecution(env, "internal/main/environment").IsEmpty()) return {};
if (env->isolate_data()->options()->build_snapshot) {
// TODO(addaleax): pass the callback to the main script more directly,
// e.g. by making StartExecution(env, builtin) parametrizable
env->set_embedder_mksnapshot_entry_point(std::move(cb));
auto reset_entry_point =
OnScopeLeave([&]() { env->set_embedder_mksnapshot_entry_point({}); });

return StartExecution(env, "internal/main/mksnapshot");
}

StartExecutionCallbackInfo info = {
if (StartExecution(env, "internal/main/environment").IsEmpty()) return {};
return scope.EscapeMaybe(cb({
env->process_object(),
env->builtin_module_require(),
};

return scope.EscapeMaybe(cb(info));
}));
}

// TODO(joyeecheung): move these conditions into JS land and let the
Expand Down
Loading

0 comments on commit bfadee5

Please sign in to comment.