fixup! src: track cppgc wrappers with a list in Realm

This commit is contained in:
Joyee Cheung 2025-03-19 23:05:46 +01:00
parent 6b36802325
commit 68803ba5af
No known key found for this signature in database
GPG key ID: 92B78A53C8303B8D
6 changed files with 99 additions and 12 deletions

View file

@ -52,6 +52,12 @@ Environment* CppgcMixin::env() const {
return realm_->env(); return realm_->env();
} }
CppgcMixin::~CppgcMixin() {
if (realm_ != nullptr) {
realm_->set_should_purge_empty_cppgc_wrappers(true);
}
}
} // namespace node } // namespace node
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS #endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS

View file

@ -4,15 +4,34 @@
namespace node { namespace node {
void CppgcWrapperList::Cleanup() { void CppgcWrapperList::Cleanup() {
for (auto handle : *this) { for (auto node : *this) {
handle->Finalize(); CppgcMixin* ptr = node->persistent.Get();
if (ptr != nullptr) {
ptr->Finalize();
}
} }
} }
void CppgcWrapperList::MemoryInfo(MemoryTracker* tracker) const { void CppgcWrapperList::MemoryInfo(MemoryTracker* tracker) const {
for (auto handle : *this) { for (auto node : *this) {
tracker->Track(handle); CppgcMixin* ptr = node->persistent.Get();
if (ptr != nullptr) {
tracker->Track(ptr);
}
} }
} }
void CppgcWrapperList::PurgeEmpty() {
for (auto weak_it = begin(); weak_it != end();) {
CppgcWrapperListNode* node = *weak_it;
auto next_it = ++weak_it;
// The underlying cppgc wrapper has already been garbage collected.
// Remove it from the list.
if (!node->persistent) {
node->persistent.Clear();
delete node;
}
weak_it = next_it;
}
}
} // namespace node } // namespace node

View file

@ -6,6 +6,7 @@
#include <type_traits> // std::remove_reference #include <type_traits> // std::remove_reference
#include "cppgc/garbage-collected.h" #include "cppgc/garbage-collected.h"
#include "cppgc/name-provider.h" #include "cppgc/name-provider.h"
#include "cppgc/persistent.h"
#include "memory_tracker.h" #include "memory_tracker.h"
#include "util.h" #include "util.h"
#include "v8-cppgc.h" #include "v8-cppgc.h"
@ -16,7 +17,7 @@ namespace node {
class Environment; class Environment;
class Realm; class Realm;
class CppgcWrapperList; class CppgcWrapperListNode;
/** /**
* This is a helper mixin with a BaseObject-like interface to help * This is a helper mixin with a BaseObject-like interface to help
@ -100,17 +101,20 @@ class CppgcMixin : public cppgc::GarbageCollectedMixin, public MemoryRetainer {
realm_ = nullptr; realm_ = nullptr;
} }
// The default implementation of Clean() is a no-op. Subclasses // The default implementation of Clean() is a no-op. If subclasses wish
// should override it to perform cleanup that require a living Realm, // to perform cleanup that require a living Realm, they should
// instead of doing these cleanups directly in the destructor. // should put the cleanups in a Clean() override, and call this->Finalize()
// in the destructor, instead of doing those cleanups directly in the
// destructor.
virtual void Clean(Realm* realm) {} virtual void Clean(Realm* realm) {}
friend class CppgcWrapperList; inline ~CppgcMixin();
friend class CppgcWrapperListNode;
private: private:
Realm* realm_ = nullptr; Realm* realm_ = nullptr;
v8::TracedReference<v8::Object> traced_reference_; v8::TracedReference<v8::Object> traced_reference_;
ListNode<CppgcMixin> wrapper_list_node_;
}; };
// If the class doesn't have additional owned traceable data, use this macro to // If the class doesn't have additional owned traceable data, use this macro to

View file

@ -130,9 +130,11 @@ void Realm::TrackBaseObject(BaseObject* bo) {
++base_object_count_; ++base_object_count_;
} }
CppgcWrapperListNode::CppgcWrapperListNode(CppgcMixin* ptr) : persistent(ptr) {}
void Realm::TrackCppgcWrapper(CppgcMixin* handle) { void Realm::TrackCppgcWrapper(CppgcMixin* handle) {
DCHECK_EQ(handle->realm(), this); DCHECK_EQ(handle->realm(), this);
cppgc_wrapper_list_.PushFront(handle); cppgc_wrapper_list_.PushFront(new CppgcWrapperListNode(handle));
} }
void Realm::UntrackBaseObject(BaseObject* bo) { void Realm::UntrackBaseObject(BaseObject* bo) {

View file

@ -10,7 +10,10 @@ namespace node {
using v8::Context; using v8::Context;
using v8::EscapableHandleScope; using v8::EscapableHandleScope;
using v8::GCCallbackFlags;
using v8::GCType;
using v8::HandleScope; using v8::HandleScope;
using v8::Isolate;
using v8::Local; using v8::Local;
using v8::MaybeLocal; using v8::MaybeLocal;
using v8::Object; using v8::Object;
@ -22,12 +25,28 @@ Realm::Realm(Environment* env, v8::Local<v8::Context> context, Kind kind)
: env_(env), isolate_(context->GetIsolate()), kind_(kind) { : env_(env), isolate_(context->GetIsolate()), kind_(kind) {
context_.Reset(isolate_, context); context_.Reset(isolate_, context);
env->AssignToContext(context, this, ContextInfo("")); env->AssignToContext(context, this, ContextInfo(""));
// The environment can also purge empty wrappers in the check callback,
// though that may be a bit excessive depending on usage patterns.
// For now using the GC epilogue is adequate.
isolate_->AddGCEpilogueCallback(PurgeEmptyCppgcWrappers, this);
} }
Realm::~Realm() { Realm::~Realm() {
isolate_->RemoveGCEpilogueCallback(PurgeEmptyCppgcWrappers, this);
CHECK_EQ(base_object_count_, 0); CHECK_EQ(base_object_count_, 0);
} }
void Realm::PurgeEmptyCppgcWrappers(Isolate* isolate,
GCType type,
GCCallbackFlags flags,
void* data) {
Realm* realm = static_cast<Realm*>(data);
if (realm->should_purge_empty_cppgc_wrappers_) {
realm->cppgc_wrapper_list_.PurgeEmpty();
realm->should_purge_empty_cppgc_wrappers_ = false;
}
}
void Realm::MemoryInfo(MemoryTracker* tracker) const { void Realm::MemoryInfo(MemoryTracker* tracker) const {
#define V(PropertyName, TypeName) \ #define V(PropertyName, TypeName) \
tracker->TrackField(#PropertyName, PropertyName()); tracker->TrackField(#PropertyName, PropertyName());

View file

@ -26,11 +26,34 @@ using BindingDataStore =
std::array<BaseObjectWeakPtr<BaseObject>, std::array<BaseObjectWeakPtr<BaseObject>,
static_cast<size_t>(BindingDataType::kBindingDataTypeCount)>; static_cast<size_t>(BindingDataType::kBindingDataTypeCount)>;
/**
* This is a wrapper around a weak persistent of CppgcMixin, used in the
* CppgcWrapperList to avoid accessing already garbage collected CppgcMixins.
*/
class CppgcWrapperListNode {
public:
explicit inline CppgcWrapperListNode(CppgcMixin* ptr);
inline explicit operator bool() const { return !persistent; }
inline CppgcMixin* operator->() const { return persistent.Get(); }
inline CppgcMixin* operator*() const { return persistent.Get(); }
cppgc::WeakPersistent<CppgcMixin> persistent;
// Used by ContainerOf in the ListNode implementation for fast manipulation of
// CppgcWrapperList.
ListNode<CppgcWrapperListNode> wrapper_list_node;
};
/**
* A per-realm list of weak persistent of cppgc wrappers, which implements
* iterations that require iterate over cppgc wrappers created by Node.js.
*/
class CppgcWrapperList class CppgcWrapperList
: public ListHead<CppgcMixin, &CppgcMixin::wrapper_list_node_>, : public ListHead<CppgcWrapperListNode,
&CppgcWrapperListNode::wrapper_list_node>,
public MemoryRetainer { public MemoryRetainer {
public: public:
void Cleanup(); void Cleanup();
void PurgeEmpty();
SET_MEMORY_INFO_NAME(CppgcWrapperList) SET_MEMORY_INFO_NAME(CppgcWrapperList)
SET_SELF_SIZE(CppgcWrapperList) SET_SELF_SIZE(CppgcWrapperList)
@ -141,6 +164,14 @@ class Realm : public MemoryRetainer {
// it's only used for tests. // it's only used for tests.
std::vector<std::string> builtins_in_snapshot; std::vector<std::string> builtins_in_snapshot;
// This used during the destruction of cppgc wrappers to inform a GC epilogue
// callback to clean up the weak persistents used to track cppgc wrappers if
// the wrappers are already garbage collected to prevent holding on to
// excessive useless persistents.
inline void set_should_purge_empty_cppgc_wrappers(bool value) {
should_purge_empty_cppgc_wrappers_ = value;
}
protected: protected:
~Realm(); ~Realm();
@ -150,11 +181,17 @@ class Realm : public MemoryRetainer {
// Shorthand for isolate pointer. // Shorthand for isolate pointer.
v8::Isolate* isolate_; v8::Isolate* isolate_;
v8::Global<v8::Context> context_; v8::Global<v8::Context> context_;
bool should_purge_empty_cppgc_wrappers_ = false;
#define V(PropertyName, TypeName) v8::Global<TypeName> PropertyName##_; #define V(PropertyName, TypeName) v8::Global<TypeName> PropertyName##_;
PER_REALM_STRONG_PERSISTENT_VALUES(V) PER_REALM_STRONG_PERSISTENT_VALUES(V)
#undef V #undef V
static void PurgeEmptyCppgcWrappers(v8::Isolate* isolate,
v8::GCType type,
v8::GCCallbackFlags flags,
void* data);
private: private:
void InitializeContext(v8::Local<v8::Context> context, void InitializeContext(v8::Local<v8::Context> context,
const RealmSerializeInfo* realm_info); const RealmSerializeInfo* realm_info);