backoff.go 1.4 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758
  1. package backoff
  2. import (
  3. "context"
  4. "errors"
  5. "iter"
  6. "math/rand"
  7. "time"
  8. )
  9. // Errors
  10. var (
  11. // ErrMaxAttempts is not used by backoff but is available for use by
  12. // callers that want to signal that a maximum number of retries has
  13. // been exceeded. This should eliminate the need for callers to invent
  14. // their own error.
  15. ErrMaxAttempts = errors.New("max retries exceeded")
  16. )
  17. // Upto implements a backoff strategy that yields nil errors until the
  18. // context is canceled, the maxRetries is exceeded, or yield returns false.
  19. //
  20. // The backoff strategy is a simple exponential backoff with a maximum
  21. // backoff of maxBackoff. The backoff is randomized between 0.5-1.5 times
  22. // the current backoff, in order to prevent accidental "thundering herd"
  23. // problems.
  24. func Upto(ctx context.Context, maxBackoff time.Duration) iter.Seq2[int, error] {
  25. var n int
  26. return func(yield func(int, error) bool) {
  27. for {
  28. if ctx.Err() != nil {
  29. yield(n, ctx.Err())
  30. return
  31. }
  32. n++
  33. // n^2 backoff timer is a little smoother than the
  34. // common choice of 2^n.
  35. d := time.Duration(n*n) * 10 * time.Millisecond
  36. if d > maxBackoff {
  37. d = maxBackoff
  38. }
  39. // Randomize the delay between 0.5-1.5 x msec, in order
  40. // to prevent accidental "thundering herd" problems.
  41. d = time.Duration(float64(d) * (rand.Float64() + 0.5))
  42. t := time.NewTimer(d)
  43. select {
  44. case <-ctx.Done():
  45. t.Stop()
  46. case <-t.C:
  47. if !yield(n, nil) {
  48. return
  49. }
  50. }
  51. }
  52. }
  53. }