mirror of
https://github.com/restic/restic.git
synced 2025-10-10 09:24:35 +00:00
archiver: unify FutureTree/File into futureNode
There is no real difference between the FutureTree and FutureFile structs. However, differentiating both increases the size of the FutureNode struct. The FutureNode struct is now only 16 bytes large on 64bit platforms. That way is has a very low overhead if the corresponding file/directory was not processed yet. There is a special case for nodes that were reused from the parent snapshot, as a go channel seems to have 96 bytes overhead which would result in a memory usage regression.
This commit is contained in:
@@ -8,35 +8,6 @@ import (
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
// FutureTree is returned by Save and will return the data once it
|
||||
// has been processed.
|
||||
type FutureTree struct {
|
||||
ch <-chan saveTreeResponse
|
||||
res saveTreeResponse
|
||||
}
|
||||
|
||||
// Wait blocks until the data has been received or ctx is cancelled.
|
||||
func (s *FutureTree) Wait(ctx context.Context) {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case res, ok := <-s.ch:
|
||||
if ok {
|
||||
s.res = res
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Node returns the node.
|
||||
func (s *FutureTree) Node() *restic.Node {
|
||||
return s.res.node
|
||||
}
|
||||
|
||||
// Stats returns the stats for the file.
|
||||
func (s *FutureTree) Stats() ItemStats {
|
||||
return s.res.stats
|
||||
}
|
||||
|
||||
// TreeSaver concurrently saves incoming trees to the repo.
|
||||
type TreeSaver struct {
|
||||
saveTree func(context.Context, *restic.Tree) (restic.ID, ItemStats, error)
|
||||
@@ -70,10 +41,11 @@ func (s *TreeSaver) TriggerShutdown() {
|
||||
}
|
||||
|
||||
// Save stores the dir d and returns the data once it has been completed.
|
||||
func (s *TreeSaver) Save(ctx context.Context, snPath string, node *restic.Node, nodes []FutureNode, complete CompleteFunc) FutureTree {
|
||||
ch := make(chan saveTreeResponse, 1)
|
||||
func (s *TreeSaver) Save(ctx context.Context, snPath string, target string, node *restic.Node, nodes []FutureNode, complete CompleteFunc) FutureNode {
|
||||
fn, ch := newFutureNode()
|
||||
job := saveTreeJob{
|
||||
snPath: snPath,
|
||||
target: target,
|
||||
node: node,
|
||||
nodes: nodes,
|
||||
ch: ch,
|
||||
@@ -86,51 +58,53 @@ func (s *TreeSaver) Save(ctx context.Context, snPath string, node *restic.Node,
|
||||
close(ch)
|
||||
}
|
||||
|
||||
return FutureTree{ch: ch}
|
||||
return fn
|
||||
}
|
||||
|
||||
type saveTreeJob struct {
|
||||
snPath string
|
||||
nodes []FutureNode
|
||||
target string
|
||||
node *restic.Node
|
||||
ch chan<- saveTreeResponse
|
||||
nodes []FutureNode
|
||||
ch chan<- futureNodeResult
|
||||
complete CompleteFunc
|
||||
}
|
||||
|
||||
type saveTreeResponse struct {
|
||||
node *restic.Node
|
||||
stats ItemStats
|
||||
}
|
||||
|
||||
// save stores the nodes as a tree in the repo.
|
||||
func (s *TreeSaver) save(ctx context.Context, snPath string, node *restic.Node, nodes []FutureNode) (*restic.Node, ItemStats, error) {
|
||||
func (s *TreeSaver) save(ctx context.Context, job *saveTreeJob) (*restic.Node, ItemStats, error) {
|
||||
var stats ItemStats
|
||||
node := job.node
|
||||
nodes := job.nodes
|
||||
// allow GC of nodes array once the loop is finished
|
||||
job.nodes = nil
|
||||
|
||||
tree := restic.NewTree(len(nodes))
|
||||
|
||||
for _, fn := range nodes {
|
||||
fn.wait(ctx)
|
||||
for i, fn := range nodes {
|
||||
// fn is a copy, so clear the original value explicitly
|
||||
nodes[i] = FutureNode{}
|
||||
fnr := fn.take(ctx)
|
||||
|
||||
// return the error if it wasn't ignored
|
||||
if fn.err != nil {
|
||||
debug.Log("err for %v: %v", fn.snPath, fn.err)
|
||||
fn.err = s.errFn(fn.target, fn.err)
|
||||
if fn.err == nil {
|
||||
if fnr.err != nil {
|
||||
debug.Log("err for %v: %v", fnr.snPath, fnr.err)
|
||||
fnr.err = s.errFn(fnr.target, fnr.err)
|
||||
if fnr.err == nil {
|
||||
// ignore error
|
||||
continue
|
||||
}
|
||||
|
||||
return nil, stats, fn.err
|
||||
return nil, stats, fnr.err
|
||||
}
|
||||
|
||||
// when the error is ignored, the node could not be saved, so ignore it
|
||||
if fn.node == nil {
|
||||
debug.Log("%v excluded: %v", fn.snPath, fn.target)
|
||||
if fnr.node == nil {
|
||||
debug.Log("%v excluded: %v", fnr.snPath, fnr.target)
|
||||
continue
|
||||
}
|
||||
|
||||
debug.Log("insert %v", fn.node.Name)
|
||||
err := tree.Insert(fn.node)
|
||||
debug.Log("insert %v", fnr.node.Name)
|
||||
err := tree.Insert(fnr.node)
|
||||
if err != nil {
|
||||
return nil, stats, err
|
||||
}
|
||||
@@ -158,7 +132,8 @@ func (s *TreeSaver) worker(ctx context.Context, jobs <-chan saveTreeJob) error {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
node, stats, err := s.save(ctx, job.snPath, job.node, job.nodes)
|
||||
|
||||
node, stats, err := s.save(ctx, &job)
|
||||
if err != nil {
|
||||
debug.Log("error saving tree blob: %v", err)
|
||||
close(job.ch)
|
||||
@@ -168,9 +143,11 @@ func (s *TreeSaver) worker(ctx context.Context, jobs <-chan saveTreeJob) error {
|
||||
if job.complete != nil {
|
||||
job.complete(node, stats)
|
||||
}
|
||||
job.ch <- saveTreeResponse{
|
||||
node: node,
|
||||
stats: stats,
|
||||
job.ch <- futureNodeResult{
|
||||
snPath: job.snPath,
|
||||
target: job.target,
|
||||
node: node,
|
||||
stats: stats,
|
||||
}
|
||||
close(job.ch)
|
||||
}
|
||||
|
Reference in New Issue
Block a user