mirror of
https://github.com/nodejs/node.git
synced 2026-11-09 15:11:11 -05:00
The `const std::vector<char>&` variant only delegates to a method that converts it to a `std::string_view` anyway, but adding this capability to the public API as well means that it’s easier to store snapshot data in embedding applications (because using `std::vector<>` enforces heap allocation and `std::string_view` does not). PR-URL: https://github.com/nodejs/node/pull/52595 Reviewed-By: Yagiz Nizipli <yagiz.nizipli@sentry.io> Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com>
358 lines
11 KiB
C++
358 lines
11 KiB
C++
#include "debug_utils-inl.h"
|
|
#include "env-inl.h"
|
|
#include "node.h"
|
|
#include "node_snapshot_builder.h"
|
|
|
|
using v8::Context;
|
|
using v8::Function;
|
|
using v8::Global;
|
|
using v8::HandleScope;
|
|
using v8::Isolate;
|
|
using v8::Just;
|
|
using v8::Local;
|
|
using v8::Locker;
|
|
using v8::Maybe;
|
|
using v8::Nothing;
|
|
using v8::SealHandleScope;
|
|
using v8::SnapshotCreator;
|
|
using v8::TryCatch;
|
|
|
|
namespace node {
|
|
|
|
Maybe<ExitCode> SpinEventLoopInternal(Environment* env) {
|
|
CHECK_NOT_NULL(env);
|
|
MultiIsolatePlatform* platform = GetMultiIsolatePlatform(env);
|
|
CHECK_NOT_NULL(platform);
|
|
|
|
Isolate* isolate = env->isolate();
|
|
HandleScope handle_scope(isolate);
|
|
Context::Scope context_scope(env->context());
|
|
SealHandleScope seal(isolate);
|
|
|
|
if (env->is_stopping()) return Nothing<ExitCode>();
|
|
|
|
env->set_trace_sync_io(env->options()->trace_sync_io);
|
|
{
|
|
bool more;
|
|
env->performance_state()->Mark(
|
|
node::performance::NODE_PERFORMANCE_MILESTONE_LOOP_START);
|
|
do {
|
|
if (env->is_stopping()) break;
|
|
uv_run(env->event_loop(), UV_RUN_DEFAULT);
|
|
if (env->is_stopping()) break;
|
|
|
|
platform->DrainTasks(isolate);
|
|
|
|
more = uv_loop_alive(env->event_loop());
|
|
if (more && !env->is_stopping()) continue;
|
|
|
|
if (EmitProcessBeforeExit(env).IsNothing())
|
|
break;
|
|
|
|
{
|
|
HandleScope handle_scope(isolate);
|
|
if (env->RunSnapshotSerializeCallback().IsEmpty()) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Emit `beforeExit` if the loop became alive either after emitting
|
|
// event, or after running some callbacks.
|
|
more = uv_loop_alive(env->event_loop());
|
|
} while (more == true && !env->is_stopping());
|
|
env->performance_state()->Mark(
|
|
node::performance::NODE_PERFORMANCE_MILESTONE_LOOP_EXIT);
|
|
}
|
|
if (env->is_stopping()) return Nothing<ExitCode>();
|
|
|
|
env->set_trace_sync_io(false);
|
|
// Clear the serialize callback even though the JS-land queue should
|
|
// be empty this point so that the deserialized instance won't
|
|
// attempt to call into JS again.
|
|
env->set_snapshot_serialize_callback(Local<Function>());
|
|
|
|
env->PrintInfoForSnapshotIfDebug();
|
|
env->ForEachRealm([](Realm* realm) { realm->VerifyNoStrongBaseObjects(); });
|
|
Maybe<ExitCode> exit_code = EmitProcessExitInternal(env);
|
|
if (exit_code.FromMaybe(ExitCode::kGenericUserError) !=
|
|
ExitCode::kNoFailure) {
|
|
return exit_code;
|
|
}
|
|
|
|
auto unsettled_tla = env->CheckUnsettledTopLevelAwait();
|
|
if (unsettled_tla.IsNothing()) {
|
|
return Nothing<ExitCode>();
|
|
}
|
|
if (!unsettled_tla.FromJust()) {
|
|
return Just(ExitCode::kUnsettledTopLevelAwait);
|
|
}
|
|
return Just(ExitCode::kNoFailure);
|
|
}
|
|
|
|
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> 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,
|
|
const SnapshotConfig* snapshot_config)
|
|
: impl_(new Impl()) {
|
|
CHECK_NOT_NULL(platform);
|
|
CHECK_NOT_NULL(errors);
|
|
|
|
impl_->platform = platform;
|
|
uv_loop_t* loop = &impl_->loop;
|
|
// Use `data` to tell the destructor whether the loop was initialized or not.
|
|
loop->data = nullptr;
|
|
int ret = uv_loop_init(loop);
|
|
if (ret != 0) {
|
|
errors->push_back(
|
|
SPrintF("Failed to initialize loop: %s", uv_err_name(ret)));
|
|
return;
|
|
}
|
|
loop->data = this;
|
|
|
|
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);
|
|
HandleScope handle_scope(isolate);
|
|
|
|
TryCatch bootstrapCatch(isolate);
|
|
auto print_Exception = OnScopeLeave([&]() {
|
|
if (bootstrapCatch.HasCaught()) {
|
|
errors->push_back(FormatCaughtException(
|
|
isolate, isolate->GetCurrentContext(), bootstrapCatch));
|
|
}
|
|
});
|
|
|
|
impl_->isolate_data.reset(CreateIsolateData(
|
|
isolate, loop, platform, impl_->allocator.get(), snapshot_data));
|
|
impl_->isolate_data->set_snapshot_config(snapshot_config);
|
|
|
|
if (snapshot_data) {
|
|
impl_->env.reset(make_env(this));
|
|
if (impl_->env) {
|
|
impl_->main_context.Reset(isolate, impl_->env->context());
|
|
}
|
|
return;
|
|
}
|
|
|
|
Local<Context> context = NewContext(isolate);
|
|
impl_->main_context.Reset(isolate, context);
|
|
if (context.IsEmpty()) {
|
|
errors->push_back("Failed to initialize V8 Context");
|
|
return;
|
|
}
|
|
|
|
Context::Scope context_scope(context);
|
|
impl_->env.reset(make_env(this));
|
|
}
|
|
}
|
|
|
|
CommonEnvironmentSetup::CommonEnvironmentSetup(
|
|
MultiIsolatePlatform* platform,
|
|
std::vector<std::string>* errors,
|
|
std::function<Environment*(const CommonEnvironmentSetup*)> 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,
|
|
const SnapshotConfig& snapshot_config) {
|
|
// 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));
|
|
},
|
|
&snapshot_config));
|
|
if (!errors->empty()) ret.reset();
|
|
return ret;
|
|
}
|
|
|
|
CommonEnvironmentSetup::~CommonEnvironmentSetup() {
|
|
if (impl_->isolate != nullptr) {
|
|
Isolate* isolate = impl_->isolate;
|
|
{
|
|
Locker locker(isolate);
|
|
Isolate::Scope isolate_scope(isolate);
|
|
|
|
impl_->main_context.Reset();
|
|
impl_->env.reset();
|
|
impl_->isolate_data.reset();
|
|
}
|
|
|
|
bool platform_finished = false;
|
|
impl_->platform->AddIsolateFinishedCallback(isolate, [](void* data) {
|
|
*static_cast<bool*>(data) = true;
|
|
}, &platform_finished);
|
|
impl_->platform->UnregisterIsolate(isolate);
|
|
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)
|
|
uv_run(&impl_->loop, UV_RUN_ONCE);
|
|
}
|
|
|
|
if (impl_->isolate || impl_->loop.data != nullptr)
|
|
CheckedUvLoopClose(&impl_->loop);
|
|
|
|
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);
|
|
if (exit_code != ExitCode::kNoFailure) return {};
|
|
|
|
return result;
|
|
}
|
|
|
|
Maybe<int> SpinEventLoop(Environment* env) {
|
|
Maybe<ExitCode> result = SpinEventLoopInternal(env);
|
|
if (result.IsNothing()) {
|
|
return Nothing<int>();
|
|
}
|
|
return Just(static_cast<int>(result.FromJust()));
|
|
}
|
|
|
|
uv_loop_t* CommonEnvironmentSetup::event_loop() const {
|
|
return &impl_->loop;
|
|
}
|
|
|
|
std::shared_ptr<ArrayBufferAllocator>
|
|
CommonEnvironmentSetup::array_buffer_allocator() const {
|
|
return impl_->allocator;
|
|
}
|
|
|
|
Isolate* CommonEnvironmentSetup::isolate() const {
|
|
return impl_->isolate;
|
|
}
|
|
|
|
IsolateData* CommonEnvironmentSetup::isolate_data() const {
|
|
return impl_->isolate_data.get();
|
|
}
|
|
|
|
Environment* CommonEnvironmentSetup::env() const {
|
|
return impl_->env.get();
|
|
}
|
|
|
|
v8::Local<v8::Context> CommonEnvironmentSetup::context() const {
|
|
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()(
|
|
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::FromBlob(
|
|
const std::vector<char>& in) {
|
|
return FromBlob(std::string_view(in.data(), in.size()));
|
|
}
|
|
|
|
EmbedderSnapshotData::Pointer EmbedderSnapshotData::FromBlob(
|
|
std::string_view 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::Pointer EmbedderSnapshotData::FromFile(FILE* in) {
|
|
return FromBlob(ReadFileSync(in));
|
|
}
|
|
|
|
std::vector<char> EmbedderSnapshotData::ToBlob() const {
|
|
return impl_->ToBlob();
|
|
}
|
|
|
|
void EmbedderSnapshotData::ToFile(FILE* out) const {
|
|
impl_->ToFile(out);
|
|
}
|
|
|
|
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
|