Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

src: track BaseObjects with an efficient list #55104

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 13 additions & 4 deletions src/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -760,10 +760,19 @@ a `void* hint` argument.
Inside these cleanup hooks, new asynchronous operations _may_ be started on the
event loop, although ideally that is avoided as much as possible.

Every [`BaseObject`][] has its own cleanup hook that deletes it. For
[`ReqWrap`][] and [`HandleWrap`][] instances, cleanup of the associated libuv
objects is performed automatically, i.e. handles are closed and requests
are cancelled if possible.
For every [`ReqWrap`][] and [`HandleWrap`][] instance, the cleanup of the
associated libuv objects is performed automatically, i.e. handles are closed
and requests are cancelled if possible.

#### Cleanup realms and BaseObjects

Realm cleanup depends on the realm types. All realms are destroyed when the
[`Environment`][] is destroyed with the cleanup hook. A [`ShadowRealm`][] can
also be destroyed by the garbage collection when there is no strong reference
to it.

Every [`BaseObject`][] is tracked with its creation realm and will be destroyed
when the realm is tearing down.

#### Closing libuv handles

Expand Down
1 change: 1 addition & 0 deletions src/aliased_buffer-inl.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS

#include "aliased_buffer.h"
#include "memory_tracker-inl.h"
#include "util-inl.h"

namespace node {
Expand Down
29 changes: 20 additions & 9 deletions src/base_object.cc
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include "base_object.h"
#include "env-inl.h"
#include "memory_tracker-inl.h"
#include "node_messaging.h"
#include "node_realm-inl.h"

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

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

if (UNLIKELY(has_pointer_data())) {
PointerData* metadata = pointer_data();
Expand Down Expand Up @@ -146,12 +145,11 @@ void BaseObject::increase_refcount() {
persistent_handle_.ClearWeak();
}

void BaseObject::DeleteMe(void* data) {
BaseObject* self = static_cast<BaseObject*>(data);
if (self->has_pointer_data() && self->pointer_data()->strong_ptr_count > 0) {
return self->Detach();
void BaseObject::DeleteMe() {
if (has_pointer_data() && pointer_data()->strong_ptr_count > 0) {
return Detach();
}
delete self;
delete this;
}

bool BaseObject::IsDoneInitializing() const {
Expand All @@ -170,4 +168,17 @@ bool BaseObject::IsNotIndicativeOfMemoryLeakAtExit() const {
return IsWeakOrDetached();
}

void BaseObjectList::Cleanup() {
while (!IsEmpty()) {
BaseObject* bo = PopFront();
bo->DeleteMe();
}
}

void BaseObjectList::MemoryInfo(node::MemoryTracker* tracker) const {
for (auto bo : *this) {
if (bo->IsDoneInitializing()) tracker->Track(bo);
}
}

} // namespace node
17 changes: 16 additions & 1 deletion src/base_object.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
#include <type_traits> // std::remove_reference
#include "base_object_types.h"
#include "memory_tracker.h"
#include "util.h"
#include "v8.h"

namespace node {
Expand Down Expand Up @@ -192,7 +193,7 @@ class BaseObject : public MemoryRetainer {
private:
v8::Local<v8::Object> WrappedObject() const override;
bool IsRootNode() const override;
static void DeleteMe(void* data);
void DeleteMe();

// persistent_handle_ 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
Expand Down Expand Up @@ -237,6 +238,20 @@ class BaseObject : public MemoryRetainer {

Realm* realm_;
PointerData* pointer_data_ = nullptr;
ListNode<BaseObject> base_object_list_node_;

friend class BaseObjectList;
};

class BaseObjectList
: public ListHead<BaseObject, &BaseObject::base_object_list_node_>,
public MemoryRetainer {
public:
void Cleanup();

SET_MEMORY_INFO_NAME(BaseObjectList)
SET_SELF_SIZE(BaseObjectList)
void MemoryInfo(node::MemoryTracker* tracker) const override;
};

#define ASSIGN_OR_RETURN_UNWRAP(ptr, obj, ...) \
Expand Down
26 changes: 0 additions & 26 deletions src/cleanup_queue-inl.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,11 @@

#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS

#include "base_object.h"
#include "cleanup_queue.h"
#include "memory_tracker-inl.h"
#include "util.h"

namespace node {

inline void CleanupQueue::MemoryInfo(MemoryTracker* tracker) const {
ForEachBaseObject([&](BaseObject* obj) {
if (obj->IsDoneInitializing()) tracker->Track(obj);
});
}

inline size_t CleanupQueue::SelfSize() const {
return sizeof(CleanupQueue) +
cleanup_hooks_.size() * sizeof(CleanupHookCallback);
Expand All @@ -37,24 +29,6 @@ void CleanupQueue::Remove(Callback cb, void* arg) {
cleanup_hooks_.erase(search);
}

template <typename T>
void CleanupQueue::ForEachBaseObject(T&& iterator) const {
std::vector<CleanupHookCallback> callbacks = GetOrdered();

for (const auto& hook : callbacks) {
BaseObject* obj = GetBaseObject(hook);
if (obj != nullptr) iterator(obj);
}
}

BaseObject* CleanupQueue::GetBaseObject(
const CleanupHookCallback& callback) const {
if (callback.fn_ == BaseObject::DeleteMe)
return static_cast<BaseObject*>(callback.arg_);
else
return nullptr;
}

} // namespace node

#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
Expand Down
8 changes: 1 addition & 7 deletions src/cleanup_queue.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@

namespace node {

class BaseObject;

class CleanupQueue : public MemoryRetainer {
public:
typedef void (*Callback)(void*);
Expand All @@ -24,7 +22,7 @@ class CleanupQueue : public MemoryRetainer {
CleanupQueue(const CleanupQueue&) = delete;

SET_MEMORY_INFO_NAME(CleanupQueue)
inline void MemoryInfo(node::MemoryTracker* tracker) const override;
SET_NO_MEMORY_INFO()
inline size_t SelfSize() const override;

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

template <typename T>
inline void ForEachBaseObject(T&& iterator) const;

private:
class CleanupHookCallback {
public:
Expand Down Expand Up @@ -68,7 +63,6 @@ class CleanupQueue : public MemoryRetainer {
};

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

// Use an unordered_set, so that we have efficient insertion and removal.
std::unordered_set<CleanupHookCallback,
Expand Down
1 change: 1 addition & 0 deletions src/debug_utils-inl.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

#include "debug_utils.h"
#include "env.h"
#include "util-inl.h"

#include <type_traits>

Expand Down
2 changes: 1 addition & 1 deletion src/env.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1279,7 +1279,7 @@ void Environment::RunCleanup() {
cleanable->Clean();
}

while (!cleanup_queue_.empty() || principal_realm_->HasCleanupHooks() ||
while (!cleanup_queue_.empty() || principal_realm_->PendingCleanup() ||
native_immediates_.size() > 0 ||
native_immediates_threadsafe_.size() > 0 ||
native_immediates_interrupts_.size() > 0) {
Expand Down
17 changes: 8 additions & 9 deletions src/node_messaging.cc
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ using v8::WasmModuleObject;

namespace node {

using BaseObjectList = std::vector<BaseObjectPtr<BaseObject>>;
using BaseObjectPtrList = std::vector<BaseObjectPtr<BaseObject>>;
using TransferMode = BaseObject::TransferMode;

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

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

Local<Value> method;
if (!target()->Get(context, method_name).ToLocal(&method)) {
return Nothing<BaseObjectList>();
return Nothing<BaseObjectPtrList>();
}
if (!method->IsFunction()) return Just(BaseObjectList {});
if (!method->IsFunction()) return Just(BaseObjectPtrList{});

Local<Value> list_v;
if (!method.As<Function>()
->Call(context, target(), 0, nullptr)
.ToLocal(&list_v)) {
return Nothing<BaseObjectList>();
return Nothing<BaseObjectPtrList>();
}
if (!list_v->IsArray()) return Just(BaseObjectList {});
if (!list_v->IsArray()) return Just(BaseObjectPtrList{});
Local<Array> list = list_v.As<Array>();

BaseObjectList ret;
BaseObjectPtrList ret;
for (size_t i = 0; i < list->Length(); i++) {
Local<Value> value;
if (!list->Get(context, i).ToLocal(&value))
return Nothing<BaseObjectList>();
return Nothing<BaseObjectPtrList>();
if (!value->IsObject()) {
continue;
}
Expand Down
25 changes: 13 additions & 12 deletions src/node_realm-inl.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS

#include "cleanup_queue-inl.h"
#include "node_context_data.h"
#include "node_realm.h"

namespace node {
Expand Down Expand Up @@ -105,11 +105,9 @@ inline BindingDataStore* Realm::binding_data_store() {

template <typename T>
void Realm::ForEachBaseObject(T&& iterator) const {
cleanup_queue_.ForEachBaseObject(std::forward<T>(iterator));
}

void Realm::modify_base_object_count(int64_t delta) {
base_object_count_ += delta;
for (auto bo : base_object_list_) {
iterator(bo);
}
}

int64_t Realm::base_object_created_after_bootstrap() const {
Expand All @@ -120,16 +118,19 @@ int64_t Realm::base_object_count() const {
return base_object_count_;
}

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

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

bool Realm::HasCleanupHooks() const {
return !cleanup_queue_.empty();
bool Realm::PendingCleanup() const {
return !base_object_list_.IsEmpty();
}

} // namespace node
Expand Down
4 changes: 2 additions & 2 deletions src/node_realm.cc
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ void Realm::MemoryInfo(MemoryTracker* tracker) const {
PER_REALM_STRONG_PERSISTENT_VALUES(V)
#undef V

tracker->TrackField("cleanup_queue", cleanup_queue_);
tracker->TrackField("base_object_list", base_object_list_);
tracker->TrackField("builtins_with_cache", builtins_with_cache);
tracker->TrackField("builtins_without_cache", builtins_without_cache);
}
Expand Down Expand Up @@ -215,7 +215,7 @@ void Realm::RunCleanup() {
for (size_t i = 0; i < binding_data_store_.size(); ++i) {
binding_data_store_[i].reset();
}
cleanup_queue_.Drain();
base_object_list_.Cleanup();
}

void Realm::PrintInfoForSnapshot() {
Expand Down
9 changes: 4 additions & 5 deletions src/node_realm.h
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,9 @@ class Realm : public MemoryRetainer {
v8::MaybeLocal<v8::Value> ExecuteBootstrapper(const char* id);
v8::MaybeLocal<v8::Value> RunBootstrapping();

inline void AddCleanupHook(CleanupQueue::Callback cb, void* arg);
inline void RemoveCleanupHook(CleanupQueue::Callback cb, void* arg);
inline bool HasCleanupHooks() const;
inline void TrackBaseObject(BaseObject* bo);
inline void UntrackBaseObject(BaseObject* bo);
inline bool PendingCleanup() const;
void RunCleanup();

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

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

BindingDataStore binding_data_store_;

CleanupQueue cleanup_queue_;
BaseObjectList base_object_list_;
joyeecheung marked this conversation as resolved.
Show resolved Hide resolved
};

class PrincipalRealm : public Realm {
Expand Down
2 changes: 1 addition & 1 deletion src/node_shadow_realm.cc
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ ShadowRealm::ShadowRealm(Environment* env)
}

ShadowRealm::~ShadowRealm() {
while (HasCleanupHooks()) {
while (PendingCleanup()) {
RunCleanup();
}

Expand Down
2 changes: 1 addition & 1 deletion src/node_task_runner.cc
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#include "node_task_runner.h"
#include "util.h"
#include "util-inl.h"

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

Expand Down
1 change: 1 addition & 0 deletions test/pummel/test-heapdump-env.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,6 @@ validateSnapshotNodes('Node / Environment', [{
validateSnapshotNodes('Node / PrincipalRealm', [{
children: [
{ node_name: 'process', edge_name: 'process_object' },
{ node_name: 'Node / BaseObjectList', edge_name: 'base_object_list' },
],
}]);
Loading