bound the tree rebuild against a cyclic channel graph
rebuild_rec followed parent/child links with no depth limit, so a channel graph in which a channel is its own ancestor recursed until the process ran out of memory. Cap the depth and give the whole rebuild a single node budget. Accumulating into one slice rather than returning a new slice per node makes that budget apply to the tree as a whole instead of to each level, and removes an allocation per node. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
314b838e33
commit
07c923c4c9
+25
-15
@@ -89,10 +89,10 @@ func (t *Tree) rebuild(preserveActive bool, sameItem func(previous, current Tree
|
||||
previousLine := t.activeLine
|
||||
lines := []renderedTreeItem{}
|
||||
for _, item := range t.Generator(nil) {
|
||||
children := t.rebuild_rec(item, 0)
|
||||
if children != nil {
|
||||
lines = append(lines, children...)
|
||||
if len(lines) >= maxTreeLines {
|
||||
break
|
||||
}
|
||||
lines = t.rebuild_rec(lines, item, 0)
|
||||
}
|
||||
t.lines = lines
|
||||
if preserveActive {
|
||||
@@ -113,21 +113,31 @@ func (t *Tree) rebuild(preserveActive bool, sameItem func(previous, current Tree
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Tree) rebuild_rec(parent TreeItem, level int) []renderedTreeItem {
|
||||
if parent == nil {
|
||||
return nil
|
||||
}
|
||||
lines := []renderedTreeItem{
|
||||
renderedTreeItem{
|
||||
Level: level,
|
||||
Item: parent,
|
||||
},
|
||||
// A server is free to describe a channel graph in which a channel is its own
|
||||
// ancestor. The generator follows parent/child links literally, so without
|
||||
// these limits such a graph recurses until the process is out of memory.
|
||||
// Real trees are orders of magnitude smaller than either bound.
|
||||
const (
|
||||
maxTreeDepth = 64
|
||||
maxTreeLines = 100000
|
||||
)
|
||||
|
||||
// rebuild_rec appends parent and its descendants to lines. Accumulating into
|
||||
// one slice keeps maxTreeLines a budget for the whole tree rather than for
|
||||
// each level, and avoids building a slice per node.
|
||||
func (t *Tree) rebuild_rec(lines []renderedTreeItem, parent TreeItem, level int) []renderedTreeItem {
|
||||
if parent == nil || level >= maxTreeDepth || len(lines) >= maxTreeLines {
|
||||
return lines
|
||||
}
|
||||
lines = append(lines, renderedTreeItem{
|
||||
Level: level,
|
||||
Item: parent,
|
||||
})
|
||||
for _, item := range t.Generator(parent) {
|
||||
children := t.rebuild_rec(item, level+1)
|
||||
if children != nil {
|
||||
lines = append(lines, children...)
|
||||
if len(lines) >= maxTreeLines {
|
||||
break
|
||||
}
|
||||
lines = t.rebuild_rec(lines, item, level+1)
|
||||
}
|
||||
return lines
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user