-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathretry.go
More file actions
142 lines (130 loc) · 3.85 KB
/
retry.go
File metadata and controls
142 lines (130 loc) · 3.85 KB
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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
package retry
import (
"errors"
"math"
"reflect"
"time"
)
type Retryable func() bool
type BackoffFunc func(uint64) uint64
type retryManager struct {
maxRetry int64
backoffFn BackoffFunc
retryUntil time.Duration
startedAt time.Time
delay time.Duration
lastBackoff uint64 // number of seconds
}
var (
ErrMaxRetryOrRetryUntilInvalidArg = errors.New("invalid argument type. maxRetry can be either integer, time.Duration or func(uint64) uint64")
ErrDelayOrBackOffFuncInvalidArg = errors.New("invalid argument type. delay can be either time.Duration or `func(uint64) uint64`")
ErrDeadlineExceeded = errors.New("retry deadline has been exceeded")
ErrMaximumRetryExceeded = errors.New("maximum retry has been exceeded")
)
const defaultDelayDuration = 1 * time.Second
func (rm *retryManager) parseParams(args ...interface{}) error {
if len(args) > 0 {
firstArgKind := reflect.TypeOf(args[0]).Kind()
if reflect.TypeOf(args[0]).String() == "time.Duration" {
maxDuration := args[0].(time.Duration)
rm.retryUntil = maxDuration
} else if isIntKind(firstArgKind) {
rm.maxRetry = int64(math.Abs(float64(reflect.ValueOf(args[0]).Int())))
} else {
return ErrMaxRetryOrRetryUntilInvalidArg
}
// delay in time.Duration or backOffFunc as func(uint64) uint64
if len(args) > 1 {
if reflect.TypeOf(args[1]).String() == "time.Duration" {
rm.delay = args[1].(time.Duration)
} else if reflect.TypeOf(args[1]).String() == "func(uint64) uint64" {
rm.backoffFn = args[1].(func(uint64) uint64)
} else {
return ErrDelayOrBackOffFuncInvalidArg
}
}
}
if rm.backoffFn == nil && rm.delay == 0 {
rm.delay = defaultDelayDuration
}
return nil
}
func (rm *retryManager) addDelay() {
var delayInBetween time.Duration
if rm.lastBackoff == 0 {
rm.lastBackoff = 1
}
if rm.backoffFn != nil {
numberOfSeconds := rm.backoffFn(rm.lastBackoff)
rm.lastBackoff = numberOfSeconds
delayInBetween = time.Duration(numberOfSeconds) * time.Second
} else {
delayInBetween = rm.delay
}
withJitter := addJitter(delayInBetween)
time.Sleep(withJitter)
}
func (rm *retryManager) execute(fn Retryable) error {
shouldRetry := fn()
for shouldRetry {
if rm.retryUntil > 0 {
rm.addDelay()
deadLineExceeded := time.Now().After(rm.startedAt.Add(rm.retryUntil))
if deadLineExceeded {
return ErrDeadlineExceeded
}
shouldRetry = fn()
continue
}
// If maxRetry is set 5 then 5-1 time will be retried. Because initially function was already excuted once.
// Which means: 1 (Initial Call) + 4 retries == 5 maxRetry
if rm.maxRetry > 0 {
rm.maxRetry -= 1
if rm.maxRetry == 0 {
return ErrMaximumRetryExceeded
}
}
rm.addDelay()
shouldRetry = fn()
}
return nil
}
// Retry excutes the function fn in an interval specified as delay or backoffFunc. And it will run until it reaches the maximum number of times specified as maxNumberOfRetry or retryUntil as duration. Retry will be terminated if the fn returns false.
//
// Accepted Parameters:
//
// Retry(fn func() bool, maxNumberOfRetry int | retryUntil time.Duration, delay time.Duration | backOffFn func(uint64) uint64))
//
// Example:
//
// err := retry.Retry(func() bool {
// fmt.Println("Hello world!")
// return true
// }, 10, 2*time.Second)
//
// Default Parameters:
//
// maxNumberOfRetry -> Infinity
// delay -> 1 * time.Second
//
// Usage:
//
// Retry(fn func() bool, maxNumberOfRetry int, delay time.Duration)
//
// Retry(fn func() bool, retryUntil time.Duration, delay time.Duration)
//
// Retry(fn func() bool, retryUntil time.Duration, backOffFn func(uint64) uint64)
//
// Retry(fn func() bool, maxNumberOfRetry int)
//
// Retry(fn)
func Retry(fn Retryable, args ...interface{}) error {
retryManager := &retryManager{
startedAt: time.Now(),
}
err := retryManager.parseParams(args...)
if err != nil {
return err
}
return retryManager.execute(fn)
}