github.com/insolar/vanilla@v0.0.0-20201023172447-248fdf805322/synckit/backoff.go (about) 1 // Copyright 2020 Insolar Network Ltd. 2 // All rights reserved. 3 // This material is licensed under the Insolar License version 1.0, 4 // available at https://github.com/insolar/assured-ledger/blob/master/LICENSE.md. 5 6 // Package backoff provides an exponential-backoff implementation. 7 package synckit 8 9 import ( 10 "math" 11 "math/rand" 12 "time" 13 ) 14 15 // Backoff is a time.Duration counter, starting at Min. After every call to 16 // the Duration method the current timing is multiplied by Factor, but it 17 // never exceeds Max. 18 // 19 // Backoff is not generally concurrent-safe, but the ForAttempt method can 20 // be used concurrently. 21 type Backoff struct { 22 attempt int 23 // Factor is the multiplying factor for each increment step 24 Factor float64 25 // Jitter eases contention by randomizing backoff steps 26 Jitter bool 27 // Min and Max are the minimum and maximum values of the counter 28 Min, Max time.Duration 29 } 30 31 // Duration returns the duration for the current attempt before incrementing 32 // the attempt counter. See ForAttempt. 33 func (b *Backoff) Duration() time.Duration { 34 d := b.ForAttempt(b.attempt) 35 b.attempt++ 36 return d 37 } 38 39 const maxInt64 = float64(math.MaxInt64 - 512) 40 41 // ForAttempt returns the duration for a specific attempt. This is useful if 42 // you have a large number of independent Backoffs, but don't want use 43 // unnecessary memory storing the Backoff parameters per Backoff. The first 44 // attempt should be 0. 45 // 46 // ForAttempt is concurrent-safe. 47 func (b *Backoff) ForAttempt(attempt int) time.Duration { 48 // Zero-values are nonsensical, so we use 49 // them to apply defaults 50 min := b.Min 51 if min <= 0 { 52 min = 100 * time.Millisecond 53 } 54 max := b.Max 55 if max <= 0 { 56 max = 10 * time.Second 57 } 58 if min >= max { 59 // short-circuit 60 return max 61 } 62 factor := b.Factor 63 if factor <= 0 { 64 factor = 2 65 } 66 // calculate this duration 67 minf := float64(min) 68 durf := minf * math.Pow(factor, float64(attempt)) 69 if b.Jitter { 70 durf = rand.Float64()*(durf-minf) + minf 71 } 72 // ensure float64 wont overflow int64 73 if durf > maxInt64 { 74 return max 75 } 76 dur := time.Duration(durf) 77 // keep within bounds 78 if dur < min { 79 return min 80 } 81 if dur > max { 82 return max 83 } 84 return dur 85 } 86 87 // Reset restarts the current attempt counter at zero. 88 func (b *Backoff) Reset() { 89 b.attempt = 0 90 } 91 92 // Attempt returns the current attempt counter value. 93 func (b *Backoff) Attempt() int { 94 return b.attempt 95 } 96 97 // Copy returns a backoff with equals constraints as the original 98 func (b *Backoff) Copy() *Backoff { 99 return &Backoff{ 100 Factor: b.Factor, 101 Jitter: b.Jitter, 102 Min: b.Min, 103 Max: b.Max, 104 } 105 }