Package backoff implements backoff logic for retry loops using Go 1.23+ iterators.
Use Attempts() for bounded retries. The iterator sleeps the backoff duration after each iteration before yielding the next attempt:
for attempt := range backoff.Attempts(ctx, time.Second, 30*time.Second, backoff.Exponential(2), 5) {
if err := doSomething(); err == nil {
return nil
}
log.Printf("attempt %d failed", attempt)
// Sleep happens automatically before next iteration
}
return errors.New("max retries exceeded")Use context.WithTimeout to limit total elapsed time:
ctx, cancel := context.WithTimeout(ctx, 2*time.Minute)
defer cancel()
for attempt := range backoff.Attempts(ctx, time.Second, 30*time.Second, backoff.Exponential(2), 10) {
if err := doSomething(); err == nil {
return nil
}
}
return ctx.Err() // context.DeadlineExceeded after 2 minutesJitter is useful in distributed systems to avoid thundering herd problems:
for attempt := range backoff.Attempts(ctx, time.Second, 30*time.Second, backoff.Jitter(backoff.Exponential(2), 0.5), 5) {
if err := doSomething(); err == nil {
return nil
}
}Use Delays() when you need control over the sleep. Unlike Attempts(), this iterator does not sleep—it yields the delay value for you to handle:
for attempt, delay := range backoff.Delays(ctx, time.Second, 30*time.Second, backoff.Exponential(2)) {
if err := doSomething(); err == nil {
return nil
}
log.Printf("attempt %d failed, retrying in %v", attempt, delay)
time.Sleep(delay)
}
// Yields: (1, 1s), (2, 2s), (3, 4s), (4, 8s), (5, 16s), (6, 30s), ...| Function | Description |
|---|---|
Fixed() |
Always returns base (equivalent to Linear(0)) |
Linear(d) |
Adds d each iteration: base, base+d, base+2d, ... |
Incremental(m) |
Multiplies base by count: base, base*m, base*2m, ... |
Exponential(f) |
Exponential growth: base, base*f, base*f^2, ... |
Jitter(fn, factor) |
Wraps any step function with randomized jitter in [d-factor*d, d] |
Contributions are welcome via Pull Requests.