You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
178 lines
4.7 KiB
178 lines
4.7 KiB
package client
|
|
|
|
import (
|
|
"math"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/aws/aws-sdk-go/aws/request"
|
|
"github.com/aws/aws-sdk-go/internal/sdkrand"
|
|
)
|
|
|
|
// DefaultRetryer implements basic retry logic using exponential backoff for
|
|
// most services. If you want to implement custom retry logic, you can implement the
|
|
// request.Retryer interface.
|
|
//
|
|
type DefaultRetryer struct {
|
|
// Num max Retries is the number of max retries that will be performed.
|
|
// By default, this is zero.
|
|
NumMaxRetries int
|
|
|
|
// MinRetryDelay is the minimum retry delay after which retry will be performed.
|
|
// If not set, the value is 0ns.
|
|
MinRetryDelay time.Duration
|
|
|
|
// MinThrottleRetryDelay is the minimum retry delay when throttled.
|
|
// If not set, the value is 0ns.
|
|
MinThrottleDelay time.Duration
|
|
|
|
// MaxRetryDelay is the maximum retry delay before which retry must be performed.
|
|
// If not set, the value is 0ns.
|
|
MaxRetryDelay time.Duration
|
|
|
|
// MaxThrottleDelay is the maximum retry delay when throttled.
|
|
// If not set, the value is 0ns.
|
|
MaxThrottleDelay time.Duration
|
|
}
|
|
|
|
const (
|
|
// DefaultRetryerMaxNumRetries sets maximum number of retries
|
|
DefaultRetryerMaxNumRetries = 3
|
|
|
|
// DefaultRetryerMinRetryDelay sets minimum retry delay
|
|
DefaultRetryerMinRetryDelay = 30 * time.Millisecond
|
|
|
|
// DefaultRetryerMinThrottleDelay sets minimum delay when throttled
|
|
DefaultRetryerMinThrottleDelay = 500 * time.Millisecond
|
|
|
|
// DefaultRetryerMaxRetryDelay sets maximum retry delay
|
|
DefaultRetryerMaxRetryDelay = 300 * time.Second
|
|
|
|
// DefaultRetryerMaxThrottleDelay sets maximum delay when throttled
|
|
DefaultRetryerMaxThrottleDelay = 300 * time.Second
|
|
)
|
|
|
|
// MaxRetries returns the number of maximum returns the service will use to make
|
|
// an individual API request.
|
|
func (d DefaultRetryer) MaxRetries() int {
|
|
return d.NumMaxRetries
|
|
}
|
|
|
|
// setRetryerDefaults sets the default values of the retryer if not set
|
|
func (d *DefaultRetryer) setRetryerDefaults() {
|
|
if d.MinRetryDelay == 0 {
|
|
d.MinRetryDelay = DefaultRetryerMinRetryDelay
|
|
}
|
|
if d.MaxRetryDelay == 0 {
|
|
d.MaxRetryDelay = DefaultRetryerMaxRetryDelay
|
|
}
|
|
if d.MinThrottleDelay == 0 {
|
|
d.MinThrottleDelay = DefaultRetryerMinThrottleDelay
|
|
}
|
|
if d.MaxThrottleDelay == 0 {
|
|
d.MaxThrottleDelay = DefaultRetryerMaxThrottleDelay
|
|
}
|
|
}
|
|
|
|
// RetryRules returns the delay duration before retrying this request again
|
|
func (d DefaultRetryer) RetryRules(r *request.Request) time.Duration {
|
|
|
|
// if number of max retries is zero, no retries will be performed.
|
|
if d.NumMaxRetries == 0 {
|
|
return 0
|
|
}
|
|
|
|
// Sets default value for retryer members
|
|
d.setRetryerDefaults()
|
|
|
|
// minDelay is the minimum retryer delay
|
|
minDelay := d.MinRetryDelay
|
|
|
|
var initialDelay time.Duration
|
|
|
|
isThrottle := r.IsErrorThrottle()
|
|
if isThrottle {
|
|
if delay, ok := getRetryAfterDelay(r); ok {
|
|
initialDelay = delay
|
|
}
|
|
minDelay = d.MinThrottleDelay
|
|
}
|
|
|
|
retryCount := r.RetryCount
|
|
|
|
// maxDelay the maximum retryer delay
|
|
maxDelay := d.MaxRetryDelay
|
|
|
|
if isThrottle {
|
|
maxDelay = d.MaxThrottleDelay
|
|
}
|
|
|
|
var delay time.Duration
|
|
|
|
// Logic to cap the retry count based on the minDelay provided
|
|
actualRetryCount := int(math.Log2(float64(minDelay))) + 1
|
|
if actualRetryCount < 63-retryCount {
|
|
delay = time.Duration(1<<uint64(retryCount)) * getJitterDelay(minDelay)
|
|
if delay > maxDelay {
|
|
delay = getJitterDelay(maxDelay / 2)
|
|
}
|
|
} else {
|
|
delay = getJitterDelay(maxDelay / 2)
|
|
}
|
|
return delay + initialDelay
|
|
}
|
|
|
|
// getJitterDelay returns a jittered delay for retry
|
|
func getJitterDelay(duration time.Duration) time.Duration {
|
|
return time.Duration(sdkrand.SeededRand.Int63n(int64(duration)) + int64(duration))
|
|
}
|
|
|
|
// ShouldRetry returns true if the request should be retried.
|
|
func (d DefaultRetryer) ShouldRetry(r *request.Request) bool {
|
|
|
|
// ShouldRetry returns false if number of max retries is 0.
|
|
if d.NumMaxRetries == 0 {
|
|
return false
|
|
}
|
|
|
|
// If one of the other handlers already set the retry state
|
|
// we don't want to override it based on the service's state
|
|
if r.Retryable != nil {
|
|
return *r.Retryable
|
|
}
|
|
return r.IsErrorRetryable() || r.IsErrorThrottle()
|
|
}
|
|
|
|
// This will look in the Retry-After header, RFC 7231, for how long
|
|
// it will wait before attempting another request
|
|
func getRetryAfterDelay(r *request.Request) (time.Duration, bool) {
|
|
if !canUseRetryAfterHeader(r) {
|
|
return 0, false
|
|
}
|
|
|
|
delayStr := r.HTTPResponse.Header.Get("Retry-After")
|
|
if len(delayStr) == 0 {
|
|
return 0, false
|
|
}
|
|
|
|
delay, err := strconv.Atoi(delayStr)
|
|
if err != nil {
|
|
return 0, false
|
|
}
|
|
|
|
return time.Duration(delay) * time.Second, true
|
|
}
|
|
|
|
// Will look at the status code to see if the retry header pertains to
|
|
// the status code.
|
|
func canUseRetryAfterHeader(r *request.Request) bool {
|
|
switch r.HTTPResponse.StatusCode {
|
|
case 429:
|
|
case 503:
|
|
default:
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|