github.com/influx6/npkg@v0.8.8/nretries/retries.go (about) 1 package nretries 2 3 import ( 4 "math" 5 "math/rand" 6 "time" 7 ) 8 9 // LinearDoUntil will continuously run the giving function until no error is returned. 10 // If duration is supplied, the goroutine is made to sleep before making next run. 11 // The same duration is consistently used for each sleep. 12 // The last error received is returned if the count is exhausted without formally 13 // ending function call without an error. 14 func LinearDoUntil(fx func() error, total int, elapse time.Duration) error { 15 var err error 16 for i := total; i > 0; i-- { 17 if err = fx(); err == nil { 18 return nil 19 } 20 21 if elapse > 0 { 22 time.Sleep(elapse) 23 } 24 } 25 return err 26 } 27 28 //*************************************************************** 29 // BackOff Generators 30 // 31 // Taken from the ff: 32 // 1. https://github.com/sethgrid/pester 33 // 2. https://github.com/cenkalti/backoff 34 // 3. https://github.com/hashicorp/go-retryablehttp 35 //*************************************************************** 36 37 var ( 38 // random is used to generate pseudo-random numbers. 39 random = rand.New(rand.NewSource(time.Now().UnixNano())) 40 ) 41 42 // LinearBackOff returns increasing durations, each a second longer than the last 43 func LinearBackOff(i int) time.Duration { 44 return time.Duration(i) * time.Second 45 } 46 47 // ExponentialBackOff returns ever increasing backoffs by a power of 2 48 func ExponentialBackOff(i int) time.Duration { 49 return time.Duration(1<<uint(i)) * time.Second 50 } 51 52 // ExponentialJitterBackOff returns ever increasing backoffs by a power of 2 53 // with +/- 0-33% to prevent synchronized requests. 54 func ExponentialJitterBackOff(i int) time.Duration { 55 return JitterDuration(int(1 << uint(i))) 56 } 57 58 // LinearJitterBackOff returns increasing durations, each a second longer than the last 59 // with +/- 0-33% to prevent synchronized requests. 60 func LinearJitterBackOff(i int) time.Duration { 61 return JitterDuration(i) 62 } 63 64 // JitterDuration keeps the +/- 0-33% logic in one place 65 func JitterDuration(i int) time.Duration { 66 ms := i * 1000 67 maxJitter := ms / 3 68 69 // ms ± rand 70 ms += random.Intn(2*maxJitter) - maxJitter 71 72 // a jitter of 0 messes up the time.Tick chan 73 if ms <= 0 { 74 ms = 1 75 } 76 77 return time.Duration(ms) * time.Millisecond 78 } 79 80 // RandomizedJitters returns a function which returns a new duration for randomized 81 // value of passed integer ranged within provided initial time duration seed. 82 func RandomizedJitters(initial time.Duration, randomFactor float64) func(int) time.Duration { 83 return func(attempt int) time.Duration { 84 rr := math.Abs(random.Float64()) 85 initial = GetRandomValueFromInterval(randomFactor, rr*float64(attempt), initial) 86 return initial 87 } 88 } 89 90 // LinearRangeJitters returns a function which returns a new duration for increasing 91 // value of passed integer ranged within provided minimum and maximum time durations. 92 func LinearRangeJitters(min, max time.Duration) func(int) time.Duration { 93 return func(attempt int) time.Duration { 94 return LinearRangedJitterBackOff(min, max, attempt) 95 } 96 } 97 98 // RangedExponential returns a function which returns a new duration for increasing 99 // value of passed integer ranged within provided minimum and maximum time durations. 100 func RangedExponential(min, max time.Duration) func(int) time.Duration { 101 return func(attempt int) time.Duration { 102 return RangeExponentialBackOff(min, max, attempt) 103 } 104 } 105 106 // RangeExponentialBackOff provides a back off value which will perform 107 // exponential back off based on the attempt number and limited 108 // by the provided minimum and maximum durations. 109 func RangeExponentialBackOff(min, max time.Duration, attemptNum int) time.Duration { 110 mult := math.Pow(2, float64(attemptNum)) * float64(min) 111 sleep := time.Duration(mult) 112 if float64(sleep) != mult || sleep > max { 113 sleep = max 114 } 115 return sleep 116 } 117 118 // LinearRangedJitterBackOff provides a back off value which will 119 // perform linear back off based on the attempt number and with jitter to 120 // prevent a thundering herd. 121 // 122 // min and max here are *not* absolute values. The number to be multiplied by 123 // the attempt number will be chosen at random from between them, thus they are 124 // bounding the jitter. 125 // 126 // For instance: 127 // * To get strictly linear back off of one second increasing each retry, set 128 // both to one second (1s, 2s, 3s, 4s, ...) 129 // * To get a small amount of jitter centered around one second increasing each 130 // retry, set to around one second, such as a min of 800ms and max of 1200ms 131 // (892ms, 2102ms, 2945ms, 4312ms, ...) 132 // * To get extreme jitter, set to a very wide spread, such as a min of 100ms 133 // and a max of 20s (15382ms, 292ms, 51321ms, 35234ms, ...) 134 func LinearRangedJitterBackOff(min, max time.Duration, attemptNum int) time.Duration { 135 // attemptNum always starts at zero but we want to start at 1 for multiplication 136 attemptNum++ 137 138 if max <= min { 139 // Unclear what to do here, or they are the same, so return min * 140 // attemptNum 141 return min * time.Duration(attemptNum) 142 } 143 144 // Pick a random number that lies somewhere between the min and max and 145 // multiply by the attemptNum. attemptNum starts at zero so we always 146 // increment here. We first get a random percentage, then apply that to the 147 // difference between min and max, and add to min. 148 jitter := random.Float64() * float64(max-min) 149 jitterMin := int64(jitter) + int64(min) 150 return time.Duration(jitterMin * int64(attemptNum)) 151 } 152 153 // GetRandomValueFromInterval returns a random value from the following interval: 154 // [randomizationFactor * currentInterval, randomizationFactor * currentInterval]. 155 func GetRandomValueFromInterval(randomizationFactor, random float64, currentInterval time.Duration) time.Duration { 156 var delta = randomizationFactor * float64(currentInterval) 157 var minInterval = float64(currentInterval) - delta 158 var maxInterval = float64(currentInterval) + delta 159 160 // Get a random value from the range [minInterval, maxInterval]. 161 // The formula used below has a +1 because if the minInterval is 1 and the maxInterval is 3 then 162 // we want a 33% chance for selecting either 1, 2 or 3. 163 return time.Duration(minInterval + (random * (maxInterval - minInterval + 1))) 164 }