go.uber.org/cadence@v1.2.9/internal/common/backoff/retrypolicy.go (about)

     1  // Copyright (c) 2017 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  package backoff
    22  
    23  import (
    24  	"math"
    25  	"math/rand"
    26  	"time"
    27  )
    28  
    29  const (
    30  	// NoInterval represents Maximim interval
    31  	NoInterval                      = 0
    32  	done              time.Duration = -1
    33  	noMaximumAttempts               = 0
    34  
    35  	// DefaultBackoffCoefficient is default backOffCoefficient for retryPolicy
    36  	DefaultBackoffCoefficient = 2.0
    37  	defaultMaximumInterval    = 10 * time.Second
    38  	defaultExpirationInterval = time.Minute
    39  	defaultMaximumAttempts    = noMaximumAttempts
    40  )
    41  
    42  type (
    43  	// RetryPolicy is the API which needs to be implemented by various retry policy implementations
    44  	RetryPolicy interface {
    45  		ComputeNextDelay(elapsedTime time.Duration, numAttempts int) time.Duration
    46  	}
    47  
    48  	// Retrier manages the state of retry operation
    49  	Retrier interface {
    50  		NextBackOff() time.Duration
    51  		Reset()
    52  	}
    53  
    54  	// Clock used by ExponentialRetryPolicy implementation to get the current time.  Mainly used for unit testing
    55  	Clock interface {
    56  		Now() time.Time
    57  	}
    58  
    59  	// ExponentialRetryPolicy provides the implementation for retry policy using a coefficient to compute the next delay.
    60  	// Formula used to compute the next delay is: initialInterval * math.Pow(backoffCoefficient, currentAttempt)
    61  	ExponentialRetryPolicy struct {
    62  		initialInterval    time.Duration
    63  		backoffCoefficient float64
    64  		maximumInterval    time.Duration
    65  		expirationInterval time.Duration
    66  		maximumAttempts    int
    67  	}
    68  
    69  	systemClock struct{}
    70  
    71  	retrierImpl struct {
    72  		policy         RetryPolicy
    73  		clock          Clock
    74  		currentAttempt int
    75  		startTime      time.Time
    76  	}
    77  )
    78  
    79  // SystemClock implements Clock interface that uses time.Now().
    80  var SystemClock = systemClock{}
    81  
    82  // NewExponentialRetryPolicy returns an instance of ExponentialRetryPolicy using the provided initialInterval
    83  func NewExponentialRetryPolicy(initialInterval time.Duration) *ExponentialRetryPolicy {
    84  	p := &ExponentialRetryPolicy{
    85  		initialInterval:    initialInterval,
    86  		backoffCoefficient: DefaultBackoffCoefficient,
    87  		maximumInterval:    defaultMaximumInterval,
    88  		expirationInterval: defaultExpirationInterval,
    89  		maximumAttempts:    defaultMaximumAttempts,
    90  	}
    91  
    92  	return p
    93  }
    94  
    95  // NewRetrier is used for creating a new instance of Retrier
    96  func NewRetrier(policy RetryPolicy, clock Clock) Retrier {
    97  	return &retrierImpl{
    98  		policy:         policy,
    99  		clock:          clock,
   100  		startTime:      clock.Now(),
   101  		currentAttempt: 0,
   102  	}
   103  }
   104  
   105  // SetInitialInterval sets the initial interval used by ExponentialRetryPolicy for the very first retry
   106  // All later retries are computed using the following formula:
   107  // initialInterval * math.Pow(backoffCoefficient, currentAttempt)
   108  func (p *ExponentialRetryPolicy) SetInitialInterval(initialInterval time.Duration) {
   109  	p.initialInterval = initialInterval
   110  }
   111  
   112  // SetBackoffCoefficient sets the coefficient used by ExponentialRetryPolicy to compute next delay for each retry
   113  // All retries are computed using the following formula:
   114  // initialInterval * math.Pow(backoffCoefficient, currentAttempt)
   115  func (p *ExponentialRetryPolicy) SetBackoffCoefficient(backoffCoefficient float64) {
   116  	p.backoffCoefficient = backoffCoefficient
   117  }
   118  
   119  // SetMaximumInterval sets the maximum interval for each retry
   120  func (p *ExponentialRetryPolicy) SetMaximumInterval(maximumInterval time.Duration) {
   121  	p.maximumInterval = maximumInterval
   122  }
   123  
   124  // SetExpirationInterval sets the absolute expiration interval for all retries
   125  func (p *ExponentialRetryPolicy) SetExpirationInterval(expirationInterval time.Duration) {
   126  	p.expirationInterval = expirationInterval
   127  }
   128  
   129  // SetMaximumAttempts sets the maximum number of retry attempts
   130  func (p *ExponentialRetryPolicy) SetMaximumAttempts(maximumAttempts int) {
   131  	p.maximumAttempts = maximumAttempts
   132  }
   133  
   134  // ComputeNextDelay returns the next delay interval.  This is used by Retrier to delay calling the operation again
   135  func (p *ExponentialRetryPolicy) ComputeNextDelay(elapsedTime time.Duration, numAttempts int) time.Duration {
   136  	// Check to see if we ran out of maximum number of attempts
   137  	if p.maximumAttempts != noMaximumAttempts && numAttempts >= p.maximumAttempts {
   138  		return done
   139  	}
   140  
   141  	// Stop retrying after expiration interval is elapsed
   142  	if p.expirationInterval != NoInterval && elapsedTime > p.expirationInterval {
   143  		return done
   144  	}
   145  
   146  	nextInterval := float64(p.initialInterval) * math.Pow(p.backoffCoefficient, float64(numAttempts))
   147  	// Disallow retries if initialInterval is negative or nextInterval overflows
   148  	if nextInterval <= 0 {
   149  		return done
   150  	}
   151  	if p.maximumInterval != NoInterval {
   152  		nextInterval = math.Min(nextInterval, float64(p.maximumInterval))
   153  	}
   154  
   155  	if p.expirationInterval != NoInterval {
   156  		remainingTime := float64(math.Max(0, float64(p.expirationInterval-elapsedTime)))
   157  		nextInterval = math.Min(remainingTime, nextInterval)
   158  	}
   159  
   160  	// Bail out if the next interval is smaller than initial retry interval
   161  	nextDuration := time.Duration(nextInterval)
   162  	if nextDuration < p.initialInterval {
   163  		return done
   164  	}
   165  
   166  	// add jitter to avoid global synchronization
   167  	jitterPortion := int(0.2 * nextInterval)
   168  	// Prevent overflow
   169  	if jitterPortion < 1 {
   170  		jitterPortion = 1
   171  	}
   172  	nextInterval = nextInterval*0.8 + float64(rand.Intn(jitterPortion))
   173  
   174  	return time.Duration(nextInterval)
   175  }
   176  
   177  // Now returns the current time using the system clock
   178  func (t systemClock) Now() time.Time {
   179  	return time.Now()
   180  }
   181  
   182  // Reset will set the Retrier into initial state
   183  func (r *retrierImpl) Reset() {
   184  	r.startTime = r.clock.Now()
   185  	r.currentAttempt = 0
   186  }
   187  
   188  // NextBackOff returns the next delay interval.  This is used by Retry to delay calling the operation again
   189  func (r *retrierImpl) NextBackOff() time.Duration {
   190  	nextInterval := r.policy.ComputeNextDelay(r.getElapsedTime(), r.currentAttempt)
   191  
   192  	// Now increment the current attempt
   193  	r.currentAttempt++
   194  	return nextInterval
   195  }
   196  
   197  func (r *retrierImpl) getElapsedTime() time.Duration {
   198  	return r.clock.Now().Sub(r.startTime)
   199  }