restic/internal/walk/walk.go

199 lines
4 KiB
Go
Raw Normal View History

2016-09-01 20:24:48 +00:00
package walk
2015-03-02 13:48:47 +00:00
import (
2017-06-04 09:16:55 +00:00
"context"
2015-12-28 23:26:29 +00:00
"fmt"
"os"
2015-03-02 13:48:47 +00:00
"path/filepath"
2015-10-27 21:44:10 +00:00
"sync"
2015-03-02 13:48:47 +00:00
2017-07-23 12:21:03 +00:00
"github.com/restic/restic/internal"
"github.com/restic/restic/internal/debug"
2015-03-02 13:48:47 +00:00
)
2016-09-01 20:24:48 +00:00
// TreeJob is a job sent from the tree walker.
type TreeJob struct {
2015-03-02 13:48:47 +00:00
Path string
Error error
2016-09-01 20:24:48 +00:00
Node *restic.Node
Tree *restic.Tree
2015-03-02 13:48:47 +00:00
}
2015-10-27 21:44:10 +00:00
// TreeWalker traverses a tree in the repository depth-first and sends a job
// for each item (file or dir) that it encounters.
type TreeWalker struct {
ch chan<- loadTreeJob
2016-09-01 20:24:48 +00:00
out chan<- TreeJob
2015-10-27 21:44:10 +00:00
}
// NewTreeWalker uses ch to load trees from the repository and sends jobs to
// out.
2016-09-01 20:24:48 +00:00
func NewTreeWalker(ch chan<- loadTreeJob, out chan<- TreeJob) *TreeWalker {
2015-10-27 21:44:10 +00:00
return &TreeWalker{ch: ch, out: out}
}
// Walk starts walking the tree given by id. When the channel done is closed,
// processing stops.
2017-06-04 09:16:55 +00:00
func (tw *TreeWalker) Walk(ctx context.Context, path string, id restic.ID) {
2016-09-27 20:35:08 +00:00
debug.Log("starting on tree %v for %v", id.Str(), path)
defer debug.Log("done walking tree %v for %v", id.Str(), path)
2015-10-27 21:44:10 +00:00
resCh := make(chan loadTreeResult, 1)
tw.ch <- loadTreeJob{
id: id,
res: resCh,
}
res := <-resCh
if res.err != nil {
2015-07-11 13:51:18 +00:00
select {
2016-09-01 20:24:48 +00:00
case tw.out <- TreeJob{Path: path, Error: res.err}:
2017-06-04 09:16:55 +00:00
case <-ctx.Done():
2015-07-11 13:51:18 +00:00
return
}
2015-03-02 13:48:47 +00:00
return
}
2017-06-04 09:16:55 +00:00
tw.walk(ctx, path, res.tree)
2015-10-27 21:44:10 +00:00
select {
2016-09-01 20:24:48 +00:00
case tw.out <- TreeJob{Path: path, Tree: res.tree}:
2017-06-04 09:16:55 +00:00
case <-ctx.Done():
2015-10-27 21:44:10 +00:00
return
}
}
2017-06-04 09:16:55 +00:00
func (tw *TreeWalker) walk(ctx context.Context, path string, tree *restic.Tree) {
2016-09-27 20:35:08 +00:00
debug.Log("start on %q", path)
defer debug.Log("done for %q", path)
2015-10-27 21:44:10 +00:00
2016-09-27 20:35:08 +00:00
debug.Log("tree %#v", tree)
2015-12-28 23:26:29 +00:00
2015-10-27 21:44:10 +00:00
// load all subtrees in parallel
results := make([]<-chan loadTreeResult, len(tree.Nodes))
for i, node := range tree.Nodes {
2016-09-01 19:20:03 +00:00
if node.Type == "dir" {
2015-10-27 21:44:10 +00:00
resCh := make(chan loadTreeResult, 1)
tw.ch <- loadTreeJob{
id: *node.Subtree,
res: resCh,
}
results[i] = resCh
}
}
for i, node := range tree.Nodes {
2015-03-02 13:48:47 +00:00
p := filepath.Join(path, node.Name)
2016-09-01 20:24:48 +00:00
var job TreeJob
2015-10-27 21:44:10 +00:00
2016-09-01 19:20:03 +00:00
if node.Type == "dir" {
2015-10-27 21:44:10 +00:00
if results[i] == nil {
panic("result chan should not be nil")
}
res := <-results[i]
2015-12-28 23:26:29 +00:00
if res.err == nil {
2017-06-04 09:16:55 +00:00
tw.walk(ctx, p, res.tree)
2015-12-28 23:26:29 +00:00
} else {
fmt.Fprintf(os.Stderr, "error loading tree: %v\n", res.err)
}
2015-10-27 21:44:10 +00:00
2016-09-01 20:24:48 +00:00
job = TreeJob{Path: p, Tree: res.tree, Error: res.err}
2015-03-02 13:48:47 +00:00
} else {
2016-09-01 20:24:48 +00:00
job = TreeJob{Path: p, Node: node}
2015-10-27 21:44:10 +00:00
}
select {
case tw.out <- job:
2017-06-04 09:16:55 +00:00
case <-ctx.Done():
2015-10-27 21:44:10 +00:00
return
}
}
}
type loadTreeResult struct {
2016-09-01 20:24:48 +00:00
tree *restic.Tree
2015-10-27 21:44:10 +00:00
err error
}
type loadTreeJob struct {
2016-09-01 20:24:48 +00:00
id restic.ID
2015-10-27 21:44:10 +00:00
res chan<- loadTreeResult
}
2016-09-01 20:24:48 +00:00
type treeLoader func(restic.ID) (*restic.Tree, error)
2015-10-27 21:44:10 +00:00
2017-06-04 09:16:55 +00:00
func loadTreeWorker(ctx context.Context, wg *sync.WaitGroup, in <-chan loadTreeJob, load treeLoader) {
2016-09-27 20:35:08 +00:00
debug.Log("start")
defer debug.Log("exit")
2015-10-27 21:44:10 +00:00
defer wg.Done()
for {
select {
2017-06-04 09:16:55 +00:00
case <-ctx.Done():
2016-09-27 20:35:08 +00:00
debug.Log("done channel closed")
2015-10-27 21:44:10 +00:00
return
case job, ok := <-in:
if !ok {
2016-09-27 20:35:08 +00:00
debug.Log("input channel closed, exiting")
2015-10-27 21:44:10 +00:00
return
}
2016-09-27 20:35:08 +00:00
debug.Log("received job to load tree %v", job.id.Str())
2015-10-27 21:44:10 +00:00
tree, err := load(job.id)
2016-09-27 20:35:08 +00:00
debug.Log("tree %v loaded, error %v", job.id.Str(), err)
2015-10-27 21:44:10 +00:00
2015-07-11 13:51:18 +00:00
select {
2015-10-27 21:44:10 +00:00
case job.res <- loadTreeResult{tree, err}:
2016-09-27 20:35:08 +00:00
debug.Log("job result sent")
2017-06-04 09:16:55 +00:00
case <-ctx.Done():
2016-09-27 20:35:08 +00:00
debug.Log("done channel closed before result could be sent")
2015-07-11 13:51:18 +00:00
return
}
2015-03-02 13:48:47 +00:00
}
}
}
// TreeLoader loads tree objects.
type TreeLoader interface {
2017-06-04 09:16:55 +00:00
LoadTree(context.Context, restic.ID) (*restic.Tree, error)
}
2015-10-27 21:44:10 +00:00
const loadTreeWorkers = 10
2016-09-01 20:24:48 +00:00
// Tree walks the tree specified by id recursively and sends a job for each
2015-03-02 13:48:47 +00:00
// file and directory it finds. When the channel done is closed, processing
// stops.
2017-06-04 09:16:55 +00:00
func Tree(ctx context.Context, repo TreeLoader, id restic.ID, jobCh chan<- TreeJob) {
2016-09-27 20:35:08 +00:00
debug.Log("start on %v, start workers", id.Str())
2015-10-27 21:44:10 +00:00
2016-09-01 20:24:48 +00:00
load := func(id restic.ID) (*restic.Tree, error) {
2017-06-04 09:16:55 +00:00
tree, err := repo.LoadTree(ctx, id)
2015-10-27 21:44:10 +00:00
if err != nil {
return nil, err
}
return tree, nil
}
ch := make(chan loadTreeJob)
var wg sync.WaitGroup
for i := 0; i < loadTreeWorkers; i++ {
wg.Add(1)
2017-06-04 09:16:55 +00:00
go loadTreeWorker(ctx, &wg, ch, load)
2015-10-27 21:44:10 +00:00
}
tw := NewTreeWalker(ch, jobCh)
2017-06-04 09:16:55 +00:00
tw.Walk(ctx, "", id)
2015-03-02 13:48:47 +00:00
close(jobCh)
2015-10-27 21:44:10 +00:00
close(ch)
wg.Wait()
2016-09-27 20:35:08 +00:00
debug.Log("done")
2015-03-02 13:48:47 +00:00
}