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  }