in exponential/exponential.go [193:264]
func (b *Backoff) Retry(ctx context.Context, op Op, options ...RetryOption) error {
r := Record{Attempt: 1}
// Make our first attempt.
err := op(ctx, r)
if err == nil {
return nil
}
// Well, that didn't work, so let's start our retry work.
r.Err = err
baseInterval := b.policy.InitialInterval
realInterval := b.randomize(baseInterval)
for {
err = b.applyTransformers(err)
if errors.Is(err, ErrPermanent) {
return err
}
if b.policy.MaxAttempts > 0 && r.Attempt >= b.policy.MaxAttempts {
return fmt.Errorf("exceeded max attempts: %w: %w", r.Err, ErrPermanent)
}
// Check to see if the error contained an interval that is longer
// than the exponential retry timer. If it is, we will use the error
// retry timer.
realInterval = b.intervalSpecified(err, realInterval)
// If our context is done or our interval goes over the context deadline,
// then we are done.
if !b.ctxOK(ctx, realInterval) {
return fmt.Errorf("r.Err: %w", ErrRetryCanceled)
}
// Do this if they did not pass the WithTesting() option.
if !b.useTest {
timer := b.newTimer(realInterval)
select {
case <-ctx.Done():
timer.Stop() // Prevent goroutine leak
return fmt.Errorf("%w: %w ", r.Err, ErrRetryCanceled)
case <-timer.C:
}
}
// Record attempt last attempt number, our last interval and total interval.
r.LastInterval = realInterval
r.TotalInterval += realInterval
r.Attempt++
// NO WHAMMIES, NO WHAMMIES, STOP!
// https://www.youtube.com/watch?v=1mGrM72Z4-Y
err = op(ctx, r)
if err == nil {
return nil
}
// Captures our last error in the record.
r.Err = err
// Create our new base interval for the next attempt.
baseInterval = time.Duration(float64(baseInterval) * b.policy.Multiplier)
// Our base interval cannot exceed the maximum interval.
if baseInterval > b.policy.MaxInterval {
baseInterval = b.policy.MaxInterval
}
// Randomize the interval based on our randomization factor.
realInterval = b.randomize(baseInterval)
}
}