2023-12-04 15:59:10 +00:00
|
|
|
package retry
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"errors"
|
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Retry executes a function with the given number of attempts and attempt timeouts.
|
|
|
|
// It returns the last error encountered during the attempts.
|
|
|
|
// If the context is canceled, it returns the context error (if there is no previous error),
|
|
|
|
// or the joined error of the last error and the context error (otherwise).
|
2023-12-04 15:59:10 +00:00
|
|
|
func Retry(
|
|
|
|
ctx context.Context,
|
|
|
|
numberOfAttempts uint,
|
|
|
|
attemptTimeout time.Duration,
|
|
|
|
f func(ctx context.Context, attempt int) error,
|
|
|
|
) (lastErr error) {
|
2023-12-04 15:59:10 +00:00
|
|
|
baseCtx, baseCancel := context.WithTimeout(ctx, time.Duration(numberOfAttempts)*attemptTimeout)
|
|
|
|
defer baseCancel()
|
|
|
|
|
|
|
|
for i := uint(0); i < numberOfAttempts; i++ {
|
|
|
|
select {
|
|
|
|
case <-ctx.Done():
|
|
|
|
if lastErr == nil {
|
|
|
|
return ctx.Err()
|
|
|
|
}
|
|
|
|
return errors.Join(lastErr, ctx.Err())
|
|
|
|
default:
|
|
|
|
attemptCtx, attemptCancel := context.WithTimeout(baseCtx, attemptTimeout)
|
|
|
|
|
|
|
|
lastErr = f(attemptCtx, int(i))
|
|
|
|
if lastErr == nil {
|
|
|
|
attemptCancel()
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if attemptCtx.Err() == nil {
|
|
|
|
<-attemptCtx.Done()
|
|
|
|
}
|
|
|
|
|
|
|
|
attemptCancel()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return lastErr
|
|
|
|
}
|