mirror of
https://github.com/restic/restic.git
synced 2026-08-15 10:03:17 +00:00
data: replace Tree with TreeNodeIterator
The TreeNodeIterator decodes nodes while iterating over a tree blob. This should reduce peak memory usage as now only the serialized tree blob and a single node have to alive at the same time. Using the iterator has implications for the error handling however. Now it is necessary that all loops that iterate through a tree check for errors before using the node returned by the iterator. The other change is that it is no longer possible to iterate over a tree multiple times. Instead it must be loaded a second time. This only affects the tree rewriting code.
This commit is contained in:
@@ -278,7 +278,7 @@ func copyTree(ctx context.Context, srcRepo restic.Repository, dstRepo restic.Rep
|
||||
visited := visitedTrees.Has(handle)
|
||||
visitedTrees.Insert(handle)
|
||||
return visited
|
||||
}, func(treeID restic.ID, err error, tree *data.Tree) error {
|
||||
}, func(treeID restic.ID, err error, nodes data.TreeNodeIterator) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("LoadTree(%v) returned error %v", treeID.Str(), err)
|
||||
}
|
||||
@@ -286,10 +286,13 @@ func copyTree(ctx context.Context, srcRepo restic.Repository, dstRepo restic.Rep
|
||||
// copy raw tree bytes to avoid problems if the serialization changes
|
||||
enqueue(restic.BlobHandle{ID: treeID, Type: restic.TreeBlob})
|
||||
|
||||
for _, entry := range tree.Nodes {
|
||||
for item := range nodes {
|
||||
if item.Error != nil {
|
||||
return item.Error
|
||||
}
|
||||
// Recursion into directories is handled by StreamTrees
|
||||
// Copy the blobs for this file.
|
||||
for _, blobID := range entry.Content {
|
||||
for _, blobID := range item.Node.Content {
|
||||
enqueue(restic.BlobHandle{Type: restic.DataBlob, ID: blobID})
|
||||
}
|
||||
}
|
||||
|
||||
+26
-8
@@ -184,11 +184,14 @@ func (c *Comparer) printDir(ctx context.Context, mode string, stats *DiffStat, b
|
||||
return err
|
||||
}
|
||||
|
||||
for _, node := range tree.Nodes {
|
||||
for item := range tree {
|
||||
if item.Error != nil {
|
||||
return item.Error
|
||||
}
|
||||
if ctx.Err() != nil {
|
||||
return ctx.Err()
|
||||
}
|
||||
|
||||
node := item.Node
|
||||
name := path.Join(prefix, node.Name)
|
||||
if node.Type == data.NodeTypeDir {
|
||||
name += "/"
|
||||
@@ -215,11 +218,15 @@ func (c *Comparer) collectDir(ctx context.Context, blobs restic.AssociatedBlobSe
|
||||
return err
|
||||
}
|
||||
|
||||
for _, node := range tree.Nodes {
|
||||
for item := range tree {
|
||||
if item.Error != nil {
|
||||
return item.Error
|
||||
}
|
||||
if ctx.Err() != nil {
|
||||
return ctx.Err()
|
||||
}
|
||||
|
||||
node := item.Node
|
||||
addBlobs(blobs, node)
|
||||
|
||||
if node.Type == data.NodeTypeDir {
|
||||
@@ -233,16 +240,24 @@ func (c *Comparer) collectDir(ctx context.Context, blobs restic.AssociatedBlobSe
|
||||
return ctx.Err()
|
||||
}
|
||||
|
||||
func uniqueNodeNames(tree1, tree2 *data.Tree) (tree1Nodes, tree2Nodes map[string]*data.Node, uniqueNames []string) {
|
||||
func uniqueNodeNames(tree1, tree2 data.TreeNodeIterator) (tree1Nodes, tree2Nodes map[string]*data.Node, uniqueNames []string, err error) {
|
||||
names := make(map[string]struct{})
|
||||
tree1Nodes = make(map[string]*data.Node)
|
||||
for _, node := range tree1.Nodes {
|
||||
for item := range tree1 {
|
||||
if item.Error != nil {
|
||||
return nil, nil, nil, item.Error
|
||||
}
|
||||
node := item.Node
|
||||
tree1Nodes[node.Name] = node
|
||||
names[node.Name] = struct{}{}
|
||||
}
|
||||
|
||||
tree2Nodes = make(map[string]*data.Node)
|
||||
for _, node := range tree2.Nodes {
|
||||
for item := range tree2 {
|
||||
if item.Error != nil {
|
||||
return nil, nil, nil, item.Error
|
||||
}
|
||||
node := item.Node
|
||||
tree2Nodes[node.Name] = node
|
||||
names[node.Name] = struct{}{}
|
||||
}
|
||||
@@ -253,7 +268,7 @@ func uniqueNodeNames(tree1, tree2 *data.Tree) (tree1Nodes, tree2Nodes map[string
|
||||
}
|
||||
|
||||
sort.Strings(uniqueNames)
|
||||
return tree1Nodes, tree2Nodes, uniqueNames
|
||||
return tree1Nodes, tree2Nodes, uniqueNames, nil
|
||||
}
|
||||
|
||||
func (c *Comparer) diffTree(ctx context.Context, stats *DiffStatsContainer, prefix string, id1, id2 restic.ID) error {
|
||||
@@ -268,7 +283,10 @@ func (c *Comparer) diffTree(ctx context.Context, stats *DiffStatsContainer, pref
|
||||
return err
|
||||
}
|
||||
|
||||
tree1Nodes, tree2Nodes, names := uniqueNodeNames(tree1, tree2)
|
||||
tree1Nodes, tree2Nodes, names, err := uniqueNodeNames(tree1, tree2)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, name := range names {
|
||||
if ctx.Err() != nil {
|
||||
|
||||
@@ -80,7 +80,7 @@ func splitPath(p string) []string {
|
||||
return append(s, f)
|
||||
}
|
||||
|
||||
func printFromTree(ctx context.Context, tree *data.Tree, repo restic.BlobLoader, prefix string, pathComponents []string, d *dump.Dumper, canWriteArchiveFunc func() error) error {
|
||||
func printFromTree(ctx context.Context, tree data.TreeNodeIterator, repo restic.BlobLoader, prefix string, pathComponents []string, d *dump.Dumper, canWriteArchiveFunc func() error) error {
|
||||
// If we print / we need to assume that there are multiple nodes at that
|
||||
// level in the tree.
|
||||
if pathComponents[0] == "" {
|
||||
@@ -92,11 +92,14 @@ func printFromTree(ctx context.Context, tree *data.Tree, repo restic.BlobLoader,
|
||||
|
||||
item := filepath.Join(prefix, pathComponents[0])
|
||||
l := len(pathComponents)
|
||||
for _, node := range tree.Nodes {
|
||||
for it := range tree {
|
||||
if it.Error != nil {
|
||||
return it.Error
|
||||
}
|
||||
if ctx.Err() != nil {
|
||||
return ctx.Err()
|
||||
}
|
||||
|
||||
node := it.Node
|
||||
// If dumping something in the highest level it will just take the
|
||||
// first item it finds and dump that according to the switch case below.
|
||||
if node.Name == pathComponents[0] {
|
||||
|
||||
@@ -97,7 +97,11 @@ func runRecover(ctx context.Context, gopts global.Options, term ui.Terminal) err
|
||||
continue
|
||||
}
|
||||
|
||||
for _, node := range tree.Nodes {
|
||||
for item := range tree {
|
||||
if item.Error != nil {
|
||||
return item.Error
|
||||
}
|
||||
node := item.Node
|
||||
if node.Type == data.NodeTypeDir && node.Subtree != nil {
|
||||
trees[*node.Subtree] = true
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"slices"
|
||||
|
||||
"github.com/restic/restic/internal/data"
|
||||
"github.com/restic/restic/internal/errors"
|
||||
@@ -130,7 +131,7 @@ func runRepairSnapshots(ctx context.Context, gopts global.Options, opts RepairOp
|
||||
node.Size = newSize
|
||||
return node
|
||||
},
|
||||
RewriteFailedTree: func(_ restic.ID, path string, _ error) (*data.Tree, error) {
|
||||
RewriteFailedTree: func(_ restic.ID, path string, _ error) (data.TreeNodeIterator, error) {
|
||||
if path == "/" {
|
||||
printer.P(" dir %q: not readable", path)
|
||||
// remove snapshots with invalid root node
|
||||
@@ -138,7 +139,7 @@ func runRepairSnapshots(ctx context.Context, gopts global.Options, opts RepairOp
|
||||
}
|
||||
// If a subtree fails to load, remove it
|
||||
printer.P(" dir %q: replaced with empty directory", path)
|
||||
return &data.Tree{}, nil
|
||||
return slices.Values([]data.NodeOrError{}), nil
|
||||
},
|
||||
AllowUnstableSerialization: true,
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user