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  }