Skip to content

Commit 8496670

Browse files
committed
src: track BaseObjects with an efficient list
PR-URL: #55104 Refs: #54880 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com>
1 parent 779e6bd commit 8496670

15 files changed

+83
-78
lines changed

src/README.md

+13-4
Original file line numberDiff line numberDiff line change
@@ -760,10 +760,19 @@ a `void* hint` argument.
760760
Inside these cleanup hooks, new asynchronous operations _may_ be started on the
761761
event loop, although ideally that is avoided as much as possible.
762762

763-
Every [`BaseObject`][] has its own cleanup hook that deletes it. For
764-
[`ReqWrap`][] and [`HandleWrap`][] instances, cleanup of the associated libuv
765-
objects is performed automatically, i.e. handles are closed and requests
766-
are cancelled if possible.
763+
For every [`ReqWrap`][] and [`HandleWrap`][] instance, the cleanup of the
764+
associated libuv objects is performed automatically, i.e. handles are closed
765+
and requests are cancelled if possible.
766+
767+
#### Cleanup realms and BaseObjects
768+
769+
Realm cleanup depends on the realm types. All realms are destroyed when the
770+
[`Environment`][] is destroyed with the cleanup hook. A [`ShadowRealm`][] can
771+
also be destroyed by the garbage collection when there is no strong reference
772+
to it.
773+
774+
Every [`BaseObject`][] is tracked with its creation realm and will be destroyed
775+
when the realm is tearing down.
767776

768777
#### Closing libuv handles
769778

src/aliased_buffer-inl.h

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
55

66
#include "aliased_buffer.h"
7+
#include "memory_tracker-inl.h"
78
#include "util-inl.h"
89

910
namespace node {

src/base_object.cc

+20-9
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#include "base_object.h"
22
#include "env-inl.h"
3+
#include "memory_tracker-inl.h"
34
#include "node_messaging.h"
45
#include "node_realm-inl.h"
56

@@ -23,13 +24,11 @@ BaseObject::BaseObject(Realm* realm, Local<Object> object)
2324
CHECK_EQ(false, object.IsEmpty());
2425
CHECK_GE(object->InternalFieldCount(), BaseObject::kInternalFieldCount);
2526
SetInternalFields(realm->isolate_data(), object, static_cast<void*>(this));
26-
realm->AddCleanupHook(DeleteMe, static_cast<void*>(this));
27-
realm->modify_base_object_count(1);
27+
realm->TrackBaseObject(this);
2828
}
2929

3030
BaseObject::~BaseObject() {
31-
realm()->modify_base_object_count(-1);
32-
realm()->RemoveCleanupHook(DeleteMe, static_cast<void*>(this));
31+
realm()->UntrackBaseObject(this);
3332

3433
if (UNLIKELY(has_pointer_data())) {
3534
PointerData* metadata = pointer_data();
@@ -146,12 +145,11 @@ void BaseObject::increase_refcount() {
146145
persistent_handle_.ClearWeak();
147146
}
148147

149-
void BaseObject::DeleteMe(void* data) {
150-
BaseObject* self = static_cast<BaseObject*>(data);
151-
if (self->has_pointer_data() && self->pointer_data()->strong_ptr_count > 0) {
152-
return self->Detach();
148+
void BaseObject::DeleteMe() {
149+
if (has_pointer_data() && pointer_data()->strong_ptr_count > 0) {
150+
return Detach();
153151
}
154-
delete self;
152+
delete this;
155153
}
156154

157155
bool BaseObject::IsDoneInitializing() const {
@@ -170,4 +168,17 @@ bool BaseObject::IsNotIndicativeOfMemoryLeakAtExit() const {
170168
return IsWeakOrDetached();
171169
}
172170

171+
void BaseObjectList::Cleanup() {
172+
while (!IsEmpty()) {
173+
BaseObject* bo = PopFront();
174+
bo->DeleteMe();
175+
}
176+
}
177+
178+
void BaseObjectList::MemoryInfo(node::MemoryTracker* tracker) const {
179+
for (auto bo : *this) {
180+
if (bo->IsDoneInitializing()) tracker->Track(bo);
181+
}
182+
}
183+
173184
} // namespace node

src/base_object.h

+16-1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
#include <type_traits> // std::remove_reference
2828
#include "base_object_types.h"
2929
#include "memory_tracker.h"
30+
#include "util.h"
3031
#include "v8.h"
3132

3233
namespace node {
@@ -192,7 +193,7 @@ class BaseObject : public MemoryRetainer {
192193
private:
193194
v8::Local<v8::Object> WrappedObject() const override;
194195
bool IsRootNode() const override;
195-
static void DeleteMe(void* data);
196+
void DeleteMe();
196197

197198
// persistent_handle_ needs to be at a fixed offset from the start of the
198199
// class because it is used by src/node_postmortem_metadata.cc to calculate
@@ -237,6 +238,20 @@ class BaseObject : public MemoryRetainer {
237238

238239
Realm* realm_;
239240
PointerData* pointer_data_ = nullptr;
241+
ListNode<BaseObject> base_object_list_node_;
242+
243+
friend class BaseObjectList;
244+
};
245+
246+
class BaseObjectList
247+
: public ListHead<BaseObject, &BaseObject::base_object_list_node_>,
248+
public MemoryRetainer {
249+
public:
250+
void Cleanup();
251+
252+
SET_MEMORY_INFO_NAME(BaseObjectList)
253+
SET_SELF_SIZE(BaseObjectList)
254+
void MemoryInfo(node::MemoryTracker* tracker) const override;
240255
};
241256

242257
#define ASSIGN_OR_RETURN_UNWRAP(ptr, obj, ...) \

src/cleanup_queue-inl.h

-26
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,11 @@
33

44
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
55

6-
#include "base_object.h"
76
#include "cleanup_queue.h"
8-
#include "memory_tracker-inl.h"
97
#include "util.h"
108

119
namespace node {
1210

13-
inline void CleanupQueue::MemoryInfo(MemoryTracker* tracker) const {
14-
ForEachBaseObject([&](BaseObject* obj) {
15-
if (obj->IsDoneInitializing()) tracker->Track(obj);
16-
});
17-
}
18-
1911
inline size_t CleanupQueue::SelfSize() const {
2012
return sizeof(CleanupQueue) +
2113
cleanup_hooks_.size() * sizeof(CleanupHookCallback);
@@ -37,24 +29,6 @@ void CleanupQueue::Remove(Callback cb, void* arg) {
3729
cleanup_hooks_.erase(search);
3830
}
3931

40-
template <typename T>
41-
void CleanupQueue::ForEachBaseObject(T&& iterator) const {
42-
std::vector<CleanupHookCallback> callbacks = GetOrdered();
43-
44-
for (const auto& hook : callbacks) {
45-
BaseObject* obj = GetBaseObject(hook);
46-
if (obj != nullptr) iterator(obj);
47-
}
48-
}
49-
50-
BaseObject* CleanupQueue::GetBaseObject(
51-
const CleanupHookCallback& callback) const {
52-
if (callback.fn_ == BaseObject::DeleteMe)
53-
return static_cast<BaseObject*>(callback.arg_);
54-
else
55-
return nullptr;
56-
}
57-
5832
} // namespace node
5933

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

src/cleanup_queue.h

+1-7
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,6 @@
1212

1313
namespace node {
1414

15-
class BaseObject;
16-
1715
class CleanupQueue : public MemoryRetainer {
1816
public:
1917
typedef void (*Callback)(void*);
@@ -24,7 +22,7 @@ class CleanupQueue : public MemoryRetainer {
2422
CleanupQueue(const CleanupQueue&) = delete;
2523

2624
SET_MEMORY_INFO_NAME(CleanupQueue)
27-
inline void MemoryInfo(node::MemoryTracker* tracker) const override;
25+
SET_NO_MEMORY_INFO()
2826
inline size_t SelfSize() const override;
2927

3028
inline bool empty() const;
@@ -33,9 +31,6 @@ class CleanupQueue : public MemoryRetainer {
3331
inline void Remove(Callback cb, void* arg);
3432
void Drain();
3533

36-
template <typename T>
37-
inline void ForEachBaseObject(T&& iterator) const;
38-
3934
private:
4035
class CleanupHookCallback {
4136
public:
@@ -68,7 +63,6 @@ class CleanupQueue : public MemoryRetainer {
6863
};
6964

7065
std::vector<CleanupHookCallback> GetOrdered() const;
71-
inline BaseObject* GetBaseObject(const CleanupHookCallback& callback) const;
7266

7367
// Use an unordered_set, so that we have efficient insertion and removal.
7468
std::unordered_set<CleanupHookCallback,

src/debug_utils-inl.h

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
#include "debug_utils.h"
77
#include "env.h"
8+
#include "util-inl.h"
89

910
#include <type_traits>
1011

src/env.cc

+1-1
Original file line numberDiff line numberDiff line change
@@ -1279,7 +1279,7 @@ void Environment::RunCleanup() {
12791279
cleanable->Clean();
12801280
}
12811281

1282-
while (!cleanup_queue_.empty() || principal_realm_->HasCleanupHooks() ||
1282+
while (!cleanup_queue_.empty() || principal_realm_->PendingCleanup() ||
12831283
native_immediates_.size() > 0 ||
12841284
native_immediates_threadsafe_.size() > 0 ||
12851285
native_immediates_interrupts_.size() > 0) {

src/node_messaging.cc

+8-9
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ using v8::WasmModuleObject;
4242

4343
namespace node {
4444

45-
using BaseObjectList = std::vector<BaseObjectPtr<BaseObject>>;
45+
using BaseObjectPtrList = std::vector<BaseObjectPtr<BaseObject>>;
4646
using TransferMode = BaseObject::TransferMode;
4747

4848
// Hack to have WriteHostObject inform ReadHostObject that the value
@@ -1347,33 +1347,32 @@ std::unique_ptr<TransferData> JSTransferable::TransferOrClone() const {
13471347
Global<Value>(env()->isolate(), data));
13481348
}
13491349

1350-
Maybe<BaseObjectList>
1351-
JSTransferable::NestedTransferables() const {
1350+
Maybe<BaseObjectPtrList> JSTransferable::NestedTransferables() const {
13521351
// Call `this[kTransferList]()` and return the resulting list of BaseObjects.
13531352
HandleScope handle_scope(env()->isolate());
13541353
Local<Context> context = env()->isolate()->GetCurrentContext();
13551354
Local<Symbol> method_name = env()->messaging_transfer_list_symbol();
13561355

13571356
Local<Value> method;
13581357
if (!target()->Get(context, method_name).ToLocal(&method)) {
1359-
return Nothing<BaseObjectList>();
1358+
return Nothing<BaseObjectPtrList>();
13601359
}
1361-
if (!method->IsFunction()) return Just(BaseObjectList {});
1360+
if (!method->IsFunction()) return Just(BaseObjectPtrList{});
13621361

13631362
Local<Value> list_v;
13641363
if (!method.As<Function>()
13651364
->Call(context, target(), 0, nullptr)
13661365
.ToLocal(&list_v)) {
1367-
return Nothing<BaseObjectList>();
1366+
return Nothing<BaseObjectPtrList>();
13681367
}
1369-
if (!list_v->IsArray()) return Just(BaseObjectList {});
1368+
if (!list_v->IsArray()) return Just(BaseObjectPtrList{});
13701369
Local<Array> list = list_v.As<Array>();
13711370

1372-
BaseObjectList ret;
1371+
BaseObjectPtrList ret;
13731372
for (size_t i = 0; i < list->Length(); i++) {
13741373
Local<Value> value;
13751374
if (!list->Get(context, i).ToLocal(&value))
1376-
return Nothing<BaseObjectList>();
1375+
return Nothing<BaseObjectPtrList>();
13771376
if (!value->IsObject()) {
13781377
continue;
13791378
}

src/node_realm-inl.h

+13-12
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
55

6-
#include "cleanup_queue-inl.h"
6+
#include "node_context_data.h"
77
#include "node_realm.h"
88

99
namespace node {
@@ -105,11 +105,9 @@ inline BindingDataStore* Realm::binding_data_store() {
105105

106106
template <typename T>
107107
void Realm::ForEachBaseObject(T&& iterator) const {
108-
cleanup_queue_.ForEachBaseObject(std::forward<T>(iterator));
109-
}
110-
111-
void Realm::modify_base_object_count(int64_t delta) {
112-
base_object_count_ += delta;
108+
for (auto bo : base_object_list_) {
109+
iterator(bo);
110+
}
113111
}
114112

115113
int64_t Realm::base_object_created_after_bootstrap() const {
@@ -120,16 +118,19 @@ int64_t Realm::base_object_count() const {
120118
return base_object_count_;
121119
}
122120

123-
void Realm::AddCleanupHook(CleanupQueue::Callback fn, void* arg) {
124-
cleanup_queue_.Add(fn, arg);
121+
void Realm::TrackBaseObject(BaseObject* bo) {
122+
DCHECK_EQ(bo->realm(), this);
123+
base_object_list_.PushBack(bo);
124+
++base_object_count_;
125125
}
126126

127-
void Realm::RemoveCleanupHook(CleanupQueue::Callback fn, void* arg) {
128-
cleanup_queue_.Remove(fn, arg);
127+
void Realm::UntrackBaseObject(BaseObject* bo) {
128+
DCHECK_EQ(bo->realm(), this);
129+
--base_object_count_;
129130
}
130131

131-
bool Realm::HasCleanupHooks() const {
132-
return !cleanup_queue_.empty();
132+
bool Realm::PendingCleanup() const {
133+
return !base_object_list_.IsEmpty();
133134
}
134135

135136
} // namespace node

src/node_realm.cc

+2-2
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ void Realm::MemoryInfo(MemoryTracker* tracker) const {
3434
PER_REALM_STRONG_PERSISTENT_VALUES(V)
3535
#undef V
3636

37-
tracker->TrackField("cleanup_queue", cleanup_queue_);
37+
tracker->TrackField("base_object_list", base_object_list_);
3838
tracker->TrackField("builtins_with_cache", builtins_with_cache);
3939
tracker->TrackField("builtins_without_cache", builtins_without_cache);
4040
}
@@ -215,7 +215,7 @@ void Realm::RunCleanup() {
215215
for (size_t i = 0; i < binding_data_store_.size(); ++i) {
216216
binding_data_store_[i].reset();
217217
}
218-
cleanup_queue_.Drain();
218+
base_object_list_.Cleanup();
219219
}
220220

221221
void Realm::PrintInfoForSnapshot() {

src/node_realm.h

+4-5
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,9 @@ class Realm : public MemoryRetainer {
7171
v8::MaybeLocal<v8::Value> ExecuteBootstrapper(const char* id);
7272
v8::MaybeLocal<v8::Value> RunBootstrapping();
7373

74-
inline void AddCleanupHook(CleanupQueue::Callback cb, void* arg);
75-
inline void RemoveCleanupHook(CleanupQueue::Callback cb, void* arg);
76-
inline bool HasCleanupHooks() const;
74+
inline void TrackBaseObject(BaseObject* bo);
75+
inline void UntrackBaseObject(BaseObject* bo);
76+
inline bool PendingCleanup() const;
7777
void RunCleanup();
7878

7979
template <typename T>
@@ -108,7 +108,6 @@ class Realm : public MemoryRetainer {
108108
// The BaseObject count is a debugging helper that makes sure that there are
109109
// no memory leaks caused by BaseObjects staying alive longer than expected
110110
// (in particular, no circular BaseObjectPtr references).
111-
inline void modify_base_object_count(int64_t delta);
112111
inline int64_t base_object_count() const;
113112

114113
// Base object count created after the bootstrap of the realm.
@@ -154,7 +153,7 @@ class Realm : public MemoryRetainer {
154153

155154
BindingDataStore binding_data_store_;
156155

157-
CleanupQueue cleanup_queue_;
156+
BaseObjectList base_object_list_;
158157
};
159158

160159
class PrincipalRealm : public Realm {

src/node_shadow_realm.cc

+1-1
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ ShadowRealm::ShadowRealm(Environment* env)
8484
}
8585

8686
ShadowRealm::~ShadowRealm() {
87-
while (HasCleanupHooks()) {
87+
while (PendingCleanup()) {
8888
RunCleanup();
8989
}
9090

src/node_task_runner.cc

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#include "node_task_runner.h"
2-
#include "util.h"
2+
#include "util-inl.h"
33

44
#include <regex> // NOLINT(build/c++11)
55

test/pummel/test-heapdump-env.js

+1
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,6 @@ validateSnapshotNodes('Node / Environment', [{
1919
validateSnapshotNodes('Node / PrincipalRealm', [{
2020
children: [
2121
{ node_name: 'process', edge_name: 'process_object' },
22+
{ node_name: 'Node / BaseObjectList', edge_name: 'base_object_list' },
2223
],
2324
}]);

0 commit comments

Comments
 (0)