Merge pull request #3164 from MichaelEischer/improve-context-cancel

Improve context cancel handling in archiver and backends
This commit is contained in:
Alexander Neumann
2020-12-29 17:03:42 +01:00
committed by GitHub
5 changed files with 125 additions and 1 deletions

View File

@@ -33,6 +33,16 @@ func NewRetryBackend(be restic.Backend, maxTries int, report func(string, error,
}
func (be *RetryBackend) retry(ctx context.Context, msg string, f func() error) error {
// Don't do anything when called with an already cancelled context. There would be
// no retries in that case either, so be consistent and abort always.
// This enforces a strict contract for backend methods: Using a cancelled context
// will prevent any backup repository modifications. This simplifies ensuring that
// a backup repository is not modified any further after a context was cancelled.
// The 'local' backend for example does not provide this guarantee on its own.
if ctx.Err() != nil {
return ctx.Err()
}
err := backoff.RetryNotify(f,
backoff.WithContext(backoff.WithMaxRetries(backoff.NewExponentialBackOff(), uint64(be.MaxTries)), ctx),
func(err error, d time.Duration) {

View File

@@ -236,3 +236,38 @@ func TestBackendLoadRetry(t *testing.T) {
test.Equals(t, data, buf)
test.Equals(t, 2, attempt)
}
func assertIsCanceled(t *testing.T, err error) {
test.Assert(t, err == context.Canceled, "got unexpected err %v", err)
}
func TestBackendCanceledContext(t *testing.T) {
// unimplemented mock backend functions return an error by default
// check that we received the expected context canceled error instead
retryBackend := NewRetryBackend(mock.NewBackend(), 2, nil)
h := restic.Handle{Type: restic.PackFile, Name: restic.NewRandomID().String()}
// create an already canceled context
ctx, cancel := context.WithCancel(context.Background())
cancel()
_, err := retryBackend.Test(ctx, h)
assertIsCanceled(t, err)
_, err = retryBackend.Stat(ctx, h)
assertIsCanceled(t, err)
err = retryBackend.Save(ctx, h, restic.NewByteReader([]byte{}))
assertIsCanceled(t, err)
err = retryBackend.Remove(ctx, h)
assertIsCanceled(t, err)
err = retryBackend.Load(ctx, restic.Handle{}, 0, 0, func(rd io.Reader) (err error) {
return nil
})
assertIsCanceled(t, err)
err = retryBackend.List(ctx, restic.PackFile, func(restic.FileInfo) error {
return nil
})
assertIsCanceled(t, err)
// don't test "Delete" as it is not used by normal code
}