Skip to content

Commit 0edbfae

Browse files
committed
src: free placeholder nodes for cppgc wrappers in MemoryTracker
`MemoryTracker::AddNode(const CppgcMixin*)` allocates a `MemoryRetainerNode` that only stands in for the wrapper's JS node while its `MemoryInfo()` runs; unlike the other node kinds it is not handed to the `EmbedderGraph`, and nothing freed it. Every heap snapshot (or other `BuildEmbedderGraph` call) leaked one node per live `vm.Script` or `vm` context. Keep the placeholders in the tracker and free them with it, and make the already-seen path in `Track(const CppgcMixin*)` add its edge to the wrapper's JS node like the first visit does, so the graph never refers to a placeholder. Refs: #56534 Signed-off-by: Shelley Vohr <shelley.vohr@gmail.com>
1 parent 46bbfc4 commit 0edbfae

3 files changed

Lines changed: 27 additions & 5 deletions

File tree

src/memory_tracker-inl.h

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -300,7 +300,7 @@ void MemoryTracker::Track(const CppgcMixin* retainer, const char* edge_name) {
300300
auto it = seen_.find(retainer);
301301
if (it != seen_.end()) {
302302
if (CurrentNode() != nullptr) {
303-
AddEdge(CurrentNode(), it->second, edge_name);
303+
AddEdge(CurrentNode(), it->second->JSWrapperNode(), edge_name);
304304
}
305305
return; // It has already been tracked, no need to call MemoryInfo again
306306
}
@@ -357,6 +357,11 @@ inline void MemoryTracker::TraitTrackInline(const T& retainer,
357357
-(static_cast<int>(MemoryRetainerTraits<T>::SelfSize(retainer))));
358358
}
359359

360+
MemoryTracker::MemoryTracker(v8::Isolate* isolate, v8::EmbedderGraph* graph)
361+
: isolate_(isolate), graph_(graph) {}
362+
363+
MemoryTracker::~MemoryTracker() = default;
364+
360365
v8::EmbedderGraph::Node* MemoryTracker::CurrentNode() const {
361366
if (node_stack_.empty()) return nullptr;
362367
MemoryRetainerNode* n = node_stack_.top();
@@ -373,7 +378,8 @@ MemoryRetainerNode* MemoryTracker::AddNode(const CppgcMixin* retainer,
373378
return it->second;
374379
}
375380

376-
MemoryRetainerNode* n = new MemoryRetainerNode(this, retainer);
381+
cppgc_nodes_.push_back(std::make_unique<MemoryRetainerNode>(this, retainer));
382+
MemoryRetainerNode* n = cppgc_nodes_.back().get();
377383
seen_[retainer] = n;
378384
if (CurrentNode() != nullptr) {
379385
AddEdge(CurrentNode(), n->JSWrapperNode(), edge_name);

src/memory_tracker.h

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,12 @@
88
#include <uv.h>
99

1010
#include <limits>
11+
#include <memory>
1112
#include <queue>
1213
#include <stack>
1314
#include <string>
1415
#include <unordered_map>
16+
#include <vector>
1517

1618
namespace v8 {
1719
class BackingStore;
@@ -294,9 +296,8 @@ class MemoryTracker {
294296
inline v8::EmbedderGraph* graph() { return graph_; }
295297
inline v8::Isolate* isolate() { return isolate_; }
296298

297-
inline explicit MemoryTracker(v8::Isolate* isolate,
298-
v8::EmbedderGraph* graph)
299-
: isolate_(isolate), graph_(graph) {}
299+
inline explicit MemoryTracker(v8::Isolate* isolate, v8::EmbedderGraph* graph);
300+
inline ~MemoryTracker();
300301

301302
// Can be passed to Track() if it is not desirable
302303
// to create a strong edge between nodes, i.e. when
@@ -334,6 +335,8 @@ class MemoryTracker {
334335
v8::EmbedderGraph* graph_;
335336
std::stack<MemoryRetainerNode*> node_stack_;
336337
NodeMap seen_;
338+
// Placeholder nodes for cppgc wrappers; the graph only owns their JS nodes.
339+
std::vector<std::unique_ptr<MemoryRetainerNode>> cppgc_nodes_;
337340
};
338341

339342
} // namespace node

test/cctest/test_environment.cc

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,19 @@ TEST_F(EnvironmentTest, CollectExternalReferencesFromSeveralThreads) {
411411
EXPECT_EQ(node::SnapshotBuilder::CollectExternalReferences().back(), 0);
412412
}
413413

414+
TEST_F(EnvironmentTest, HeapSnapshotWithCppgcWrappersDoesNotLeak) {
415+
const v8::HandleScope handle_scope(isolate_);
416+
const Argv argv;
417+
Env env{handle_scope, argv};
418+
node::LoadEnvironment(*env,
419+
"const vm = require('vm');"
420+
"globalThis.script = new vm.Script('1');"
421+
"globalThis.context = vm.createContext();")
422+
.ToLocalChecked();
423+
node::heap::HeapSnapshotPointer snapshot{
424+
isolate_->GetHeapProfiler()->TakeHeapSnapshot()};
425+
}
426+
414427
TEST_F(EnvironmentTest, NoEnvironmentSanity) {
415428
const v8::HandleScope handle_scope(isolate_);
416429
v8::Local<v8::Context> context = v8::Context::New(isolate_);

0 commit comments

Comments
 (0)