go.temporal.io/server@v1.23.0/common/backoff/retry_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 "context" 29 "fmt" 30 "testing" 31 "time" 32 33 "github.com/stretchr/testify/require" 34 "github.com/stretchr/testify/suite" 35 "go.temporal.io/api/serviceerror" 36 ) 37 38 type ( 39 RetrySuite struct { 40 *require.Assertions // override suite.Suite.Assertions with require.Assertions; this means that s.NotNil(nil) will stop the test, not merely log an error 41 suite.Suite 42 } 43 44 someError struct{} 45 ) 46 47 func TestRetrySuite(t *testing.T) { 48 suite.Run(t, new(RetrySuite)) 49 } 50 51 func (s *RetrySuite) SetupTest() { 52 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 53 } 54 55 func (s *RetrySuite) TestRetrySuccess() { 56 i := 0 57 op := func() error { 58 i++ 59 60 if i == 5 { 61 return nil 62 } 63 64 return &someError{} 65 } 66 67 policy := NewExponentialRetryPolicy(1 * time.Millisecond). 68 WithMaximumInterval(5 * time.Millisecond). 69 WithMaximumAttempts(10) 70 71 err := ThrottleRetry(op, policy, nil) 72 s.NoError(err) 73 s.Equal(5, i) 74 } 75 76 func (s *RetrySuite) TestRetryFailed() { 77 i := 0 78 op := func() error { 79 i++ 80 81 if i == 7 { 82 return nil 83 } 84 85 return &someError{} 86 } 87 88 policy := NewExponentialRetryPolicy(1 * time.Millisecond). 89 WithMaximumInterval(5 * time.Millisecond). 90 WithMaximumAttempts(5) 91 92 err := ThrottleRetry(op, policy, nil) 93 s.Error(err) 94 } 95 96 func (s *RetrySuite) TestIsRetryableSuccess() { 97 i := 0 98 op := func() error { 99 i++ 100 101 if i == 5 { 102 return nil 103 } 104 105 return &someError{} 106 } 107 108 isRetryable := func(err error) bool { 109 if _, ok := err.(*someError); ok { 110 return true 111 } 112 113 return false 114 } 115 116 policy := NewExponentialRetryPolicy(1 * time.Millisecond). 117 WithMaximumInterval(5 * time.Millisecond). 118 WithMaximumAttempts(10) 119 120 err := ThrottleRetry(op, policy, isRetryable) 121 s.NoError(err, "Retry count: %v", i) 122 s.Equal(5, i) 123 } 124 125 func (s *RetrySuite) TestIsRetryableFailure() { 126 i := 0 127 theErr := someError{} 128 op := func() error { 129 i++ 130 131 if i == 5 { 132 return nil 133 } 134 135 return &theErr 136 } 137 138 policy := NewExponentialRetryPolicy(1 * time.Millisecond). 139 WithMaximumInterval(5 * time.Millisecond). 140 WithMaximumAttempts(10) 141 142 err := ThrottleRetry(op, policy, IgnoreErrors([]error{&theErr})) 143 s.Error(err) 144 s.Equal(1, i) 145 } 146 147 func (s *RetrySuite) TestConcurrentRetrier() { 148 policy := NewExponentialRetryPolicy(1 * time.Millisecond). 149 WithMaximumInterval(10 * time.Millisecond). 150 WithMaximumAttempts(4) 151 152 // Basic checks 153 retrier := NewConcurrentRetrier(policy) 154 retrier.Failed() 155 s.Equal(int64(1), retrier.failureCount) 156 retrier.Succeeded() 157 s.Equal(int64(0), retrier.failureCount) 158 sleepDuration := retrier.throttleInternal() 159 s.Equal(done, sleepDuration) 160 161 // Multiple count check. 162 retrier.Failed() 163 retrier.Failed() 164 s.Equal(int64(2), retrier.failureCount) 165 // Verify valid sleep times. 166 ch := make(chan time.Duration, 3) 167 go func() { 168 for i := 0; i < 3; i++ { 169 ch <- retrier.throttleInternal() 170 } 171 }() 172 for i := 0; i < 3; i++ { 173 val := <-ch 174 fmt.Printf("Duration: %d\n", val) 175 s.True(val > 0) 176 } 177 retrier.Succeeded() 178 s.Equal(int64(0), retrier.failureCount) 179 // Verify we don't have any sleep times. 180 go func() { 181 for i := 0; i < 3; i++ { 182 ch <- retrier.throttleInternal() 183 } 184 }() 185 for i := 0; i < 3; i++ { 186 val := <-ch 187 fmt.Printf("Duration: %d\n", val) 188 s.Equal(done, val) 189 } 190 } 191 192 func (s *RetrySuite) TestRetryContextCancel() { 193 ctx, cancel := context.WithCancel(context.Background()) 194 cancel() 195 err := ThrottleRetryContext(ctx, func(ctx context.Context) error { return ctx.Err() }, 196 NewExponentialRetryPolicy(1*time.Millisecond), retryEverything) 197 s.ErrorIs(err, context.Canceled) 198 } 199 200 func (s *RetrySuite) TestRetryContextTimeout() { 201 timeout := 10 * time.Millisecond 202 ctx, cancel := context.WithTimeout(context.Background(), timeout) 203 defer cancel() 204 start := time.Now() 205 err := ThrottleRetryContext(ctx, func(ctx context.Context) error { return &someError{} }, 206 NewExponentialRetryPolicy(1*time.Second), retryEverything) 207 elapsed := time.Since(start) 208 s.ErrorIs(err, &someError{}) 209 s.Less(elapsed, timeout, 210 "Call to retry should return early if backoff exceeds context timeout") 211 } 212 213 func (s *RetrySuite) TestContextErrorFromSomeOtherContext() { 214 // These errors are returned by the function being retried by they are not 215 // actually from the context.Context passed to RetryContext 216 script := []error{context.Canceled, context.DeadlineExceeded, nil} 217 invocation := -1 218 err := ThrottleRetryContext(context.Background(), 219 func(ctx context.Context) error { 220 invocation++ 221 return script[invocation] 222 }, 223 NewExponentialRetryPolicy(1*time.Millisecond), 224 retryEverything) 225 s.NoError(err) 226 } 227 228 func (s *RetrySuite) TestThrottleRetryContext() { 229 throttleInitialInterval := 100 * time.Millisecond 230 testThrottleRetryPolicy := NewExponentialRetryPolicy(throttleInitialInterval). 231 WithMaximumInterval(throttleRetryMaxInterval). 232 WithExpirationInterval(throttleRetryExpirationInterval) 233 originalThrottleRetryPolicy := throttleRetryPolicy 234 throttleRetryPolicy = testThrottleRetryPolicy 235 236 policy := NewExponentialRetryPolicy(10 * time.Millisecond). 237 WithMaximumAttempts(2) 238 239 // test if throttle retry policy is used on resource exhausted error 240 attempt := 1 241 op := func(_ context.Context) error { 242 if attempt == 1 { 243 attempt++ 244 return &serviceerror.ResourceExhausted{} 245 } 246 247 return &someError{} 248 } 249 250 start := SystemClock.Now() 251 err := ThrottleRetryContext(context.Background(), op, policy, retryEverything) 252 s.Equal(&someError{}, err) 253 s.GreaterOrEqual( 254 time.Since(start), 255 throttleInitialInterval/2, // due to jitter 256 "Resource exhausted error should use throttle retry policy", 257 ) 258 259 // test if context timeout is respected 260 start = SystemClock.Now() 261 ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond) 262 err = ThrottleRetryContext(ctx, func(_ context.Context) error { return &serviceerror.ResourceExhausted{} }, policy, retryEverything) 263 s.Equal(&serviceerror.ResourceExhausted{}, err) 264 s.LessOrEqual( 265 time.Since(start), 266 throttleInitialInterval, 267 "Context timeout should be respected", 268 ) 269 cancel() 270 271 // test if retry will stop if there's no more retry indicated by the retry policy 272 attempt = 0 273 op = func(_ context.Context) error { 274 attempt++ 275 return &serviceerror.ResourceExhausted{} 276 } 277 err = ThrottleRetryContext(context.Background(), op, policy, retryEverything) 278 s.Equal(&serviceerror.ResourceExhausted{}, err) 279 s.Equal(2, attempt) 280 281 // set the default global throttle retry policy back to its original value 282 throttleRetryPolicy = originalThrottleRetryPolicy 283 } 284 285 var retryEverything IsRetryable = nil 286 287 func (e *someError) Error() string { 288 return "Some Error" 289 }