github.com/pingcap/ticdc@v0.0.0-20220526033649-485a10ef2652/pkg/retry/retry_test.go (about) 1 // Copyright 2021 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 retry 15 16 import ( 17 "context" 18 "math" 19 "testing" 20 "time" 21 22 "github.com/pingcap/check" 23 "github.com/pingcap/errors" 24 "github.com/pingcap/ticdc/pkg/util/testleak" 25 ) 26 27 func Test(t *testing.T) { check.TestingT(t) } 28 29 type runSuite struct{} 30 31 var _ = check.Suite(&runSuite{}) 32 33 func (s *runSuite) TestShouldRetryAtMostSpecifiedTimes(c *check.C) { 34 defer testleak.AfterTest(c)() 35 var callCount int 36 f := func() error { 37 callCount++ 38 return errors.New("test") 39 } 40 41 err := Run(500*time.Millisecond, 3, f) 42 c.Assert(err, check.ErrorMatches, "test") 43 // 👇 i think tries = first call + maxRetries, so not weird 😎 44 45 // It's weird that backoff may retry one more time than maxTries. 46 // Because the steps in backoff.Retry is: 47 // 1. Call function 48 // 2. Compare numTries and maxTries 49 // 3. Increment numTries 50 c.Assert(callCount, check.Equals, 3+1) 51 } 52 53 func (s *runSuite) TestShouldStopOnSuccess(c *check.C) { 54 defer testleak.AfterTest(c)() 55 var callCount int 56 f := func() error { 57 callCount++ 58 if callCount == 2 { 59 return nil 60 } 61 return errors.New("test") 62 } 63 64 err := Run(500*time.Millisecond, 3, f) 65 c.Assert(err, check.IsNil) 66 c.Assert(callCount, check.Equals, 2) 67 } 68 69 func (s *runSuite) TestShouldBeCtxAware(c *check.C) { 70 defer testleak.AfterTest(c)() 71 var callCount int 72 f := func() error { 73 callCount++ 74 return context.Canceled 75 } 76 77 err := Run(500*time.Millisecond, 3, f) 78 c.Assert(err, check.Equals, context.Canceled) 79 c.Assert(callCount, check.Equals, 1) 80 81 callCount = 0 82 f = func() error { 83 callCount++ 84 return errors.Annotate(context.Canceled, "test") 85 } 86 err = Run(500*time.Millisecond, 3, f) 87 c.Assert(errors.Cause(err), check.Equals, context.Canceled) 88 c.Assert(callCount, check.Equals, 1) 89 } 90 91 func (s *runSuite) TestInfiniteRetry(c *check.C) { 92 defer testleak.AfterTest(c)() 93 var callCount int 94 f := func() error { 95 callCount++ 96 return context.Canceled 97 } 98 99 var reportedElapsed time.Duration 100 notify := func(elapsed time.Duration) { 101 reportedElapsed = elapsed 102 } 103 104 err := RunWithInfiniteRetry(10*time.Millisecond, f, notify) 105 c.Assert(err, check.Equals, context.Canceled) 106 c.Assert(callCount, check.Equals, 1) 107 c.Assert(reportedElapsed, check.Equals, 0*time.Second) 108 109 callCount = 0 110 ctx, cancel := context.WithTimeout(context.Background(), time.Second*2) 111 defer cancel() 112 f = func() error { 113 select { 114 case <-ctx.Done(): 115 return ctx.Err() 116 default: 117 } 118 callCount++ 119 return errors.New("test") 120 } 121 122 err = RunWithInfiniteRetry(10*time.Millisecond, f, notify) 123 c.Assert(err, check.Equals, context.DeadlineExceeded) 124 c.Assert(reportedElapsed, check.Greater, time.Second) 125 c.Assert(reportedElapsed, check.LessEqual, 3*time.Second) 126 } 127 128 func (s *runSuite) TestDoShouldRetryAtMostSpecifiedTimes(c *check.C) { 129 defer testleak.AfterTest(c)() 130 var callCount int 131 f := func() error { 132 callCount++ 133 return errors.New("test") 134 } 135 136 err := Do(context.Background(), f, WithMaxTries(3)) 137 c.Assert(errors.Cause(err), check.ErrorMatches, "test") 138 c.Assert(callCount, check.Equals, 3) 139 } 140 141 func (s *runSuite) TestDoShouldStopOnSuccess(c *check.C) { 142 defer testleak.AfterTest(c)() 143 var callCount int 144 f := func() error { 145 callCount++ 146 if callCount == 2 { 147 return nil 148 } 149 return errors.New("test") 150 } 151 152 err := Do(context.Background(), f, WithMaxTries(3)) 153 c.Assert(err, check.IsNil) 154 c.Assert(callCount, check.Equals, 2) 155 } 156 157 func (s *runSuite) TestIsRetryable(c *check.C) { 158 defer testleak.AfterTest(c)() 159 var callCount int 160 f := func() error { 161 callCount++ 162 return errors.Annotate(context.Canceled, "test") 163 } 164 165 err := Do(context.Background(), f, WithMaxTries(3), WithIsRetryableErr(func(err error) bool { 166 switch errors.Cause(err) { 167 case context.Canceled: 168 return false 169 } 170 return true 171 })) 172 173 c.Assert(errors.Cause(err), check.Equals, context.Canceled) 174 c.Assert(callCount, check.Equals, 1) 175 176 callCount = 0 177 err = Do(context.Background(), f, WithMaxTries(3)) 178 179 c.Assert(errors.Cause(err), check.Equals, context.Canceled) 180 c.Assert(callCount, check.Equals, 3) 181 } 182 183 func (s *runSuite) TestDoCancelInfiniteRetry(c *check.C) { 184 defer testleak.AfterTest(c)() 185 callCount := 0 186 ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*20) 187 defer cancel() 188 f := func() error { 189 select { 190 case <-ctx.Done(): 191 return ctx.Err() 192 default: 193 } 194 callCount++ 195 return errors.New("test") 196 } 197 198 err := Do(ctx, f, WithInfiniteTries(), WithBackoffBaseDelay(2), WithBackoffMaxDelay(10)) 199 c.Assert(errors.Cause(err), check.Equals, context.DeadlineExceeded) 200 c.Assert(callCount, check.GreaterEqual, 1, check.Commentf("tries: %d", callCount)) 201 c.Assert(callCount, check.Less, math.MaxInt64) 202 } 203 204 func (s *runSuite) TestDoCancelAtBeginning(c *check.C) { 205 defer testleak.AfterTest(c)() 206 callCount := 0 207 ctx, cancel := context.WithCancel(context.Background()) 208 cancel() 209 f := func() error { 210 callCount++ 211 return errors.New("test") 212 } 213 214 err := Do(ctx, f, WithInfiniteTries(), WithBackoffBaseDelay(2), WithBackoffMaxDelay(10)) 215 c.Assert(errors.Cause(err), check.Equals, context.Canceled) 216 c.Assert(callCount, check.Equals, 0, check.Commentf("tries:%d", callCount)) 217 } 218 219 func (s *runSuite) TestDoCornerCases(c *check.C) { 220 defer testleak.AfterTest(c)() 221 var callCount int 222 f := func() error { 223 callCount++ 224 return errors.New("test") 225 } 226 227 err := Do(context.Background(), f, WithBackoffBaseDelay(math.MinInt64), WithBackoffMaxDelay(math.MaxInt64), WithMaxTries(2)) 228 c.Assert(errors.Cause(err), check.ErrorMatches, "test") 229 c.Assert(callCount, check.Equals, 2) 230 231 callCount = 0 232 err = Do(context.Background(), f, WithBackoffBaseDelay(math.MaxInt64), WithBackoffMaxDelay(math.MinInt64), WithMaxTries(2)) 233 c.Assert(errors.Cause(err), check.ErrorMatches, "test") 234 c.Assert(callCount, check.Equals, 2) 235 236 callCount = 0 237 err = Do(context.Background(), f, WithBackoffBaseDelay(math.MinInt64), WithBackoffMaxDelay(math.MinInt64), WithMaxTries(2)) 238 c.Assert(errors.Cause(err), check.ErrorMatches, "test") 239 c.Assert(callCount, check.Equals, 2) 240 241 callCount = 0 242 err = Do(context.Background(), f, WithBackoffBaseDelay(math.MaxInt64), WithBackoffMaxDelay(math.MaxInt64), WithMaxTries(2)) 243 c.Assert(errors.Cause(err), check.ErrorMatches, "test") 244 c.Assert(callCount, check.Equals, 2) 245 246 var i int64 247 for i = -10; i < 10; i++ { 248 callCount = 0 249 err = Do(context.Background(), f, WithBackoffBaseDelay(i), WithBackoffMaxDelay(i), WithMaxTries(i)) 250 c.Assert(errors.Cause(err), check.ErrorMatches, "test") 251 c.Assert(err, check.ErrorMatches, ".*CDC:ErrReachMaxTry.*") 252 if i > 0 { 253 c.Assert(int64(callCount), check.Equals, i) 254 } else { 255 c.Assert(callCount, check.Equals, defaultMaxTries) 256 } 257 } 258 }