76 lines
1.8 KiB
Go
76 lines
1.8 KiB
Go
|
package utils
|
||
|
|
||
|
import (
|
||
|
"context"
|
||
|
"time"
|
||
|
)
|
||
|
|
||
|
// NOTE: copied from github.com/hashicorp/consul/lib/retry to avoid a heavy dependency
|
||
|
// NOTE2: Jitter was removed
|
||
|
|
||
|
// Waiter records the number of failures and performs exponential backoff when
|
||
|
// when there are consecutive failures.
|
||
|
type Waiter struct {
|
||
|
// MinFailures before exponential backoff starts. Any failures before
|
||
|
// MinFailures is reached will wait MinWait time.
|
||
|
MinFailures uint
|
||
|
// MinWait time. Returned after the first failure.
|
||
|
MinWait time.Duration
|
||
|
// MaxWait time.
|
||
|
MaxWait time.Duration
|
||
|
// Factor is the multiplier to use when calculating the delay. Defaults to
|
||
|
// 1 second.
|
||
|
Factor time.Duration
|
||
|
failures uint
|
||
|
}
|
||
|
|
||
|
// delay calculates the time to wait based on the number of failures
|
||
|
func (w *Waiter) delay() time.Duration {
|
||
|
if w.failures <= w.MinFailures {
|
||
|
return w.MinWait
|
||
|
}
|
||
|
factor := w.Factor
|
||
|
if factor == 0 {
|
||
|
factor = time.Second
|
||
|
}
|
||
|
|
||
|
shift := w.failures - w.MinFailures - 1
|
||
|
waitTime := w.MaxWait
|
||
|
if shift < 31 {
|
||
|
waitTime = (1 << shift) * factor
|
||
|
}
|
||
|
if w.MaxWait != 0 && waitTime > w.MaxWait {
|
||
|
waitTime = w.MaxWait
|
||
|
}
|
||
|
if waitTime < w.MinWait {
|
||
|
return w.MinWait
|
||
|
}
|
||
|
return waitTime
|
||
|
}
|
||
|
|
||
|
// Reset the failure count to 0.
|
||
|
func (w *Waiter) Reset() {
|
||
|
w.failures = 0
|
||
|
}
|
||
|
|
||
|
// Failures returns the count of consecutive failures.
|
||
|
func (w *Waiter) Failures() int {
|
||
|
return int(w.failures)
|
||
|
}
|
||
|
|
||
|
// Wait increase the number of failures by one, and then blocks until the context
|
||
|
// is cancelled, or until the wait time is reached.
|
||
|
// The wait time increases exponentially as the number of failures increases.
|
||
|
// Wait will return ctx.Err() if the context is cancelled.
|
||
|
func (w *Waiter) Wait(ctx context.Context) error {
|
||
|
w.failures++
|
||
|
timer := time.NewTimer(w.delay())
|
||
|
select {
|
||
|
case <-ctx.Done():
|
||
|
timer.Stop()
|
||
|
return ctx.Err()
|
||
|
case <-timer.C:
|
||
|
return nil
|
||
|
}
|
||
|
}
|