mirror of
https://github.com/restic/restic.git
synced 2025-08-20 03:47:29 +00:00
backend/retry: return worker function error and abort
This is a bug fix: Before, when the worker function fn in List() of the RetryBackend returned an error, the operation is retried with the next file. This is not consistent with the documentation, the intention was that when fn returns an error, this is passed on to the caller and the List() operation is aborted. Only errors happening on the underlying backend are retried. The error leads to restic ignoring exclusive locks that are present in the repo, so it may happen that a new backup is written which references data that is going to be removed by a concurrently running `prune` operation. The bug was reported by a user here: https://forum.restic.net/t/restic-backup-returns-0-exit-code-when-already-locked/484
This commit is contained in:
@@ -125,16 +125,39 @@ func (be *RetryBackend) Test(ctx context.Context, h restic.Handle) (exists bool,
|
||||
return exists, err
|
||||
}
|
||||
|
||||
// List runs fn for each file in the backend which has the type t.
|
||||
// List runs fn for each file in the backend which has the type t. When an
|
||||
// error is returned by the underlying backend, the request is retried. When fn
|
||||
// returns an error, the operation is aborted and the error is returned to the
|
||||
// caller.
|
||||
func (be *RetryBackend) List(ctx context.Context, t restic.FileType, fn func(restic.FileInfo) error) error {
|
||||
listed := make(map[string]struct{})
|
||||
return be.retry(ctx, fmt.Sprintf("List(%v)", t), func() error {
|
||||
// create a new context that we can cancel when fn returns an error, so
|
||||
// that listing is aborted
|
||||
listCtx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
|
||||
listed := make(map[string]struct{}) // remember for which files we already ran fn
|
||||
var innerErr error // remember when fn returned an error, so we can return that to the caller
|
||||
|
||||
err := be.retry(listCtx, fmt.Sprintf("List(%v)", t), func() error {
|
||||
return be.Backend.List(ctx, t, func(fi restic.FileInfo) error {
|
||||
if _, ok := listed[fi.Name]; ok {
|
||||
return nil
|
||||
}
|
||||
listed[fi.Name] = struct{}{}
|
||||
return fn(fi)
|
||||
|
||||
innerErr = fn(fi)
|
||||
if innerErr != nil {
|
||||
// if fn returned an error, listing is aborted, so we cancel the context
|
||||
cancel()
|
||||
}
|
||||
return innerErr
|
||||
})
|
||||
})
|
||||
|
||||
// the error fn returned takes precedence
|
||||
if innerErr != nil {
|
||||
return innerErr
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
Reference in New Issue
Block a user