go.uber.org/cadence@v1.2.9/internal/common/backoff/retrypolicy.go (about) 1 // Copyright (c) 2017 Uber Technologies, Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 package backoff 22 23 import ( 24 "math" 25 "math/rand" 26 "time" 27 ) 28 29 const ( 30 // NoInterval represents Maximim interval 31 NoInterval = 0 32 done time.Duration = -1 33 noMaximumAttempts = 0 34 35 // DefaultBackoffCoefficient is default backOffCoefficient for retryPolicy 36 DefaultBackoffCoefficient = 2.0 37 defaultMaximumInterval = 10 * time.Second 38 defaultExpirationInterval = time.Minute 39 defaultMaximumAttempts = noMaximumAttempts 40 ) 41 42 type ( 43 // RetryPolicy is the API which needs to be implemented by various retry policy implementations 44 RetryPolicy interface { 45 ComputeNextDelay(elapsedTime time.Duration, numAttempts int) time.Duration 46 } 47 48 // Retrier manages the state of retry operation 49 Retrier interface { 50 NextBackOff() time.Duration 51 Reset() 52 } 53 54 // Clock used by ExponentialRetryPolicy implementation to get the current time. Mainly used for unit testing 55 Clock interface { 56 Now() time.Time 57 } 58 59 // ExponentialRetryPolicy provides the implementation for retry policy using a coefficient to compute the next delay. 60 // Formula used to compute the next delay is: initialInterval * math.Pow(backoffCoefficient, currentAttempt) 61 ExponentialRetryPolicy struct { 62 initialInterval time.Duration 63 backoffCoefficient float64 64 maximumInterval time.Duration 65 expirationInterval time.Duration 66 maximumAttempts int 67 } 68 69 systemClock struct{} 70 71 retrierImpl struct { 72 policy RetryPolicy 73 clock Clock 74 currentAttempt int 75 startTime time.Time 76 } 77 ) 78 79 // SystemClock implements Clock interface that uses time.Now(). 80 var SystemClock = systemClock{} 81 82 // NewExponentialRetryPolicy returns an instance of ExponentialRetryPolicy using the provided initialInterval 83 func NewExponentialRetryPolicy(initialInterval time.Duration) *ExponentialRetryPolicy { 84 p := &ExponentialRetryPolicy{ 85 initialInterval: initialInterval, 86 backoffCoefficient: DefaultBackoffCoefficient, 87 maximumInterval: defaultMaximumInterval, 88 expirationInterval: defaultExpirationInterval, 89 maximumAttempts: defaultMaximumAttempts, 90 } 91 92 return p 93 } 94 95 // NewRetrier is used for creating a new instance of Retrier 96 func NewRetrier(policy RetryPolicy, clock Clock) Retrier { 97 return &retrierImpl{ 98 policy: policy, 99 clock: clock, 100 startTime: clock.Now(), 101 currentAttempt: 0, 102 } 103 } 104 105 // SetInitialInterval sets the initial interval used by ExponentialRetryPolicy for the very first retry 106 // All later retries are computed using the following formula: 107 // initialInterval * math.Pow(backoffCoefficient, currentAttempt) 108 func (p *ExponentialRetryPolicy) SetInitialInterval(initialInterval time.Duration) { 109 p.initialInterval = initialInterval 110 } 111 112 // SetBackoffCoefficient sets the coefficient used by ExponentialRetryPolicy to compute next delay for each retry 113 // All retries are computed using the following formula: 114 // initialInterval * math.Pow(backoffCoefficient, currentAttempt) 115 func (p *ExponentialRetryPolicy) SetBackoffCoefficient(backoffCoefficient float64) { 116 p.backoffCoefficient = backoffCoefficient 117 } 118 119 // SetMaximumInterval sets the maximum interval for each retry 120 func (p *ExponentialRetryPolicy) SetMaximumInterval(maximumInterval time.Duration) { 121 p.maximumInterval = maximumInterval 122 } 123 124 // SetExpirationInterval sets the absolute expiration interval for all retries 125 func (p *ExponentialRetryPolicy) SetExpirationInterval(expirationInterval time.Duration) { 126 p.expirationInterval = expirationInterval 127 } 128 129 // SetMaximumAttempts sets the maximum number of retry attempts 130 func (p *ExponentialRetryPolicy) SetMaximumAttempts(maximumAttempts int) { 131 p.maximumAttempts = maximumAttempts 132 } 133 134 // ComputeNextDelay returns the next delay interval. This is used by Retrier to delay calling the operation again 135 func (p *ExponentialRetryPolicy) ComputeNextDelay(elapsedTime time.Duration, numAttempts int) time.Duration { 136 // Check to see if we ran out of maximum number of attempts 137 if p.maximumAttempts != noMaximumAttempts && numAttempts >= p.maximumAttempts { 138 return done 139 } 140 141 // Stop retrying after expiration interval is elapsed 142 if p.expirationInterval != NoInterval && elapsedTime > p.expirationInterval { 143 return done 144 } 145 146 nextInterval := float64(p.initialInterval) * math.Pow(p.backoffCoefficient, float64(numAttempts)) 147 // Disallow retries if initialInterval is negative or nextInterval overflows 148 if nextInterval <= 0 { 149 return done 150 } 151 if p.maximumInterval != NoInterval { 152 nextInterval = math.Min(nextInterval, float64(p.maximumInterval)) 153 } 154 155 if p.expirationInterval != NoInterval { 156 remainingTime := float64(math.Max(0, float64(p.expirationInterval-elapsedTime))) 157 nextInterval = math.Min(remainingTime, nextInterval) 158 } 159 160 // Bail out if the next interval is smaller than initial retry interval 161 nextDuration := time.Duration(nextInterval) 162 if nextDuration < p.initialInterval { 163 return done 164 } 165 166 // add jitter to avoid global synchronization 167 jitterPortion := int(0.2 * nextInterval) 168 // Prevent overflow 169 if jitterPortion < 1 { 170 jitterPortion = 1 171 } 172 nextInterval = nextInterval*0.8 + float64(rand.Intn(jitterPortion)) 173 174 return time.Duration(nextInterval) 175 } 176 177 // Now returns the current time using the system clock 178 func (t systemClock) Now() time.Time { 179 return time.Now() 180 } 181 182 // Reset will set the Retrier into initial state 183 func (r *retrierImpl) Reset() { 184 r.startTime = r.clock.Now() 185 r.currentAttempt = 0 186 } 187 188 // NextBackOff returns the next delay interval. This is used by Retry to delay calling the operation again 189 func (r *retrierImpl) NextBackOff() time.Duration { 190 nextInterval := r.policy.ComputeNextDelay(r.getElapsedTime(), r.currentAttempt) 191 192 // Now increment the current attempt 193 r.currentAttempt++ 194 return nextInterval 195 } 196 197 func (r *retrierImpl) getElapsedTime() time.Duration { 198 return r.clock.Now().Sub(r.startTime) 199 }