github.com/mailgun/holster/v4@v4.20.0/retry/backoff.go (about)

     1  package retry
     2  
     3  import (
     4  	"math"
     5  	"sync/atomic"
     6  	"time"
     7  )
     8  
     9  type BackOff interface {
    10  	New() BackOff
    11  	Next() (time.Duration, bool)
    12  	NumRetries() int
    13  	NextIteration() time.Duration
    14  	CalcDuration(int64) time.Duration
    15  	Reset()
    16  }
    17  
    18  func Interval(t time.Duration) *ConstBackOff {
    19  	return &ConstBackOff{Interval: t}
    20  }
    21  
    22  // ConstBackOff indefinitely retry, returns `interval` for each call to Next()
    23  type ConstBackOff struct {
    24  	Interval time.Duration
    25  	retries  int64
    26  }
    27  
    28  func (b *ConstBackOff) NumRetries() int                          { return int(atomic.LoadInt64(&b.retries)) }
    29  func (b *ConstBackOff) CalcDuration(retries int64) time.Duration { return b.Interval }
    30  func (b *ConstBackOff) Reset()                                   {}
    31  func (b *ConstBackOff) Next() (time.Duration, bool) {
    32  	atomic.AddInt64(&b.retries, 1)
    33  	return b.Interval, true
    34  }
    35  
    36  func (b *ConstBackOff) NextIteration() time.Duration {
    37  	atomic.AddInt64(&b.retries, 1)
    38  	return b.Interval
    39  }
    40  
    41  func (b *ConstBackOff) New() BackOff {
    42  	return &ConstBackOff{
    43  		retries:  atomic.LoadInt64(&b.retries),
    44  		Interval: b.Interval,
    45  	}
    46  }
    47  
    48  func Attempts(a int, t time.Duration) *AttemptsBackOff {
    49  	return &AttemptsBackOff{Interval: t, Attempts: int64(a)}
    50  }
    51  
    52  // AttemptsBackOff retry for `attempts` number of retries sleeping for `interval` between each retry
    53  type AttemptsBackOff struct {
    54  	Interval time.Duration
    55  	Attempts int64
    56  	retries  int64
    57  }
    58  
    59  func (b *AttemptsBackOff) NumRetries() int                          { return int(atomic.LoadInt64(&b.retries)) }
    60  func (b *AttemptsBackOff) CalcDuration(retries int64) time.Duration { return b.Interval }
    61  func (b *AttemptsBackOff) Reset()                                   { atomic.StoreInt64(&b.retries, 0) }
    62  func (b *AttemptsBackOff) Next() (time.Duration, bool) {
    63  	retries := atomic.AddInt64(&b.retries, 1)
    64  	if retries < b.Attempts {
    65  		return b.Interval, true
    66  	}
    67  	return b.Interval, false
    68  }
    69  
    70  func (b *AttemptsBackOff) NextIteration() time.Duration {
    71  	atomic.AddInt64(&b.retries, 1)
    72  	return b.Interval
    73  }
    74  
    75  func (b *AttemptsBackOff) New() BackOff {
    76  	return &AttemptsBackOff{
    77  		retries:  atomic.LoadInt64(&b.retries),
    78  		Interval: b.Interval,
    79  		Attempts: b.Attempts,
    80  	}
    81  }
    82  
    83  type ExponentialBackOff struct {
    84  	Min, Max time.Duration
    85  	Factor   float64
    86  	Attempts int64
    87  	retries  int64
    88  }
    89  
    90  func (b *ExponentialBackOff) NumRetries() int { return int(atomic.LoadInt64(&b.retries)) }
    91  func (b *ExponentialBackOff) Reset()          { atomic.StoreInt64(&b.retries, 0) }
    92  
    93  func (b *ExponentialBackOff) Next() (time.Duration, bool) {
    94  	retries := atomic.AddInt64(&b.retries, 1)
    95  	interval := b.CalcDuration(retries)
    96  	if b.Attempts != 0 && retries > b.Attempts {
    97  		return interval, false
    98  	}
    99  	return interval, true
   100  }
   101  
   102  // NextIteration returns the next backoff duration. Is identical to Next() but
   103  // never indicates if we have reached our max attempts
   104  func (b *ExponentialBackOff) NextIteration() time.Duration {
   105  	d, _ := b.Next()
   106  	return d
   107  }
   108  
   109  // New returns a copy of the current backoff
   110  func (b *ExponentialBackOff) New() BackOff {
   111  	return &ExponentialBackOff{
   112  		retries:  atomic.LoadInt64(&b.retries),
   113  		Attempts: b.Attempts,
   114  		Factor:   b.Factor,
   115  		Min:      b.Min,
   116  		Max:      b.Max,
   117  	}
   118  }
   119  
   120  // CalcDuration returns the next duration to sleep given the number of retries.
   121  //
   122  // This function is useful when keeping track of the attempts and attempt
   123  // resets is external to our code.
   124  func (b *ExponentialBackOff) CalcDuration(retries int64) time.Duration {
   125  	// TODO(thrawn01): Implement jitter?
   126  
   127  	d := time.Duration(float64(b.Min) * math.Pow(b.Factor, float64(retries)))
   128  	if d > b.Max {
   129  		return b.Max
   130  	}
   131  	if d < b.Min {
   132  		return b.Min
   133  	}
   134  	return d
   135  }