github.com/bingoohuang/gg@v0.0.0-20240325092523-45da7dee9335/pkg/backoff/backoff.go (about)

     1  // Package backoff implements backoff algorithms for retrying operations.
     2  //
     3  // Use Retry function for retrying operations that may fail.
     4  // If Retry does not meet your needs,
     5  // copy/paste the function into your project and modify as you wish.
     6  //
     7  // There is also Ticker type similar to time.Ticker.
     8  // You can use it if you need to work with channels.
     9  //
    10  // See Examples section below for usage examples.
    11  package backoff
    12  
    13  import (
    14  	"context"
    15  	"errors"
    16  	"math/rand"
    17  	"sync"
    18  	"time"
    19  )
    20  
    21  // BackOff is a backoff policy for retrying an operation.
    22  type BackOff interface {
    23  	// NextBackOff returns the duration to wait before retrying the operation,
    24  	// or backoff. Stop to indicate that no more retries should be made.
    25  	//
    26  	// Example usage:
    27  	//
    28  	// 	duration := backoff.NextBackOff();
    29  	// 	if (duration == backoff.Stop) {
    30  	// 		// Do not retry operation.
    31  	// 	} else {
    32  	// 		// Sleep for duration and retry operation.
    33  	// 	}
    34  	//
    35  	NextBackOff() time.Duration
    36  
    37  	// Reset to initial state.
    38  	Reset()
    39  }
    40  
    41  // Stop indicates that no more retries should be made for use in NextBackOff().
    42  const Stop time.Duration = -1
    43  
    44  // ZeroBackOff is a fixed backoff policy whose backoff time is always zero,
    45  // meaning that the operation is retried immediately without waiting, indefinitely.
    46  type ZeroBackOff struct{}
    47  
    48  func (b *ZeroBackOff) Reset() {}
    49  
    50  func (b *ZeroBackOff) NextBackOff() time.Duration { return 0 }
    51  
    52  // StopBackOff is a fixed backoff policy that always returns backoff.Stop for
    53  // NextBackOff(), meaning that the operation should never be retried.
    54  type StopBackOff struct{}
    55  
    56  func (b *StopBackOff) Reset() {}
    57  
    58  func (b *StopBackOff) NextBackOff() time.Duration { return Stop }
    59  
    60  // ConstantBackOff is a backoff policy that always returns the same backoff delay.
    61  // This is in contrast to an exponential backoff policy,
    62  // which returns a delay that grows longer as you call NextBackOff() over and over again.
    63  type ConstantBackOff struct {
    64  	Interval time.Duration
    65  }
    66  
    67  func (b *ConstantBackOff) Reset()                     {}
    68  func (b *ConstantBackOff) NextBackOff() time.Duration { return b.Interval }
    69  
    70  func NewConstantBackOff(d time.Duration) *ConstantBackOff {
    71  	return &ConstantBackOff{Interval: d}
    72  }
    73  
    74  // Context is a backoff policy that stops retrying after the context
    75  // is canceled.
    76  type Context interface { // nolint: golint
    77  	BackOff
    78  	Context() context.Context
    79  }
    80  
    81  type backOffContext struct {
    82  	BackOff
    83  	ctx context.Context
    84  }
    85  
    86  // WithContext returns a Context with context ctx
    87  //
    88  // ctx must not be nil
    89  func WithContext(b BackOff, ctx context.Context) Context { // nolint: golint
    90  	if ctx == nil {
    91  		panic("nil context")
    92  	}
    93  
    94  	if b, ok := b.(*backOffContext); ok {
    95  		return &backOffContext{BackOff: b.BackOff, ctx: ctx}
    96  	}
    97  
    98  	return &backOffContext{BackOff: b, ctx: ctx}
    99  }
   100  
   101  func getContext(b BackOff) context.Context {
   102  	if cb, ok := b.(Context); ok {
   103  		return cb.Context()
   104  	}
   105  	if tb, ok := b.(*backOffTries); ok {
   106  		return getContext(tb.delegate)
   107  	}
   108  	return context.Background()
   109  }
   110  
   111  func (b *backOffContext) Context() context.Context { return b.ctx }
   112  
   113  func (b *backOffContext) NextBackOff() time.Duration {
   114  	select {
   115  	case <-b.ctx.Done():
   116  		return Stop
   117  	default:
   118  		return b.BackOff.NextBackOff()
   119  	}
   120  }
   121  
   122  /*
   123  ExponentialBackOff is a backoff implementation that increases the backoff
   124  period for each retry attempt using a randomization function that grows exponentially.
   125  
   126  NextBackOff() is calculated using the following formula:
   127  
   128  	randomized interval =
   129  	    RetryInterval * (random value in range [1 - RandomizationFactor, 1 + RandomizationFactor])
   130  
   131  In other words NextBackOff() will range between the randomization factor
   132  percentage below and above the retry interval.
   133  
   134  For example, given the following parameters:
   135  
   136  	RetryInterval = 2
   137  	RandomizationFactor = 0.5
   138  	Multiplier = 2
   139  
   140  the actual backoff period used in the next retry attempt will range between 1 and 3 seconds,
   141  multiplied by the exponential, that is, between 2 and 6 seconds.
   142  
   143  Note: MaxInterval caps the RetryInterval and not the randomized interval.
   144  
   145  If the time elapsed since an ExponentialBackOff instance is created goes past the
   146  MaxElapsedTime, then the method NextBackOff() starts returning backoff.Stop.
   147  
   148  The elapsed time can be reset by calling Reset().
   149  
   150  Example: Given the following default arguments, for 10 tries the sequence will be,
   151  and assuming we go over the MaxElapsedTime on the 10th try:
   152  
   153  	Request #  RetryInterval (seconds)  Randomized Interval (seconds)
   154  
   155  	 1          0.5                     [0.25,   0.75]
   156  	 2          0.75                    [0.375,  1.125]
   157  	 3          1.125                   [0.562,  1.687]
   158  	 4          1.687                   [0.8435, 2.53]
   159  	 5          2.53                    [1.265,  3.795]
   160  	 6          3.795                   [1.897,  5.692]
   161  	 7          5.692                   [2.846,  8.538]
   162  	 8          8.538                   [4.269, 12.807]
   163  	 9         12.807                   [6.403, 19.210]
   164  	10         19.210                   backoff.Stop
   165  
   166  Note: Implementation is not thread-safe.
   167  */
   168  type ExponentialBackOff struct {
   169  	InitialInterval     time.Duration
   170  	RandomizationFactor float64
   171  	Multiplier          float64
   172  	MaxInterval         time.Duration
   173  	// After MaxElapsedTime the ExponentialBackOff returns Stop.
   174  	// It never stops if MaxElapsedTime == 0.
   175  	MaxElapsedTime time.Duration
   176  	Stop           time.Duration
   177  	Clock          Clock
   178  
   179  	currentInterval time.Duration
   180  	startTime       time.Time
   181  }
   182  
   183  // Clock is an interface that returns current time for BackOff.
   184  type Clock interface {
   185  	Now() time.Time
   186  }
   187  
   188  // Default values for ExponentialBackOff.
   189  const (
   190  	DefaultInitialInterval     = 500 * time.Millisecond
   191  	DefaultRandomizationFactor = 0.5
   192  	DefaultMultiplier          = 1.5
   193  	DefaultMaxInterval         = 60 * time.Second
   194  	DefaultMaxElapsedTime      = 15 * time.Minute
   195  )
   196  
   197  // NewExponentialBackOff creates an instance of ExponentialBackOff using default values.
   198  func NewExponentialBackOff() *ExponentialBackOff {
   199  	b := &ExponentialBackOff{
   200  		InitialInterval:     DefaultInitialInterval,
   201  		RandomizationFactor: DefaultRandomizationFactor,
   202  		Multiplier:          DefaultMultiplier,
   203  		MaxInterval:         DefaultMaxInterval,
   204  		MaxElapsedTime:      DefaultMaxElapsedTime,
   205  		Stop:                Stop,
   206  		Clock:               SystemClock,
   207  	}
   208  	b.Reset()
   209  	return b
   210  }
   211  
   212  type systemClock struct{}
   213  
   214  func (t systemClock) Now() time.Time { return time.Now() }
   215  
   216  // SystemClock implements Clock interface that uses time.Now().
   217  var SystemClock = systemClock{}
   218  
   219  // Reset the interval back to the initial retry interval and restarts the timer.
   220  // Reset must be called before using b.
   221  func (b *ExponentialBackOff) Reset() {
   222  	b.currentInterval = b.InitialInterval
   223  	b.startTime = b.Clock.Now()
   224  }
   225  
   226  // NextBackOff calculates the next backoff interval using the formula:
   227  //
   228  //	Randomized interval = RetryInterval * (1 ± RandomizationFactor)
   229  func (b *ExponentialBackOff) NextBackOff() time.Duration {
   230  	// Make sure we have not gone over the maximum elapsed time.
   231  	elapsed := b.GetElapsedTime()
   232  	next := getRandomValueFromInterval(b.RandomizationFactor, rand.Float64(), b.currentInterval)
   233  	b.incrementCurrentInterval()
   234  	if b.MaxElapsedTime != 0 && elapsed+next > b.MaxElapsedTime {
   235  		return b.Stop
   236  	}
   237  	return next
   238  }
   239  
   240  // GetElapsedTime returns the elapsed time since an ExponentialBackOff instance
   241  // is created and is reset when Reset() is called.
   242  //
   243  // The elapsed time is computed using time.Now().UnixNano(). It is
   244  // safe to call even while the backoff policy is used by a running
   245  // ticker.
   246  func (b *ExponentialBackOff) GetElapsedTime() time.Duration {
   247  	return b.Clock.Now().Sub(b.startTime)
   248  }
   249  
   250  // Increments the current interval by multiplying it with the multiplier.
   251  func (b *ExponentialBackOff) incrementCurrentInterval() {
   252  	// Check for overflow, if overflow is detected set the current interval to the max interval.
   253  	if float64(b.currentInterval) >= float64(b.MaxInterval)/b.Multiplier {
   254  		b.currentInterval = b.MaxInterval
   255  	} else {
   256  		b.currentInterval = time.Duration(float64(b.currentInterval) * b.Multiplier)
   257  	}
   258  }
   259  
   260  // Returns a random value from the following interval:
   261  //
   262  //	[currentInterval - randomizationFactor * currentInterval, currentInterval + randomizationFactor * currentInterval].
   263  func getRandomValueFromInterval(randomizationFactor, random float64, currentInterval time.Duration) time.Duration {
   264  	if randomizationFactor == 0 {
   265  		return currentInterval // make sure no randomness is used when randomizationFactor is 0.
   266  	}
   267  	delta := randomizationFactor * float64(currentInterval)
   268  	minInterval := float64(currentInterval) - delta
   269  	maxInterval := float64(currentInterval) + delta
   270  
   271  	// Get a random value from the range [minInterval, maxInterval].
   272  	// The formula used below has a +1 because if the minInterval is 1 and the maxInterval is 3 then
   273  	// we want a 33% chance for selecting either 1, 2 or 3.
   274  	return time.Duration(minInterval + (random * (maxInterval - minInterval + 1)))
   275  }
   276  
   277  // An Operation is executing by Retry() or RetryNotify().
   278  // The operation will be retried using a backoff policy if it returns an error.
   279  type Operation func(retryTimes int) error
   280  
   281  // Notify is a notify-on-error function. It receives an operation error and
   282  // backoff delay if the operation failed (with an error) or recovered (err = nil).
   283  //
   284  // NOTE that if the backoff policy stated to stop retrying,
   285  // the notify function isn't called.
   286  type Notify func(retryTimes int, err error, nextBackOff time.Duration)
   287  
   288  // Retry the operation o until it does not return error or BackOff stops.
   289  // o is guaranteed to be run at least once.
   290  //
   291  // If o returns a *PermanentError, the operation is not retried, and the
   292  // wrapped error is returned.
   293  //
   294  // Retry sleeps the goroutine for the duration returned by BackOff after a
   295  // failed operation returns.
   296  func Retry(o Operation, b BackOff) error {
   297  	return RetryNotify(o, b, nil)
   298  }
   299  
   300  // RetryNotify calls notify function with the error and wait duration
   301  // for each failed attempt before sleep.
   302  func RetryNotify(operation Operation, b BackOff, notify Notify) error {
   303  	return RetryNotifyWithTimer(operation, b, notify, nil)
   304  }
   305  
   306  // RetryNotifyWithTimer calls notify function with the error and wait duration using the given Timer
   307  // for each failed attempt before sleep.
   308  // A default timer that uses system timer is used when nil is passed.
   309  func RetryNotifyWithTimer(operation Operation, b BackOff, notify Notify, t Timer) error {
   310  	var err error
   311  	var next time.Duration
   312  	if t == nil {
   313  		t = &defaultTimer{}
   314  	}
   315  
   316  	defer func() {
   317  		t.Stop()
   318  	}()
   319  
   320  	ctx := getContext(b)
   321  
   322  	b.Reset()
   323  
   324  	for retryTimes := 0; ; retryTimes++ {
   325  		if err = operation(retryTimes); err == nil {
   326  			if retryTimes > 0 && notify != nil {
   327  				notify(retryTimes, err, 0)
   328  			}
   329  			return nil
   330  		}
   331  
   332  		var permanent *PermanentError
   333  		if errors.As(err, &permanent) {
   334  			return permanent.Err
   335  		}
   336  
   337  		if next = b.NextBackOff(); next == Stop {
   338  			if cerr := ctx.Err(); cerr != nil {
   339  				return cerr
   340  			}
   341  
   342  			return err
   343  		}
   344  
   345  		if notify != nil {
   346  			notify(retryTimes, err, next)
   347  		}
   348  
   349  		t.Start(next)
   350  
   351  		select {
   352  		case <-ctx.Done():
   353  			return ctx.Err()
   354  		case <-t.C():
   355  		}
   356  	}
   357  }
   358  
   359  // PermanentError signals that the operation should not be retried.
   360  type PermanentError struct {
   361  	Err error
   362  }
   363  
   364  func (e *PermanentError) Error() string        { return e.Err.Error() }
   365  func (e *PermanentError) Unwrap() error        { return e.Err }
   366  func (e *PermanentError) Is(target error) bool { _, ok := target.(*PermanentError); return ok }
   367  
   368  // Permanent wraps the given err in a *PermanentError.
   369  func Permanent(err error) error {
   370  	if err == nil {
   371  		return nil
   372  	}
   373  	return &PermanentError{Err: err}
   374  }
   375  
   376  // Ticker holds a channel that delivers `ticks' of a clock at times reported by a BackOff.
   377  //
   378  // Ticks will continue to arrive when the previous operation is still running,
   379  // so operations that take a while to fail could run in quick succession.
   380  type Ticker struct {
   381  	C        <-chan time.Time
   382  	c        chan time.Time
   383  	b        BackOff
   384  	ctx      context.Context
   385  	timer    Timer
   386  	stop     chan struct{}
   387  	stopOnce sync.Once
   388  }
   389  
   390  // NewTicker returns a new Ticker containing a channel that will send
   391  // the time at times specified by the BackOff argument. Ticker is
   392  // guaranteed to tick at least once.  The channel is closed when Stop
   393  // method is called or BackOff stops. It is not safe to manipulate the
   394  // provided backoff policy (notably calling NextBackOff or Reset)
   395  // while the ticker is running.
   396  func NewTicker(b BackOff) *Ticker {
   397  	return NewTickerWithTimer(b, &defaultTimer{})
   398  }
   399  
   400  // NewTickerWithTimer returns a new Ticker with a custom timer.
   401  // A default timer that uses system timer is used when nil is passed.
   402  func NewTickerWithTimer(b BackOff, timer Timer) *Ticker {
   403  	if timer == nil {
   404  		timer = &defaultTimer{}
   405  	}
   406  	c := make(chan time.Time)
   407  	t := &Ticker{
   408  		C:     c,
   409  		c:     c,
   410  		b:     b,
   411  		ctx:   getContext(b),
   412  		timer: timer,
   413  		stop:  make(chan struct{}),
   414  	}
   415  	t.b.Reset()
   416  	go t.run()
   417  	return t
   418  }
   419  
   420  // Stop turns off a ticker. After Stop, no more ticks will be sent.
   421  func (t *Ticker) Stop() {
   422  	t.stopOnce.Do(func() { close(t.stop) })
   423  }
   424  
   425  func (t *Ticker) run() {
   426  	c := t.c
   427  	defer close(c)
   428  
   429  	// Ticker is guaranteed to tick at least once.
   430  	afterC := t.send(time.Now())
   431  
   432  	for {
   433  		if afterC == nil {
   434  			return
   435  		}
   436  
   437  		select {
   438  		case tick := <-afterC:
   439  			afterC = t.send(tick)
   440  		case <-t.stop:
   441  			t.c = nil // Prevent future ticks from being sent to the channel.
   442  			return
   443  		case <-t.ctx.Done():
   444  			return
   445  		}
   446  	}
   447  }
   448  
   449  func (t *Ticker) send(tick time.Time) <-chan time.Time {
   450  	select {
   451  	case t.c <- tick:
   452  	case <-t.stop:
   453  		return nil
   454  	}
   455  
   456  	next := t.b.NextBackOff()
   457  	if next == Stop {
   458  		t.Stop()
   459  		return nil
   460  	}
   461  
   462  	t.timer.Start(next)
   463  	return t.timer.C()
   464  }
   465  
   466  type Timer interface {
   467  	Start(duration time.Duration)
   468  	Stop()
   469  	C() <-chan time.Time
   470  }
   471  
   472  // defaultTimer implements Timer interface using time.Timer
   473  type defaultTimer struct {
   474  	timer *time.Timer
   475  }
   476  
   477  // C returns the timers channel which receives the current time when the timer fires.
   478  func (t *defaultTimer) C() <-chan time.Time {
   479  	return t.timer.C
   480  }
   481  
   482  // Start starts the timer to fire after the given duration
   483  func (t *defaultTimer) Start(duration time.Duration) {
   484  	if t.timer == nil {
   485  		t.timer = time.NewTimer(duration)
   486  	} else {
   487  		t.timer.Reset(duration)
   488  	}
   489  }
   490  
   491  // Stop is called when the timer is not used anymore and resources may be freed.
   492  func (t *defaultTimer) Stop() {
   493  	if t.timer != nil {
   494  		t.timer.Stop()
   495  	}
   496  }
   497  
   498  /*
   499  WithMaxRetries creates a wrapper around another BackOff, which will
   500  return Stop if NextBackOff() has been called too many times since
   501  the last time Reset() was called
   502  
   503  Note: Implementation is not thread-safe.
   504  */
   505  func WithMaxRetries(b BackOff, max uint64) BackOff {
   506  	return &backOffTries{delegate: b, maxTries: max}
   507  }
   508  
   509  type backOffTries struct {
   510  	delegate BackOff
   511  	maxTries uint64
   512  	numTries uint64
   513  }
   514  
   515  func (b *backOffTries) NextBackOff() time.Duration {
   516  	if b.maxTries == 0 {
   517  		return Stop
   518  	}
   519  	if b.maxTries > 0 {
   520  		if b.maxTries <= b.numTries {
   521  			return Stop
   522  		}
   523  		b.numTries++
   524  	}
   525  	return b.delegate.NextBackOff()
   526  }
   527  
   528  func (b *backOffTries) Reset() {
   529  	b.numTries = 0
   530  	b.delegate.Reset()
   531  }