backoff.go 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. package core
  2. // Package backoff contains an implementation of an intelligent backoff
  3. // strategy. It is based on the approach in the AWS architecture blog
  4. // article titled "Exponential Backoff And Jitter", which is found at
  5. // http://www.awsarchitectureblog.com/2015/03/backoff.html.
  6. //
  7. // Essentially, the backoff has an interval `time.Duration`; the nth
  8. // call to backoff will return a `time.Duration` that is 2^n *
  9. // interval. If jitter is enabled (which is the default behaviour),
  10. // the duration is a random value between 0 and 2^n * interval. The
  11. // backoff is configured with a maximum duration that will not be
  12. // exceeded.
  13. //
  14. // The `New` function will attempt to use the system's cryptographic
  15. // random number generator to seed a Go math/rand random number
  16. // source. If this fails, the package will panic on startup.
  17. import (
  18. "crypto/rand"
  19. "encoding/binary"
  20. "io"
  21. "math"
  22. mrand "math/rand"
  23. "sync"
  24. "time"
  25. )
  26. var prngMu sync.Mutex
  27. var prng *mrand.Rand
  28. // DefaultInterval is used when a Backoff is initialised with a
  29. // zero-value Interval.
  30. var DefaultInterval = 5 * time.Minute
  31. // DefaultMaxDuration is maximum amount of time that the backoff will
  32. // delay for.
  33. var DefaultMaxDuration = 6 * time.Hour
  34. // A Backoff contains the information needed to intelligently backoff
  35. // and retry operations using an exponential backoff algorithm. It should
  36. // be initialised with a call to `New`.
  37. //
  38. // Only use a Backoff from a single goroutine, it is not safe for concurrent
  39. // access.
  40. type Backoff struct {
  41. // maxDuration is the largest possible duration that can be
  42. // returned from a call to Duration.
  43. maxDuration time.Duration
  44. // interval controls the time step for backing off.
  45. interval time.Duration
  46. // noJitter controls whether to use the "Full Jitter"
  47. // improvement to attempt to smooth out spikes in a high
  48. // contention scenario. If noJitter is set to true, no
  49. // jitter will be introduced.
  50. noJitter bool
  51. // decay controls the decay of n. If it is non-zero, n is
  52. // reset if more than the last backoff + decay has elapsed since
  53. // the last try.
  54. decay time.Duration
  55. n uint64
  56. lastTry time.Time
  57. }
  58. // New creates a new backoff with the specified max duration and
  59. // interval. Zero values may be used to use the default values.
  60. //
  61. // Panics if either max or interval is negative.
  62. func New(max time.Duration, interval time.Duration) *Backoff {
  63. if max < 0 || interval < 0 {
  64. panic("backoff: max or interval is negative")
  65. }
  66. b := &Backoff{
  67. maxDuration: max,
  68. interval: interval,
  69. }
  70. b.setup()
  71. return b
  72. }
  73. // NewWithoutJitter works similarly to New, except that the created
  74. // Backoff will not use jitter.
  75. func NewWithoutJitter(max time.Duration, interval time.Duration) *Backoff {
  76. b := New(max, interval)
  77. b.noJitter = true
  78. return b
  79. }
  80. func init() {
  81. var buf [8]byte
  82. var n int64
  83. _, err := io.ReadFull(rand.Reader, buf[:])
  84. if err != nil {
  85. panic(err.Error())
  86. }
  87. n = int64(binary.LittleEndian.Uint64(buf[:]))
  88. src := mrand.NewSource(n)
  89. prng = mrand.New(src)
  90. }
  91. func (b *Backoff) setup() {
  92. if b.interval == 0 {
  93. b.interval = DefaultInterval
  94. }
  95. if b.maxDuration == 0 {
  96. b.maxDuration = DefaultMaxDuration
  97. }
  98. }
  99. // Duration returns a time.Duration appropriate for the backoff,
  100. // incrementing the attempt counter.
  101. func (b *Backoff) Duration() time.Duration {
  102. b.setup()
  103. b.decayN()
  104. t := b.duration(b.n)
  105. if b.n < math.MaxUint64 {
  106. b.n++
  107. }
  108. if !b.noJitter {
  109. prngMu.Lock()
  110. t = time.Duration(prng.Int63n(int64(t)))
  111. prngMu.Unlock()
  112. }
  113. return t
  114. }
  115. // requires b to be locked.
  116. func (b *Backoff) duration(n uint64) (t time.Duration) {
  117. // Saturate pow
  118. pow := time.Duration(math.MaxInt64)
  119. if n < 63 {
  120. pow = 1 << n
  121. }
  122. t = b.interval * pow
  123. if t/pow != b.interval || t > b.maxDuration {
  124. t = b.maxDuration
  125. }
  126. return
  127. }
  128. // Reset resets the attempt counter of a backoff.
  129. //
  130. // It should be called when the rate-limited action succeeds.
  131. func (b *Backoff) Reset() {
  132. b.lastTry = time.Time{}
  133. b.n = 0
  134. }
  135. // SetDecay sets the duration after which the try counter will be reset.
  136. // Panics if decay is smaller than 0.
  137. //
  138. // The decay only kicks in if at least the last backoff + decay has elapsed
  139. // since the last try.
  140. func (b *Backoff) SetDecay(decay time.Duration) {
  141. if decay < 0 {
  142. panic("backoff: decay < 0")
  143. }
  144. b.decay = decay
  145. }
  146. // requires b to be locked
  147. func (b *Backoff) decayN() {
  148. if b.decay == 0 {
  149. return
  150. }
  151. if b.lastTry.IsZero() {
  152. b.lastTry = time.Now()
  153. return
  154. }
  155. lastDuration := b.duration(b.n - 1)
  156. decayed := time.Since(b.lastTry) > lastDuration+b.decay
  157. b.lastTry = time.Now()
  158. if !decayed {
  159. return
  160. }
  161. b.n = 0
  162. }