feat: retry mechanism with exponential backoff
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
package retry
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Retrier struct {
|
||||
maxRetries int
|
||||
backoff []time.Duration
|
||||
}
|
||||
|
||||
func NewRetrier(maxRetries int, backoff []time.Duration) *Retrier {
|
||||
return &Retrier{maxRetries: maxRetries, backoff: backoff}
|
||||
}
|
||||
|
||||
// DefaultRetrier returns a retrier with 3 attempts, exponential backoff: 1s, 5s, 30s.
|
||||
func DefaultRetrier() *Retrier {
|
||||
return NewRetrier(3, []time.Duration{1 * time.Second, 5 * time.Second, 30 * time.Second})
|
||||
}
|
||||
|
||||
func (r *Retrier) Do(ctx context.Context, fn func() error) error {
|
||||
var lastErr error
|
||||
for attempt := 0; attempt <= r.maxRetries; attempt++ {
|
||||
if attempt > 0 {
|
||||
delay := r.backoff[attempt-1]
|
||||
slog.Info("retrying", "attempt", attempt, "delay", delay)
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-time.After(delay):
|
||||
}
|
||||
}
|
||||
|
||||
err := fn()
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
lastErr = err
|
||||
slog.Warn("attempt failed", "attempt", attempt, "error", err)
|
||||
}
|
||||
return fmt.Errorf("all %d attempts failed, last error: %w", r.maxRetries+1, lastErr)
|
||||
}
|
||||
Reference in New Issue
Block a user