2023-11-27 22:11:07 +00:00
|
|
|
// Package backoff contains a configurable retry-functionality using
|
|
|
|
// either exponential or constant backoff
|
2019-11-15 15:35:59 +00:00
|
|
|
package backoff
|
|
|
|
|
|
|
|
import (
|
2023-11-27 22:11:07 +00:00
|
|
|
"errors"
|
2019-11-15 15:35:59 +00:00
|
|
|
"fmt"
|
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
2023-11-27 22:11:07 +00:00
|
|
|
// DefaultMaxIterations contains the default value to use for number of iterations: infinite
|
2019-11-15 15:35:59 +00:00
|
|
|
DefaultMaxIterations uint64 = 0
|
2023-11-27 22:11:07 +00:00
|
|
|
// DefaultMaxIterationTime contains the default value to use for maximum iteration time
|
2019-11-15 15:35:59 +00:00
|
|
|
DefaultMaxIterationTime = 60 * time.Second
|
2023-11-27 22:11:07 +00:00
|
|
|
// DefaultMaxTotalTime contains the default value to use for maximum execution time: infinite
|
2019-11-15 15:35:59 +00:00
|
|
|
DefaultMaxTotalTime time.Duration = 0
|
2023-11-27 22:11:07 +00:00
|
|
|
// DefaultMinIterationTime contains the default value to use for initial iteration time
|
2019-11-15 15:35:59 +00:00
|
|
|
DefaultMinIterationTime = 100 * time.Millisecond
|
2023-11-27 22:11:07 +00:00
|
|
|
// DefaultMultipler contains the default multiplier to apply to iteration time after each iteration
|
2019-11-15 15:35:59 +00:00
|
|
|
DefaultMultipler float64 = 1.5
|
|
|
|
)
|
|
|
|
|
|
|
|
// Backoff holds the configuration for backoff function retries
|
|
|
|
type Backoff struct {
|
|
|
|
MaxIterations uint64
|
|
|
|
MaxIterationTime time.Duration
|
|
|
|
MaxTotalTime time.Duration
|
|
|
|
MinIterationTime time.Duration
|
|
|
|
Multiplier float64
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewBackoff creates a new Backoff configuration with default values (see constants)
|
|
|
|
func NewBackoff() *Backoff {
|
|
|
|
return &Backoff{
|
|
|
|
MaxIterations: DefaultMaxIterations,
|
|
|
|
MaxIterationTime: DefaultMaxIterationTime,
|
|
|
|
MaxTotalTime: DefaultMaxTotalTime,
|
|
|
|
MinIterationTime: DefaultMinIterationTime,
|
|
|
|
Multiplier: DefaultMultipler,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Retry executes the function and waits for it to end successul or for the
|
|
|
|
// given limites to be reached. The returned error uses Go1.13 wrapping of
|
|
|
|
// errors and can be unwrapped into the error of the function itself.
|
2023-11-27 22:11:07 +00:00
|
|
|
//
|
|
|
|
// To break free from the Retry function ignoring the remaining retries
|
|
|
|
// return an ErrCannotRetry containing the original error. At this
|
|
|
|
// point the ErrCannotRetry will NOT be returned but unwrapped. So
|
|
|
|
// returning NewErrCannotRetry(errors.New("foo")) will give you the
|
|
|
|
// errors.New("foo") as a return value from Retry.
|
2019-11-15 15:35:59 +00:00
|
|
|
func (b Backoff) Retry(f Retryable) error {
|
|
|
|
var (
|
|
|
|
iterations uint64
|
|
|
|
sleepTime = b.MinIterationTime
|
|
|
|
start = time.Now()
|
|
|
|
)
|
|
|
|
|
|
|
|
for {
|
|
|
|
err := f()
|
|
|
|
|
|
|
|
if err == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-11-27 22:11:07 +00:00
|
|
|
var ecr ErrCannotRetry
|
|
|
|
if errors.As(err, &ecr) {
|
|
|
|
return ecr.Unwrap()
|
|
|
|
}
|
|
|
|
|
2019-11-15 15:35:59 +00:00
|
|
|
iterations++
|
|
|
|
if b.MaxIterations > 0 && iterations == b.MaxIterations {
|
2023-11-27 22:11:07 +00:00
|
|
|
return fmt.Errorf("maximum iterations reached: %w", err)
|
2019-11-15 15:35:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if b.MaxTotalTime > 0 && time.Since(start) >= b.MaxTotalTime {
|
2023-11-27 22:11:07 +00:00
|
|
|
return fmt.Errorf("maximum execution time reached: %w", err)
|
2019-11-15 15:35:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
time.Sleep(sleepTime)
|
|
|
|
sleepTime = b.nextIterationSleep(sleepTime)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-07 12:30:56 +00:00
|
|
|
// WithMaxIterations is a wrapper around setting the MaxIterations
|
|
|
|
// and then returning the Backoff object to use in chained creation
|
|
|
|
func (b *Backoff) WithMaxIterations(v uint64) *Backoff {
|
|
|
|
b.MaxIterations = v
|
|
|
|
return b
|
|
|
|
}
|
|
|
|
|
|
|
|
// WithMaxIterationTime is a wrapper around setting the MaxIterationTime
|
|
|
|
// and then returning the Backoff object to use in chained creation
|
|
|
|
func (b *Backoff) WithMaxIterationTime(v time.Duration) *Backoff {
|
|
|
|
b.MaxIterationTime = v
|
|
|
|
return b
|
|
|
|
}
|
|
|
|
|
|
|
|
// WithMaxTotalTime is a wrapper around setting the MaxTotalTime
|
|
|
|
// and then returning the Backoff object to use in chained creation
|
|
|
|
func (b *Backoff) WithMaxTotalTime(v time.Duration) *Backoff {
|
|
|
|
b.MaxTotalTime = v
|
|
|
|
return b
|
|
|
|
}
|
|
|
|
|
|
|
|
// WithMinIterationTime is a wrapper around setting the MinIterationTime
|
|
|
|
// and then returning the Backoff object to use in chained creation
|
|
|
|
func (b *Backoff) WithMinIterationTime(v time.Duration) *Backoff {
|
|
|
|
b.MinIterationTime = v
|
|
|
|
return b
|
|
|
|
}
|
|
|
|
|
|
|
|
// WithMultiplier is a wrapper around setting the Multiplier
|
|
|
|
// and then returning the Backoff object to use in chained creation
|
|
|
|
func (b *Backoff) WithMultiplier(v float64) *Backoff {
|
|
|
|
b.Multiplier = v
|
|
|
|
return b
|
|
|
|
}
|
|
|
|
|
2019-11-15 15:35:59 +00:00
|
|
|
func (b Backoff) nextIterationSleep(currentSleep time.Duration) time.Duration {
|
|
|
|
next := time.Duration(float64(currentSleep) * b.Multiplier)
|
|
|
|
if next > b.MaxIterationTime {
|
|
|
|
next = b.MaxIterationTime
|
|
|
|
}
|
|
|
|
return next
|
|
|
|
}
|
|
|
|
|
|
|
|
// Retryable is a function which takes no parameters and yields an error
|
|
|
|
// when it should be retried and nil when it was successful
|
|
|
|
type Retryable func() error
|
|
|
|
|
|
|
|
// Retry is a convenience wrapper to execute the retry with default values
|
|
|
|
// (see exported constants)
|
|
|
|
func Retry(f Retryable) error { return NewBackoff().Retry(f) }
|