This repository has been archived by the owner on Apr 5, 2019. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 6
/
retro.go
126 lines (107 loc) · 3.23 KB
/
retro.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
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
package retro
import (
"math"
"regexp"
"time"
)
// RetryableError is an interface for any kind of error allowing retries
type RetryableError interface {
Error() string
MaxAttempts() int
Wait(int)
}
// ErrorCreator is a type of function that creates retryable errors from standard errors
type ErrorCreator func(error) RetryableError
type backoffRetryableError struct {
error
maxAttempts int
}
func (err *backoffRetryableError) MaxAttempts() int {
return err.maxAttempts
}
func (err *backoffRetryableError) Wait(count int) {
backoffInt := int(math.Pow(float64(count), 4.0)) + count + 10
time.Sleep(time.Duration(backoffInt) * time.Second)
}
// NewBackoffRetryableError creates a RetryableError which will retry up to maxAttempts times
// using an exponential backoff
func NewBackoffRetryableError(err error, maxAttempts int) RetryableError {
return &backoffRetryableError{
err,
maxAttempts,
}
}
type staticRetryableError struct {
error
maxAttempts int
waitSeconds time.Duration
}
func (err *staticRetryableError) MaxAttempts() int {
return err.maxAttempts
}
func (err *staticRetryableError) Wait(count int) {
time.Sleep(err.waitSeconds * time.Second)
}
// NewStaticRetryableError creates a RetryableError which will retry up
// to maxAttempts times sleeping waitSeconds in between tries
func NewStaticRetryableError(err error, maxAttempts, waitSeconds int) RetryableError {
return &staticRetryableError{err, maxAttempts, time.Duration(waitSeconds)}
}
type logarithmicRetryableError struct {
error
maxAttempts int
baseOffset float64
scale float64
power float64
}
func (err *logarithmicRetryableError) MaxAttempts() int {
return err.maxAttempts
}
func (err *logarithmicRetryableError) Wait(count int) {
backoffFloat := err.scale*math.Log(math.Pow(float64(count), err.power)) + err.baseOffset
time.Sleep(time.Duration(backoffFloat) * time.Second)
}
// NewLogarithmicRetryableError creates a RetryableError which will retry up
// to maxAttempts times, with a sleep pattern defined by a logarithmic curve
func NewLogarithmicRetryableError(err error, maxAttempts int, baseOffset, scale, power float64) RetryableError {
return &logarithmicRetryableError{err, maxAttempts, baseOffset, scale, power}
}
// DoWithRetry will execute a function as many times as is dictated by any
// retryable errors propagated by the function
func DoWithRetry(f func() error) error {
var err error
try := true
handler := &retryHandler{}
for try {
try, err = handler.Try(f)
}
return err
}
type retryHandler struct {
attempts int
}
func (handler *retryHandler) Try(f func() error) (bool, error) {
err := f()
if errRetry, ok := err.(RetryableError); ok {
retrying := handler.attempts < errRetry.MaxAttempts()
if retrying {
errRetry.Wait(handler.attempts)
}
handler.attempts++
return retrying, errRetry
}
return false, err
}
// WrapRetryableError takes an error, and given a set of retryable error regexes, returns
// a suitable error type, either a standard error or a retryable error
func WrapRetryableError(err error, errorList []*regexp.Regexp, errorCreator ErrorCreator) error {
if err == nil {
return nil
}
for _, errorRegex := range errorList {
if errorRegex.MatchString(err.Error()) {
return errorCreator(err)
}
}
return err
}