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