mirror of
https://github.com/restic/restic.git
synced 2026-08-21 13:03:25 +00:00
data: rework StreamTrees to use synchronous callbacks
The tree.Nodes will be replaced by an iterator to loads and serializes tree node ondemand. Thus, the processing moves from StreamTrees into the callback. Schedule them onto the workers used by StreamTrees for proper load distribution.
This commit is contained in:
+24
-40
@@ -3,7 +3,6 @@ package checker
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"runtime"
|
||||
"sync"
|
||||
|
||||
"github.com/restic/restic/internal/data"
|
||||
@@ -12,7 +11,6 @@ import (
|
||||
"github.com/restic/restic/internal/repository"
|
||||
"github.com/restic/restic/internal/restic"
|
||||
"github.com/restic/restic/internal/ui/progress"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
// Checker runs various checks on a repository. It is advisable to create an
|
||||
@@ -92,31 +90,6 @@ func (e *TreeError) Error() string {
|
||||
return fmt.Sprintf("tree %v: %v", e.ID, e.Errors)
|
||||
}
|
||||
|
||||
// checkTreeWorker checks the trees received and sends out errors to errChan.
|
||||
func (c *Checker) checkTreeWorker(ctx context.Context, trees <-chan data.TreeItem, out chan<- error) {
|
||||
for job := range trees {
|
||||
debug.Log("check tree %v (tree %v, err %v)", job.ID, job.Tree, job.Error)
|
||||
|
||||
var errs []error
|
||||
if job.Error != nil {
|
||||
errs = append(errs, job.Error)
|
||||
} else {
|
||||
errs = c.checkTree(job.ID, job.Tree)
|
||||
}
|
||||
|
||||
if len(errs) == 0 {
|
||||
continue
|
||||
}
|
||||
treeError := &TreeError{ID: job.ID, Errors: errs}
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case out <- treeError:
|
||||
debug.Log("tree %v: sent %d errors", treeError.ID, len(treeError.Errors))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func loadSnapshotTreeIDs(ctx context.Context, lister restic.Lister, repo restic.LoaderUnpacked) (ids restic.IDs, errs []error) {
|
||||
err := data.ForAllSnapshots(ctx, lister, repo, nil, func(id restic.ID, sn *data.Snapshot, err error) error {
|
||||
if err != nil {
|
||||
@@ -171,6 +144,7 @@ func (c *Checker) Structure(ctx context.Context, p *progress.Counter, errChan ch
|
||||
p.SetMax(uint64(len(trees)))
|
||||
debug.Log("need to check %d trees from snapshots, %d errs returned", len(trees), len(errs))
|
||||
|
||||
defer close(errChan)
|
||||
for _, err := range errs {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
@@ -179,8 +153,7 @@ func (c *Checker) Structure(ctx context.Context, p *progress.Counter, errChan ch
|
||||
}
|
||||
}
|
||||
|
||||
wg, ctx := errgroup.WithContext(ctx)
|
||||
treeStream := data.StreamTrees(ctx, wg, c.repo, trees, func(treeID restic.ID) bool {
|
||||
err := data.StreamTrees(ctx, c.repo, trees, p, func(treeID restic.ID) bool {
|
||||
// blobRefs may be accessed in parallel by checkTree
|
||||
c.blobRefs.Lock()
|
||||
h := restic.BlobHandle{ID: treeID, Type: restic.TreeBlob}
|
||||
@@ -189,21 +162,32 @@ func (c *Checker) Structure(ctx context.Context, p *progress.Counter, errChan ch
|
||||
c.blobRefs.M.Insert(h)
|
||||
c.blobRefs.Unlock()
|
||||
return blobReferenced
|
||||
}, p)
|
||||
}, func(treeID restic.ID, err error, tree *data.Tree) error {
|
||||
debug.Log("check tree %v (err %v)", treeID, err)
|
||||
|
||||
defer close(errChan)
|
||||
// The checkTree worker only processes already decoded trees and is thus CPU-bound
|
||||
workerCount := runtime.GOMAXPROCS(0)
|
||||
for i := 0; i < workerCount; i++ {
|
||||
wg.Go(func() error {
|
||||
c.checkTreeWorker(ctx, treeStream, errChan)
|
||||
var errs []error
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
} else {
|
||||
errs = c.checkTree(treeID, tree)
|
||||
}
|
||||
if len(errs) == 0 {
|
||||
return nil
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// the wait group should not return an error because no worker returns an
|
||||
treeError := &TreeError{ID: treeID, Errors: errs}
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
case errChan <- treeError:
|
||||
debug.Log("tree %v: sent %d errors", treeError.ID, len(treeError.Errors))
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
// StreamTrees should not return an error because no worker returns an
|
||||
// error, so panic if that has changed somehow.
|
||||
err := wg.Wait()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user