Skip to content

Commit

Permalink
src: iterate over base objects to prepare for snapshot
Browse files Browse the repository at this point in the history
Instead of iterating over the bindings, iterate over the base
objects that are snapshottable. This allows us to snapshot
base objects that are not bindings. In addition this refactors
the InternalFieldInfo class to eliminate potential undefined
behaviors, and renames it to InternalFieldInfoBase.
The {de}serialize callbacks now expect a InternalFieldInfo struct
nested in Snapshotable classes that can be used to carry
serialization data around. This allows us to create structs
inheriting from InternalFieldInfo for Snapshotable objects
that need custom fields.

PR-URL: nodejs#44192
Refs: nodejs#37476
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Chengzhong Wu <legendecas@gmail.com>
  • Loading branch information
joyeecheung committed Oct 14, 2022
1 parent 7847629 commit 5144634
Show file tree
Hide file tree
Showing 13 changed files with 139 additions and 116 deletions.
11 changes: 0 additions & 11 deletions src/env-inl.h
Original file line number Diff line number Diff line change
Expand Up @@ -801,17 +801,6 @@ void Environment::ForEachBaseObject(T&& iterator) {
cleanup_queue_.ForEachBaseObject(std::forward<T>(iterator));
}

template <typename T>
void Environment::ForEachBindingData(T&& iterator) {
BindingDataStore* map = static_cast<BindingDataStore*>(
context()->GetAlignedPointerFromEmbedderData(
ContextEmbedderIndex::kBindingListIndex));
DCHECK_NOT_NULL(map);
for (auto& it : *map) {
iterator(it.first, it.second);
}
}

void Environment::modify_base_object_count(int64_t delta) {
base_object_count_ += delta;
}
Expand Down
13 changes: 8 additions & 5 deletions src/env.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1664,7 +1664,6 @@ EnvSerializeInfo Environment::Serialize(SnapshotCreator* creator) {
EnvSerializeInfo info;
Local<Context> ctx = context();

SerializeBindingData(this, creator, &info);
// Currently all modules are compiled without cache in builtin snapshot
// builder.
info.builtins = std::vector<std::string>(builtins_without_cache.begin(),
Expand Down Expand Up @@ -1692,6 +1691,10 @@ EnvSerializeInfo Environment::Serialize(SnapshotCreator* creator) {
ENVIRONMENT_STRONG_PERSISTENT_VALUES(V)
#undef V

// Do this after other creator->AddData() calls so that Snapshotable objects
// can use 0 to indicate that a SnapshotIndex is invalid.
SerializeSnapshotableObjects(this, creator, &info);

info.context = creator->AddData(ctx, context());
return info;
}
Expand Down Expand Up @@ -1724,9 +1727,9 @@ std::ostream& operator<<(std::ostream& output,

std::ostream& operator<<(std::ostream& output, const EnvSerializeInfo& i) {
output << "{\n"
<< "// -- bindings begins --\n"
<< i.bindings << ",\n"
<< "// -- bindings ends --\n"
<< "// -- native_objects begins --\n"
<< i.native_objects << ",\n"
<< "// -- native_objects ends --\n"
<< "// -- builtins begins --\n"
<< i.builtins << ",\n"
<< "// -- builtins ends --\n"
Expand All @@ -1753,7 +1756,7 @@ std::ostream& operator<<(std::ostream& output, const EnvSerializeInfo& i) {
void Environment::EnqueueDeserializeRequest(DeserializeRequestCallback cb,
Local<Object> holder,
int index,
InternalFieldInfo* info) {
InternalFieldInfoBase* info) {
DCHECK_EQ(index, BaseObject::kEmbedderType);
DeserializeRequest request{cb, {isolate(), holder}, index, info};
deserialize_requests_.push_back(std::move(request));
Expand Down
13 changes: 5 additions & 8 deletions src/env.h
Original file line number Diff line number Diff line change
Expand Up @@ -926,19 +926,19 @@ class ShouldNotAbortOnUncaughtScope {
typedef void (*DeserializeRequestCallback)(v8::Local<v8::Context> context,
v8::Local<v8::Object> holder,
int index,
InternalFieldInfo* info);
InternalFieldInfoBase* info);
struct DeserializeRequest {
DeserializeRequestCallback cb;
v8::Global<v8::Object> holder;
int index;
InternalFieldInfo* info = nullptr; // Owned by the request
InternalFieldInfoBase* info = nullptr; // Owned by the request

// Move constructor
DeserializeRequest(DeserializeRequest&& other) = default;
};

struct EnvSerializeInfo {
std::vector<PropInfo> bindings;
std::vector<PropInfo> native_objects;
std::vector<std::string> builtins;
AsyncHooks::SerializeInfo async_hooks;
TickInfo::SerializeInfo tick_info;
Expand Down Expand Up @@ -1033,7 +1033,7 @@ class Environment : public MemoryRetainer {
void EnqueueDeserializeRequest(DeserializeRequestCallback cb,
v8::Local<v8::Object> holder,
int index,
InternalFieldInfo* info);
InternalFieldInfoBase* info);
void RunDeserializeRequests();
// Should be called before InitializeInspector()
void InitializeDiagnostics();
Expand Down Expand Up @@ -1455,7 +1455,7 @@ class Environment : public MemoryRetainer {
void RemoveUnmanagedFd(int fd);

template <typename T>
void ForEachBindingData(T&& iterator);
void ForEachBaseObject(T&& iterator);

inline void set_heap_snapshot_near_heap_limit(uint32_t limit);
inline bool is_in_heapsnapshot_heap_limit_callback() const;
Expand Down Expand Up @@ -1615,9 +1615,6 @@ class Environment : public MemoryRetainer {
std::function<void(Environment*, int)> process_exit_handler_ {
DefaultProcessExitHandler };

template <typename T>
void ForEachBaseObject(T&& iterator);

#define V(PropertyName, TypeName) v8::Global<TypeName> PropertyName ## _;
ENVIRONMENT_STRONG_PERSISTENT_VALUES(V)
#undef V
Expand Down
22 changes: 12 additions & 10 deletions src/node_blob.cc
Original file line number Diff line number Diff line change
Expand Up @@ -460,11 +460,10 @@ BlobBindingData::StoredDataObject BlobBindingData::get_data_object(
return entry->second;
}

void BlobBindingData::Deserialize(
Local<Context> context,
Local<Object> holder,
int index,
InternalFieldInfo* info) {
void BlobBindingData::Deserialize(Local<Context> context,
Local<Object> holder,
int index,
InternalFieldInfoBase* info) {
DCHECK_EQ(index, BaseObject::kEmbedderType);
HandleScope scope(context->GetIsolate());
Environment* env = Environment::GetCurrent(context);
Expand All @@ -473,15 +472,18 @@ void BlobBindingData::Deserialize(
CHECK_NOT_NULL(binding);
}

void BlobBindingData::PrepareForSerialization(
Local<Context> context,
v8::SnapshotCreator* creator) {
bool BlobBindingData::PrepareForSerialization(Local<Context> context,
v8::SnapshotCreator* creator) {
// Stored blob objects are not actually persisted.
// Return true because we need to maintain the reference to the binding from
// JS land.
return true;
}

InternalFieldInfo* BlobBindingData::Serialize(int index) {
InternalFieldInfoBase* BlobBindingData::Serialize(int index) {
DCHECK_EQ(index, BaseObject::kEmbedderType);
InternalFieldInfo* info = InternalFieldInfo::New(type());
InternalFieldInfo* info =
InternalFieldInfoBase::New<InternalFieldInfo>(type());
return info;
}

Expand Down
2 changes: 2 additions & 0 deletions src/node_blob.h
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,8 @@ class BlobBindingData : public SnapshotableObject {
public:
explicit BlobBindingData(Environment* env, v8::Local<v8::Object> wrap);

using InternalFieldInfo = InternalFieldInfoBase;

SERIALIZABLE_OBJECT_METHODS()

static constexpr FastStringKey type_name{"node::BlobBindingData"};
Expand Down
12 changes: 8 additions & 4 deletions src/node_file.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2566,26 +2566,30 @@ BindingData::BindingData(Environment* env, v8::Local<v8::Object> wrap)
void BindingData::Deserialize(Local<Context> context,
Local<Object> holder,
int index,
InternalFieldInfo* info) {
InternalFieldInfoBase* info) {
DCHECK_EQ(index, BaseObject::kEmbedderType);
HandleScope scope(context->GetIsolate());
Environment* env = Environment::GetCurrent(context);
BindingData* binding = env->AddBindingData<BindingData>(context, holder);
CHECK_NOT_NULL(binding);
}

void BindingData::PrepareForSerialization(Local<Context> context,
bool BindingData::PrepareForSerialization(Local<Context> context,
v8::SnapshotCreator* creator) {
CHECK(file_handle_read_wrap_freelist.empty());
// We'll just re-initialize the buffers in the constructor since their
// contents can be thrown away once consumed in the previous call.
stats_field_array.Release();
stats_field_bigint_array.Release();
// Return true because we need to maintain the reference to the binding from
// JS land.
return true;
}

InternalFieldInfo* BindingData::Serialize(int index) {
InternalFieldInfoBase* BindingData::Serialize(int index) {
DCHECK_EQ(index, BaseObject::kEmbedderType);
InternalFieldInfo* info = InternalFieldInfo::New(type());
InternalFieldInfo* info =
InternalFieldInfoBase::New<InternalFieldInfo>(type());
return info;
}

Expand Down
1 change: 1 addition & 0 deletions src/node_file.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ class BindingData : public SnapshotableObject {
std::vector<BaseObjectPtr<FileHandleReadWrap>>
file_handle_read_wrap_freelist;

using InternalFieldInfo = InternalFieldInfoBase;
SERIALIZABLE_OBJECT_METHODS()
static constexpr FastStringKey type_name{"node::fs::BindingData"};
static constexpr EmbedderObjectType type_int =
Expand Down
2 changes: 2 additions & 0 deletions src/node_process.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ class BindingData : public SnapshotableObject {
void AddMethods();
static void RegisterExternalReferences(ExternalReferenceRegistry* registry);

using InternalFieldInfo = InternalFieldInfoBase;

SERIALIZABLE_OBJECT_METHODS()
static constexpr FastStringKey type_name{"node::process::BindingData"};
static constexpr EmbedderObjectType type_int =
Expand Down
12 changes: 8 additions & 4 deletions src/node_process_methods.cc
Original file line number Diff line number Diff line change
Expand Up @@ -524,23 +524,27 @@ void BindingData::SlowNumber(const v8::FunctionCallbackInfo<v8::Value>& args) {
NumberImpl(FromJSObject<BindingData>(args.Holder()));
}

void BindingData::PrepareForSerialization(Local<Context> context,
bool BindingData::PrepareForSerialization(Local<Context> context,
v8::SnapshotCreator* creator) {
// It's not worth keeping.
// Release it, we will recreate it when the instance is dehydrated.
array_buffer_.Reset();
// Return true because we need to maintain the reference to the binding from
// JS land.
return true;
}

InternalFieldInfo* BindingData::Serialize(int index) {
InternalFieldInfoBase* BindingData::Serialize(int index) {
DCHECK_EQ(index, BaseObject::kEmbedderType);
InternalFieldInfo* info = InternalFieldInfo::New(type());
InternalFieldInfo* info =
InternalFieldInfoBase::New<InternalFieldInfo>(type());
return info;
}

void BindingData::Deserialize(Local<Context> context,
Local<Object> holder,
int index,
InternalFieldInfo* info) {
InternalFieldInfoBase* info) {
DCHECK_EQ(index, BaseObject::kEmbedderType);
v8::HandleScope scope(context->GetIsolate());
Environment* env = Environment::GetCurrent(context);
Expand Down
86 changes: 46 additions & 40 deletions src/node_snapshotable.cc
Original file line number Diff line number Diff line change
Expand Up @@ -640,7 +640,7 @@ template <>
EnvSerializeInfo FileReader::Read() {
per_process::Debug(DebugCategory::MKSNAPSHOT, "Read<EnvSerializeInfo>()\n");
EnvSerializeInfo result;
result.bindings = ReadVector<PropInfo>();
result.native_objects = ReadVector<PropInfo>();
result.builtins = ReadVector<std::string>();
result.async_hooks = Read<AsyncHooks::SerializeInfo>();
result.tick_info = Read<TickInfo::SerializeInfo>();
Expand All @@ -663,7 +663,7 @@ size_t FileWriter::Write(const EnvSerializeInfo& data) {
}

// Use += here to ensure order of evaluation.
size_t written_total = WriteVector<PropInfo>(data.bindings);
size_t written_total = WriteVector<PropInfo>(data.native_objects);
written_total += WriteVector<std::string>(data.builtins);
written_total += Write<AsyncHooks::SerializeInfo>(data.async_hooks);
written_total += Write<TickInfo::SerializeInfo>(data.tick_info);
Expand Down Expand Up @@ -1209,17 +1209,6 @@ const char* SnapshotableObject::GetTypeNameChars() const {
}
}

bool IsSnapshotableType(FastStringKey key) {
#define V(PropertyName, NativeTypeName) \
if (key == NativeTypeName::type_name) { \
return true; \
}
SERIALIZABLE_OBJECT_TYPES(V)
#undef V

return false;
}

void DeserializeNodeInternalFields(Local<Object> holder,
int index,
StartupData payload,
Expand All @@ -1242,10 +1231,10 @@ void DeserializeNodeInternalFields(Local<Object> holder,
DCHECK_EQ(index, BaseObject::kEmbedderType);

Environment* env_ptr = static_cast<Environment*>(env);
const InternalFieldInfo* info =
reinterpret_cast<const InternalFieldInfo*>(payload.data);
const InternalFieldInfoBase* info =
reinterpret_cast<const InternalFieldInfoBase*>(payload.data);
// TODO(joyeecheung): we can add a constant kNodeEmbedderId to the
// beginning of every InternalFieldInfo to ensure that we don't
// beginning of every InternalFieldInfoBase to ensure that we don't
// step on payloads that were not serialized by Node.js.
switch (info->type) {
#define V(PropertyName, NativeTypeName) \
Expand All @@ -1255,12 +1244,25 @@ void DeserializeNodeInternalFields(Local<Object> holder,
(*holder), \
NativeTypeName::type_name.c_str()); \
env_ptr->EnqueueDeserializeRequest( \
NativeTypeName::Deserialize, holder, index, info->Copy()); \
NativeTypeName::Deserialize, \
holder, \
index, \
info->Copy<NativeTypeName::InternalFieldInfo>()); \
break; \
}
SERIALIZABLE_OBJECT_TYPES(V)
#undef V
default: { UNREACHABLE(); }
default: {
// This should only be reachable during development when trying to
// deserialize a snapshot blob built by a version of Node.js that
// has more recognizable EmbedderObjectTypes than the deserializing
// Node.js binary.
fprintf(stderr,
"Unknown embedder object type %" PRIu8 ", possibly caused by "
"mismatched Node.js versions\n",
static_cast<uint8_t>(info->type));
ABORT();
}
}
}

Expand Down Expand Up @@ -1293,17 +1295,17 @@ StartupData SerializeNodeContextInternalFields(Local<Object> holder,
static_cast<int>(index),
*holder);

void* binding_ptr =
void* native_ptr =
holder->GetAlignedPointerFromInternalField(BaseObject::kSlot);
per_process::Debug(DebugCategory::MKSNAPSHOT, "binding = %p\n", binding_ptr);
DCHECK(static_cast<BaseObject*>(binding_ptr)->is_snapshotable());
SnapshotableObject* obj = static_cast<SnapshotableObject*>(binding_ptr);
per_process::Debug(DebugCategory::MKSNAPSHOT, "native = %p\n", native_ptr);
DCHECK(static_cast<BaseObject*>(native_ptr)->is_snapshotable());
SnapshotableObject* obj = static_cast<SnapshotableObject*>(native_ptr);

per_process::Debug(DebugCategory::MKSNAPSHOT,
"Object %p is %s, ",
*holder,
obj->GetTypeNameChars());
InternalFieldInfo* info = obj->Serialize(index);
InternalFieldInfoBase* info = obj->Serialize(index);

per_process::Debug(DebugCategory::MKSNAPSHOT,
"payload size=%d\n",
Expand All @@ -1312,31 +1314,35 @@ StartupData SerializeNodeContextInternalFields(Local<Object> holder,
static_cast<int>(info->length)};
}

void SerializeBindingData(Environment* env,
SnapshotCreator* creator,
EnvSerializeInfo* info) {
void SerializeSnapshotableObjects(Environment* env,
SnapshotCreator* creator,
EnvSerializeInfo* info) {
uint32_t i = 0;
env->ForEachBindingData([&](FastStringKey key,
BaseObjectPtr<BaseObject> binding) {
env->ForEachBaseObject([&](BaseObject* obj) {
// If there are any BaseObjects that are not snapshotable left
// during context serialization, V8 would crash due to unregistered
// global handles and print detailed information about them.
if (!obj->is_snapshotable()) {
return;
}
SnapshotableObject* ptr = static_cast<SnapshotableObject*>(obj);

const char* type_name = ptr->GetTypeNameChars();
per_process::Debug(DebugCategory::MKSNAPSHOT,
"Serialize binding %i (%p), object=%p, type=%s\n",
"Serialize snapshotable object %i (%p), "
"object=%p, type=%s\n",
static_cast<int>(i),
binding.get(),
*(binding->object()),
key.c_str());
ptr,
*(ptr->object()),
type_name);

if (IsSnapshotableType(key)) {
SnapshotIndex index = creator->AddData(env->context(), binding->object());
if (ptr->PrepareForSerialization(env->context(), creator)) {
SnapshotIndex index = creator->AddData(env->context(), obj->object());
per_process::Debug(DebugCategory::MKSNAPSHOT,
"Serialized with index=%d\n",
static_cast<int>(index));
info->bindings.push_back({key.c_str(), i, index});
SnapshotableObject* ptr = static_cast<SnapshotableObject*>(binding.get());
ptr->PrepareForSerialization(env->context(), creator);
} else {
UNREACHABLE();
info->native_objects.push_back({type_name, i, index});
}

i++;
});
}
Expand Down
Loading

0 comments on commit 5144634

Please sign in to comment.