github.com/vmg/backoff@v1.0.0/exponential.go (about)

     1  package backoff
     2  
     3  import (
     4  	"math/rand"
     5  	"time"
     6  )
     7  
     8  /*
     9  ExponentialBackOff is a backoff implementation that increases the backoff
    10  period for each retry attempt using a randomization function that grows exponentially.
    11  
    12  NextBackOff() is calculated using the following formula:
    13  
    14   randomized interval =
    15       RetryInterval * (random value in range [1 - RandomizationFactor, 1 + RandomizationFactor])
    16  
    17  In other words NextBackOff() will range between the randomization factor
    18  percentage below and above the retry interval.
    19  
    20  For example, given the following parameters:
    21  
    22   RetryInterval = 2
    23   RandomizationFactor = 0.5
    24   Multiplier = 2
    25  
    26  the actual backoff period used in the next retry attempt will range between 1 and 3 seconds,
    27  multiplied by the exponential, that is, between 2 and 6 seconds.
    28  
    29  Note: MaxInterval caps the RetryInterval and not the randomized interval.
    30  
    31  If the time elapsed since an ExponentialBackOff instance is created goes past the
    32  MaxElapsedTime, then the method NextBackOff() starts returning backoff.Stop.
    33  
    34  The elapsed time can be reset by calling Reset().
    35  
    36  Example: Given the following default arguments, for 10 tries the sequence will be,
    37  and assuming we go over the MaxElapsedTime on the 10th try:
    38  
    39   Request #  RetryInterval (seconds)  Randomized Interval (seconds)
    40  
    41    1          0.5                     [0.25,   0.75]
    42    2          0.75                    [0.375,  1.125]
    43    3          1.125                   [0.562,  1.687]
    44    4          1.687                   [0.8435, 2.53]
    45    5          2.53                    [1.265,  3.795]
    46    6          3.795                   [1.897,  5.692]
    47    7          5.692                   [2.846,  8.538]
    48    8          8.538                   [4.269, 12.807]
    49    9         12.807                   [6.403, 19.210]
    50   10         19.210                   backoff.Stop
    51  
    52  Note: Implementation is not thread-safe.
    53  */
    54  type ExponentialBackOff struct {
    55  	InitialInterval     time.Duration
    56  	RandomizationFactor float64
    57  	Multiplier          float64
    58  	MaxInterval         time.Duration
    59  	// After MaxElapsedTime the ExponentialBackOff stops.
    60  	// It never stops if MaxElapsedTime == 0.
    61  	MaxElapsedTime time.Duration
    62  	Clock          Clock
    63  
    64  	currentInterval time.Duration
    65  	startTime       time.Time
    66  }
    67  
    68  // Clock is an interface that returns current time for BackOff.
    69  type Clock interface {
    70  	Now() time.Time
    71  }
    72  
    73  // Default values for ExponentialBackOff.
    74  const (
    75  	DefaultInitialInterval     = 500 * time.Millisecond
    76  	DefaultRandomizationFactor = 0.5
    77  	DefaultMultiplier          = 1.5
    78  	DefaultMaxInterval         = 60 * time.Second
    79  	DefaultMaxElapsedTime      = 15 * time.Minute
    80  )
    81  
    82  // NewExponentialBackOff creates an instance of ExponentialBackOff using default values.
    83  func NewExponentialBackOff() *ExponentialBackOff {
    84  	b := &ExponentialBackOff{
    85  		InitialInterval:     DefaultInitialInterval,
    86  		RandomizationFactor: DefaultRandomizationFactor,
    87  		Multiplier:          DefaultMultiplier,
    88  		MaxInterval:         DefaultMaxInterval,
    89  		MaxElapsedTime:      DefaultMaxElapsedTime,
    90  		Clock:               SystemClock,
    91  	}
    92  	if b.RandomizationFactor < 0 {
    93  		b.RandomizationFactor = 0
    94  	} else if b.RandomizationFactor > 1 {
    95  		b.RandomizationFactor = 1
    96  	}
    97  	b.Reset()
    98  	return b
    99  }
   100  
   101  type systemClock struct{}
   102  
   103  func (t systemClock) Now() time.Time {
   104  	return time.Now()
   105  }
   106  
   107  // SystemClock implements Clock interface that uses time.Now().
   108  var SystemClock = systemClock{}
   109  
   110  // Reset the interval back to the initial retry interval and restarts the timer.
   111  func (b *ExponentialBackOff) Reset() {
   112  	b.currentInterval = b.InitialInterval
   113  	b.startTime = b.Clock.Now()
   114  }
   115  
   116  // NextBackOff calculates the next backoff interval using the formula:
   117  // 	Randomized interval = RetryInterval +/- (RandomizationFactor * RetryInterval)
   118  func (b *ExponentialBackOff) NextBackOff() time.Duration {
   119  	// Make sure we have not gone over the maximum elapsed time.
   120  	if b.MaxElapsedTime != 0 && b.GetElapsedTime() > b.MaxElapsedTime {
   121  		return Stop
   122  	}
   123  	defer b.incrementCurrentInterval()
   124  	return getRandomValueFromInterval(b.RandomizationFactor, rand.Float64(), b.currentInterval)
   125  }
   126  
   127  // GetElapsedTime returns the elapsed time since an ExponentialBackOff instance
   128  // is created and is reset when Reset() is called.
   129  //
   130  // The elapsed time is computed using time.Now().UnixNano().
   131  func (b *ExponentialBackOff) GetElapsedTime() time.Duration {
   132  	return b.Clock.Now().Sub(b.startTime)
   133  }
   134  
   135  // Increments the current interval by multiplying it with the multiplier.
   136  func (b *ExponentialBackOff) incrementCurrentInterval() {
   137  	// Check for overflow, if overflow is detected set the current interval to the max interval.
   138  	if float64(b.currentInterval) >= float64(b.MaxInterval)/b.Multiplier {
   139  		b.currentInterval = b.MaxInterval
   140  	} else {
   141  		b.currentInterval = time.Duration(float64(b.currentInterval) * b.Multiplier)
   142  	}
   143  }
   144  
   145  // Returns a random value from the following interval:
   146  // 	[randomizationFactor * currentInterval, randomizationFactor * currentInterval].
   147  func getRandomValueFromInterval(randomizationFactor, random float64, currentInterval time.Duration) time.Duration {
   148  	var delta = randomizationFactor * float64(currentInterval)
   149  	var minInterval = float64(currentInterval) - delta
   150  	var maxInterval = float64(currentInterval) + delta
   151  
   152  	// Get a random value from the range [minInterval, maxInterval].
   153  	// The formula used below has a +1 because if the minInterval is 1 and the maxInterval is 3 then
   154  	// we want a 33% chance for selecting either 1, 2 or 3.
   155  	return time.Duration(minInterval + (random * (maxInterval - minInterval + 1)))
   156  }