github.com/mailgun/holster/v4@v4.20.0/retry/backoff.go (about) 1 package retry 2 3 import ( 4 "math" 5 "sync/atomic" 6 "time" 7 ) 8 9 type BackOff interface { 10 New() BackOff 11 Next() (time.Duration, bool) 12 NumRetries() int 13 NextIteration() time.Duration 14 CalcDuration(int64) time.Duration 15 Reset() 16 } 17 18 func Interval(t time.Duration) *ConstBackOff { 19 return &ConstBackOff{Interval: t} 20 } 21 22 // ConstBackOff indefinitely retry, returns `interval` for each call to Next() 23 type ConstBackOff struct { 24 Interval time.Duration 25 retries int64 26 } 27 28 func (b *ConstBackOff) NumRetries() int { return int(atomic.LoadInt64(&b.retries)) } 29 func (b *ConstBackOff) CalcDuration(retries int64) time.Duration { return b.Interval } 30 func (b *ConstBackOff) Reset() {} 31 func (b *ConstBackOff) Next() (time.Duration, bool) { 32 atomic.AddInt64(&b.retries, 1) 33 return b.Interval, true 34 } 35 36 func (b *ConstBackOff) NextIteration() time.Duration { 37 atomic.AddInt64(&b.retries, 1) 38 return b.Interval 39 } 40 41 func (b *ConstBackOff) New() BackOff { 42 return &ConstBackOff{ 43 retries: atomic.LoadInt64(&b.retries), 44 Interval: b.Interval, 45 } 46 } 47 48 func Attempts(a int, t time.Duration) *AttemptsBackOff { 49 return &AttemptsBackOff{Interval: t, Attempts: int64(a)} 50 } 51 52 // AttemptsBackOff retry for `attempts` number of retries sleeping for `interval` between each retry 53 type AttemptsBackOff struct { 54 Interval time.Duration 55 Attempts int64 56 retries int64 57 } 58 59 func (b *AttemptsBackOff) NumRetries() int { return int(atomic.LoadInt64(&b.retries)) } 60 func (b *AttemptsBackOff) CalcDuration(retries int64) time.Duration { return b.Interval } 61 func (b *AttemptsBackOff) Reset() { atomic.StoreInt64(&b.retries, 0) } 62 func (b *AttemptsBackOff) Next() (time.Duration, bool) { 63 retries := atomic.AddInt64(&b.retries, 1) 64 if retries < b.Attempts { 65 return b.Interval, true 66 } 67 return b.Interval, false 68 } 69 70 func (b *AttemptsBackOff) NextIteration() time.Duration { 71 atomic.AddInt64(&b.retries, 1) 72 return b.Interval 73 } 74 75 func (b *AttemptsBackOff) New() BackOff { 76 return &AttemptsBackOff{ 77 retries: atomic.LoadInt64(&b.retries), 78 Interval: b.Interval, 79 Attempts: b.Attempts, 80 } 81 } 82 83 type ExponentialBackOff struct { 84 Min, Max time.Duration 85 Factor float64 86 Attempts int64 87 retries int64 88 } 89 90 func (b *ExponentialBackOff) NumRetries() int { return int(atomic.LoadInt64(&b.retries)) } 91 func (b *ExponentialBackOff) Reset() { atomic.StoreInt64(&b.retries, 0) } 92 93 func (b *ExponentialBackOff) Next() (time.Duration, bool) { 94 retries := atomic.AddInt64(&b.retries, 1) 95 interval := b.CalcDuration(retries) 96 if b.Attempts != 0 && retries > b.Attempts { 97 return interval, false 98 } 99 return interval, true 100 } 101 102 // NextIteration returns the next backoff duration. Is identical to Next() but 103 // never indicates if we have reached our max attempts 104 func (b *ExponentialBackOff) NextIteration() time.Duration { 105 d, _ := b.Next() 106 return d 107 } 108 109 // New returns a copy of the current backoff 110 func (b *ExponentialBackOff) New() BackOff { 111 return &ExponentialBackOff{ 112 retries: atomic.LoadInt64(&b.retries), 113 Attempts: b.Attempts, 114 Factor: b.Factor, 115 Min: b.Min, 116 Max: b.Max, 117 } 118 } 119 120 // CalcDuration returns the next duration to sleep given the number of retries. 121 // 122 // This function is useful when keeping track of the attempts and attempt 123 // resets is external to our code. 124 func (b *ExponentialBackOff) CalcDuration(retries int64) time.Duration { 125 // TODO(thrawn01): Implement jitter? 126 127 d := time.Duration(float64(b.Min) * math.Pow(b.Factor, float64(retries))) 128 if d > b.Max { 129 return b.Max 130 } 131 if d < b.Min { 132 return b.Min 133 } 134 return d 135 }