| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081 |
- // Copyright (c) Tailscale Inc & AUTHORS
- // SPDX-License-Identifier: BSD-3-Clause
- // Package backoff provides a back-off timer type.
- package backoff
- import (
- "context"
- "math/rand"
- "time"
- "tailscale.com/tstime"
- "tailscale.com/types/logger"
- )
- // Backoff tracks state the history of consecutive failures and sleeps
- // an increasing amount of time, up to a provided limit.
- type Backoff struct {
- n int // number of consecutive failures
- maxBackoff time.Duration
- // Name is the name of this backoff timer, for logging purposes.
- name string
- // logf is the function used for log messages when backing off.
- logf logger.Logf
- // tstime.Clock.NewTimer is used instead time.NewTimer.
- Clock tstime.Clock
- // LogLongerThan sets the minimum time of a single backoff interval
- // before we mention it in the log.
- LogLongerThan time.Duration
- }
- // NewBackoff returns a new Backoff timer with the provided name (for logging), logger,
- // and max backoff time. By default, all failures (calls to BackOff with a non-nil err)
- // are logged unless the returned Backoff.LogLongerThan is adjusted.
- func NewBackoff(name string, logf logger.Logf, maxBackoff time.Duration) *Backoff {
- return &Backoff{
- name: name,
- logf: logf,
- maxBackoff: maxBackoff,
- Clock: tstime.StdClock{},
- }
- }
- // Backoff sleeps an increasing amount of time if err is non-nil.
- // and the context is not a
- // It resets the backoff schedule once err is nil.
- func (b *Backoff) BackOff(ctx context.Context, err error) {
- if err == nil {
- // No error. Reset number of consecutive failures.
- b.n = 0
- return
- }
- if ctx.Err() != nil {
- // Fast path.
- return
- }
- b.n++
- // n^2 backoff timer is a little smoother than the
- // common choice of 2^n.
- d := time.Duration(b.n*b.n) * 10 * time.Millisecond
- if d > b.maxBackoff {
- d = b.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))
- if d >= b.LogLongerThan {
- b.logf("%s: [v1] backoff: %d msec", b.name, d.Milliseconds())
- }
- t, tChannel := b.Clock.NewTimer(d)
- select {
- case <-ctx.Done():
- t.Stop()
- case <-tChannel:
- }
- }
|