blob: 63848e869f49ef61e1cad66764dc4ced4648dc54 [file] [log] [blame]
package shared
import (
"context"
"fmt"
"log"
"math"
"time"
)
// Options wraps retry options.
type Options struct {
BaseDelay time.Duration // backoff base delay.
BackoffBase float64 // base for exponential backoff
Retries int // allowed number of retries.
}
// DoFunc is a function type that can be retried by DoWithRetry if the return error is not nil.
type DoFunc func() error
var (
DefaultOpts = Options{BaseDelay: time.Second, BackoffBase: 2.0, Retries: 5}
)
// DoWithRetry executes function doFunc. If there is an error, it will retry with a backoff delay
// until max retry times reached or context done.
// If retryOpts.Retries == 0, it will execute doFunc just once without any retries.
// If retryOpts.Retries < 0, it retries an infinite number of times.
func DoWithRetry(ctx context.Context, retryOpts Options, doFunc DoFunc) error {
for i := 0; retryOpts.Retries < 0 || i <= retryOpts.Retries; i++ {
var d time.Duration
if i > 0 {
d = time.Duration(float64(retryOpts.BaseDelay) * math.Pow(retryOpts.BackoffBase, float64(i-1)))
log.Printf("Sleeping for %s before trying again", d.String())
}
select {
case <-ctx.Done():
return ctx.Err()
case <-time.After(d):
err := doFunc()
if err == nil {
return nil
}
log.Printf("DoWithRetry [%d]: %v", i, err)
}
}
return fmt.Errorf("failed after %d retries", retryOpts.Retries)
}