backoff.go 2.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. // Package backoff provides a back-off timer type.
  4. package backoff
  5. import (
  6. "context"
  7. "math/rand"
  8. "time"
  9. "tailscale.com/tstime"
  10. "tailscale.com/types/logger"
  11. )
  12. // Backoff tracks state the history of consecutive failures and sleeps
  13. // an increasing amount of time, up to a provided limit.
  14. type Backoff struct {
  15. n int // number of consecutive failures
  16. maxBackoff time.Duration
  17. // Name is the name of this backoff timer, for logging purposes.
  18. name string
  19. // logf is the function used for log messages when backing off.
  20. logf logger.Logf
  21. // tstime.Clock.NewTimer is used instead time.NewTimer.
  22. Clock tstime.Clock
  23. // LogLongerThan sets the minimum time of a single backoff interval
  24. // before we mention it in the log.
  25. LogLongerThan time.Duration
  26. }
  27. // NewBackoff returns a new Backoff timer with the provided name (for logging), logger,
  28. // and max backoff time. By default, all failures (calls to BackOff with a non-nil err)
  29. // are logged unless the returned Backoff.LogLongerThan is adjusted.
  30. func NewBackoff(name string, logf logger.Logf, maxBackoff time.Duration) *Backoff {
  31. return &Backoff{
  32. name: name,
  33. logf: logf,
  34. maxBackoff: maxBackoff,
  35. Clock: tstime.StdClock{},
  36. }
  37. }
  38. // Backoff sleeps an increasing amount of time if err is non-nil.
  39. // and the context is not a
  40. // It resets the backoff schedule once err is nil.
  41. func (b *Backoff) BackOff(ctx context.Context, err error) {
  42. if err == nil {
  43. // No error. Reset number of consecutive failures.
  44. b.n = 0
  45. return
  46. }
  47. if ctx.Err() != nil {
  48. // Fast path.
  49. return
  50. }
  51. b.n++
  52. // n^2 backoff timer is a little smoother than the
  53. // common choice of 2^n.
  54. d := time.Duration(b.n*b.n) * 10 * time.Millisecond
  55. if d > b.maxBackoff {
  56. d = b.maxBackoff
  57. }
  58. // Randomize the delay between 0.5-1.5 x msec, in order
  59. // to prevent accidental "thundering herd" problems.
  60. d = time.Duration(float64(d) * (rand.Float64() + 0.5))
  61. if d >= b.LogLongerThan {
  62. b.logf("%s: [v1] backoff: %d msec", b.name, d.Milliseconds())
  63. }
  64. t, tChannel := b.Clock.NewTimer(d)
  65. select {
  66. case <-ctx.Done():
  67. t.Stop()
  68. case <-tChannel:
  69. }
  70. }