blake.io/pqx@v0.2.2-0.20231231055241-83f2254c0a07/internal/backoff/backoff.go (about) 1 // This was copied from tailscale.com/logtail/backoff and modified only enough 2 // to break the dependencies on tailscale. 3 4 // Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved. 5 // Use of this source code is governed by a BSD-style 6 // license that can be found in the LICENSE file. 7 8 // Package backoff provides a back-off timer type. 9 package backoff 10 11 import ( 12 "context" 13 "math/rand" 14 "time" 15 ) 16 17 // Backoff tracks state the history of consecutive failures and sleeps 18 // an increasing amount of time, up to a provided limit. 19 type Backoff struct { 20 n int // number of consecutive failures 21 maxBackoff time.Duration 22 23 // Name is the name of this backoff timer, for logging purposes. 24 name string 25 // logf is the function used for log messages when backing off. 26 logf func(format string, args ...any) 27 28 // NewTimer is the function that acts like time.NewTimer. 29 // It's for use in unit tests. 30 NewTimer func(time.Duration) *time.Timer 31 32 // LogLongerThan sets the minimum time of a single backoff interval 33 // before we mention it in the log. 34 LogLongerThan time.Duration 35 } 36 37 // NewBackoff returns a new Backoff timer with the provided name (for logging), logger, 38 // and max backoff time. By default, all failures (calls to BackOff with a non-nil err) 39 // are logged unless the returned Backoff.LogLongerThan is adjusted. 40 func NewBackoff(name string, logf func(string, ...any), maxBackoff time.Duration) *Backoff { 41 return &Backoff{ 42 name: name, 43 logf: logf, 44 maxBackoff: maxBackoff, 45 NewTimer: time.NewTimer, 46 } 47 } 48 49 // Backoff sleeps an increasing amount of time if err is non-nil. 50 // and the context is not a 51 // It resets the backoff schedule once err is nil. 52 func (b *Backoff) BackOff(ctx context.Context, err error) { 53 if err == nil { 54 // No error. Reset number of consecutive failures. 55 b.n = 0 56 return 57 } 58 if ctx.Err() != nil { 59 // Fast path. 60 return 61 } 62 63 b.n++ 64 // n^2 backoff timer is a little smoother than the 65 // common choice of 2^n. 66 d := time.Duration(b.n*b.n) * 10 * time.Millisecond 67 if d > b.maxBackoff { 68 d = b.maxBackoff 69 } 70 // Randomize the delay between 0.5-1.5 x msec, in order 71 // to prevent accidental "thundering herd" problems. 72 d = time.Duration(float64(d) * (rand.Float64() + 0.5)) 73 74 if d >= b.LogLongerThan { 75 b.logf("%s: [v1] backoff: %d msec", b.name, d.Milliseconds()) 76 } 77 t := b.NewTimer(d) 78 select { 79 case <-ctx.Done(): 80 t.Stop() 81 case <-t.C: 82 } 83 }