Skip to content

Commit 83aaad7

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 f8dddd3 commit 83aaad7

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
@@ -1030,33 +1030,16 @@ Environment* Environment::worker_parent_env() const {
10301030
return worker_context()->env();
10311031
}
10321032

1033-
void MemoryTracker::TrackField(const char* edge_name,
1034-
const CleanupHookCallback& value,
1035-
const char* node_name) {
1036-
HandleScope handle_scope(isolate_);
1037-
// Here, we utilize the fact that CleanupHookCallback instances
1038-
// are all unique and won't be tracked twice in one BuildEmbedderGraph
1039-
// callback.
1040-
MemoryRetainerNode* n =
1041-
PushNode("CleanupHookCallback", sizeof(value), edge_name);
1042-
// TODO(joyeecheung): at the moment only arguments of type BaseObject will be
1043-
// identified and tracked here (based on their deleters),
1044-
// but we may convert and track other known types here.
1045-
BaseObject* obj = value.GetBaseObject();
1046-
if (obj != nullptr && obj->IsDoneInitializing()) {
1047-
TrackField("arg", obj);
1048-
}
1049-
CHECK_EQ(CurrentNode(), n);
1050-
CHECK_NE(n->size_, 0);
1051-
PopNode();
1052-
}
1053-
10541033
void Environment::BuildEmbedderGraph(Isolate* isolate,
10551034
EmbedderGraph* graph,
10561035
void* data) {
10571036
MemoryTracker tracker(isolate, graph);
10581037
Environment* env = static_cast<Environment*>(data);
10591038
tracker.Track(env);
1039+
env->ForEachBaseObject([&](BaseObject* obj) {
1040+
if (obj->IsDoneInitializing())
1041+
tracker.Track(obj);
1042+
});
10601043
}
10611044

10621045
inline size_t Environment::SelfSize() const {
@@ -1083,7 +1066,8 @@ void Environment::MemoryInfo(MemoryTracker* tracker) const {
10831066
tracker->TrackField("should_abort_on_uncaught_toggle",
10841067
should_abort_on_uncaught_toggle_);
10851068
tracker->TrackField("stream_base_state", stream_base_state_);
1086-
tracker->TrackField("cleanup_hooks", cleanup_hooks_);
1069+
tracker->TrackFieldWithSize(
1070+
"cleanup_hooks", cleanup_hooks_.size() * sizeof(CleanupHookCallback));
10871071
tracker->TrackField("async_hooks", async_hooks_);
10881072
tracker->TrackField("immediate_info", immediate_info_);
10891073
tracker->TrackField("tick_info", tick_info_);
@@ -1124,4 +1108,8 @@ Local<Object> BaseObject::WrappedObject() const {
11241108
return object();
11251109
}
11261110

1111+
bool BaseObject::IsRootNode() const {
1112+
return !persistent_handle_.IsWeak();
1113+
}
1114+
11271115
} // namespace node

src/memory_tracker.h

-7
Original file line numberDiff line numberDiff line change
@@ -216,13 +216,6 @@ class MemoryTracker {
216216
inline void TrackField(const char* edge_name,
217217
const v8::BackingStore* value,
218218
const char* node_name = nullptr);
219-
// We do not implement CleanupHookCallback as MemoryRetainer
220-
// but instead specialize the method here to avoid the cost of
221-
// virtual pointers.
222-
// TODO(joyeecheung): do this for BaseObject and remove WrappedObject()
223-
void TrackField(const char* edge_name,
224-
const CleanupHookCallback& value,
225-
const char* node_name = nullptr);
226219
inline void TrackField(const char* edge_name,
227220
const uv_buf_t& value,
228221
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)