github.com/searKing/golang/go@v1.2.74/time/backoff.go (about)

     1  // Copyright 2020 The searKing Author. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package time
     6  
     7  import (
     8  	"math"
     9  	"time"
    10  )
    11  
    12  // see https://cloud.google.com/iot/docs/how-tos/exponential-backoff
    13  // Implementation of {@link BackOff} that increases the back off period for each retry attempt using
    14  // a randomization function that grows exponentially.
    15  //
    16  // <p>{@link #NextBackOff()} is calculated using the following formula:
    17  //
    18  // <pre>
    19  // randomized_interval =
    20  // retry_interval // (random value in range [1 - randomization_factor, 1 + randomization_factor])
    21  // </pre>
    22  //
    23  // <p>In other words {@link #NextBackOff()} will range between the randomization factor
    24  // percentage below and above the retry interval. For example, using 2 seconds as the base retry
    25  // interval and 0.5 as the randomization factor, the actual back off period used in the next retry
    26  // attempt will be between 1 and 3 seconds.
    27  //
    28  // <p><b>Note:</b> max_interval caps the retry_interval and not the randomized_interval.
    29  //
    30  // <p>If the time elapsed since an {@link ExponentialBackOff} instance is created goes past the
    31  // max_elapsed_time then the method {@link #NextBackOff()} starts returning {@link
    32  // BackOff#STOP}. The elapsed time can be reset by calling {@link #reset()}.
    33  //
    34  // <p>Example: The default retry_interval is .5 seconds, default randomization_factor is 0.5,
    35  // default multiplier is 1.5 and the default max_interval is 1 minute. For 10 tries the sequence
    36  // will be (values in seconds) and assuming we go over the max_elapsed_time on the 10th try:
    37  //
    38  // <pre>
    39  // request#     retry_interval     randomized_interval
    40  //
    41  // 1             0.5                [0.25,   0.75]
    42  // 2             0.75               [0.375,  1.125]
    43  // 3             1.125              [0.562,  1.687]
    44  // 4             1.687              [0.8435, 2.53]
    45  // 5             2.53               [1.265,  3.795]
    46  // 6             3.795              [1.897,  5.692]
    47  // 7             5.692              [2.846,  8.538]
    48  // 8             8.538              [4.269, 12.807]
    49  // 9            12.807              [6.403, 19.210]
    50  // 10           19.210              {@link BackOff#STOP}
    51  // </pre>
    52  //
    53  // <p>Implementation is not thread-safe.
    54  //
    55  
    56  const (
    57  
    58  	// DefaultInitialInterval The default initial interval value (0.5 seconds).
    59  	DefaultInitialInterval = 500 * time.Millisecond
    60  
    61  	// DefaultRandomizationFactor The default randomization factor (0.5 which results in a random period ranging between 50%
    62  	// below and 50% above the retry interval).
    63  	DefaultRandomizationFactor = 0.5
    64  
    65  	// DefaultMultiplier The default multiplier value (1.5 which is 50% increase per back off).
    66  	DefaultMultiplier = 1.5
    67  
    68  	// DefaultMaxInterval The default maximum back off time (1 minute).
    69  	DefaultMaxInterval = time.Minute
    70  
    71  	// DefaultMaxElapsedDuration The default maximum elapsed time (15 minutes).
    72  	DefaultMaxElapsedDuration = 15 * time.Minute
    73  
    74  	// DefaultMaxElapsedCount The default maximum elapsed count (-1).
    75  	DefaultMaxElapsedCount = -1
    76  )
    77  
    78  // BackOff
    79  // Code borrowed from https://github.com/googleapis/google-http-java-client/blob/master/google-http-client/
    80  // src/main/java/com/google/api/client/util/BackOff.java
    81  type BackOff interface {
    82  	// Reset to initial state.
    83  	Reset()
    84  
    85  	// NextBackOff Gets duration to wait before retrying the operation to
    86  	// indicate that no retries should be made.
    87  	// ok indicates that no more retries should be made, max duration is returned also.
    88  	// Example usage:
    89  	// var backOffDuration, ok = backoff.NextBackOff();
    90  	// if (!ok) {
    91  	// 	// do not retry operation
    92  	// } else {
    93  	//	// sleep for backOffDuration milliseconds and retry operation
    94  	// }
    95  	NextBackOff() (backoff time.Duration, ok bool)
    96  }
    97  
    98  // ZeroBackOff Fixed back-off policy whose back-off time is always zero, meaning that the operation is retried
    99  //  immediately without waiting.
   100  const ZeroBackOff = NonSlidingBackOff(0)
   101  
   102  // StopBackOff Fixed back-off policy that always returns {@code #STOP} for {@link #NextBackOff()},
   103  // meaning that the operation should not be retried.
   104  type StopBackOff struct{}
   105  
   106  func (o *StopBackOff) Reset() {}
   107  func (o *StopBackOff) NextBackOff() (backoff time.Duration, ok bool) {
   108  	return 0, false
   109  }
   110  
   111  // NonSlidingBackOff Fixed back-off policy whose back-off time is always const, meaning that the operation is retried
   112  // after waiting every duration.
   113  type NonSlidingBackOff time.Duration
   114  
   115  func (o *NonSlidingBackOff) Reset() {}
   116  func (o *NonSlidingBackOff) NextBackOff() (backoff time.Duration, ok bool) {
   117  	return time.Duration(*o), false
   118  }
   119  
   120  // JitterBackOff returns a time.Duration between
   121  // [duration - maxFactor*duration, duration + maxFactor*duration].
   122  //
   123  // This allows clients to avoid converging on periodic behavior.
   124  func JitterBackOff(duration time.Duration, maxFactor float64) *jitterBackOff {
   125  	return &jitterBackOff{
   126  		duration:  duration,
   127  		maxFactor: maxFactor,
   128  	}
   129  }
   130  
   131  type jitterBackOff struct {
   132  	duration  time.Duration
   133  	maxFactor float64
   134  }
   135  
   136  func (o *jitterBackOff) Reset() {}
   137  func (o *jitterBackOff) NextBackOff() (backoff time.Duration, ok bool) {
   138  	return Jitter(o.duration, o.maxFactor), false
   139  }
   140  
   141  // ExponentialBackOff Code borrowed from https://github.com/googleapis/google-http-java-client/blob/master/google-http-client/
   142  // src/main/java/com/google/api/client/util/ExponentialBackOff.java
   143  //go:generate go-option -type "ExponentialBackOff"
   144  type ExponentialBackOff struct {
   145  	// The current retry interval.
   146  	currentInterval time.Duration
   147  	// The initial retry interval.
   148  	initialInterval time.Duration
   149  	// The current retry count.
   150  	currentCount int
   151  
   152  	// The randomization factor to use for creating a range around the retry interval.
   153  	// A randomization factor of 0.5 results in a random period ranging between 50% below and 50%
   154  	// above the retry interval.
   155  	randomizationFactor float64
   156  
   157  	// The value to multiply the current interval with for each retry attempt.
   158  	multiplier float64
   159  
   160  	// The maximum value of the back off period. Once the retry interval reaches this
   161  	// value it stops increasing.
   162  	// It takes no effect If maxInterval < 0
   163  	maxInterval time.Duration
   164  
   165  	// The system time in nanoseconds. It is calculated when an ExponentialBackOffPolicy instance is
   166  	// created and is reset when {@link #reset()} is called.
   167  	startTime time.Time
   168  
   169  	// The maximum elapsed time after instantiating {@link ExponentialBackOff} or calling {@link
   170  	// #reset()} after which {@link #NextBackOff()} returns {@link BackOff#STOP}.
   171  	// It takes no effect If maxElapsedDuration < 0
   172  	maxElapsedDuration time.Duration
   173  
   174  	// The maximum elapsed count after instantiating {@link ExponentialBackOff} or calling {@link
   175  	// #reset()} after which {@link #NextBackOff()} returns {@link BackOff#STOP}.
   176  	// It takes no effect If maxElapsedCount < 0
   177  	maxElapsedCount int
   178  }
   179  
   180  func (o *ExponentialBackOff) SetDefault() {
   181  	o.initialInterval = DefaultInitialInterval
   182  	o.randomizationFactor = DefaultRandomizationFactor
   183  	o.multiplier = DefaultMultiplier
   184  	o.maxInterval = DefaultMaxInterval
   185  	o.maxElapsedDuration = DefaultMaxElapsedDuration
   186  	o.maxElapsedCount = DefaultMaxElapsedCount
   187  }
   188  
   189  // NewExponentialBackOff returns a no limit backoff
   190  func NewExponentialBackOff(opts ...ExponentialBackOffOption) *ExponentialBackOff {
   191  	opts = append([]ExponentialBackOffOption{WithExponentialBackOffOptionNoLimit()}, opts...)
   192  	o := &ExponentialBackOff{}
   193  	o.SetDefault()
   194  	o.ApplyOptions(opts...)
   195  	o.Reset()
   196  	return o
   197  }
   198  
   199  // NewDefaultExponentialBackOff returns a backoff with default limit
   200  func NewDefaultExponentialBackOff(opts ...ExponentialBackOffOption) *ExponentialBackOff {
   201  	o := &ExponentialBackOff{}
   202  	o.SetDefault()
   203  	o.ApplyOptions(opts...)
   204  	o.Reset()
   205  	return o
   206  }
   207  
   208  // NewGrpcExponentialBackOff is a backoff from configuration with the default values specified
   209  // at https://github.com/grpc/grpc/blob/master/doc/connection-backoff.md.
   210  //
   211  // This should be useful for callers who want to configure backoff with
   212  // non-default values only for a subset of the options.
   213  func NewGrpcExponentialBackOff(opts ...ExponentialBackOffOption) *ExponentialBackOff {
   214  	opts = append([]ExponentialBackOffOption{WithExponentialBackOffOptionGRPC()}, opts...)
   215  	o := &ExponentialBackOff{}
   216  	o.SetDefault()
   217  	o.ApplyOptions(opts...)
   218  	o.Reset()
   219  	return o
   220  }
   221  
   222  // Reset Sets the interval back to the initial retry interval and restarts the timer.
   223  func (o *ExponentialBackOff) Reset() {
   224  	o.currentInterval = o.initialInterval
   225  	o.currentCount = 0
   226  	o.startTime = time.Now()
   227  }
   228  
   229  // NextBackOff This method calculates the next back off interval using the formula: randomized_interval =
   230  // retry_interval +/- (randomization_factor * retry_interval)
   231  // Subclasses may override if a different algorithm is required.
   232  func (o *ExponentialBackOff) NextBackOff() (backoff time.Duration, ok bool) {
   233  	// Make sure we have not gone over the maximum elapsed count.
   234  	if o.maxElapsedCount > 0 && o.GetElapsedCount() >= o.maxElapsedCount {
   235  		return o.currentInterval, false
   236  	}
   237  
   238  	// Make sure we have not gone over the maximum elapsed time.
   239  	if o.maxElapsedDuration > 0 && o.GetElapsedDuration() > o.maxElapsedDuration {
   240  		return o.currentInterval, false
   241  	}
   242  
   243  	randomizedInterval := o.GetRandomValueFromInterval(o.randomizationFactor, o.currentInterval)
   244  	o.incrementCurrentInterval()
   245  	o.incrementCurrentCount()
   246  	return randomizedInterval, true
   247  }
   248  
   249  // GetRandomValueFromInterval Returns a random value from the interval
   250  // [randomizationFactor * currentInterval, randomizationFactor * currentInterval].
   251  func (o *ExponentialBackOff) GetRandomValueFromInterval(
   252  	randomizationFactor float64, currentInterval time.Duration) time.Duration {
   253  	return Jitter(currentInterval, randomizationFactor)
   254  }
   255  
   256  // GetInitialInterval Returns the initial retry interval.
   257  func (o *ExponentialBackOff) GetInitialInterval() time.Duration {
   258  	return o.initialInterval
   259  }
   260  
   261  // GetRandomizationFactor Returns the randomization factor to use for creating a range around the retry interval.
   262  // A randomization factor of 0.5 results in a random period ranging between 50% below and 50%
   263  // above the retry interval.
   264  func (o *ExponentialBackOff) GetRandomizationFactor() float64 {
   265  	return o.randomizationFactor
   266  }
   267  
   268  // GetCurrentInterval Returns the current retry interval.
   269  func (o *ExponentialBackOff) GetCurrentInterval() time.Duration {
   270  	return o.currentInterval
   271  }
   272  
   273  // GetMultiplier Returns the value to multiply the current interval with for each retry attempt.
   274  func (o *ExponentialBackOff) GetMultiplier() float64 {
   275  	return o.multiplier
   276  }
   277  
   278  // GetMaxInterval Returns the maximum value of the back off period. Once the current interval
   279  // reaches this value it stops increasing.
   280  func (o *ExponentialBackOff) GetMaxInterval() time.Duration {
   281  	return o.maxInterval
   282  }
   283  
   284  // GetMaxElapsedDuration Returns the maximum elapsed time.
   285  // If the time elapsed since an {@link ExponentialBackOff} instance is created goes past the
   286  // max_elapsed_time then the method {@link #NextBackOff()} starts returning STOP.
   287  // The elapsed time can be reset by calling
   288  func (o *ExponentialBackOff) GetMaxElapsedDuration() time.Duration {
   289  	return o.maxElapsedDuration
   290  }
   291  
   292  // GetElapsedDuration Returns the elapsed time since an {@link ExponentialBackOff} instance is
   293  // created and is reset when {@link #reset()} is called.
   294  // The elapsed time is computed using {@link System#nanoTime()}.
   295  func (o *ExponentialBackOff) GetElapsedDuration() time.Duration {
   296  	return time.Now().Sub(o.startTime)
   297  }
   298  
   299  // GetElapsedCount Returns the elapsed count since an {@link ExponentialBackOff} instance is
   300  // created and is reset when {@link #reset()} is called.
   301  func (o *ExponentialBackOff) GetElapsedCount() int {
   302  	return o.currentCount
   303  }
   304  
   305  // Increments the current interval by multiplying it with the multiplier.
   306  func (o *ExponentialBackOff) incrementCurrentInterval() {
   307  	// Check for overflow, if overflow is detected set the current interval to the max interval.
   308  	if o.maxInterval >= 0 && o.currentInterval*time.Duration(o.multiplier) >= o.maxInterval {
   309  		o.currentInterval = o.maxInterval
   310  		return
   311  	}
   312  	o.currentInterval = time.Duration(float64(o.currentInterval) * o.multiplier)
   313  }
   314  
   315  // Increments the current count by ++.
   316  func (o *ExponentialBackOff) incrementCurrentCount() {
   317  	// Check for overflow, if overflow is detected set the current interval to the max interval.
   318  	if o.currentCount >= math.MaxInt64 {
   319  		o.currentCount = math.MaxInt64
   320  		return
   321  	}
   322  	o.currentCount++
   323  }