Skip to content

Commit f59ec2a

Browse files
committed
src: implement MemoryRetainer in Environment
This allows us to track the essentially-global objects in Environment in the heap snapshot. Note that this patch only tracks the fields that can be tracked correctly. There are still several types of fields that cannot be tracked: - v8::Data including v8::Private, v8::ObjectTemplate etc. - Internal types that do not implement MemoryRetainer yet - STL containers with MemoryRetainer* inside - STL containers with numeric types inside that should not have their nodes elided e.g. numeric keys in maps. The `BaseObject`s are now no longer globals. They are tracked as arguments in CleanupHookCallbacks referenced by the Environment node. This model is closer to how their lifetime is managed internally. To track the per-environment strong persistent properties, this patch divides them into those that are also `v8::Value` and those that are just `v8::Data`. The values can be tracked by the current memory tracker while the data cannot. This patch also implements the `MemoryRetainer` interface in several internal classes so that they can be tracked in the heap snapshot. PR-URL: #27018 Refs: #26776 Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: James M Snell <[email protected]>
1 parent ceb80f4 commit f59ec2a

7 files changed

+292
-66
lines changed

src/base_object.h

+1-2
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,6 @@ class BaseObject : public MemoryRetainer {
8686

8787
private:
8888
v8::Local<v8::Object> WrappedObject() const override;
89-
bool IsRootNode() const override;
9089
static void DeleteMe(void* data);
9190

9291
// persistent_handle_ needs to be at a fixed offset from the start of the
@@ -95,7 +94,7 @@ class BaseObject : public MemoryRetainer {
9594
// position of members in memory are predictable. For more information please
9695
// refer to `doc/guides/node-postmortem-support.md`
9796
friend int GenDebugSymbols();
98-
friend class Environment;
97+
friend class CleanupHookCallback;
9998

10099
Persistent<v8::Object> persistent_handle_;
101100
Environment* env_;

src/env-inl.h

+4-3
Original file line numberDiff line numberDiff line change
@@ -980,17 +980,17 @@ void Environment::RemoveCleanupHook(void (*fn)(void*), void* arg) {
980980
cleanup_hooks_.erase(search);
981981
}
982982

983-
size_t Environment::CleanupHookCallback::Hash::operator()(
983+
size_t CleanupHookCallback::Hash::operator()(
984984
const CleanupHookCallback& cb) const {
985985
return std::hash<void*>()(cb.arg_);
986986
}
987987

988-
bool Environment::CleanupHookCallback::Equal::operator()(
988+
bool CleanupHookCallback::Equal::operator()(
989989
const CleanupHookCallback& a, const CleanupHookCallback& b) const {
990990
return a.fn_ == b.fn_ && a.arg_ == b.arg_;
991991
}
992992

993-
BaseObject* Environment::CleanupHookCallback::GetBaseObject() const {
993+
BaseObject* CleanupHookCallback::GetBaseObject() const {
994994
if (fn_ == BaseObject::DeleteMe)
995995
return static_cast<BaseObject*>(arg_);
996996
else
@@ -1054,6 +1054,7 @@ void AsyncRequest::set_stopped(bool flag) {
10541054
PropertyName ## _.Reset(isolate(), value); \
10551055
}
10561056
ENVIRONMENT_STRONG_PERSISTENT_PROPERTIES(V)
1057+
ENVIRONMENT_STRONG_PERSISTENT_VALUES(V)
10571058
#undef V
10581059

10591060
} // namespace node

src/env.cc

+112-7
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,29 @@ IsolateData::IsolateData(Isolate* isolate,
112112
#undef V
113113
}
114114

115+
void IsolateData::MemoryInfo(MemoryTracker* tracker) const {
116+
#define V(PropertyName, StringValue) \
117+
tracker->TrackField(#PropertyName, PropertyName(isolate()));
118+
PER_ISOLATE_SYMBOL_PROPERTIES(V)
119+
#undef V
120+
121+
#define V(PropertyName, StringValue) \
122+
tracker->TrackField(#PropertyName, PropertyName(isolate()));
123+
PER_ISOLATE_STRING_PROPERTIES(V)
124+
#undef V
125+
126+
if (node_allocator_ != nullptr) {
127+
tracker->TrackFieldWithSize(
128+
"node_allocator", sizeof(*node_allocator_), "NodeArrayBufferAllocator");
129+
} else {
130+
tracker->TrackFieldWithSize(
131+
"allocator", sizeof(*allocator_), "v8::ArrayBuffer::Allocator");
132+
}
133+
tracker->TrackFieldWithSize(
134+
"platform", sizeof(*platform_), "MultiIsolatePlatform");
135+
// TODO(joyeecheung): implement MemoryRetainer in the option classes.
136+
}
137+
115138
void InitThreadLocalOnce() {
116139
CHECK_EQ(0, uv_key_create(&Environment::thread_local_env));
117140
}
@@ -707,6 +730,7 @@ void Environment::set_debug_categories(const std::string& cats, bool enabled) {
707730
}
708731

709732
DEBUG_CATEGORY_NAMES(V)
733+
#undef V
710734

711735
if (comma_pos == std::string::npos)
712736
break;
@@ -775,6 +799,21 @@ void Environment::CollectUVExceptionInfo(Local<Value> object,
775799
syscall, message, path, dest);
776800
}
777801

802+
void ImmediateInfo::MemoryInfo(MemoryTracker* tracker) const {
803+
tracker->TrackField("fields", fields_);
804+
}
805+
806+
void TickInfo::MemoryInfo(MemoryTracker* tracker) const {
807+
tracker->TrackField("fields", fields_);
808+
}
809+
810+
void AsyncHooks::MemoryInfo(MemoryTracker* tracker) const {
811+
tracker->TrackField("providers", providers_);
812+
tracker->TrackField("async_ids_stack", async_ids_stack_);
813+
tracker->TrackField("fields", fields_);
814+
tracker->TrackField("async_id_fields", async_id_fields_);
815+
}
816+
778817
void AsyncHooks::grow_async_ids_stack() {
779818
async_ids_stack_.reserve(async_ids_stack_.Length() * 3);
780819

@@ -805,13 +844,83 @@ void Environment::stop_sub_worker_contexts() {
805844
}
806845
}
807846

847+
void MemoryTracker::TrackField(const char* edge_name,
848+
const CleanupHookCallback& value,
849+
const char* node_name) {
850+
v8::HandleScope handle_scope(isolate_);
851+
// Here, we utilize the fact that CleanupHookCallback instances
852+
// are all unique and won't be tracked twice in one BuildEmbedderGraph
853+
// callback.
854+
MemoryRetainerNode* n =
855+
PushNode("CleanupHookCallback", sizeof(value), edge_name);
856+
// TODO(joyeecheung): at the moment only arguments of type BaseObject will be
857+
// identified and tracked here (based on their deleters),
858+
// but we may convert and track other known types here.
859+
BaseObject* obj = value.GetBaseObject();
860+
if (obj != nullptr) {
861+
this->TrackField("arg", obj);
862+
}
863+
CHECK_EQ(CurrentNode(), n);
864+
CHECK_NE(n->size_, 0);
865+
PopNode();
866+
}
867+
808868
void Environment::BuildEmbedderGraph(Isolate* isolate,
809869
EmbedderGraph* graph,
810870
void* data) {
811871
MemoryTracker tracker(isolate, graph);
812-
static_cast<Environment*>(data)->ForEachBaseObject([&](BaseObject* obj) {
813-
tracker.Track(obj);
814-
});
872+
Environment* env = static_cast<Environment*>(data);
873+
tracker.Track(env);
874+
}
875+
876+
inline size_t Environment::SelfSize() const {
877+
size_t size = sizeof(*this);
878+
// Remove non pointer fields that will be tracked in MemoryInfo()
879+
// TODO(joyeecheung): refactor the MemoryTracker interface so
880+
// this can be done for common types within the Track* calls automatically
881+
// if a certain scope is entered.
882+
size -= sizeof(thread_stopper_);
883+
size -= sizeof(async_hooks_);
884+
size -= sizeof(tick_info_);
885+
size -= sizeof(immediate_info_);
886+
return size;
887+
}
888+
889+
void Environment::MemoryInfo(MemoryTracker* tracker) const {
890+
// Iteratable STLs have their own sizes subtracted from the parent
891+
// by default.
892+
tracker->TrackField("isolate_data", isolate_data_);
893+
tracker->TrackField("native_modules_with_cache", native_modules_with_cache);
894+
tracker->TrackField("native_modules_without_cache",
895+
native_modules_without_cache);
896+
tracker->TrackField("destroy_async_id_list", destroy_async_id_list_);
897+
tracker->TrackField("exec_argv", exec_argv_);
898+
tracker->TrackField("should_abort_on_uncaught_toggle",
899+
should_abort_on_uncaught_toggle_);
900+
tracker->TrackField("stream_base_state", stream_base_state_);
901+
tracker->TrackField("fs_stats_field_array", fs_stats_field_array_);
902+
tracker->TrackField("fs_stats_field_bigint_array",
903+
fs_stats_field_bigint_array_);
904+
tracker->TrackField("thread_stopper", thread_stopper_);
905+
tracker->TrackField("cleanup_hooks", cleanup_hooks_);
906+
tracker->TrackField("async_hooks", async_hooks_);
907+
tracker->TrackField("immediate_info", immediate_info_);
908+
tracker->TrackField("tick_info", tick_info_);
909+
910+
#define V(PropertyName, TypeName) \
911+
tracker->TrackField(#PropertyName, PropertyName());
912+
ENVIRONMENT_STRONG_PERSISTENT_VALUES(V)
913+
#undef V
914+
915+
// FIXME(joyeecheung): track other fields in Environment.
916+
// Currently MemoryTracker is unable to track these
917+
// correctly:
918+
// - Internal types that do not implement MemoryRetainer yet
919+
// - STL containers with MemoryRetainer* inside
920+
// - STL containers with numeric types inside that should not have their
921+
// nodes elided e.g. numeric keys in maps.
922+
// We also need to make sure that when we add a non-pointer field as its own
923+
// node, we shift its sizeof() size out of the Environment node.
815924
}
816925

817926
char* Environment::Reallocate(char* data, size_t old_size, size_t size) {
@@ -875,8 +984,4 @@ Local<Object> BaseObject::WrappedObject() const {
875984
return object();
876985
}
877986

878-
bool BaseObject::IsRootNode() const {
879-
return !persistent_handle_.IsWeak();
880-
}
881-
882987
} // namespace node

0 commit comments

Comments
 (0)