Retry logic with exponential backoff for database operations and external services.
Provides automatic retry with configurable backoff:
- Configurable max attempts
- Multiple backoff strategies (constant, linear, exponential)
- Jitter support for distributed systems
- Context cancellation support
- Error filtering
err := retry.Do(ctx, func() error {
return db.Tx(ctx, func(tx *sql.Tx) error {
// operation
})
}, retry.WithMaxAttempts(3))err := retry.Do(ctx, fn, retry.WithMaxAttempts(5),
retry.WithBaseDelay(100*time.Millisecond))err := retry.Do(ctx, fn,
retry.WithBackoff(retry.Linear),
retry.WithBaseDelay(50*time.Millisecond))Prevents "thundering herd" when multiple clients retry:
err := retry.Do(ctx, fn, retry.WithJitter())err := retry.Do(ctx, fn, retry.WithRetryIf(func(e error) bool {
// Only retry on network errors
return strings.Contains(e.Error(), "connection")
}))| Option | Default | Description |
|---|---|---|
WithMaxAttempts |
3 | Max retry attempts |
WithBaseDelay |
100ms | Base delay |
WithMaxDelay |
30s | Cap delay |
WithBackoff |
Exponential | Strategy |
WithJitter |
true | Enable jitter |
WithRetryIf |
DefaultRetryIf | Retry filter |
// Always delays by base amount
retry.WithBackoff(retry.Constant)
// Delays: 100ms, 100ms, 100ms// Increases linearly
retry.WithBackoff(retry.Linear)
// Delays: 100ms, 200ms, 300ms// Increases exponentially
retry.WithBackoff(retry.Exponential)
// Delays: 100ms, 200ms, 400msFor functions that return a value:
user, err := retry.DoWithResult(ctx, func() (User, error) {
return db.GetUser(ctx, id)
}, retry.WithMaxAttempts(3))// Retry up to N times
err := retry.Retry(ctx, 3, fn)// Retry until success (until context cancels)
err := retry.Forever(ctx, fn)The retry respects context cancellation:
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
err := retry.Do(ctx, fn, retry.WithMaxAttempts(10))
// Returns context.DeadlineExceeded if timeout- Retries on any error except:
context.Canceledcontext.DeadlineExceeded
err := retry.Do(ctx, fn, retry.WithRetryIf(func(e error) bool {
var perr *mysql.MySQLError
if errors.As(e, &perr) {
// Retry on deadlock (1213) or lost connection (2006)
return perr.Number == 1213 || perr.Number == 2006
}
return true
}))func withRetry(ctx context.Context, fn func(*sql.Tx) error) error {
return retry.Do(ctx, func() error {
return db.WithTx(ctx, fn)
}, retry.WithMaxAttempts(3),
retry.WithBaseDelay(50*time.Millisecond),
retry.WithMaxAttempts(3))
}err := retry.Do(ctx, func() error {
resp, err := httpclient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode >= 500 {
return errors.New("server error")
}
return nil
}, retry.WithMaxAttempts(3), retry.WithExponential())err := retry.Do(ctx, func() error {
return callAPI(ctx, endpoint, payload)
}, retry.WithMaxAttempts(5),
retry.WithBaseDelay(200*time.Millisecond),
retry.WithMaxDelay(10*time.Second),
retry.WithJitter())