backoff.go

  1package retry
  2
  3import (
  4	"sync"
  5	"time"
  6)
  7
  8// Backoff is an interface that backs off.
  9type Backoff interface {
 10	// Next returns the time duration to wait and whether to stop.
 11	Next() (next time.Duration, stop bool)
 12}
 13
 14var _ Backoff = (BackoffFunc)(nil)
 15
 16// BackoffFunc is a backoff expressed as a function.
 17type BackoffFunc func() (time.Duration, bool)
 18
 19// Next implements Backoff.
 20func (b BackoffFunc) Next() (time.Duration, bool) {
 21	return b()
 22}
 23
 24// WithJitter wraps a backoff function and adds the specified jitter. j can be
 25// interpreted as "+/- j". For example, if j were 5 seconds and the backoff
 26// returned 20s, the value could be between 15 and 25 seconds. The value can
 27// never be less than 0.
 28func WithJitter(j time.Duration, next Backoff) Backoff {
 29	r := newLockedRandom(time.Now().UnixNano())
 30
 31	return BackoffFunc(func() (time.Duration, bool) {
 32		val, stop := next.Next()
 33		if stop {
 34			return 0, true
 35		}
 36
 37		diff := time.Duration(r.Int63n(int64(j)*2) - int64(j))
 38		val = val + diff
 39		if val < 0 {
 40			val = 0
 41		}
 42		return val, false
 43	})
 44}
 45
 46// WithJitterPercent wraps a backoff function and adds the specified jitter
 47// percentage. j can be interpreted as "+/- j%". For example, if j were 5 and
 48// the backoff returned 20s, the value could be between 19 and 21 seconds. The
 49// value can never be less than 0 or greater than 100.
 50func WithJitterPercent(j uint64, next Backoff) Backoff {
 51	r := newLockedRandom(time.Now().UnixNano())
 52
 53	return BackoffFunc(func() (time.Duration, bool) {
 54		val, stop := next.Next()
 55		if stop {
 56			return 0, true
 57		}
 58
 59		// Get a value between -j and j, the convert to a percentage
 60		top := r.Int63n(int64(j)*2) - int64(j)
 61		pct := 1 - float64(top)/100.0
 62
 63		val = time.Duration(float64(val) * pct)
 64		if val < 0 {
 65			val = 0
 66		}
 67		return val, false
 68	})
 69}
 70
 71// WithMaxRetries executes the backoff function up until the maximum attempts.
 72func WithMaxRetries(max uint64, next Backoff) Backoff {
 73	var l sync.Mutex
 74	var attempt uint64
 75
 76	return BackoffFunc(func() (time.Duration, bool) {
 77		l.Lock()
 78		defer l.Unlock()
 79
 80		if attempt >= max {
 81			return 0, true
 82		}
 83		attempt++
 84
 85		val, stop := next.Next()
 86		if stop {
 87			return 0, true
 88		}
 89
 90		return val, false
 91	})
 92}
 93
 94// WithCappedDuration sets a maximum on the duration returned from the next
 95// backoff. This is NOT a total backoff time, but rather a cap on the maximum
 96// value a backoff can return. Without another middleware, the backoff will
 97// continue infinitely.
 98func WithCappedDuration(cap time.Duration, next Backoff) Backoff {
 99	return BackoffFunc(func() (time.Duration, bool) {
100		val, stop := next.Next()
101		if stop {
102			return 0, true
103		}
104
105		if val <= 0 || val > cap {
106			val = cap
107		}
108		return val, false
109	})
110}
111
112// WithMaxDuration sets a maximum on the total amount of time a backoff should
113// execute. It's best-effort, and should not be used to guarantee an exact
114// amount of time.
115func WithMaxDuration(timeout time.Duration, next Backoff) Backoff {
116	start := time.Now()
117
118	return BackoffFunc(func() (time.Duration, bool) {
119		diff := timeout - time.Since(start)
120		if diff <= 0 {
121			return 0, true
122		}
123
124		val, stop := next.Next()
125		if stop {
126			return 0, true
127		}
128
129		if val <= 0 || val > diff {
130			val = diff
131		}
132		return val, false
133	})
134}