-
Notifications
You must be signed in to change notification settings - Fork 122
/
backoff.go
197 lines (179 loc) · 4.91 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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
// Copyright 2020 New Relic Corporation. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
// Package backoff provides an exponential-backoff implementation.
// https://github.com/jpillora/backoff inlined for customizations.
package backoff
import (
"context"
backendhttp "github.com/newrelic/infrastructure-agent/pkg/backend/http"
"math"
"math/rand"
"sync"
"time"
)
// Backoff is a time.Duration counter, starting at Min. After every call to
// the Duration method the current timing is multiplied by Factor, but it
// never exceeds Max.
//
// Backoff is not generally concurrent-safe, but the ForAttempt method can
// be used concurrently.
type Backoff struct {
lock sync.Mutex
//Factor is the multiplying factor for each increment step
attempt, Factor float64
//Jitter eases contention by randomizing backoff steps
Jitter bool
//Min and Max are the minimum and maximum values of the counter
Min, Max time.Duration
// GetBackoffTimer function to handle the backoff.
GetBackoffTimer func(time.Duration) *time.Timer
}
// Default values
const (
DefaultFactor = 2
DefaultJitter = true
DefaultMin = 1 * time.Second
DefaultMax = 5 * time.Minute
)
// NewDefaultBackoff default behaviour for Vortex.
func NewDefaultBackoff() *Backoff {
return &Backoff{
Factor: DefaultFactor,
Jitter: DefaultJitter,
Min: DefaultMin,
Max: DefaultMax,
GetBackoffTimer: time.NewTimer,
}
}
// Duration returns the duration for the current attempt. The result will be limited to max value.
func (b *Backoff) DurationWithMax(max time.Duration) time.Duration {
b.lock.Lock()
defer b.lock.Unlock()
if max <= 0 {
max = b.Max
}
return b.duration(b.Min, max)
}
// Duration returns the duration for the current attempt.
func (b *Backoff) Duration() time.Duration {
b.lock.Lock()
defer b.lock.Unlock()
return b.duration(b.Min, b.Max)
}
// duration returns the duration for the current attempt before incrementing
// the attempt counter. See ForAttempt.
func (b *Backoff) duration(min, max time.Duration) time.Duration {
d := b.forAttempt(b.attempt, min, max)
b.attempt++
return d
}
const maxInt64 = float64(math.MaxInt64 - 512)
// ForAttempt calls forAttempt with configured max/min values.
func (b *Backoff) ForAttempt(attempt float64) time.Duration {
b.lock.Lock()
defer b.lock.Unlock()
return b.forAttempt(attempt, b.Min, b.Max)
}
// ForAttemptWithMax calls forAttempt with configured a max value limit.
func (b *Backoff) ForAttemptWithMax(attempt float64, max time.Duration) time.Duration {
b.lock.Lock()
defer b.lock.Unlock()
if max <= 0 {
max = b.Max
}
return b.forAttempt(attempt, b.Min, max)
}
// forAttempt returns the duration for a specific attempt. This is useful if
// you have a large number of independent Backoffs, but don't want use
// unnecessary memory storing the Backoff parameters per Backoff. The first
// attempt should be 0.
//
// forAttempt is concurrent-safe.
func (b *Backoff) forAttempt(attempt float64, min, max time.Duration) time.Duration {
// Zero-values are nonsensical, so we use
// them to apply defaults
if min <= 0 {
min = 100 * time.Millisecond
}
if max <= 0 {
max = 10 * time.Second
}
if min >= max {
// short-circuit
return max
}
factor := b.Factor
if factor <= 0 {
factor = 2
}
//calculate this duration
minf := float64(min)
durf := minf * math.Pow(factor, attempt)
if b.Jitter {
durf = rand.Float64()*(durf-minf) + minf
}
//ensure float64 wont overflow int64
if durf > maxInt64 {
return max
}
dur := time.Duration(durf)
//keep within bounds
if dur < min {
return min
}
if dur > max {
return max
}
return dur
}
// Reset restarts the current attempt counter at zero.
func (b *Backoff) Reset() {
b.lock.Lock()
defer b.lock.Unlock()
b.attempt = 0
}
// Attempt returns the current attempt counter value.
func (b *Backoff) Attempt() float64 {
b.lock.Lock()
defer b.lock.Unlock()
return b.attempt
}
// IncreaseAttempt increases attempt counter value.
func (b *Backoff) IncreaseAttempt() {
b.lock.Lock()
defer b.lock.Unlock()
b.attempt++
}
// Copy returns a backoff with equals constraints as the original
func (b *Backoff) Copy() *Backoff {
b.lock.Lock()
defer b.lock.Unlock()
return &Backoff{
Factor: b.Factor,
Jitter: b.Jitter,
Min: b.Min,
Max: b.Max,
}
}
// Backoff waits for the specified duration or a signal from the ctx
// channel, whichever happens first.
func (b *Backoff) Backoff(ctx context.Context, d time.Duration) {
b.lock.Lock()
backoffTimer := b.GetBackoffTimer(d)
b.lock.Unlock()
select {
case <-ctx.Done():
case <-backoffTimer.C:
}
}
// GetMaxBackoffByCause will return the maximum backoff value based on the error cause.
func GetMaxBackoffByCause(cause backendhttp.ErrorCause) time.Duration {
switch cause {
case backendhttp.InvalidLicense, backendhttp.TrialExpired:
return 1 * time.Hour
case backendhttp.TrialInactive, backendhttp.ServiceError:
return 5 * time.Minute
default:
return 0
}
}