mirror of
https://github.com/restic/restic.git
synced 2025-08-20 07:17:31 +00:00
Merge pull request #3164 from MichaelEischer/improve-context-cancel
Improve context cancel handling in archiver and backends
This commit is contained in:
@@ -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) {
|
||||
|
@@ -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
|
||||
}
|
||||
|
Reference in New Issue
Block a user