exponential.go 5.6 KB

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