diff --git a/checker/checker.go b/checker/checker.go index aff7c1ef7..d3d1b5016 100644 --- a/checker/checker.go +++ b/checker/checker.go @@ -192,28 +192,22 @@ outer: } // Packs checks that all packs referenced in the index are still available and -// there are no packs that aren't in an index. -func (c *Checker) Packs() []error { +// there are no packs that aren't in an index. errChan is closed after all +// packs have been checked. +func (c *Checker) Packs(errChan chan<- error, done <-chan struct{}) { + defer close(errChan) + debug.Log("Checker.Packs", "checking for %d packs", len(c.packs)) seenPacks := make(map[mapID]struct{}) - done := make(chan struct{}) - defer close(done) - var workerWG sync.WaitGroup IDChan := make(chan mapID) - errChan := make(chan error) - for i := 0; i < defaultParallelism; i++ { workerWG.Add(1) go packIDTester(c.repo, IDChan, errChan, &workerWG, done) } - errsChan := make(chan []error, 1) - - go collectErrors(errChan, errsChan, done) - for id := range c.packs { seenPacks[id] = struct{}{} IDChan <- id @@ -223,19 +217,17 @@ func (c *Checker) Packs() []error { debug.Log("Checker.Packs", "waiting for %d workers to terminate", defaultParallelism) workerWG.Wait() debug.Log("Checker.Packs", "workers terminated") - close(errChan) - - errs := <-errsChan - debug.Log("Checker.Packs", "error worker terminated") for id := range c.repo.List(backend.Data, done) { debug.Log("Checker.Packs", "check data blob %v", id) if _, ok := seenPacks[id2map(id)]; !ok { - errs = append(errs, PackError{id, errors.New("not referenced in any index")}) + select { + case <-done: + return + case errChan <- PackError{id, errors.New("not referenced in any index")}: + } } } - - return errs } // Error is an error that occurred while checking a repository. @@ -276,10 +268,9 @@ func loadTreeFromSnapshot(repo *repository.Repository, id backend.ID) (backend.I } // Structure checks that for all snapshots all referenced blobs are available -// in the index. -func (c *Checker) Structure() (errs []error) { - done := make(chan struct{}) - defer close(done) +// in the index. errChan is closed after all trees have been traversed. +func (c *Checker) Structure(errChan chan<- error, done <-chan struct{}) { + defer close(errChan) var todo backend.IDs @@ -288,7 +279,11 @@ func (c *Checker) Structure() (errs []error) { treeID, err := loadTreeFromSnapshot(c.repo, id) if err != nil { - errs = append(errs, err) + select { + case <-done: + return + case errChan <- err: + } continue } @@ -296,8 +291,13 @@ func (c *Checker) Structure() (errs []error) { todo = append(todo, treeID) } - errs = append(errs, c.trees(todo)...) - return errs + for _, err := range c.trees(todo) { + select { + case <-done: + return + case errChan <- err: + } + } } func (c *Checker) trees(treeIDs backend.IDs) (errs []error) { diff --git a/checker/checker_test.go b/checker/checker_test.go index 5fe49e22d..eadb71181 100644 --- a/checker/checker_test.go +++ b/checker/checker_test.go @@ -24,14 +24,44 @@ func list(repo *repository.Repository, t backend.Type) (IDs []string) { return IDs } +func checkPacks(chkr *checker.Checker) (errs []error) { + done := make(chan struct{}) + defer close(done) + + errChan := make(chan error) + + go chkr.Packs(errChan, done) + + for err := range errChan { + errs = append(errs, err) + } + + return errs +} + +func checkStruct(chkr *checker.Checker) (errs []error) { + done := make(chan struct{}) + defer close(done) + + errChan := make(chan error) + + go chkr.Structure(errChan, done) + + for err := range errChan { + errs = append(errs, err) + } + + return errs +} + func TestCheckRepo(t *testing.T) { WithTestEnvironment(t, checkerTestData, func(repodir string) { repo := OpenLocalRepo(t, repodir) chkr := checker.New(repo) OK(t, chkr.LoadIndex()) - OKs(t, chkr.Packs()) - OKs(t, chkr.Structure()) + OKs(t, checkPacks(chkr)) + OKs(t, checkStruct(chkr)) }) } @@ -44,7 +74,7 @@ func TestMissingPack(t *testing.T) { chkr := checker.New(repo) OK(t, chkr.LoadIndex()) - errs := chkr.Packs() + errs := checkPacks(chkr) Assert(t, len(errs) == 1, "expected exactly one error, got %v", len(errs)) @@ -68,7 +98,7 @@ func TestUnreferencedPack(t *testing.T) { chkr := checker.New(repo) OK(t, chkr.LoadIndex()) - errs := chkr.Packs() + errs := checkPacks(chkr) Assert(t, len(errs) == 1, "expected exactly one error, got %v", len(errs)) @@ -101,8 +131,8 @@ func TestUnreferencedBlobs(t *testing.T) { chkr := checker.New(repo) OK(t, chkr.LoadIndex()) - OKs(t, chkr.Packs()) - OKs(t, chkr.Structure()) + OKs(t, checkPacks(chkr)) + OKs(t, checkStruct(chkr)) blobs := chkr.UnusedBlobs() sort.Sort(blobs) diff --git a/cmd/restic/cmd_check.go b/cmd/restic/cmd_check.go index a40cf2fd1..ccf65d4d7 100644 --- a/cmd/restic/cmd_check.go +++ b/cmd/restic/cmd_check.go @@ -52,15 +52,25 @@ func (cmd CmdCheck) Execute(args []string) error { return err } + done := make(chan struct{}) + defer close(done) + errorsFound := false + errChan := make(chan error) + cmd.global.Verbosef("Check all packs\n") - for _, err := range checker.Packs() { + go checker.Packs(errChan, done) + + for err := range errChan { errorsFound = true fmt.Fprintf(os.Stderr, "error: %v\n", err) } cmd.global.Verbosef("Check snapshots, trees and blobs\n") - for _, err := range checker.Structure() { + errChan = make(chan error) + go checker.Structure(errChan, done) + + for err := range errChan { errorsFound = true fmt.Fprintf(os.Stderr, "error: %v\n", err) }