| package utils |
| |
| import ( |
| "context" |
| "fmt" |
| "time" |
| |
| "github.com/golang/glog" |
| ) |
| |
| type RetryOptions struct { |
| // Number of times to try a function before failing. |
| // If unset, the function will be attempted once. |
| Attempts int |
| // The time to wait between function attempts. |
| Delay time.Duration |
| // Function which determines whether or not the function should be retried after a given error. |
| // If this is unset, every error is considered retryable. |
| ShouldRetry func(error) bool |
| } |
| |
| // Retry calls a function repeatedly until it succeeds or another stopping condition is met. |
| // |
| // The stopping conditions are determined by the provided RetryOptions. |
| func Retry(ctx context.Context, fn func(ctx context.Context) error, opts RetryOptions) error { |
| attempts := opts.Attempts |
| if attempts <= 0 { |
| attempts = 1 |
| } |
| |
| for i := range attempts { |
| err := fn(ctx) |
| if err == nil { |
| return nil |
| } |
| |
| if i + 1 == attempts { |
| break |
| } |
| |
| if opts.ShouldRetry == nil || opts.ShouldRetry(err) { |
| glog.Infof("error %w; retrying", err) |
| } else { |
| return fmt.Errorf("fatal error, not retrying: %w", err) |
| } |
| |
| if opts.Delay > 0 { |
| // Block until the delay interval or the context deadline, whichever happens first. |
| select { |
| case <-time.After(opts.Delay): {} |
| case <-ctx.Done(): |
| return fmt.Errorf("context deadline exceeded") |
| } |
| } |
| } |
| |
| return fmt.Errorf("no successes after %d attempts", opts.Attempts) |
| } |