-
Notifications
You must be signed in to change notification settings - Fork 1
/
backoff.go
74 lines (63 loc) · 1.38 KB
/
backoff.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
package synx
import (
"context"
"errors"
"time"
)
// Backoff logic for retry.
type Backoff interface {
// Next returns a time to wait and flag to stop.
Next() (next time.Duration, stop bool)
}
// DoWithBackoff invokes function and retry with a backoff if it is needed.
// Wrap error from function as RetryableError to continue retries.
func DoWithBackoff(ctx context.Context, b Backoff, fn func(ctx context.Context) error) error {
for {
err := fn(ctx)
if err == nil || !IsRetryableError(err) {
return err
}
next, stop := b.Next()
if stop {
return err
}
if err := wait(ctx, next); err != nil {
return err
}
}
}
func wait(ctx context.Context, d time.Duration) error {
timer := time.NewTimer(d)
defer timer.Stop()
select {
case <-ctx.Done():
return ctx.Err()
case <-timer.C:
return nil
}
}
// RetryableError wraps error as retryable.
func RetryableError(err error) error {
if err == nil {
return nil
}
return &retryableError{err: err}
}
// IsRetryableError reports whether a given error is retryable.
// Returns false for nil.
func IsRetryableError(err error) bool {
var rerr *retryableError
return errors.As(err, &rerr)
}
type retryableError struct {
err error
}
func (e *retryableError) Error() string {
if e.err == nil {
return "retryable: <nil>"
}
return "retryable: " + e.err.Error()
}
func (e *retryableError) Unwrap() error {
return e.err
}