forked from kopia/kopia
/
retrying_storage.go
80 lines (63 loc) · 2.3 KB
/
retrying_storage.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
// Package retrying implements wrapper around blob.Storage that adds retry loop around all operations in case they return unexpected errors.
package retrying
import (
"context"
"errors"
"fmt"
"github.com/kopia/kopia/internal/retry"
"github.com/kopia/kopia/repo/blob"
)
// retryingStorage adds retry loop around all operations of the underlying storage.
type retryingStorage struct {
blob.Storage
}
func (s retryingStorage) GetBlob(ctx context.Context, id blob.ID, offset, length int64, output blob.OutputBuffer) error {
// nolint:wrapcheck
return retry.WithExponentialBackoffNoValue(ctx, fmt.Sprintf("GetBlob(%v,%v,%v)", id, offset, length), func() error {
output.Reset()
// nolint:wrapcheck
return s.Storage.GetBlob(ctx, id, offset, length, output)
}, isRetriable)
}
func (s retryingStorage) GetMetadata(ctx context.Context, id blob.ID) (blob.Metadata, error) {
v, err := retry.WithExponentialBackoff(ctx, "GetMetadata("+string(id)+")", func() (interface{}, error) {
// nolint:wrapcheck
return s.Storage.GetMetadata(ctx, id)
}, isRetriable)
if err != nil {
return blob.Metadata{}, err // nolint:wrapcheck
}
return v.(blob.Metadata), nil
}
func (s retryingStorage) PutBlob(ctx context.Context, id blob.ID, data blob.Bytes, opts blob.PutOptions) error {
_, err := retry.WithExponentialBackoff(ctx, "PutBlob("+string(id)+")", func() (interface{}, error) {
// nolint:wrapcheck
return true, s.Storage.PutBlob(ctx, id, data, opts)
}, isRetriable)
return err // nolint:wrapcheck
}
func (s retryingStorage) DeleteBlob(ctx context.Context, id blob.ID) error {
_, err := retry.WithExponentialBackoff(ctx, "DeleteBlob("+string(id)+")", func() (interface{}, error) {
// nolint:wrapcheck
return true, s.Storage.DeleteBlob(ctx, id)
}, isRetriable)
return err // nolint:wrapcheck
}
// NewWrapper returns a Storage wrapper that adds retry loop around all operations of the underlying storage.
func NewWrapper(wrapped blob.Storage) blob.Storage {
return &retryingStorage{Storage: wrapped}
}
func isRetriable(err error) bool {
switch {
case errors.Is(err, blob.ErrBlobNotFound):
return false
case errors.Is(err, blob.ErrInvalidRange):
return false
case errors.Is(err, blob.ErrSetTimeUnsupported):
return false
case errors.Is(err, blob.ErrBlobAlreadyExists):
return false
default:
return true
}
}