| package request |
| |
| import ( |
| "time" |
| |
| "github.com/aws/aws-sdk-go/aws" |
| "github.com/aws/aws-sdk-go/aws/awserr" |
| ) |
| |
| // Retryer is an interface to control retry logic for a given service. |
| // The default implementation used by most services is the client.DefaultRetryer |
| // structure, which contains basic retry logic using exponential backoff. |
| type Retryer interface { |
| RetryRules(*Request) time.Duration |
| ShouldRetry(*Request) bool |
| MaxRetries() int |
| } |
| |
| // WithRetryer sets a config Retryer value to the given Config returning it |
| // for chaining. |
| func WithRetryer(cfg *aws.Config, retryer Retryer) *aws.Config { |
| cfg.Retryer = retryer |
| return cfg |
| } |
| |
| // retryableCodes is a collection of service response codes which are retry-able |
| // without any further action. |
| var retryableCodes = map[string]struct{}{ |
| "RequestError": {}, |
| "RequestTimeout": {}, |
| ErrCodeResponseTimeout: {}, |
| "RequestTimeoutException": {}, // Glacier's flavor of RequestTimeout |
| } |
| |
| var throttleCodes = map[string]struct{}{ |
| "ProvisionedThroughputExceededException": {}, |
| "Throttling": {}, |
| "ThrottlingException": {}, |
| "RequestLimitExceeded": {}, |
| "RequestThrottled": {}, |
| "TooManyRequestsException": {}, // Lambda functions |
| "PriorRequestNotComplete": {}, // Route53 |
| "TransactionInProgressException": {}, |
| } |
| |
| // credsExpiredCodes is a collection of error codes which signify the credentials |
| // need to be refreshed. Expired tokens require refreshing of credentials, and |
| // resigning before the request can be retried. |
| var credsExpiredCodes = map[string]struct{}{ |
| "ExpiredToken": {}, |
| "ExpiredTokenException": {}, |
| "RequestExpired": {}, // EC2 Only |
| } |
| |
| func isCodeThrottle(code string) bool { |
| _, ok := throttleCodes[code] |
| return ok |
| } |
| |
| func isCodeRetryable(code string) bool { |
| if _, ok := retryableCodes[code]; ok { |
| return true |
| } |
| |
| return isCodeExpiredCreds(code) |
| } |
| |
| func isCodeExpiredCreds(code string) bool { |
| _, ok := credsExpiredCodes[code] |
| return ok |
| } |
| |
| var validParentCodes = map[string]struct{}{ |
| ErrCodeSerialization: {}, |
| ErrCodeRead: {}, |
| } |
| |
| type temporaryError interface { |
| Temporary() bool |
| } |
| |
| func isNestedErrorRetryable(parentErr awserr.Error) bool { |
| if parentErr == nil { |
| return false |
| } |
| |
| if _, ok := validParentCodes[parentErr.Code()]; !ok { |
| return false |
| } |
| |
| err := parentErr.OrigErr() |
| if err == nil { |
| return false |
| } |
| |
| if aerr, ok := err.(awserr.Error); ok { |
| return isCodeRetryable(aerr.Code()) |
| } |
| |
| if t, ok := err.(temporaryError); ok { |
| return t.Temporary() || isErrConnectionReset(err) |
| } |
| |
| return isErrConnectionReset(err) |
| } |
| |
| // IsErrorRetryable returns whether the error is retryable, based on its Code. |
| // Returns false if error is nil. |
| func IsErrorRetryable(err error) bool { |
| if err != nil { |
| if aerr, ok := err.(awserr.Error); ok { |
| return isCodeRetryable(aerr.Code()) || isNestedErrorRetryable(aerr) |
| } |
| } |
| return false |
| } |
| |
| // IsErrorThrottle returns whether the error is to be throttled based on its code. |
| // Returns false if error is nil. |
| func IsErrorThrottle(err error) bool { |
| if err != nil { |
| if aerr, ok := err.(awserr.Error); ok { |
| return isCodeThrottle(aerr.Code()) |
| } |
| } |
| return false |
| } |
| |
| // IsErrorExpiredCreds returns whether the error code is a credential expiry error. |
| // Returns false if error is nil. |
| func IsErrorExpiredCreds(err error) bool { |
| if err != nil { |
| if aerr, ok := err.(awserr.Error); ok { |
| return isCodeExpiredCreds(aerr.Code()) |
| } |
| } |
| return false |
| } |
| |
| // IsErrorRetryable returns whether the error is retryable, based on its Code. |
| // Returns false if the request has no Error set. |
| // |
| // Alias for the utility function IsErrorRetryable |
| func (r *Request) IsErrorRetryable() bool { |
| return IsErrorRetryable(r.Error) |
| } |
| |
| // IsErrorThrottle returns whether the error is to be throttled based on its code. |
| // Returns false if the request has no Error set |
| // |
| // Alias for the utility function IsErrorThrottle |
| func (r *Request) IsErrorThrottle() bool { |
| return IsErrorThrottle(r.Error) |
| } |
| |
| // IsErrorExpired returns whether the error code is a credential expiry error. |
| // Returns false if the request has no Error set. |
| // |
| // Alias for the utility function IsErrorExpiredCreds |
| func (r *Request) IsErrorExpired() bool { |
| return IsErrorExpiredCreds(r.Error) |
| } |