github.com/kaydxh/golang@v0.0.131/go/time/exponential_backoff.go (about) 1 /* 2 *Copyright (c) 2022, kaydxh 3 * 4 *Permission is hereby granted, free of charge, to any person obtaining a copy 5 *of this software and associated documentation files (the "Software"), to deal 6 *in the Software without restriction, including without limitation the rights 7 *to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 *copies of the Software, and to permit persons to whom the Software is 9 *furnished to do so, subject to the following conditions: 10 * 11 *The above copyright notice and this permission notice shall be included in all 12 *copies or substantial portions of the Software. 13 * 14 *THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 *IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 *FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 *AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 *LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 *OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 *SOFTWARE. 21 */ 22 package time 23 24 import ( 25 "math/rand" 26 "time" 27 ) 28 29 // Default values for ExponentialBackOff. 30 const ( 31 DefaultInitialInterval = 500 * time.Millisecond 32 DefaultRandomizationFactor = 0.5 33 // The default multiplier value used for increment current interval 34 DefaultMultiplier = 1.5 35 DefaultMaxInterval = 60 * time.Second 36 DefaultMinInterval = DefaultInitialInterval 37 DefaultMaxElapsedTime = 15 * time.Minute 38 DefaultMaxElapsedCount = -1 39 ) 40 41 type ExponentialBackOff struct { 42 currentInterval time.Duration 43 startTime time.Time 44 elapsedCount int 45 46 opts struct { 47 InitialInterval time.Duration 48 RandomizationFactor float64 49 Multiplier float64 50 MinInterval time.Duration 51 MaxInterval time.Duration 52 // After MaxElapsedTime the ExponentialBackOff returns Stop. 53 // It never stops if MaxElapsedTime == 0. 54 MaxElapsedTime time.Duration 55 // It never stops if MaxElapsedCount == -1. 56 MaxElapsedCount int 57 //notes: when to stop deps on which condition come first, MaxElapsedTime or MaxElapsedCount 58 } 59 } 60 61 func NewExponentialBackOff(opts ...ExponentialBackOffOption) *ExponentialBackOff { 62 bo := &ExponentialBackOff{} 63 bo.opts.InitialInterval = DefaultInitialInterval 64 bo.opts.RandomizationFactor = DefaultRandomizationFactor 65 bo.opts.Multiplier = DefaultMultiplier 66 bo.opts.MaxInterval = DefaultMaxInterval 67 bo.opts.MinInterval = DefaultMinInterval 68 bo.opts.MaxElapsedTime = DefaultMaxElapsedTime 69 bo.opts.MaxElapsedCount = DefaultMaxElapsedCount 70 71 bo.ApplyOptions(opts...) 72 bo.Reset() 73 return bo 74 } 75 76 func (b *ExponentialBackOff) Reset() { 77 b.currentInterval = b.opts.InitialInterval 78 b.startTime = time.Now() 79 } 80 81 func (b *ExponentialBackOff) ResetWithInterval(initialInterval time.Duration) { 82 b.currentInterval = initialInterval 83 b.startTime = time.Now() 84 } 85 86 func (b *ExponentialBackOff) GetCurrentInterval() time.Duration { 87 return b.currentInterval 88 } 89 90 // PreBackOff is get previos time duration 91 // false : have gone over the maximu elapsed time 92 // true : return remaining time 93 func (b *ExponentialBackOff) PreBackOff() (time.Duration, bool) { 94 nextRandomizedInterval, ok := b.validateAndGetNextInterval() 95 if !ok { 96 return nextRandomizedInterval, false 97 } 98 b.elapsedCount++ 99 100 //update currentInterval 101 b.decrementCurrentInterval() 102 103 return nextRandomizedInterval, true 104 } 105 106 // NextBackOff is get next time duration 107 func (b *ExponentialBackOff) NextBackOff() (time.Duration, bool) { 108 b.elapsedCount++ 109 nextRandomizedInterval, ok := b.validateAndGetNextInterval() 110 if !ok { 111 return nextRandomizedInterval, false 112 } 113 114 //update currentInterval 115 b.incrementCurrentInterval() 116 117 return nextRandomizedInterval, true 118 } 119 120 func (b *ExponentialBackOff) GetElapsedTime() time.Duration { 121 return time.Now().Sub(b.startTime) 122 } 123 124 func (b *ExponentialBackOff) MaxElapsedTime() time.Duration { 125 return b.opts.MaxElapsedTime 126 } 127 128 func (b *ExponentialBackOff) validateAndGetNextInterval() (time.Duration, bool) { 129 elapsed := b.GetElapsedTime() 130 nextRandomizedInterval := getRandomValueFromInterval(b.opts.RandomizationFactor, b.currentInterval) 131 132 if b.opts.MaxElapsedTime > 0 && elapsed > b.opts.MaxElapsedTime { 133 return nextRandomizedInterval, false 134 } 135 136 if b.opts.MaxElapsedCount > -1 && b.elapsedCount > b.opts.MaxElapsedCount { 137 return nextRandomizedInterval, false 138 } 139 140 return nextRandomizedInterval, true 141 } 142 143 // Increment the current interval by multiplying it with the multiplier 144 func (b *ExponentialBackOff) incrementCurrentInterval() { 145 if b.opts.MaxInterval > 0 && time.Duration(float64(b.currentInterval)*b.opts.Multiplier) > b.opts.MaxInterval { 146 b.currentInterval = b.opts.MaxInterval 147 return 148 } 149 150 if b.opts.MinInterval > 0 && time.Duration(float64(b.currentInterval)*b.opts.Multiplier) < b.opts.MinInterval { 151 b.currentInterval = b.opts.MinInterval 152 return 153 } 154 155 b.currentInterval = time.Duration(float64(b.currentInterval) * b.opts.Multiplier) 156 } 157 158 // decrement the current interval by multiplying it with the multiplier 159 func (b *ExponentialBackOff) decrementCurrentInterval() { 160 if b.opts.MaxInterval > 0 && 161 time.Duration(float64(b.currentInterval)*(1.0/b.opts.Multiplier)) > b.opts.MaxInterval { 162 b.currentInterval = b.opts.MaxInterval 163 return 164 } 165 166 if b.opts.MinInterval > 0 && 167 time.Duration(float64(b.currentInterval)*(1.0/b.opts.Multiplier)) < b.opts.MinInterval { 168 b.currentInterval = b.opts.MinInterval 169 return 170 } 171 172 b.currentInterval = time.Duration(float64(b.currentInterval) * (1.0 / b.opts.Multiplier)) 173 } 174 175 func getRandomValueFromInterval( 176 randomizationFactor float64, 177 currentInterval time.Duration, 178 ) time.Duration { 179 var delta = randomizationFactor * float64(currentInterval) 180 var minInterval = float64(currentInterval) - delta 181 var maxInterval = float64(currentInterval) + delta 182 183 // Get a random value from the range [minInterval, maxInterval]. 184 // The formula used below has a +1 because if the minInterval is 1 and the maxInterval is 3 then 185 // we want a 33% chance for selecting either 1, 2 or 3. 186 //Float64 returns, as a float64, a pseudo-random number in [0.0,1.0) 187 //from the default Source. 188 return time.Duration(minInterval + (rand.Float64() * (maxInterval - minInterval + 1))) 189 190 }