git-subtree-dir: x git-subtree-mainline: 7d05a6ee8f44b314fa697a427439e5fa4d78c3d7 git-subtree-split: a10a11b9d371f36b7c3510da32a1d70b74e27bd1
59 lines
1.4 KiB
Go
59 lines
1.4 KiB
Go
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
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|