go.temporal.io/server@v1.23.0/common/backoff/retrypolicy_test.go (about) 1 // The MIT License 2 // 3 // Copyright (c) 2020 Temporal Technologies Inc. All rights reserved. 4 // 5 // Copyright (c) 2020 Uber Technologies, Inc. 6 // 7 // Permission is hereby granted, free of charge, to any person obtaining a copy 8 // of this software and associated documentation files (the "Software"), to deal 9 // in the Software without restriction, including without limitation the rights 10 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 // copies of the Software, and to permit persons to whom the Software is 12 // furnished to do so, subject to the following conditions: 13 // 14 // The above copyright notice and this permission notice shall be included in 15 // all copies or substantial portions of the Software. 16 // 17 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 // THE SOFTWARE. 24 25 package backoff 26 27 import ( 28 "fmt" 29 "math/rand" 30 "testing" 31 "time" 32 33 "github.com/stretchr/testify/require" 34 "github.com/stretchr/testify/suite" 35 ) 36 37 type ( 38 RetryPolicySuite struct { 39 *require.Assertions // override suite.Suite.Assertions with require.Assertions; this means that s.NotNil(nil) will stop the test, not merely log an error 40 suite.Suite 41 } 42 43 TestClock struct { 44 currentTime time.Time 45 } 46 ) 47 48 // ExampleExponentialRetryPolicy_WithMaximumInterval demonstrates example delays with a backoff coefficient of 2 and a 49 // maximum interval of 10 seconds. Keep in mind that there is a random jitter in these times, so they are not exactly 50 // what you'd expect. 51 func ExampleExponentialRetryPolicy_WithMaximumInterval() { 52 rand.Seed(42) 53 p1 := NewExponentialRetryPolicy(time.Second). 54 WithBackoffCoefficient(2.0). 55 WithMaximumInterval(0). 56 WithMaximumAttempts(0). 57 WithExpirationInterval(0) 58 p1copy := *p1 59 p2 := &p1copy 60 p2 = p2.WithMaximumInterval(time.Second * 10) 61 var e1, e2 time.Duration 62 fmt.Printf("%-10s| %15s| %15s\n", "Attempt", "Delay", "Capped Delay") 63 for attempts := 0; attempts < 10; attempts++ { 64 d1 := p1.ComputeNextDelay(e1, attempts) 65 d2 := p2.ComputeNextDelay(e2, attempts) 66 e1 += d1 67 e2 += d2 68 _, _ = fmt.Printf( 69 "%-10d| %14.1fs| %14.1fs\n", 70 attempts, 71 d1.Round(100*time.Millisecond).Seconds(), 72 d2.Round(100*time.Millisecond).Seconds(), 73 ) 74 } 75 // Output: 76 // Attempt | Delay| Capped Delay 77 // 0 | 0.0s| 0.0s 78 // 1 | 0.8s| 0.9s 79 // 2 | 1.7s| 1.6s 80 // 3 | 3.3s| 3.2s 81 // 4 | 7.2s| 7.2s 82 // 5 | 15.1s| 9.6s 83 // 6 | 26.2s| 8.8s 84 // 7 | 62.8s| 9.4s 85 // 8 | 112.8s| 9.5s 86 // 9 | 219.7s| 8.3s 87 } 88 89 func TestRetryPolicySuite(t *testing.T) { 90 suite.Run(t, new(RetryPolicySuite)) 91 } 92 93 func (s *RetryPolicySuite) SetupTest() { 94 s.Assertions = require.New(s.T()) // Have to define our overridden assertions in the test setup. If we did it earlier, s.T() will return nil 95 } 96 97 func (s *RetryPolicySuite) TestExponentialBackoff() { 98 policy := createPolicy(time.Second). 99 WithMaximumInterval(10 * time.Second) 100 101 expectedResult := []time.Duration{1, 2, 4, 8, 10} 102 for i, d := range expectedResult { 103 expectedResult[i] = d * time.Second 104 } 105 106 r, _ := createRetrier(policy) 107 for _, expected := range expectedResult { 108 min, max := getNextBackoffRange(expected) 109 next := r.NextBackOff() 110 s.True(next >= min, "NextBackoff too low") 111 s.True(next < max, "NextBackoff too high") 112 } 113 } 114 115 func (s *RetryPolicySuite) TestNumberOfAttempts() { 116 maxAttempts := 5 117 policy := createPolicy(time.Second). 118 WithMaximumAttempts(maxAttempts) 119 120 r, _ := createRetrier(policy) 121 var next time.Duration 122 for i := 0; i < maxAttempts-1; i++ { 123 next = r.NextBackOff() 124 s.NotEqual(done, next) 125 } 126 127 s.Equal(done, r.NextBackOff()) 128 } 129 130 // Test to make sure relative maximum interval for each retry is honoured 131 func (s *RetryPolicySuite) TestMaximumInterval() { 132 policy := createPolicy(time.Second). 133 WithMaximumInterval(10 * time.Second) 134 135 expectedResult := []time.Duration{1, 2, 4, 8, 10, 10, 10, 10, 10, 10} 136 for i, d := range expectedResult { 137 expectedResult[i] = d * time.Second 138 } 139 140 r, _ := createRetrier(policy) 141 for _, expected := range expectedResult { 142 min, max := getNextBackoffRange(expected) 143 next := r.NextBackOff() 144 s.True(next >= min, "NextBackoff too low") 145 s.True(next < max, "NextBackoff too high") 146 } 147 } 148 149 func (s *RetryPolicySuite) TestBackoffCoefficient() { 150 policy := createPolicy(2 * time.Second). 151 WithBackoffCoefficient(1.0) 152 153 r, _ := createRetrier(policy) 154 min, max := getNextBackoffRange(2 * time.Second) 155 for i := 0; i < 10; i++ { 156 next := r.NextBackOff() 157 s.True(next >= min, "NextBackoff too low") 158 s.True(next < max, "NextBackoff too high") 159 } 160 } 161 162 func (s *RetryPolicySuite) TestExpirationInterval() { 163 policy := createPolicy(2 * time.Second). 164 WithExpirationInterval(5 * time.Minute) 165 166 r, clock := createRetrier(policy) 167 clock.moveClock(6 * time.Minute) 168 next := r.NextBackOff() 169 170 s.Equal(done, next) 171 } 172 173 func (s *RetryPolicySuite) TestExpirationOverflow() { 174 policy := createPolicy(2 * time.Second). 175 WithExpirationInterval(5 * time.Second) 176 177 r, clock := createRetrier(policy) 178 next := r.NextBackOff() 179 min, max := getNextBackoffRange(2 * time.Second) 180 s.True(next >= min, "NextBackoff too low") 181 s.True(next < max, "NextBackoff too high") 182 183 clock.moveClock(2 * time.Second) 184 185 next = r.NextBackOff() 186 min, max = getNextBackoffRange(3 * time.Second) 187 s.True(next >= min, "NextBackoff too low") 188 s.True(next < max, "NextBackoff too high") 189 } 190 191 func (s *RetryPolicySuite) TestDefaultPublishRetryPolicy() { 192 policy := NewExponentialRetryPolicy(50 * time.Millisecond). 193 WithExpirationInterval(time.Minute). 194 WithMaximumInterval(10 * time.Second) 195 196 r, clock := createRetrier(policy) 197 expectedResult := []time.Duration{ 198 50 * time.Millisecond, 199 100 * time.Millisecond, 200 200 * time.Millisecond, 201 400 * time.Millisecond, 202 800 * time.Millisecond, 203 1600 * time.Millisecond, 204 3200 * time.Millisecond, 205 6400 * time.Millisecond, 206 10000 * time.Millisecond, 207 10000 * time.Millisecond, 208 10000 * time.Millisecond, 209 10000 * time.Millisecond, 210 7250 * time.Millisecond, 211 done, 212 } 213 214 for _, expected := range expectedResult { 215 next := r.NextBackOff() 216 if expected == done { 217 s.Equal(done, next, "backoff not done yet!!!") 218 } else { 219 min, max := getNextBackoffRange(expected) 220 s.True(next >= min, "NextBackoff too low: actual: %v, min: %v", next, min) 221 s.True(next < max, "NextBackoff too high: actual: %v, max: %v", next, max) 222 clock.moveClock(expected) 223 } 224 } 225 } 226 227 func (s *RetryPolicySuite) TestNoMaxAttempts() { 228 policy := createPolicy(50 * time.Millisecond). 229 WithExpirationInterval(time.Minute). 230 WithMaximumInterval(10 * time.Second) 231 232 r, clock := createRetrier(policy) 233 for i := 0; i < 100; i++ { 234 next := r.NextBackOff() 235 s.True(next > 0 || next == done, "Unexpected value for next retry duration: %v", next) 236 clock.moveClock(next) 237 } 238 } 239 240 func (s *RetryPolicySuite) TestUnbounded() { 241 policy := createPolicy(50 * time.Millisecond) 242 243 r, clock := createRetrier(policy) 244 for i := 0; i < 100; i++ { 245 next := r.NextBackOff() 246 s.True(next > 0 || next == done, "Unexpected value for next retry duration: %v", next) 247 clock.moveClock(next) 248 } 249 } 250 251 func (c *TestClock) Now() time.Time { 252 return c.currentTime 253 } 254 255 func (c *TestClock) moveClock(duration time.Duration) { 256 c.currentTime = c.currentTime.Add(duration) 257 } 258 259 func createPolicy(initialInterval time.Duration) *ExponentialRetryPolicy { 260 policy := NewExponentialRetryPolicy(initialInterval). 261 WithBackoffCoefficient(2). 262 WithMaximumInterval(NoInterval). 263 WithExpirationInterval(NoInterval). 264 WithMaximumAttempts(noMaximumAttempts) 265 266 return policy 267 } 268 269 func createRetrier(policy RetryPolicy) (Retrier, *TestClock) { 270 clock := &TestClock{currentTime: time.Time{}} 271 return NewRetrier(policy, clock), clock 272 } 273 274 func getNextBackoffRange(duration time.Duration) (time.Duration, time.Duration) { 275 rangeMin := time.Duration(0.8 * float64(duration)) 276 return rangeMin, duration 277 }