github.com/aavshr/aws-sdk-go@v1.41.3/aws/request/retryer.go (about)

     1  package request
     2  
     3  import (
     4  	"net"
     5  	"net/url"
     6  	"strings"
     7  	"time"
     8  
     9  	"github.com/aavshr/aws-sdk-go/aws"
    10  	"github.com/aavshr/aws-sdk-go/aws/awserr"
    11  )
    12  
    13  // Retryer provides the interface drive the SDK's request retry behavior. The
    14  // Retryer implementation is responsible for implementing exponential backoff,
    15  // and determine if a request API error should be retried.
    16  //
    17  // client.DefaultRetryer is the SDK's default implementation of the Retryer. It
    18  // uses the which uses the Request.IsErrorRetryable and Request.IsErrorThrottle
    19  // methods to determine if the request is retried.
    20  type Retryer interface {
    21  	// RetryRules return the retry delay that should be used by the SDK before
    22  	// making another request attempt for the failed request.
    23  	RetryRules(*Request) time.Duration
    24  
    25  	// ShouldRetry returns if the failed request is retryable.
    26  	//
    27  	// Implementations may consider request attempt count when determining if a
    28  	// request is retryable, but the SDK will use MaxRetries to limit the
    29  	// number of attempts a request are made.
    30  	ShouldRetry(*Request) bool
    31  
    32  	// MaxRetries is the number of times a request may be retried before
    33  	// failing.
    34  	MaxRetries() int
    35  }
    36  
    37  // WithRetryer sets a Retryer value to the given Config returning the Config
    38  // value for chaining. The value must not be nil.
    39  func WithRetryer(cfg *aws.Config, retryer Retryer) *aws.Config {
    40  	if retryer == nil {
    41  		if cfg.Logger != nil {
    42  			cfg.Logger.Log("ERROR: Request.WithRetryer called with nil retryer. Replacing with retry disabled Retryer.")
    43  		}
    44  		retryer = noOpRetryer{}
    45  	}
    46  	cfg.Retryer = retryer
    47  	return cfg
    48  
    49  }
    50  
    51  // noOpRetryer is a internal no op retryer used when a request is created
    52  // without a retryer.
    53  //
    54  // Provides a retryer that performs no retries.
    55  // It should be used when we do not want retries to be performed.
    56  type noOpRetryer struct{}
    57  
    58  // MaxRetries returns the number of maximum returns the service will use to make
    59  // an individual API; For NoOpRetryer the MaxRetries will always be zero.
    60  func (d noOpRetryer) MaxRetries() int {
    61  	return 0
    62  }
    63  
    64  // ShouldRetry will always return false for NoOpRetryer, as it should never retry.
    65  func (d noOpRetryer) ShouldRetry(_ *Request) bool {
    66  	return false
    67  }
    68  
    69  // RetryRules returns the delay duration before retrying this request again;
    70  // since NoOpRetryer does not retry, RetryRules always returns 0.
    71  func (d noOpRetryer) RetryRules(_ *Request) time.Duration {
    72  	return 0
    73  }
    74  
    75  // retryableCodes is a collection of service response codes which are retry-able
    76  // without any further action.
    77  var retryableCodes = map[string]struct{}{
    78  	ErrCodeRequestError:       {},
    79  	"RequestTimeout":          {},
    80  	ErrCodeResponseTimeout:    {},
    81  	"RequestTimeoutException": {}, // Glacier's flavor of RequestTimeout
    82  }
    83  
    84  var throttleCodes = map[string]struct{}{
    85  	"ProvisionedThroughputExceededException": {},
    86  	"ThrottledException":                     {}, // SNS, XRay, ResourceGroupsTagging API
    87  	"Throttling":                             {},
    88  	"ThrottlingException":                    {},
    89  	"RequestLimitExceeded":                   {},
    90  	"RequestThrottled":                       {},
    91  	"RequestThrottledException":              {},
    92  	"TooManyRequestsException":               {}, // Lambda functions
    93  	"PriorRequestNotComplete":                {}, // Route53
    94  	"TransactionInProgressException":         {},
    95  	"EC2ThrottledException":                  {}, // EC2
    96  }
    97  
    98  // credsExpiredCodes is a collection of error codes which signify the credentials
    99  // need to be refreshed. Expired tokens require refreshing of credentials, and
   100  // resigning before the request can be retried.
   101  var credsExpiredCodes = map[string]struct{}{
   102  	"ExpiredToken":          {},
   103  	"ExpiredTokenException": {},
   104  	"RequestExpired":        {}, // EC2 Only
   105  }
   106  
   107  func isCodeThrottle(code string) bool {
   108  	_, ok := throttleCodes[code]
   109  	return ok
   110  }
   111  
   112  func isCodeRetryable(code string) bool {
   113  	if _, ok := retryableCodes[code]; ok {
   114  		return true
   115  	}
   116  
   117  	return isCodeExpiredCreds(code)
   118  }
   119  
   120  func isCodeExpiredCreds(code string) bool {
   121  	_, ok := credsExpiredCodes[code]
   122  	return ok
   123  }
   124  
   125  var validParentCodes = map[string]struct{}{
   126  	ErrCodeSerialization: {},
   127  	ErrCodeRead:          {},
   128  }
   129  
   130  func isNestedErrorRetryable(parentErr awserr.Error) bool {
   131  	if parentErr == nil {
   132  		return false
   133  	}
   134  
   135  	if _, ok := validParentCodes[parentErr.Code()]; !ok {
   136  		return false
   137  	}
   138  
   139  	err := parentErr.OrigErr()
   140  	if err == nil {
   141  		return false
   142  	}
   143  
   144  	if aerr, ok := err.(awserr.Error); ok {
   145  		return isCodeRetryable(aerr.Code())
   146  	}
   147  
   148  	if t, ok := err.(temporary); ok {
   149  		return t.Temporary() || isErrConnectionReset(err)
   150  	}
   151  
   152  	return isErrConnectionReset(err)
   153  }
   154  
   155  // IsErrorRetryable returns whether the error is retryable, based on its Code.
   156  // Returns false if error is nil.
   157  func IsErrorRetryable(err error) bool {
   158  	if err == nil {
   159  		return false
   160  	}
   161  	return shouldRetryError(err)
   162  }
   163  
   164  type temporary interface {
   165  	Temporary() bool
   166  }
   167  
   168  func shouldRetryError(origErr error) bool {
   169  	switch err := origErr.(type) {
   170  	case awserr.Error:
   171  		if err.Code() == CanceledErrorCode {
   172  			return false
   173  		}
   174  		if isNestedErrorRetryable(err) {
   175  			return true
   176  		}
   177  
   178  		origErr := err.OrigErr()
   179  		var shouldRetry bool
   180  		if origErr != nil {
   181  			shouldRetry = shouldRetryError(origErr)
   182  			if err.Code() == ErrCodeRequestError && !shouldRetry {
   183  				return false
   184  			}
   185  		}
   186  		if isCodeRetryable(err.Code()) {
   187  			return true
   188  		}
   189  		return shouldRetry
   190  
   191  	case *url.Error:
   192  		if strings.Contains(err.Error(), "connection refused") {
   193  			// Refused connections should be retried as the service may not yet
   194  			// be running on the port. Go TCP dial considers refused
   195  			// connections as not temporary.
   196  			return true
   197  		}
   198  		// *url.Error only implements Temporary after golang 1.6 but since
   199  		// url.Error only wraps the error:
   200  		return shouldRetryError(err.Err)
   201  
   202  	case temporary:
   203  		if netErr, ok := err.(*net.OpError); ok && netErr.Op == "dial" {
   204  			return true
   205  		}
   206  		// If the error is temporary, we want to allow continuation of the
   207  		// retry process
   208  		return err.Temporary() || isErrConnectionReset(origErr)
   209  
   210  	case nil:
   211  		// `awserr.Error.OrigErr()` can be nil, meaning there was an error but
   212  		// because we don't know the cause, it is marked as retryable. See
   213  		// TestRequest4xxUnretryable for an example.
   214  		return true
   215  
   216  	default:
   217  		switch err.Error() {
   218  		case "net/http: request canceled",
   219  			"net/http: request canceled while waiting for connection":
   220  			// known 1.5 error case when an http request is cancelled
   221  			return false
   222  		}
   223  		// here we don't know the error; so we allow a retry.
   224  		return true
   225  	}
   226  }
   227  
   228  // IsErrorThrottle returns whether the error is to be throttled based on its code.
   229  // Returns false if error is nil.
   230  func IsErrorThrottle(err error) bool {
   231  	if aerr, ok := err.(awserr.Error); ok && aerr != nil {
   232  		return isCodeThrottle(aerr.Code())
   233  	}
   234  	return false
   235  }
   236  
   237  // IsErrorExpiredCreds returns whether the error code is a credential expiry
   238  // error. Returns false if error is nil.
   239  func IsErrorExpiredCreds(err error) bool {
   240  	if aerr, ok := err.(awserr.Error); ok && aerr != nil {
   241  		return isCodeExpiredCreds(aerr.Code())
   242  	}
   243  	return false
   244  }
   245  
   246  // IsErrorRetryable returns whether the error is retryable, based on its Code.
   247  // Returns false if the request has no Error set.
   248  //
   249  // Alias for the utility function IsErrorRetryable
   250  func (r *Request) IsErrorRetryable() bool {
   251  	if isErrCode(r.Error, r.RetryErrorCodes) {
   252  		return true
   253  	}
   254  
   255  	// HTTP response status code 501 should not be retried.
   256  	// 501 represents Not Implemented which means the request method is not
   257  	// supported by the server and cannot be handled.
   258  	if r.HTTPResponse != nil {
   259  		// HTTP response status code 500 represents internal server error and
   260  		// should be retried without any throttle.
   261  		if r.HTTPResponse.StatusCode == 500 {
   262  			return true
   263  		}
   264  	}
   265  	return IsErrorRetryable(r.Error)
   266  }
   267  
   268  // IsErrorThrottle returns whether the error is to be throttled based on its
   269  // code. Returns false if the request has no Error set.
   270  //
   271  // Alias for the utility function IsErrorThrottle
   272  func (r *Request) IsErrorThrottle() bool {
   273  	if isErrCode(r.Error, r.ThrottleErrorCodes) {
   274  		return true
   275  	}
   276  
   277  	if r.HTTPResponse != nil {
   278  		switch r.HTTPResponse.StatusCode {
   279  		case
   280  			429, // error caused due to too many requests
   281  			502, // Bad Gateway error should be throttled
   282  			503, // caused when service is unavailable
   283  			504: // error occurred due to gateway timeout
   284  			return true
   285  		}
   286  	}
   287  
   288  	return IsErrorThrottle(r.Error)
   289  }
   290  
   291  func isErrCode(err error, codes []string) bool {
   292  	if aerr, ok := err.(awserr.Error); ok && aerr != nil {
   293  		for _, code := range codes {
   294  			if code == aerr.Code() {
   295  				return true
   296  			}
   297  		}
   298  	}
   299  
   300  	return false
   301  }
   302  
   303  // IsErrorExpired returns whether the error code is a credential expiry error.
   304  // Returns false if the request has no Error set.
   305  //
   306  // Alias for the utility function IsErrorExpiredCreds
   307  func (r *Request) IsErrorExpired() bool {
   308  	return IsErrorExpiredCreds(r.Error)
   309  }