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  }