Skip to content

Commit 67a7857

Browse files
authored
Merge pull request #17111 from karalabe/trie-memleak
trie: fix a temporary memory leak in the memcache
2 parents c73b654 + 319098c commit 67a7857

File tree

1 file changed

+49
-1
lines changed

1 file changed

+49
-1
lines changed

trie/database.go

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -466,7 +466,13 @@ func (db *Database) dereference(child common.Hash, parent common.Hash) {
466466
return
467467
}
468468
// If there are no more references to the child, delete it and cascade
469-
node.parents--
469+
if node.parents > 0 {
470+
// This is a special cornercase where a node loaded from disk (i.e. not in the
471+
// memcache any more) gets reinjected as a new node (short node split into full,
472+
// then reverted into short), causing a cached node to have no parents. That is
473+
// no problem in itself, but don't make maxint parents out of it.
474+
node.parents--
475+
}
470476
if node.parents == 0 {
471477
// Remove the node from the flush-list
472478
if child == db.oldest {
@@ -717,3 +723,45 @@ func (db *Database) Size() (common.StorageSize, common.StorageSize) {
717723
var flushlistSize = common.StorageSize((len(db.nodes) - 1) * 2 * common.HashLength)
718724
return db.nodesSize + flushlistSize, db.preimagesSize
719725
}
726+
727+
// verifyIntegrity is a debug method to iterate over the entire trie stored in
728+
// memory and check whether every node is reachable from the meta root. The goal
729+
// is to find any errors that might cause memory leaks and or trie nodes to go
730+
// missing.
731+
//
732+
// This method is extremely CPU and memory intensive, only use when must.
733+
func (db *Database) verifyIntegrity() {
734+
// Iterate over all the cached nodes and accumulate them into a set
735+
reachable := map[common.Hash]struct{}{{}: {}}
736+
737+
for child := range db.nodes[common.Hash{}].children {
738+
db.accumulate(child, reachable)
739+
}
740+
// Find any unreachable but cached nodes
741+
unreachable := []string{}
742+
for hash, node := range db.nodes {
743+
if _, ok := reachable[hash]; !ok {
744+
unreachable = append(unreachable, fmt.Sprintf("%x: {Node: %v, Parents: %d, Prev: %x, Next: %x}",
745+
hash, node.node, node.parents, node.flushPrev, node.flushNext))
746+
}
747+
}
748+
if len(unreachable) != 0 {
749+
panic(fmt.Sprintf("trie cache memory leak: %v", unreachable))
750+
}
751+
}
752+
753+
// accumulate iterates over the trie defined by hash and accumulates all the
754+
// cached children found in memory.
755+
func (db *Database) accumulate(hash common.Hash, reachable map[common.Hash]struct{}) {
756+
// Mark the node reachable if present in the memory cache
757+
node, ok := db.nodes[hash]
758+
if !ok {
759+
return
760+
}
761+
reachable[hash] = struct{}{}
762+
763+
// Iterate over all the children and accumulate them too
764+
for _, child := range node.childs() {
765+
db.accumulate(child, reachable)
766+
}
767+
}

0 commit comments

Comments
 (0)