Skip to content

Commit 68803ba

Browse files
committed
fixup! src: track cppgc wrappers with a list in Realm
1 parent 6b36802 commit 68803ba

6 files changed

+99
-12
lines changed

src/cppgc_helpers-inl.h

+6
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,12 @@ Environment* CppgcMixin::env() const {
5252
return realm_->env();
5353
}
5454

55+
CppgcMixin::~CppgcMixin() {
56+
if (realm_ != nullptr) {
57+
realm_->set_should_purge_empty_cppgc_wrappers(true);
58+
}
59+
}
60+
5561
} // namespace node
5662

5763
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS

src/cppgc_helpers.cc

+23-4
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,34 @@
44
namespace node {
55

66
void CppgcWrapperList::Cleanup() {
7-
for (auto handle : *this) {
8-
handle->Finalize();
7+
for (auto node : *this) {
8+
CppgcMixin* ptr = node->persistent.Get();
9+
if (ptr != nullptr) {
10+
ptr->Finalize();
11+
}
912
}
1013
}
1114

1215
void CppgcWrapperList::MemoryInfo(MemoryTracker* tracker) const {
13-
for (auto handle : *this) {
14-
tracker->Track(handle);
16+
for (auto node : *this) {
17+
CppgcMixin* ptr = node->persistent.Get();
18+
if (ptr != nullptr) {
19+
tracker->Track(ptr);
20+
}
1521
}
1622
}
1723

24+
void CppgcWrapperList::PurgeEmpty() {
25+
for (auto weak_it = begin(); weak_it != end();) {
26+
CppgcWrapperListNode* node = *weak_it;
27+
auto next_it = ++weak_it;
28+
// The underlying cppgc wrapper has already been garbage collected.
29+
// Remove it from the list.
30+
if (!node->persistent) {
31+
node->persistent.Clear();
32+
delete node;
33+
}
34+
weak_it = next_it;
35+
}
36+
}
1837
} // namespace node

src/cppgc_helpers.h

+10-6
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include <type_traits> // std::remove_reference
77
#include "cppgc/garbage-collected.h"
88
#include "cppgc/name-provider.h"
9+
#include "cppgc/persistent.h"
910
#include "memory_tracker.h"
1011
#include "util.h"
1112
#include "v8-cppgc.h"
@@ -16,7 +17,7 @@ namespace node {
1617

1718
class Environment;
1819
class Realm;
19-
class CppgcWrapperList;
20+
class CppgcWrapperListNode;
2021

2122
/**
2223
* This is a helper mixin with a BaseObject-like interface to help
@@ -100,17 +101,20 @@ class CppgcMixin : public cppgc::GarbageCollectedMixin, public MemoryRetainer {
100101
realm_ = nullptr;
101102
}
102103

103-
// The default implementation of Clean() is a no-op. Subclasses
104-
// should override it to perform cleanup that require a living Realm,
105-
// instead of doing these cleanups directly in the destructor.
104+
// The default implementation of Clean() is a no-op. If subclasses wish
105+
// to perform cleanup that require a living Realm, they should
106+
// should put the cleanups in a Clean() override, and call this->Finalize()
107+
// in the destructor, instead of doing those cleanups directly in the
108+
// destructor.
106109
virtual void Clean(Realm* realm) {}
107110

108-
friend class CppgcWrapperList;
111+
inline ~CppgcMixin();
112+
113+
friend class CppgcWrapperListNode;
109114

110115
private:
111116
Realm* realm_ = nullptr;
112117
v8::TracedReference<v8::Object> traced_reference_;
113-
ListNode<CppgcMixin> wrapper_list_node_;
114118
};
115119

116120
// If the class doesn't have additional owned traceable data, use this macro to

src/node_realm-inl.h

+3-1
Original file line numberDiff line numberDiff line change
@@ -130,9 +130,11 @@ void Realm::TrackBaseObject(BaseObject* bo) {
130130
++base_object_count_;
131131
}
132132

133+
CppgcWrapperListNode::CppgcWrapperListNode(CppgcMixin* ptr) : persistent(ptr) {}
134+
133135
void Realm::TrackCppgcWrapper(CppgcMixin* handle) {
134136
DCHECK_EQ(handle->realm(), this);
135-
cppgc_wrapper_list_.PushFront(handle);
137+
cppgc_wrapper_list_.PushFront(new CppgcWrapperListNode(handle));
136138
}
137139

138140
void Realm::UntrackBaseObject(BaseObject* bo) {

src/node_realm.cc

+19
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@ namespace node {
1010

1111
using v8::Context;
1212
using v8::EscapableHandleScope;
13+
using v8::GCCallbackFlags;
14+
using v8::GCType;
1315
using v8::HandleScope;
16+
using v8::Isolate;
1417
using v8::Local;
1518
using v8::MaybeLocal;
1619
using v8::Object;
@@ -22,12 +25,28 @@ Realm::Realm(Environment* env, v8::Local<v8::Context> context, Kind kind)
2225
: env_(env), isolate_(context->GetIsolate()), kind_(kind) {
2326
context_.Reset(isolate_, context);
2427
env->AssignToContext(context, this, ContextInfo(""));
28+
// The environment can also purge empty wrappers in the check callback,
29+
// though that may be a bit excessive depending on usage patterns.
30+
// For now using the GC epilogue is adequate.
31+
isolate_->AddGCEpilogueCallback(PurgeEmptyCppgcWrappers, this);
2532
}
2633

2734
Realm::~Realm() {
35+
isolate_->RemoveGCEpilogueCallback(PurgeEmptyCppgcWrappers, this);
2836
CHECK_EQ(base_object_count_, 0);
2937
}
3038

39+
void Realm::PurgeEmptyCppgcWrappers(Isolate* isolate,
40+
GCType type,
41+
GCCallbackFlags flags,
42+
void* data) {
43+
Realm* realm = static_cast<Realm*>(data);
44+
if (realm->should_purge_empty_cppgc_wrappers_) {
45+
realm->cppgc_wrapper_list_.PurgeEmpty();
46+
realm->should_purge_empty_cppgc_wrappers_ = false;
47+
}
48+
}
49+
3150
void Realm::MemoryInfo(MemoryTracker* tracker) const {
3251
#define V(PropertyName, TypeName) \
3352
tracker->TrackField(#PropertyName, PropertyName());

src/node_realm.h

+38-1
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,34 @@ using BindingDataStore =
2626
std::array<BaseObjectWeakPtr<BaseObject>,
2727
static_cast<size_t>(BindingDataType::kBindingDataTypeCount)>;
2828

29+
/**
30+
* This is a wrapper around a weak persistent of CppgcMixin, used in the
31+
* CppgcWrapperList to avoid accessing already garbage collected CppgcMixins.
32+
*/
33+
class CppgcWrapperListNode {
34+
public:
35+
explicit inline CppgcWrapperListNode(CppgcMixin* ptr);
36+
inline explicit operator bool() const { return !persistent; }
37+
inline CppgcMixin* operator->() const { return persistent.Get(); }
38+
inline CppgcMixin* operator*() const { return persistent.Get(); }
39+
40+
cppgc::WeakPersistent<CppgcMixin> persistent;
41+
// Used by ContainerOf in the ListNode implementation for fast manipulation of
42+
// CppgcWrapperList.
43+
ListNode<CppgcWrapperListNode> wrapper_list_node;
44+
};
45+
46+
/**
47+
* A per-realm list of weak persistent of cppgc wrappers, which implements
48+
* iterations that require iterate over cppgc wrappers created by Node.js.
49+
*/
2950
class CppgcWrapperList
30-
: public ListHead<CppgcMixin, &CppgcMixin::wrapper_list_node_>,
51+
: public ListHead<CppgcWrapperListNode,
52+
&CppgcWrapperListNode::wrapper_list_node>,
3153
public MemoryRetainer {
3254
public:
3355
void Cleanup();
56+
void PurgeEmpty();
3457

3558
SET_MEMORY_INFO_NAME(CppgcWrapperList)
3659
SET_SELF_SIZE(CppgcWrapperList)
@@ -141,6 +164,14 @@ class Realm : public MemoryRetainer {
141164
// it's only used for tests.
142165
std::vector<std::string> builtins_in_snapshot;
143166

167+
// This used during the destruction of cppgc wrappers to inform a GC epilogue
168+
// callback to clean up the weak persistents used to track cppgc wrappers if
169+
// the wrappers are already garbage collected to prevent holding on to
170+
// excessive useless persistents.
171+
inline void set_should_purge_empty_cppgc_wrappers(bool value) {
172+
should_purge_empty_cppgc_wrappers_ = value;
173+
}
174+
144175
protected:
145176
~Realm();
146177

@@ -150,11 +181,17 @@ class Realm : public MemoryRetainer {
150181
// Shorthand for isolate pointer.
151182
v8::Isolate* isolate_;
152183
v8::Global<v8::Context> context_;
184+
bool should_purge_empty_cppgc_wrappers_ = false;
153185

154186
#define V(PropertyName, TypeName) v8::Global<TypeName> PropertyName##_;
155187
PER_REALM_STRONG_PERSISTENT_VALUES(V)
156188
#undef V
157189

190+
static void PurgeEmptyCppgcWrappers(v8::Isolate* isolate,
191+
v8::GCType type,
192+
v8::GCCallbackFlags flags,
193+
void* data);
194+
158195
private:
159196
void InitializeContext(v8::Local<v8::Context> context,
160197
const RealmSerializeInfo* realm_info);

0 commit comments

Comments
 (0)