Skip to content

Commit 9199808

Browse files
addaleaxcodebytere
authored andcommitted
src: do not track BaseObjects via cleanup hooks
Since f59ec2a, `BaseObject` instances were tracked in heap snapshots through their associated `CleanupHookCallback`s which were stored on the `Environment`; however, this is inaccurate, because: - Edges in heap dumps imply a keeps-alive relationship, but cleanup hooks do not keep the `BaseObject`s that they point to alive. - It loses information about whether `BaseObject` instances are GC roots: Even weak `BaseObject`s are now, practically speaking, showing up as hanging off a GC root when that isn’t actually the case (e.g. in the description of #33468). Thus, this is a partial revert of f59ec2a. Refs: #33468 Refs: #27018 PR-URL: #33809 Reviewed-By: James M Snell <[email protected]> Reviewed-By: Colin Ihrig <[email protected]>
1 parent 2e97d82 commit 9199808

File tree

4 files changed

+11
-71
lines changed

4 files changed

+11
-71
lines changed

src/base_object.h

+1
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ class BaseObject : public MemoryRetainer {
103103

104104
private:
105105
v8::Local<v8::Object> WrappedObject() const override;
106+
bool IsRootNode() const override;
106107
static void DeleteMe(void* data);
107108

108109
// persistent_handle_ needs to be at a fixed offset from the start of the

src/env.cc

+10-22
Original file line numberDiff line numberDiff line change
@@ -986,33 +986,16 @@ Environment* Environment::worker_parent_env() const {
986986
return worker_context_->env();
987987
}
988988

989-
void MemoryTracker::TrackField(const char* edge_name,
990-
const CleanupHookCallback& value,
991-
const char* node_name) {
992-
HandleScope handle_scope(isolate_);
993-
// Here, we utilize the fact that CleanupHookCallback instances
994-
// are all unique and won't be tracked twice in one BuildEmbedderGraph
995-
// callback.
996-
MemoryRetainerNode* n =
997-
PushNode("CleanupHookCallback", sizeof(value), edge_name);
998-
// TODO(joyeecheung): at the moment only arguments of type BaseObject will be
999-
// identified and tracked here (based on their deleters),
1000-
// but we may convert and track other known types here.
1001-
BaseObject* obj = value.GetBaseObject();
1002-
if (obj != nullptr && obj->IsDoneInitializing()) {
1003-
TrackField("arg", obj);
1004-
}
1005-
CHECK_EQ(CurrentNode(), n);
1006-
CHECK_NE(n->size_, 0);
1007-
PopNode();
1008-
}
1009-
1010989
void Environment::BuildEmbedderGraph(Isolate* isolate,
1011990
EmbedderGraph* graph,
1012991
void* data) {
1013992
MemoryTracker tracker(isolate, graph);
1014993
Environment* env = static_cast<Environment*>(data);
1015994
tracker.Track(env);
995+
env->ForEachBaseObject([&](BaseObject* obj) {
996+
if (obj->IsDoneInitializing())
997+
tracker.Track(obj);
998+
});
1016999
}
10171000

10181001
inline size_t Environment::SelfSize() const {
@@ -1042,7 +1025,8 @@ void Environment::MemoryInfo(MemoryTracker* tracker) const {
10421025
tracker->TrackField("fs_stats_field_array", fs_stats_field_array_);
10431026
tracker->TrackField("fs_stats_field_bigint_array",
10441027
fs_stats_field_bigint_array_);
1045-
tracker->TrackField("cleanup_hooks", cleanup_hooks_);
1028+
tracker->TrackFieldWithSize(
1029+
"cleanup_hooks", cleanup_hooks_.size() * sizeof(CleanupHookCallback));
10461030
tracker->TrackField("async_hooks", async_hooks_);
10471031
tracker->TrackField("immediate_info", immediate_info_);
10481032
tracker->TrackField("tick_info", tick_info_);
@@ -1136,4 +1120,8 @@ Local<Object> BaseObject::WrappedObject() const {
11361120
return object();
11371121
}
11381122

1123+
bool BaseObject::IsRootNode() const {
1124+
return !persistent_handle_.IsWeak();
1125+
}
1126+
11391127
} // namespace node

src/memory_tracker.h

-7
Original file line numberDiff line numberDiff line change
@@ -208,13 +208,6 @@ class MemoryTracker {
208208
inline void TrackField(const char* edge_name,
209209
const MallocedBuffer<T>& value,
210210
const char* node_name = nullptr);
211-
// We do not implement CleanupHookCallback as MemoryRetainer
212-
// but instead specialize the method here to avoid the cost of
213-
// virtual pointers.
214-
// TODO(joyeecheung): do this for BaseObject and remove WrappedObject()
215-
void TrackField(const char* edge_name,
216-
const CleanupHookCallback& value,
217-
const char* node_name = nullptr);
218211
inline void TrackField(const char* edge_name,
219212
const uv_buf_t& value,
220213
const char* node_name = nullptr);

test/pummel/test-heapdump-env.js

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

66
require('../common');
77
const { validateSnapshotNodes } = require('../common/heap');
8-
const assert = require('assert');
98

109
// This is just using ContextifyScript as an example here, it can be replaced
1110
// with any BaseObject that we can easily instantiate here and register in
@@ -16,51 +15,10 @@ const context = require('vm').createScript('const foo = 123');
1615

1716
validateSnapshotNodes('Node / Environment', [{
1817
children: [
19-
cleanupHooksFilter,
2018
{ node_name: 'Node / cleanup_hooks', edge_name: 'cleanup_hooks' },
2119
{ node_name: 'process', edge_name: 'process_object' },
2220
{ node_name: 'Node / IsolateData', edge_name: 'isolate_data' },
2321
]
2422
}]);
2523

26-
function cleanupHooksFilter(edge) {
27-
if (edge.name !== 'cleanup_hooks') {
28-
return false;
29-
}
30-
if (edge.to.type === 'native') {
31-
verifyCleanupHooksInSnapshot(edge.to);
32-
} else {
33-
verifyCleanupHooksInGraph(edge.to);
34-
}
35-
return true;
36-
}
37-
38-
function verifyCleanupHooksInSnapshot(node) {
39-
assert.strictEqual(node.name, 'Node / cleanup_hooks');
40-
const baseObjects = [];
41-
for (const hook of node.outgoingEdges) {
42-
for (const hookEdge of hook.to.outgoingEdges) {
43-
if (hookEdge.name === 'arg') {
44-
baseObjects.push(hookEdge.to);
45-
}
46-
}
47-
}
48-
// Make sure our ContextifyScript show up.
49-
assert(baseObjects.some((node) => node.name === 'Node / ContextifyScript'));
50-
}
51-
52-
function verifyCleanupHooksInGraph(node) {
53-
assert.strictEqual(node.name, 'Node / cleanup_hooks');
54-
const baseObjects = [];
55-
for (const hook of node.edges) {
56-
for (const hookEdge of hook.to.edges) {
57-
if (hookEdge.name === 'arg') {
58-
baseObjects.push(hookEdge.to);
59-
}
60-
}
61-
}
62-
// Make sure our ContextifyScript show up.
63-
assert(baseObjects.some((node) => node.name === 'Node / ContextifyScript'));
64-
}
65-
6624
console.log(context); // Make sure it's not GC'ed

0 commit comments

Comments
 (0)