github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/dm/pkg/backoff/backoff.go (about) 1 // Copyright 2019 PingCAP, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 package backoff 15 16 import ( 17 "math" 18 "math/rand" 19 "time" 20 21 "github.com/pingcap/tiflow/dm/pkg/terror" 22 ) 23 24 // Backoff is an exponential counter, it starts from `Min` duration, and after 25 // every call to `Duration` method the duration will be multiplied by `Factor`, 26 // but it never exceeds `Max`. Backoff is not thread-safe. 27 type Backoff struct { 28 // cwnd is the congestion window 29 cwnd int 30 31 // Factor is the multiplying factor for each increment step 32 Factor float64 33 34 // Jitter eases contention by randomizing backoff steps 35 Jitter bool 36 37 // Min and Max are the minimum and maximum values of the counter 38 Min, Max time.Duration 39 } 40 41 // NewBackoff creates a new backoff instance. 42 func NewBackoff(factor float64, jitter bool, min, max time.Duration) (*Backoff, error) { 43 if factor <= 0 { 44 return nil, terror.ErrBackoffArgsNotValid.Generate("factor", factor) 45 } 46 if min < 0 { 47 return nil, terror.ErrBackoffArgsNotValid.Generate("min", min) 48 } 49 if max < 0 || max < min { 50 return nil, terror.ErrBackoffArgsNotValid.Generate("max", max) 51 } 52 return &Backoff{ 53 Factor: factor, 54 Jitter: jitter, 55 Min: min, 56 Max: max, 57 }, nil 58 } 59 60 // Duration returns the duration for the current cwnd and increases the cwnd counter. 61 func (b *Backoff) Duration() time.Duration { 62 d := b.Current() 63 b.Forward() 64 return d 65 } 66 67 // Current returns the duration for the current cwnd, but doesn't increase the 68 // cwnd counter. 69 func (b *Backoff) Current() time.Duration { 70 return b.durationcwnd(b.cwnd) 71 } 72 73 // BoundaryForward checks whether `Current` reaches `Max` duration, if not then 74 // increases the cwnd counter, else does nothing. 75 func (b *Backoff) BoundaryForward() { 76 if b.Current() < b.Max { 77 b.cwnd++ 78 } 79 } 80 81 // Forward increases the cwnd counter. 82 func (b *Backoff) Forward() { 83 b.cwnd++ 84 } 85 86 // Rollback try to decrease cwnd by one if it is greater or equal than one. 87 // This is used for we have a long enough duration without backoff try. 88 func (b *Backoff) Rollback() { 89 if b.cwnd > 0 { 90 b.cwnd-- 91 } 92 } 93 94 // durationcwnd returns the duration for a specific cwnd. The first 95 // cwnd should be 0. 96 func (b *Backoff) durationcwnd(cwnd int) time.Duration { 97 minf := float64(b.Min) 98 durf := minf * math.Pow(b.Factor, float64(cwnd)) 99 // Full jitter and Decorr jitter is more aggressive and more suitable for fast recovery 100 // We use Equal jitter here to achieve more stability 101 // refer to: https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/ 102 if b.Jitter { 103 if durf <= minf { 104 return b.Min 105 } 106 durf = durf/2 + rand.Float64()*(durf/2) 107 } 108 // why minus 512 here, because i = 512 is the minimal value that ensures 109 // float64(math.MaxInt64) > float64(math.MaxInt64-i)) is true 110 if durf > float64(math.MaxInt64-512) { 111 return b.Max 112 } 113 dur := time.Duration(durf) 114 if dur > b.Max { 115 return b.Max 116 } 117 return dur 118 } 119 120 // Reset restarts the current cwnd counter to zero. 121 func (b *Backoff) Reset() { 122 b.cwnd = 0 123 }