Skip to content

Commit

Permalink
src: support serialization of binding data
Browse files Browse the repository at this point in the history
This patch adds the SnapshotableObject interface. Native objects
supporting serialization can inherit from it, implementing
PrepareForSerialization(), Serialize() and Deserialize()
to control how the native states should be serialized and
deserialized.

See doc: https://docs.google.com/document/d/15bu038I36oILq5t4Qju1sS2nKudVB6NSGWz00oD48Q8/edit

PR-URL: #36943
Fixes: #35930
Refs: #35711
Reviewed-By: James M Snell <jasnell@gmail.com>
  • Loading branch information
joyeecheung committed Feb 19, 2021
1 parent 05286b9 commit 3c8290c
Show file tree
Hide file tree
Showing 7 changed files with 283 additions and 12 deletions.
5 changes: 5 additions & 0 deletions src/aliased_buffer.h
Expand Up @@ -198,6 +198,11 @@ class AliasedBufferBase {
return js_array_.Get(isolate_);
}

void Release() {
DCHECK_NULL(index_);
js_array_.Reset();
}

/**
* Get the underlying v8::ArrayBuffer underlying the TypedArray and
* overlaying the native buffer
Expand Down
4 changes: 4 additions & 0 deletions src/base_object.h
Expand Up @@ -158,6 +158,9 @@ class BaseObject : public MemoryRetainer {

virtual inline void OnGCCollect();

bool is_snapshotable() const { return is_snapshotable_; }
void set_is_snapshotable(bool val) { is_snapshotable_ = val; }

private:
v8::Local<v8::Object> WrappedObject() const override;
bool IsRootNode() const override;
Expand Down Expand Up @@ -206,6 +209,7 @@ class BaseObject : public MemoryRetainer {

Environment* env_;
PointerData* pointer_data_ = nullptr;
bool is_snapshotable_ = false;
};

// Global alias for FromJSObject() to avoid churn.
Expand Down
11 changes: 11 additions & 0 deletions src/env-inl.h
Expand Up @@ -1085,6 +1085,17 @@ void Environment::ForEachBaseObject(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
28 changes: 28 additions & 0 deletions src/env.cc
Expand Up @@ -1251,6 +1251,7 @@ 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.native_modules = std::vector<std::string>(
Expand Down Expand Up @@ -1317,6 +1318,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_modules begins --\n"
<< i.native_modules << ",\n"
<< "// -- native_modules ends --\n"
Expand All @@ -1342,9 +1346,33 @@ std::ostream& operator<<(std::ostream& output, const EnvSerializeInfo& i) {
return output;
}

void Environment::EnqueueDeserializeRequest(DeserializeRequestCallback cb,
Local<Object> holder,
int index,
InternalFieldInfo* info) {
DeserializeRequest request{cb, {isolate(), holder}, index, info};
deserialize_requests_.push_back(std::move(request));
}

void Environment::RunDeserializeRequests() {
HandleScope scope(isolate());
Local<Context> ctx = context();
Isolate* is = isolate();
while (!deserialize_requests_.empty()) {
DeserializeRequest request(std::move(deserialize_requests_.front()));
deserialize_requests_.pop_front();
Local<Object> holder = request.holder.Get(is);
request.cb(ctx, holder, request.index, request.info);
request.holder.Reset();
request.info->Delete();
}
}

void Environment::DeserializeProperties(const EnvSerializeInfo* info) {
Local<Context> ctx = context();

RunDeserializeRequests();

native_modules_in_snapshot = info->native_modules;
async_hooks_.Deserialize(ctx);
immediate_info_.Deserialize(ctx);
Expand Down
26 changes: 26 additions & 0 deletions src/env.h
Expand Up @@ -37,6 +37,7 @@
#include "node_main_instance.h"
#include "node_options.h"
#include "node_perf_common.h"
#include "node_snapshotable.h"
#include "req_wrap.h"
#include "util.h"
#include "uv.h"
Expand Down Expand Up @@ -899,7 +900,22 @@ struct PropInfo {
SnapshotIndex index; // In the snapshot
};

typedef void (*DeserializeRequestCallback)(v8::Local<v8::Context> context,
v8::Local<v8::Object> holder,
int index,
InternalFieldInfo* info);
struct DeserializeRequest {
DeserializeRequestCallback cb;
v8::Global<v8::Object> holder;
int index;
InternalFieldInfo* info = nullptr; // Owned by the request

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

struct EnvSerializeInfo {
std::vector<PropInfo> bindings;
std::vector<std::string> native_modules;
AsyncHooks::SerializeInfo async_hooks;
TickInfo::SerializeInfo tick_info;
Expand Down Expand Up @@ -934,6 +950,11 @@ class Environment : public MemoryRetainer {

void PrintAllBaseObjects();
void VerifyNoStrongBaseObjects();
void EnqueueDeserializeRequest(DeserializeRequestCallback cb,
v8::Local<v8::Object> holder,
int index,
InternalFieldInfo* info);
void RunDeserializeRequests();
// Should be called before InitializeInspector()
void InitializeDiagnostics();

Expand Down Expand Up @@ -1371,6 +1392,9 @@ class Environment : public MemoryRetainer {
void AddUnmanagedFd(int fd);
void RemoveUnmanagedFd(int fd);

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

private:
inline void ThrowError(v8::Local<v8::Value> (*fun)(v8::Local<v8::String>),
const char* errmsg);
Expand Down Expand Up @@ -1459,6 +1483,8 @@ class Environment : public MemoryRetainer {
bool is_in_inspector_console_call_ = false;
#endif

std::list<DeserializeRequest> deserialize_requests_;

// handle_wrap_queue_ and req_wrap_queue_ needs to be at a fixed offset from
// the start of the class because it is used by
// src/node_postmortem_metadata.cc to calculate offsets and generate debug
Expand Down
117 changes: 106 additions & 11 deletions src/node_snapshotable.cc
@@ -1,39 +1,134 @@

#include "node_snapshotable.h"
#include "base_object-inl.h"
#include "debug_utils-inl.h"
#include "node_file.h"
#include "node_v8.h"

namespace node {

using v8::Local;
using v8::Object;
using v8::SnapshotCreator;
using v8::StartupData;

SnapshotableObject::SnapshotableObject(Environment* env,
Local<Object> wrap,
EmbedderObjectType type)
: BaseObject(env, wrap), type_(type) {
set_is_snapshotable(true);
}

const char* SnapshotableObject::GetTypeNameChars() const {
switch (type_) {
#define V(PropertyName, NativeTypeName) \
case EmbedderObjectType::k_##PropertyName: { \
return NativeTypeName::type_name.c_str(); \
}
SERIALIZABLE_OBJECT_TYPES(V)
#undef V
default: { UNREACHABLE(); }
}
}

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,
v8::StartupData payload,
StartupData payload,
void* env) {
per_process::Debug(DebugCategory::MKSNAPSHOT,
"Deserialize internal field %d of %p, size=%d\n",
static_cast<int>(index),
(*holder),
static_cast<int>(payload.raw_size));
if (payload.raw_size == 0) {
holder->SetAlignedPointerInInternalField(index, nullptr);
return;
}
// No embedder object in the builtin snapshot yet.
UNREACHABLE();

Environment* env_ptr = static_cast<Environment*>(env);
const InternalFieldInfo* info =
reinterpret_cast<const InternalFieldInfo*>(payload.data);

switch (info->type) {
#define V(PropertyName, NativeTypeName) \
case EmbedderObjectType::k_##PropertyName: { \
per_process::Debug(DebugCategory::MKSNAPSHOT, \
"Object %p is %s\n", \
(*holder), \
NativeTypeName::type_name.c_str()); \
env_ptr->EnqueueDeserializeRequest( \
NativeTypeName::Deserialize, holder, index, info->Copy()); \
break; \
}
SERIALIZABLE_OBJECT_TYPES(V)
#undef V
default: { UNREACHABLE(); }
}
}

StartupData SerializeNodeContextInternalFields(Local<Object> holder,
int index,
void* env) {
void* ptr = holder->GetAlignedPointerFromInternalField(index);
if (ptr == nullptr || ptr == env) {
return StartupData{nullptr, 0};
}
if (ptr == env && index == ContextEmbedderIndex::kEnvironment) {
per_process::Debug(DebugCategory::MKSNAPSHOT,
"Serialize internal field, index=%d, holder=%p\n",
static_cast<int>(index),
*holder);
void* ptr = holder->GetAlignedPointerFromInternalField(BaseObject::kSlot);
if (ptr == nullptr) {
return StartupData{nullptr, 0};
}

// No embedder objects in the builtin snapshot yet.
UNREACHABLE();
return StartupData{nullptr, 0};
DCHECK(static_cast<BaseObject*>(ptr)->is_snapshotable());
SnapshotableObject* obj = static_cast<SnapshotableObject*>(ptr);
per_process::Debug(DebugCategory::MKSNAPSHOT,
"Object %p is %s, ",
*holder,
obj->GetTypeNameChars());
InternalFieldInfo* info = obj->Serialize(index);
per_process::Debug(DebugCategory::MKSNAPSHOT,
"payload size=%d\n",
static_cast<int>(info->length));
return StartupData{reinterpret_cast<const char*>(info),
static_cast<int>(info->length)};
}

void SerializeBindingData(Environment* env,
SnapshotCreator* creator,
EnvSerializeInfo* info) {
size_t i = 0;
env->ForEachBindingData([&](FastStringKey key,
BaseObjectPtr<BaseObject> binding) {
per_process::Debug(DebugCategory::MKSNAPSHOT,
"Serialize binding %i, %p, type=%s\n",
static_cast<int>(i),
*(binding->object()),
key.c_str());

if (IsSnapshotableType(key)) {
size_t index = creator->AddData(env->context(), binding->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();
}

i++;
});
}

} // namespace node

0 comments on commit 3c8290c

Please sign in to comment.