Skip to content

Commit 2fb168f

Browse files
addaleaxnodejs-github-bot
authored andcommitted
src: do not track weak BaseObjects as childrens of Realms
Heap dumps are used for analyzing the relationship between objects that keep each other alive, so that retainers of memory can be detected and memory leaks fixed. 6e60ab7 broke this for a wide range of `BaseObject` instances. Marking all `BaseObject`s, including weak ones, as children of `Realm` and `Environment` instances gives the incorrect impression that they are held alive by those objects, effectively hiding the "real" set of strong roots that keep other objects alive through GC. An upcoming change in V8 will allow us to represent this relationship between `Realm` and `Environment` and its `BaseObject` and `CppgcMixin` instances properly. Refs: v8/v8@e37cadf Refs: #57417 Signed-off-by: Anna Henningsen <anna@addaleax.net> PR-URL: #63842 Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com> Reviewed-By: Chengzhong Wu <legendecas@gmail.com>
1 parent 17285d3 commit 2fb168f

4 files changed

Lines changed: 39 additions & 4 deletions

File tree

src/base_object.cc

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -174,9 +174,15 @@ void BaseObjectList::Cleanup() {
174174
}
175175
}
176176

177-
void BaseObjectList::MemoryInfo(node::MemoryTracker* tracker) const {
177+
void BaseObjectList::MemoryInfo(MemoryTracker* tracker) const {
178178
for (auto bo : *this) {
179-
if (bo->IsDoneInitializing()) tracker->Track(bo);
179+
if (bo->IsDoneInitializing()) {
180+
// TODO(addaleax): Add weak edges instead of no edges once
181+
// https://github.com/v8/v8/commit/e37cadf1143a8c5bbe44c0408186b5a26cc23863
182+
// is available for us
183+
tracker->Track(
184+
bo, bo->persistent().IsWeak() ? MemoryTracker::kWeakEdge : nullptr);
185+
}
180186
}
181187
}
182188

src/cppgc_helpers.cc

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,10 @@ void CppgcWrapperList::MemoryInfo(MemoryTracker* tracker) const {
1616
for (auto node : *this) {
1717
CppgcMixin* ptr = node->persistent.Get();
1818
if (ptr != nullptr) {
19-
tracker->Track(ptr);
19+
// TODO(addaleax): Add weak edges instead of no edges once
20+
// https://github.com/v8/v8/commit/e37cadf1143a8c5bbe44c0408186b5a26cc23863
21+
// is available for us
22+
tracker->Track(ptr, MemoryTracker::kWeakEdge);
2023
}
2124
}
2225
}

test/common/heap.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,11 +336,25 @@ function validateByRetainingPath(...args) {
336336
return validateByRetainingPathFromNodes(nodes, ...args);
337337
}
338338

339+
function getRetainingNodes(startingNode, filter) {
340+
const seen = new Set();
341+
function listNodes(node) {
342+
if (!filter(node) || seen.has(node)) return;
343+
seen.add(node);
344+
for (const edge of node.incomingEdges) {
345+
listNodes(edge.from);
346+
}
347+
}
348+
listNodes(startingNode);
349+
return [...seen];
350+
}
351+
339352
module.exports = {
340353
recordState,
341354
validateSnapshotNodes,
342355
validateByRetainingPath,
343356
validateByRetainingPathFromNodes,
344357
getHeapSnapshotOptionTests,
345358
createJSHeapSnapshot,
359+
getRetainingNodes,
346360
};

test/pummel/test-heapdump-zlib.js

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
const common = require('../common');
55
const assert = require('assert');
6-
const { validateByRetainingPath, validateByRetainingPathFromNodes } = require('../common/heap');
6+
const { validateByRetainingPath, validateByRetainingPathFromNodes, getRetainingNodes } = require('../common/heap');
77
const zlib = require('zlib');
88

99
// Before zlib stream is created, no ZlibStream should be created.
@@ -27,6 +27,18 @@ const gzip = zlib.createGzip();
2727
assert.strictEqual(withMemory.length, 0);
2828
}
2929

30+
{
31+
// Assert that the `ZlibStream` has no unexpected connections
32+
// to other `Node / ...` nodes.
33+
const contexts = validateByRetainingPath('Node / ZlibContext', []);
34+
assert.deepStrictEqual(
35+
getRetainingNodes(contexts[0], (node) => node.name?.startsWith('Node /'))
36+
.map((node) => node.name).sort(), [
37+
'Node / ZlibContext',
38+
'Node / ZlibStream',
39+
]);
40+
}
41+
3042
// After zlib stream is written, zlib_memory should be created.
3143
gzip.write('hello world', common.mustCall(() => {
3244
validateByRetainingPath('Node / ZlibStream', [

0 commit comments

Comments
 (0)