mirror of
https://github.com/opentffoundation/opentf.git
synced 2026-03-13 10:01:08 -04:00
Nomad was manually updated, so revert that to the version in master, remove it from vendor.json and add it to the ignore list. Update all packages that were in an unknown state to their latest master commits.
157 lines
5.4 KiB
Go
157 lines
5.4 KiB
Go
package backoff
|
|
|
|
import (
|
|
"math/rand"
|
|
"time"
|
|
)
|
|
|
|
/*
|
|
ExponentialBackOff is a backoff implementation that increases the backoff
|
|
period for each retry attempt using a randomization function that grows exponentially.
|
|
|
|
NextBackOff() is calculated using the following formula:
|
|
|
|
randomized interval =
|
|
RetryInterval * (random value in range [1 - RandomizationFactor, 1 + RandomizationFactor])
|
|
|
|
In other words NextBackOff() will range between the randomization factor
|
|
percentage below and above the retry interval.
|
|
|
|
For example, given the following parameters:
|
|
|
|
RetryInterval = 2
|
|
RandomizationFactor = 0.5
|
|
Multiplier = 2
|
|
|
|
the actual backoff period used in the next retry attempt will range between 1 and 3 seconds,
|
|
multiplied by the exponential, that is, between 2 and 6 seconds.
|
|
|
|
Note: MaxInterval caps the RetryInterval and not the randomized interval.
|
|
|
|
If the time elapsed since an ExponentialBackOff instance is created goes past the
|
|
MaxElapsedTime, then the method NextBackOff() starts returning backoff.Stop.
|
|
|
|
The elapsed time can be reset by calling Reset().
|
|
|
|
Example: Given the following default arguments, for 10 tries the sequence will be,
|
|
and assuming we go over the MaxElapsedTime on the 10th try:
|
|
|
|
Request # RetryInterval (seconds) Randomized Interval (seconds)
|
|
|
|
1 0.5 [0.25, 0.75]
|
|
2 0.75 [0.375, 1.125]
|
|
3 1.125 [0.562, 1.687]
|
|
4 1.687 [0.8435, 2.53]
|
|
5 2.53 [1.265, 3.795]
|
|
6 3.795 [1.897, 5.692]
|
|
7 5.692 [2.846, 8.538]
|
|
8 8.538 [4.269, 12.807]
|
|
9 12.807 [6.403, 19.210]
|
|
10 19.210 backoff.Stop
|
|
|
|
Note: Implementation is not thread-safe.
|
|
*/
|
|
type ExponentialBackOff struct {
|
|
InitialInterval time.Duration
|
|
RandomizationFactor float64
|
|
Multiplier float64
|
|
MaxInterval time.Duration
|
|
// After MaxElapsedTime the ExponentialBackOff stops.
|
|
// It never stops if MaxElapsedTime == 0.
|
|
MaxElapsedTime time.Duration
|
|
Clock Clock
|
|
|
|
currentInterval time.Duration
|
|
startTime time.Time
|
|
}
|
|
|
|
// Clock is an interface that returns current time for BackOff.
|
|
type Clock interface {
|
|
Now() time.Time
|
|
}
|
|
|
|
// Default values for ExponentialBackOff.
|
|
const (
|
|
DefaultInitialInterval = 500 * time.Millisecond
|
|
DefaultRandomizationFactor = 0.5
|
|
DefaultMultiplier = 1.5
|
|
DefaultMaxInterval = 60 * time.Second
|
|
DefaultMaxElapsedTime = 15 * time.Minute
|
|
)
|
|
|
|
// NewExponentialBackOff creates an instance of ExponentialBackOff using default values.
|
|
func NewExponentialBackOff() *ExponentialBackOff {
|
|
b := &ExponentialBackOff{
|
|
InitialInterval: DefaultInitialInterval,
|
|
RandomizationFactor: DefaultRandomizationFactor,
|
|
Multiplier: DefaultMultiplier,
|
|
MaxInterval: DefaultMaxInterval,
|
|
MaxElapsedTime: DefaultMaxElapsedTime,
|
|
Clock: SystemClock,
|
|
}
|
|
if b.RandomizationFactor < 0 {
|
|
b.RandomizationFactor = 0
|
|
} else if b.RandomizationFactor > 1 {
|
|
b.RandomizationFactor = 1
|
|
}
|
|
b.Reset()
|
|
return b
|
|
}
|
|
|
|
type systemClock struct{}
|
|
|
|
func (t systemClock) Now() time.Time {
|
|
return time.Now()
|
|
}
|
|
|
|
// SystemClock implements Clock interface that uses time.Now().
|
|
var SystemClock = systemClock{}
|
|
|
|
// Reset the interval back to the initial retry interval and restarts the timer.
|
|
func (b *ExponentialBackOff) Reset() {
|
|
b.currentInterval = b.InitialInterval
|
|
b.startTime = b.Clock.Now()
|
|
}
|
|
|
|
// NextBackOff calculates the next backoff interval using the formula:
|
|
// Randomized interval = RetryInterval +/- (RandomizationFactor * RetryInterval)
|
|
func (b *ExponentialBackOff) NextBackOff() time.Duration {
|
|
// Make sure we have not gone over the maximum elapsed time.
|
|
if b.MaxElapsedTime != 0 && b.GetElapsedTime() > b.MaxElapsedTime {
|
|
return Stop
|
|
}
|
|
defer b.incrementCurrentInterval()
|
|
return getRandomValueFromInterval(b.RandomizationFactor, rand.Float64(), b.currentInterval)
|
|
}
|
|
|
|
// GetElapsedTime returns the elapsed time since an ExponentialBackOff instance
|
|
// is created and is reset when Reset() is called.
|
|
//
|
|
// The elapsed time is computed using time.Now().UnixNano().
|
|
func (b *ExponentialBackOff) GetElapsedTime() time.Duration {
|
|
return b.Clock.Now().Sub(b.startTime)
|
|
}
|
|
|
|
// Increments the current interval by multiplying it with the multiplier.
|
|
func (b *ExponentialBackOff) incrementCurrentInterval() {
|
|
// Check for overflow, if overflow is detected set the current interval to the max interval.
|
|
if float64(b.currentInterval) >= float64(b.MaxInterval)/b.Multiplier {
|
|
b.currentInterval = b.MaxInterval
|
|
} else {
|
|
b.currentInterval = time.Duration(float64(b.currentInterval) * b.Multiplier)
|
|
}
|
|
}
|
|
|
|
// Returns a random value from the following interval:
|
|
// [randomizationFactor * currentInterval, randomizationFactor * currentInterval].
|
|
func getRandomValueFromInterval(randomizationFactor, random float64, currentInterval time.Duration) time.Duration {
|
|
var delta = randomizationFactor * float64(currentInterval)
|
|
var minInterval = float64(currentInterval) - delta
|
|
var maxInterval = float64(currentInterval) + delta
|
|
|
|
// Get a random value from the range [minInterval, maxInterval].
|
|
// The formula used below has a +1 because if the minInterval is 1 and the maxInterval is 3 then
|
|
// we want a 33% chance for selecting either 1, 2 or 3.
|
|
return time.Duration(minInterval + (random * (maxInterval - minInterval + 1)))
|
|
}
|