github.com/searKing/golang/go@v1.2.117/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  //
   100  //	immediately without waiting.
   101  const ZeroBackOff = NonSlidingBackOff(0)
   102  
   103  // StopBackOff Fixed back-off policy that always returns {@code #STOP} for {@link #NextBackOff()},
   104  // meaning that the operation should not be retried.
   105  type StopBackOff struct{}
   106  
   107  func (o *StopBackOff) Reset() {}
   108  func (o *StopBackOff) NextBackOff() (backoff time.Duration, ok bool) {
   109  	return 0, false
   110  }
   111  
   112  // NonSlidingBackOff Fixed back-off policy whose back-off time is always const, meaning that the operation is retried
   113  // after waiting every duration.
   114  type NonSlidingBackOff time.Duration
   115  
   116  func (o *NonSlidingBackOff) Reset() {}
   117  func (o *NonSlidingBackOff) NextBackOff() (backoff time.Duration, ok bool) {
   118  	return time.Duration(*o), false
   119  }
   120  
   121  // JitterBackOff returns a time.Duration between
   122  // [duration - maxFactor*duration, duration + maxFactor*duration].
   123  //
   124  // This allows clients to avoid converging on periodic behavior.
   125  func JitterBackOff(duration time.Duration, maxFactor float64) *jitterBackOff {
   126  	return &jitterBackOff{
   127  		duration:  duration,
   128  		maxFactor: maxFactor,
   129  	}
   130  }
   131  
   132  type jitterBackOff struct {
   133  	duration  time.Duration
   134  	maxFactor float64
   135  }
   136  
   137  func (o *jitterBackOff) Reset() {}
   138  func (o *jitterBackOff) NextBackOff() (backoff time.Duration, ok bool) {
   139  	return Jitter(o.duration, o.maxFactor), false
   140  }
   141  
   142  // ExponentialBackOff Code borrowed from https://github.com/googleapis/google-http-java-client/blob/master/google-http-client/
   143  // src/main/java/com/google/api/client/util/ExponentialBackOff.java
   144  //
   145  //go:generate go-option -type "ExponentialBackOff"
   146  type ExponentialBackOff struct {
   147  	// The current retry interval.
   148  	currentInterval time.Duration
   149  	// The initial retry interval.
   150  	initialInterval time.Duration
   151  	// The current retry count.
   152  	currentCount int
   153  
   154  	// The randomization factor to use for creating a range around the retry interval.
   155  	// A randomization factor of 0.5 results in a random period ranging between 50% below and 50%
   156  	// above the retry interval.
   157  	randomizationFactor float64
   158  
   159  	// The value to multiply the current interval with for each retry attempt.
   160  	multiplier float64
   161  
   162  	// The maximum value of the back off period. Once the retry interval reaches this
   163  	// value it stops increasing.
   164  	// It takes no effect If maxInterval < 0
   165  	maxInterval time.Duration
   166  
   167  	// The system time in nanoseconds. It is calculated when an ExponentialBackOffPolicy instance is
   168  	// created and is reset when {@link #reset()} is called.
   169  	startTime time.Time
   170  
   171  	// The maximum elapsed time after instantiating {@link ExponentialBackOff} or calling {@link
   172  	// #reset()} after which {@link #NextBackOff()} returns {@link BackOff#STOP}.
   173  	// It takes no effect If maxElapsedDuration < 0
   174  	maxElapsedDuration time.Duration
   175  
   176  	// The maximum elapsed count after instantiating {@link ExponentialBackOff} or calling {@link
   177  	// #reset()} after which {@link #NextBackOff()} returns {@link BackOff#STOP}.
   178  	// It takes no effect If maxElapsedCount < 0
   179  	maxElapsedCount int
   180  }
   181  
   182  func (o *ExponentialBackOff) SetDefault() {
   183  	o.initialInterval = DefaultInitialInterval
   184  	o.randomizationFactor = DefaultRandomizationFactor
   185  	o.multiplier = DefaultMultiplier
   186  	o.maxInterval = DefaultMaxInterval
   187  	o.maxElapsedDuration = DefaultMaxElapsedDuration
   188  	o.maxElapsedCount = DefaultMaxElapsedCount
   189  }
   190  
   191  // NewExponentialBackOff returns a no limit backoff
   192  func NewExponentialBackOff(opts ...ExponentialBackOffOption) *ExponentialBackOff {
   193  	opts = append([]ExponentialBackOffOption{WithExponentialBackOffOptionNoLimit()}, opts...)
   194  	o := &ExponentialBackOff{}
   195  	o.SetDefault()
   196  	o.ApplyOptions(opts...)
   197  	o.Reset()
   198  	return o
   199  }
   200  
   201  // NewDefaultExponentialBackOff returns a backoff with default limit
   202  func NewDefaultExponentialBackOff(opts ...ExponentialBackOffOption) *ExponentialBackOff {
   203  	o := &ExponentialBackOff{}
   204  	o.SetDefault()
   205  	o.ApplyOptions(opts...)
   206  	o.Reset()
   207  	return o
   208  }
   209  
   210  // NewGrpcExponentialBackOff is a backoff from configuration with the default values specified
   211  // at https://github.com/grpc/grpc/blob/master/doc/connection-backoff.md.
   212  //
   213  // This should be useful for callers who want to configure backoff with
   214  // non-default values only for a subset of the options.
   215  func NewGrpcExponentialBackOff(opts ...ExponentialBackOffOption) *ExponentialBackOff {
   216  	opts = append([]ExponentialBackOffOption{WithExponentialBackOffOptionGRPC()}, opts...)
   217  	o := &ExponentialBackOff{}
   218  	o.SetDefault()
   219  	o.ApplyOptions(opts...)
   220  	o.Reset()
   221  	return o
   222  }
   223  
   224  // Reset Sets the interval back to the initial retry interval and restarts the timer.
   225  func (o *ExponentialBackOff) Reset() {
   226  	o.currentInterval = o.initialInterval
   227  	o.currentCount = 0
   228  	o.startTime = time.Now()
   229  }
   230  
   231  // NextBackOff This method calculates the next back off interval using the formula: randomized_interval =
   232  // retry_interval +/- (randomization_factor * retry_interval)
   233  // Subclasses may override if a different algorithm is required.
   234  func (o *ExponentialBackOff) NextBackOff() (backoff time.Duration, ok bool) {
   235  	// Make sure we have not gone over the maximum elapsed count.
   236  	if o.maxElapsedCount > 0 && o.GetElapsedCount() >= o.maxElapsedCount {
   237  		return o.currentInterval, false
   238  	}
   239  
   240  	// Make sure we have not gone over the maximum elapsed time.
   241  	if o.maxElapsedDuration > 0 && o.GetElapsedDuration() > o.maxElapsedDuration {
   242  		return o.currentInterval, false
   243  	}
   244  
   245  	randomizedInterval := o.GetRandomValueFromInterval(o.randomizationFactor, o.currentInterval)
   246  	o.incrementCurrentInterval()
   247  	o.incrementCurrentCount()
   248  	return randomizedInterval, true
   249  }
   250  
   251  // GetRandomValueFromInterval Returns a random value from the interval
   252  // [randomizationFactor * currentInterval, randomizationFactor * currentInterval].
   253  func (o *ExponentialBackOff) GetRandomValueFromInterval(
   254  	randomizationFactor float64, currentInterval time.Duration) time.Duration {
   255  	return Jitter(currentInterval, randomizationFactor)
   256  }
   257  
   258  // GetInitialInterval Returns the initial retry interval.
   259  func (o *ExponentialBackOff) GetInitialInterval() time.Duration {
   260  	return o.initialInterval
   261  }
   262  
   263  // GetRandomizationFactor Returns the randomization factor to use for creating a range around the retry interval.
   264  // A randomization factor of 0.5 results in a random period ranging between 50% below and 50%
   265  // above the retry interval.
   266  func (o *ExponentialBackOff) GetRandomizationFactor() float64 {
   267  	return o.randomizationFactor
   268  }
   269  
   270  // GetCurrentInterval Returns the current retry interval.
   271  func (o *ExponentialBackOff) GetCurrentInterval() time.Duration {
   272  	return o.currentInterval
   273  }
   274  
   275  // GetMultiplier Returns the value to multiply the current interval with for each retry attempt.
   276  func (o *ExponentialBackOff) GetMultiplier() float64 {
   277  	return o.multiplier
   278  }
   279  
   280  // GetMaxInterval Returns the maximum value of the back off period. Once the current interval
   281  // reaches this value it stops increasing.
   282  func (o *ExponentialBackOff) GetMaxInterval() time.Duration {
   283  	return o.maxInterval
   284  }
   285  
   286  // GetMaxElapsedDuration Returns the maximum elapsed time.
   287  // If the time elapsed since an {@link ExponentialBackOff} instance is created goes past the
   288  // max_elapsed_time then the method {@link #NextBackOff()} starts returning STOP.
   289  // The elapsed time can be reset by calling
   290  func (o *ExponentialBackOff) GetMaxElapsedDuration() time.Duration {
   291  	return o.maxElapsedDuration
   292  }
   293  
   294  // GetElapsedDuration Returns the elapsed time since an {@link ExponentialBackOff} instance is
   295  // created and is reset when {@link #reset()} is called.
   296  // The elapsed time is computed using {@link System#nanoTime()}.
   297  func (o *ExponentialBackOff) GetElapsedDuration() time.Duration {
   298  	return time.Now().Sub(o.startTime)
   299  }
   300  
   301  // GetElapsedCount Returns the elapsed count since an {@link ExponentialBackOff} instance is
   302  // created and is reset when {@link #reset()} is called.
   303  func (o *ExponentialBackOff) GetElapsedCount() int {
   304  	return o.currentCount
   305  }
   306  
   307  // Increments the current interval by multiplying it with the multiplier.
   308  func (o *ExponentialBackOff) incrementCurrentInterval() {
   309  	// Check for overflow, if overflow is detected set the current interval to the max interval.
   310  	if o.maxInterval >= 0 && o.currentInterval*time.Duration(o.multiplier) >= o.maxInterval {
   311  		o.currentInterval = o.maxInterval
   312  		return
   313  	}
   314  	o.currentInterval = time.Duration(float64(o.currentInterval) * o.multiplier)
   315  }
   316  
   317  // Increments the current count by ++.
   318  func (o *ExponentialBackOff) incrementCurrentCount() {
   319  	// Check for overflow, if overflow is detected set the current interval to the max interval.
   320  	if o.currentCount >= math.MaxInt64 {
   321  		o.currentCount = math.MaxInt64
   322  		return
   323  	}
   324  	o.currentCount++
   325  }