12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758 |
- package backoff
- import (
- "context"
- "errors"
- "iter"
- "math/rand"
- "time"
- )
- // Errors
- var (
- // ErrMaxAttempts is not used by backoff but is available for use by
- // callers that want to signal that a maximum number of retries has
- // been exceeded. This should eliminate the need for callers to invent
- // their own error.
- ErrMaxAttempts = errors.New("max retries exceeded")
- )
- // Upto implements a backoff strategy that yields nil errors until the
- // context is canceled, the maxRetries is exceeded, or yield returns false.
- //
- // The backoff strategy is a simple exponential backoff with a maximum
- // backoff of maxBackoff. The backoff is randomized between 0.5-1.5 times
- // the current backoff, in order to prevent accidental "thundering herd"
- // problems.
- func Upto(ctx context.Context, maxBackoff time.Duration) iter.Seq2[int, error] {
- var n int
- return func(yield func(int, error) bool) {
- for {
- if ctx.Err() != nil {
- yield(n, ctx.Err())
- return
- }
- n++
- // n^2 backoff timer is a little smoother than the
- // common choice of 2^n.
- d := time.Duration(n*n) * 10 * time.Millisecond
- if d > maxBackoff {
- d = maxBackoff
- }
- // Randomize the delay between 0.5-1.5 x msec, in order
- // to prevent accidental "thundering herd" problems.
- d = time.Duration(float64(d) * (rand.Float64() + 0.5))
- t := time.NewTimer(d)
- select {
- case <-ctx.Done():
- t.Stop()
- case <-t.C:
- if !yield(n, nil) {
- return
- }
- }
- }
- }
- }
|