forked from projectdiscovery/retryablehttp-go
-
Notifications
You must be signed in to change notification settings - Fork 0
/
backoff.go
131 lines (111 loc) · 4.8 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
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
package retryablehttp
import (
"math"
"math/rand"
"net/http"
"sync"
"time"
)
// Backoff specifies a policy for how long to wait between retries.
// It is called after a failing request to determine the amount of time
// that should pass before trying again.
type Backoff func(min, max time.Duration, attemptNum int, resp *http.Response) time.Duration
// DefaultBackoff provides a default callback for Client.Backoff which
// will perform exponential backoff based on the attempt number and limited
// by the provided minimum and maximum durations.
func DefaultBackoff() func(min, max time.Duration, attemptNum int, resp *http.Response) time.Duration {
return func(min, max time.Duration, attemptNum int, resp *http.Response) time.Duration {
mult := math.Pow(2, float64(attemptNum)) * float64(min)
sleep := time.Duration(mult)
if float64(sleep) != mult || sleep > max {
sleep = max
}
return sleep
}
}
// LinearJitterBackoff provides a callback for Client.Backoff which will
// perform linear backoff based on the attempt number and with jitter to
// prevent a thundering herd.
//
// min and max here are *not* absolute values. The number to be multipled by
// the attempt number will be chosen at random from between them, thus they are
// bounding the jitter.
//
// For instance:
// - To get strictly linear backoff of one second increasing each retry, set
// both to one second (1s, 2s, 3s, 4s, ...)
// - To get a small amount of jitter centered around one second increasing each
// retry, set to around one second, such as a min of 800ms and max of 1200ms
// (892ms, 2102ms, 2945ms, 4312ms, ...)
// - To get extreme jitter, set to a very wide spread, such as a min of 100ms
// and a max of 20s (15382ms, 292ms, 51321ms, 35234ms, ...)
func LinearJitterBackoff() func(min, max time.Duration, attemptNum int, resp *http.Response) time.Duration {
// Seed a global random number generator and use it to generate random
// numbers for the backoff. Use a mutex for protecting the source
rand := rand.New(rand.NewSource(int64(time.Now().Nanosecond())))
randMutex := &sync.Mutex{}
return func(min, max time.Duration, attemptNum int, resp *http.Response) time.Duration {
// attemptNum always starts at zero but we want to start at 1 for multiplication
attemptNum++
if max <= min {
// Unclear what to do here, or they are the same, so return min *
// attemptNum
return min * time.Duration(attemptNum)
}
// Pick a random number that lies somewhere between the min and max and
// multiply by the attemptNum. attemptNum starts at zero so we always
// increment here. We first get a random percentage, then apply that to the
// difference between min and max, and add to min.
randMutex.Lock()
jitter := rand.Float64() * float64(max-min)
randMutex.Unlock()
jitterMin := int64(jitter) + int64(min)
return time.Duration(jitterMin * int64(attemptNum))
}
}
// FullJitterBackoff implements capped exponential backoff
// with jitter. Algorithm is fast because it does not use floating
// point arithmetics. It returns a random number between [0...n]
// https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/
func FullJitterBackoff() func(min, max time.Duration, attemptNum int, resp *http.Response) time.Duration {
// Seed a global random number generator and use it to generate random
// numbers for the backoff. Use a mutex for protecting the source
rand := rand.New(rand.NewSource(int64(time.Now().Nanosecond())))
randMutex := &sync.Mutex{}
return func(min, max time.Duration, attemptNum int, resp *http.Response) time.Duration {
duration := attemptNum * 1000000000 << 1
randMutex.Lock()
jitter := rand.Intn(duration-attemptNum) + int(min)
randMutex.Unlock()
if jitter > int(max) {
return max
}
return time.Duration(jitter)
}
}
// ExponentialJitterBackoff provides a callback for Client.Backoff which will
// perform en exponential backoff based on the attempt number and with jitter to
// prevent a thundering herd.
//
// min and max here are *not* absolute values. The number to be multipled by
// the attempt number will be chosen at random from between them, thus they are
// bounding the jitter.
func ExponentialJitterBackoff() func(min, max time.Duration, attemptNum int, resp *http.Response) time.Duration {
// Seed a global random number generator and use it to generate random
// numbers for the backoff. Use a mutex for protecting the source
rand := rand.New(rand.NewSource(int64(time.Now().Nanosecond())))
randMutex := &sync.Mutex{}
return func(min, max time.Duration, attemptNum int, resp *http.Response) time.Duration {
minf := float64(min)
mult := math.Pow(2, float64(attemptNum)) * minf
randMutex.Lock()
jitter := rand.Float64() * (mult - minf)
randMutex.Unlock()
mult = mult + jitter
sleep := time.Duration(mult)
if sleep > max {
sleep = max
}
return sleep
}
}