in gollm/factory.go [165:236]
func Retry[T any](
ctx context.Context,
config RetryConfig,
isRetryable IsRetryableFunc,
operation func(ctx context.Context) (T, error),
) (T, error) {
var lastErr error
var zero T // Zero value of the return type T
log := klog.FromContext(ctx)
backoff := config.InitialBackoff
for attempt := 1; attempt <= config.MaxAttempts; attempt++ {
// log.Printf("Executing operation, attempt %d of %d", attempt, config.MaxAttempts) // Optional verbose log
result, err := operation(ctx)
if err == nil {
// Success
return result, nil
}
lastErr = err // Store the last error encountered
// Check if context was cancelled *after* the operation
select {
case <-ctx.Done():
log.Info("Context cancelled after attempt %d failed.", "attempt", attempt)
return zero, ctx.Err() // Return context error preferentially
default:
// Context not cancelled, proceed with error checking
}
if !isRetryable(lastErr) {
log.Info("Attempt failed with non-retryable error", "attempt", attempt, "error", lastErr)
return zero, lastErr // Return the non-retryable error immediately
}
log.Info("Attempt failed with retryable error", "attempt", attempt, "error", lastErr)
if attempt == config.MaxAttempts {
// Max attempts reached
break
}
// Calculate wait time
waitTime := backoff
if config.Jitter {
waitTime += time.Duration(rand.Float64() * float64(backoff) / 2)
}
log.Info("Waiting before next attempt", "waitTime", waitTime, "attempt", attempt+1, "maxAttempts", config.MaxAttempts)
// Wait or react to context cancellation
select {
case <-time.After(waitTime):
// Wait finished
case <-ctx.Done():
log.Info("Context cancelled while waiting for retry after attempt %d.", "attempt", attempt)
return zero, ctx.Err()
}
// Increase backoff
backoff = time.Duration(float64(backoff) * config.BackoffFactor)
if backoff > config.MaxBackoff {
backoff = config.MaxBackoff
}
}
// If the loop finished, it means all attempts failed
errFinal := fmt.Errorf("operation failed after %d attempts: %w", config.MaxAttempts, lastErr)
return zero, errFinal
}