From e400f8c9c8165db20d9baf39ac6d0ee09f1444c6 Mon Sep 17 00:00:00 2001 From: Joyee Cheung Date: Mon, 8 Feb 2021 20:34:02 +0800 Subject: [PATCH] src: support serialization of binding data 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: https://github.com/nodejs/node/pull/36943 Fixes: https://github.com/nodejs/node/issues/35930 Refs: https://github.com/nodejs/node/issues/35711 Reviewed-By: James M Snell --- src/aliased_buffer.h | 5 ++ src/base_object.h | 4 ++ src/env-inl.h | 11 ++++ src/env.cc | 28 ++++++++++ src/env.h | 26 +++++++++ src/node_snapshotable.cc | 117 +++++++++++++++++++++++++++++++++++---- src/node_snapshotable.h | 104 +++++++++++++++++++++++++++++++++- 7 files changed, 283 insertions(+), 12 deletions(-) diff --git a/src/aliased_buffer.h b/src/aliased_buffer.h index 134685542d4a75..53011c5fe0af25 100644 --- a/src/aliased_buffer.h +++ b/src/aliased_buffer.h @@ -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 diff --git a/src/base_object.h b/src/base_object.h index 3eac795e783e9d..6482bd85f86a56 100644 --- a/src/base_object.h +++ b/src/base_object.h @@ -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 WrappedObject() const override; bool IsRootNode() const override; @@ -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. diff --git a/src/env-inl.h b/src/env-inl.h index 93d2423580a88b..91e70908f1e594 100644 --- a/src/env-inl.h +++ b/src/env-inl.h @@ -1089,6 +1089,17 @@ void Environment::ForEachBaseObject(T&& iterator) { } } +template +void Environment::ForEachBindingData(T&& iterator) { + BindingDataStore* map = static_cast( + 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; } diff --git a/src/env.cc b/src/env.cc index 49d8ffd31308a0..119706129713bd 100644 --- a/src/env.cc +++ b/src/env.cc @@ -1261,6 +1261,7 @@ EnvSerializeInfo Environment::Serialize(SnapshotCreator* creator) { EnvSerializeInfo info; Local ctx = context(); + SerializeBindingData(this, creator, &info); // Currently all modules are compiled without cache in builtin snapshot // builder. info.native_modules = std::vector( @@ -1327,6 +1328,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" @@ -1352,9 +1356,33 @@ std::ostream& operator<<(std::ostream& output, const EnvSerializeInfo& i) { return output; } +void Environment::EnqueueDeserializeRequest(DeserializeRequestCallback cb, + Local 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 ctx = context(); + Isolate* is = isolate(); + while (!deserialize_requests_.empty()) { + DeserializeRequest request(std::move(deserialize_requests_.front())); + deserialize_requests_.pop_front(); + Local 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 ctx = context(); + RunDeserializeRequests(); + native_modules_in_snapshot = info->native_modules; async_hooks_.Deserialize(ctx); immediate_info_.Deserialize(ctx); diff --git a/src/env.h b/src/env.h index f3bb8f51dda562..fa61450d0945d5 100644 --- a/src/env.h +++ b/src/env.h @@ -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" @@ -899,7 +900,22 @@ struct PropInfo { SnapshotIndex index; // In the snapshot }; +typedef void (*DeserializeRequestCallback)(v8::Local context, + v8::Local holder, + int index, + InternalFieldInfo* info); +struct DeserializeRequest { + DeserializeRequestCallback cb; + v8::Global holder; + int index; + InternalFieldInfo* info = nullptr; // Owned by the request + + // Move constructor + DeserializeRequest(DeserializeRequest&& other) = default; +}; + struct EnvSerializeInfo { + std::vector bindings; std::vector native_modules; AsyncHooks::SerializeInfo async_hooks; TickInfo::SerializeInfo tick_info; @@ -934,6 +950,11 @@ class Environment : public MemoryRetainer { void PrintAllBaseObjects(); void VerifyNoStrongBaseObjects(); + void EnqueueDeserializeRequest(DeserializeRequestCallback cb, + v8::Local holder, + int index, + InternalFieldInfo* info); + void RunDeserializeRequests(); // Should be called before InitializeInspector() void InitializeDiagnostics(); @@ -1374,6 +1395,9 @@ class Environment : public MemoryRetainer { void AddUnmanagedFd(int fd); void RemoveUnmanagedFd(int fd); + template + void ForEachBindingData(T&& iterator); + private: inline void ThrowError(v8::Local (*fun)(v8::Local), const char* errmsg); @@ -1462,6 +1486,8 @@ class Environment : public MemoryRetainer { bool is_in_inspector_console_call_ = false; #endif + std::list 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 diff --git a/src/node_snapshotable.cc b/src/node_snapshotable.cc index 21de10868564d3..04ae6e26131b4e 100644 --- a/src/node_snapshotable.cc +++ b/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 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 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(index), + (*holder), + static_cast(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(env); + const InternalFieldInfo* info = + reinterpret_cast(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 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(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(ptr)->is_snapshotable()); + SnapshotableObject* obj = static_cast(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(info->length)); + return StartupData{reinterpret_cast(info), + static_cast(info->length)}; +} + +void SerializeBindingData(Environment* env, + SnapshotCreator* creator, + EnvSerializeInfo* info) { + size_t i = 0; + env->ForEachBindingData([&](FastStringKey key, + BaseObjectPtr binding) { + per_process::Debug(DebugCategory::MKSNAPSHOT, + "Serialize binding %i, %p, type=%s\n", + static_cast(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(index)); + info->bindings.push_back({key.c_str(), i, index}); + SnapshotableObject* ptr = static_cast(binding.get()); + ptr->PrepareForSerialization(env->context(), creator); + } else { + UNREACHABLE(); + } + + i++; + }); } } // namespace node diff --git a/src/node_snapshotable.h b/src/node_snapshotable.h index c45c1381b5554f..94a5a7a03ca731 100644 --- a/src/node_snapshotable.h +++ b/src/node_snapshotable.h @@ -4,9 +4,106 @@ #if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS -#include "v8.h" +#include "base_object.h" +#include "util.h" + namespace node { +class Environment; +struct EnvSerializeInfo; + +#define SERIALIZABLE_OBJECT_TYPES(V) + +enum class EmbedderObjectType : uint8_t { + k_default = 0, +#define V(PropertyName, NativeType) k_##PropertyName, + SERIALIZABLE_OBJECT_TYPES(V) +#undef V +}; + +// When serializing an embedder object, we'll serialize the native states +// into a chunk that can be mapped into a subclass of InternalFieldInfo, +// and pass it into the V8 callback as the payload of StartupData. +// TODO(joyeecheung): the classification of types seem to be wrong. +// We'd need a type for each field of each class of native object. +// Maybe it's fine - we'll just use the type to invoke BaseObject constructors +// and specify that the BaseObject has only one field for us to serialize. +// And for non-BaseObject embedder objects, we'll use field-wise types. +// The memory chunk looks like this: +// +// [ type ] - EmbedderObjectType (a uint8_t) +// [ length ] - a size_t +// [ ... ] - custom bytes of size |length - header size| +struct InternalFieldInfo { + EmbedderObjectType type; + size_t length; + + InternalFieldInfo() = delete; + + static InternalFieldInfo* New(EmbedderObjectType type) { + return New(type, sizeof(InternalFieldInfo)); + } + + static InternalFieldInfo* New(EmbedderObjectType type, size_t length) { + InternalFieldInfo* result = + reinterpret_cast(::operator new(length)); + result->type = type; + result->length = length; + return result; + } + + InternalFieldInfo* Copy() const { + InternalFieldInfo* result = + reinterpret_cast(::operator new(length)); + memcpy(result, this, length); + return result; + } + + void Delete() { ::operator delete(this); } +}; + +// An interface for snapshotable native objects to inherit from. +// Use the SERIALIZABLE_OBJECT_METHODS() macro in the class to define +// the following methods to implement: +// +// - PrepareForSerialization(): This would be run prior to context +// serialization. Use this method to e.g. release references that +// can be re-initialized, or perform property store operations +// that needs a V8 context. +// - Serialize(): This would be called during context serialization, +// once for each embedder field of the object. +// Allocate and construct an InternalFieldInfo object that contains +// data that can be used to deserialize native states. +// - Deserialize(): This would be called after the context is +// deserialized and the object graph is complete, once for each +// embedder field of the object. Use this to restore native states +// in the object. +class SnapshotableObject : public BaseObject { + public: + SnapshotableObject(Environment* env, + v8::Local wrap, + EmbedderObjectType type = EmbedderObjectType::k_default); + const char* GetTypeNameChars() const; + + virtual void PrepareForSerialization(v8::Local context, + v8::SnapshotCreator* creator) = 0; + virtual InternalFieldInfo* Serialize(int index) = 0; + // We'll make sure that the type is set in the constructor + EmbedderObjectType type() { return type_; } + + private: + EmbedderObjectType type_; +}; + +#define SERIALIZABLE_OBJECT_METHODS() \ + void PrepareForSerialization(v8::Local context, \ + v8::SnapshotCreator* creator) override; \ + InternalFieldInfo* Serialize(int index) override; \ + static void Deserialize(v8::Local context, \ + v8::Local holder, \ + int index, \ + InternalFieldInfo* info); + v8::StartupData SerializeNodeContextInternalFields(v8::Local holder, int index, void* env); @@ -14,6 +111,11 @@ void DeserializeNodeInternalFields(v8::Local holder, int index, v8::StartupData payload, void* env); +void SerializeBindingData(Environment* env, + v8::SnapshotCreator* creator, + EnvSerializeInfo* info); + +bool IsSnapshotableType(FastStringKey key); } // namespace node #endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS