Retry attempts the operation until it succeeds, returns a Permanent error, or backoff completes. It ensures the operation is executed at least once. On success it returns the operation result and a nil error. On any failure it returns the last result and a *RetryError whose Cause reports why it sto
(ctx context.Context, operation Operation[T], opts ...RetryOption)
| 106 | // abort an in-flight attempt. To bound only how long backoff keeps retrying, |
| 107 | // without affecting in-flight attempts, use WithMaxElapsedTime instead. |
| 108 | func Retry[T any](ctx context.Context, operation Operation[T], opts ...RetryOption) (T, error) { |
| 109 | // Initialize default retry options. |
| 110 | args := &retryOptions{ |
| 111 | BackOff: NewExponentialBackOff(), |
| 112 | Timer: &defaultTimer{}, |
| 113 | MaxElapsedTime: DefaultMaxElapsedTime, |
| 114 | } |
| 115 | |
| 116 | // Apply user-provided options to the default settings. |
| 117 | for _, opt := range opts { |
| 118 | opt(args) |
| 119 | } |
| 120 | |
| 121 | defer args.Timer.Stop() |
| 122 | |
| 123 | startedAt := time.Now() |
| 124 | args.BackOff.Reset() |
| 125 | for numTries := uint(1); ; numTries++ { |
| 126 | // Execute the operation. |
| 127 | res, err := operation() |
| 128 | if err == nil { |
| 129 | return res, nil |
| 130 | } |
| 131 | |
| 132 | // Stop immediately on a permanent error; surface it as a RetryError. |
| 133 | var perm *permanent |
| 134 | if errors.As(err, &perm) { |
| 135 | return res, &RetryError{LastErr: perm.err, Cause: ErrPermanent} |
| 136 | } |
| 137 | |
| 138 | // A RetryAfterError carries the delay before the next attempt; if it also |
| 139 | // carries an underlying error, that is the meaningful error to report as |
| 140 | // LastErr should retrying stop (mirrors how a permanent error surfaces its |
| 141 | // inner error). errors.As matches it whether returned directly or wrapped. |
| 142 | lastErr := err |
| 143 | var retryAfter *RetryAfterError |
| 144 | if errors.As(err, &retryAfter) && retryAfter.err != nil { |
| 145 | lastErr = retryAfter.err |
| 146 | } |
| 147 | |
| 148 | // Stop retrying if maximum tries exceeded. |
| 149 | if args.MaxTries > 0 && numTries >= args.MaxTries { |
| 150 | return res, &RetryError{LastErr: lastErr, Cause: ErrExhausted} |
| 151 | } |
| 152 | |
| 153 | // Stop retrying if context is cancelled. |
| 154 | if cerr := context.Cause(ctx); cerr != nil { |
| 155 | return res, &RetryError{LastErr: lastErr, Cause: cerr} |
| 156 | } |
| 157 | |
| 158 | // Calculate next backoff duration. |
| 159 | next := args.BackOff.NextBackOff() |
| 160 | if next == Stop { |
| 161 | return res, &RetryError{LastErr: lastErr, Cause: ErrExhausted} |
| 162 | } |
| 163 | |
| 164 | // Reset backoff if a RetryAfterError requested a specific delay. |
| 165 | if retryAfter != nil { |
searching dependent graphs…