github.com/kelleygo/clashcore@v1.0.2/component/slowdown/backoff.go (about) 1 // modify from https://github.com/jpillora/backoff/blob/v1.0.0/backoff.go 2 3 package slowdown 4 5 import ( 6 "math" 7 "math/rand" 8 "sync/atomic" 9 "time" 10 ) 11 12 // Backoff is a time.Duration counter, starting at Min. After every call to 13 // the Duration method the current timing is multiplied by Factor, but it 14 // never exceeds Max. 15 // 16 // Backoff is not generally concurrent-safe, but the ForAttempt method can 17 // be used concurrently. 18 type Backoff struct { 19 attempt atomic.Uint64 20 // Factor is the multiplying factor for each increment step 21 Factor float64 22 // Jitter eases contention by randomizing backoff steps 23 Jitter bool 24 // Min and Max are the minimum and maximum values of the counter 25 Min, Max time.Duration 26 } 27 28 // Duration returns the duration for the current attempt before incrementing 29 // the attempt counter. See ForAttempt. 30 func (b *Backoff) Duration() time.Duration { 31 d := b.ForAttempt(float64(b.attempt.Add(1) - 1)) 32 return d 33 } 34 35 const maxInt64 = float64(math.MaxInt64 - 512) 36 37 // ForAttempt returns the duration for a specific attempt. This is useful if 38 // you have a large number of independent Backoffs, but don't want use 39 // unnecessary memory storing the Backoff parameters per Backoff. The first 40 // attempt should be 0. 41 // 42 // ForAttempt is concurrent-safe. 43 func (b *Backoff) ForAttempt(attempt float64) time.Duration { 44 // Zero-values are nonsensical, so we use 45 // them to apply defaults 46 min := b.Min 47 if min <= 0 { 48 min = 100 * time.Millisecond 49 } 50 max := b.Max 51 if max <= 0 { 52 max = 10 * time.Second 53 } 54 if min >= max { 55 // short-circuit 56 return max 57 } 58 factor := b.Factor 59 if factor <= 0 { 60 factor = 2 61 } 62 //calculate this duration 63 minf := float64(min) 64 durf := minf * math.Pow(factor, attempt) 65 if b.Jitter { 66 durf = rand.Float64()*(durf-minf) + minf 67 } 68 //ensure float64 wont overflow int64 69 if durf > maxInt64 { 70 return max 71 } 72 dur := time.Duration(durf) 73 //keep within bounds 74 if dur < min { 75 return min 76 } 77 if dur > max { 78 return max 79 } 80 return dur 81 } 82 83 // Reset restarts the current attempt counter at zero. 84 func (b *Backoff) Reset() { 85 b.attempt.Store(0) 86 } 87 88 // Attempt returns the current attempt counter value. 89 func (b *Backoff) Attempt() float64 { 90 return float64(b.attempt.Load()) 91 } 92 93 // Copy returns a backoff with equals constraints as the original 94 func (b *Backoff) Copy() *Backoff { 95 return &Backoff{ 96 Factor: b.Factor, 97 Jitter: b.Jitter, 98 Min: b.Min, 99 Max: b.Max, 100 } 101 }