summaryrefslogtreecommitdiff
path: root/vendor/github.com/aws/aws-sdk-go/aws/request/retryer.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/aws/aws-sdk-go/aws/request/retryer.go')
-rw-r--r--vendor/github.com/aws/aws-sdk-go/aws/request/retryer.go204
1 files changed, 176 insertions, 28 deletions
diff --git a/vendor/github.com/aws/aws-sdk-go/aws/request/retryer.go b/vendor/github.com/aws/aws-sdk-go/aws/request/retryer.go
index f35fef213e..752ae47f84 100644
--- a/vendor/github.com/aws/aws-sdk-go/aws/request/retryer.go
+++ b/vendor/github.com/aws/aws-sdk-go/aws/request/retryer.go
@@ -1,32 +1,81 @@
package request
import (
+ "net"
+ "net/url"
+ "strings"
"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.
+// Retryer provides the interface drive the SDK's request retry behavior. The
+// Retryer implementation is responsible for implementing exponential backoff,
+// and determine if a request API error should be retried.
+//
+// client.DefaultRetryer is the SDK's default implementation of the Retryer. It
+// uses the which uses the Request.IsErrorRetryable and Request.IsErrorThrottle
+// methods to determine if the request is retried.
type Retryer interface {
+ // RetryRules return the retry delay that should be used by the SDK before
+ // making another request attempt for the failed request.
RetryRules(*Request) time.Duration
+
+ // ShouldRetry returns if the failed request is retryable.
+ //
+ // Implementations may consider request attempt count when determining if a
+ // request is retryable, but the SDK will use MaxRetries to limit the
+ // number of attempts a request are made.
ShouldRetry(*Request) bool
+
+ // MaxRetries is the number of times a request may be retried before
+ // failing.
MaxRetries() int
}
-// WithRetryer sets a config Retryer value to the given Config returning it
-// for chaining.
+// WithRetryer sets a Retryer value to the given Config returning the Config
+// value for chaining. The value must not be nil.
func WithRetryer(cfg *aws.Config, retryer Retryer) *aws.Config {
+ if retryer == nil {
+ if cfg.Logger != nil {
+ cfg.Logger.Log("ERROR: Request.WithRetryer called with nil retryer. Replacing with retry disabled Retryer.")
+ }
+ retryer = noOpRetryer{}
+ }
cfg.Retryer = retryer
return cfg
+
+}
+
+// noOpRetryer is a internal no op retryer used when a request is created
+// without a retryer.
+//
+// Provides a retryer that performs no retries.
+// It should be used when we do not want retries to be performed.
+type noOpRetryer struct{}
+
+// MaxRetries returns the number of maximum returns the service will use to make
+// an individual API; For NoOpRetryer the MaxRetries will always be zero.
+func (d noOpRetryer) MaxRetries() int {
+ return 0
+}
+
+// ShouldRetry will always return false for NoOpRetryer, as it should never retry.
+func (d noOpRetryer) ShouldRetry(_ *Request) bool {
+ return false
+}
+
+// RetryRules returns the delay duration before retrying this request again;
+// since NoOpRetryer does not retry, RetryRules always returns 0.
+func (d noOpRetryer) RetryRules(_ *Request) time.Duration {
+ return 0
}
// retryableCodes is a collection of service response codes which are retry-able
// without any further action.
var retryableCodes = map[string]struct{}{
- "RequestError": {},
+ ErrCodeRequestError: {},
"RequestTimeout": {},
ErrCodeResponseTimeout: {},
"RequestTimeoutException": {}, // Glacier's flavor of RequestTimeout
@@ -34,12 +83,16 @@ var retryableCodes = map[string]struct{}{
var throttleCodes = map[string]struct{}{
"ProvisionedThroughputExceededException": {},
+ "ThrottledException": {}, // SNS, XRay, ResourceGroupsTagging API
"Throttling": {},
"ThrottlingException": {},
"RequestLimitExceeded": {},
"RequestThrottled": {},
+ "RequestThrottledException": {},
"TooManyRequestsException": {}, // Lambda functions
"PriorRequestNotComplete": {}, // Route53
+ "TransactionInProgressException": {},
+ "EC2ThrottledException": {}, // EC2
}
// credsExpiredCodes is a collection of error codes which signify the credentials
@@ -74,10 +127,6 @@ var validParentCodes = map[string]struct{}{
ErrCodeRead: {},
}
-type temporaryError interface {
- Temporary() bool
-}
-
func isNestedErrorRetryable(parentErr awserr.Error) bool {
if parentErr == nil {
return false
@@ -96,8 +145,8 @@ func isNestedErrorRetryable(parentErr awserr.Error) bool {
return isCodeRetryable(aerr.Code())
}
- if t, ok := err.(temporaryError); ok {
- return t.Temporary()
+ if t, ok := err.(temporary); ok {
+ return t.Temporary() || isErrConnectionReset(err)
}
return isErrConnectionReset(err)
@@ -106,32 +155,90 @@ func isNestedErrorRetryable(parentErr awserr.Error) bool {
// 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)
+ if err == nil {
+ return false
+ }
+ return shouldRetryError(err)
+}
+
+type temporary interface {
+ Temporary() bool
+}
+
+func shouldRetryError(origErr error) bool {
+ switch err := origErr.(type) {
+ case awserr.Error:
+ if err.Code() == CanceledErrorCode {
+ return false
}
+ if isNestedErrorRetryable(err) {
+ return true
+ }
+
+ origErr := err.OrigErr()
+ var shouldRetry bool
+ if origErr != nil {
+ shouldRetry = shouldRetryError(origErr)
+ if err.Code() == ErrCodeRequestError && !shouldRetry {
+ return false
+ }
+ }
+ if isCodeRetryable(err.Code()) {
+ return true
+ }
+ return shouldRetry
+
+ case *url.Error:
+ if strings.Contains(err.Error(), "connection refused") {
+ // Refused connections should be retried as the service may not yet
+ // be running on the port. Go TCP dial considers refused
+ // connections as not temporary.
+ return true
+ }
+ // *url.Error only implements Temporary after golang 1.6 but since
+ // url.Error only wraps the error:
+ return shouldRetryError(err.Err)
+
+ case temporary:
+ if netErr, ok := err.(*net.OpError); ok && netErr.Op == "dial" {
+ return true
+ }
+ // If the error is temporary, we want to allow continuation of the
+ // retry process
+ return err.Temporary() || isErrConnectionReset(origErr)
+
+ case nil:
+ // `awserr.Error.OrigErr()` can be nil, meaning there was an error but
+ // because we don't know the cause, it is marked as retryable. See
+ // TestRequest4xxUnretryable for an example.
+ return true
+
+ default:
+ switch err.Error() {
+ case "net/http: request canceled",
+ "net/http: request canceled while waiting for connection":
+ // known 1.5 error case when an http request is cancelled
+ return false
+ }
+ // here we don't know the error; so we allow a retry.
+ return true
}
- 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())
- }
+ if aerr, ok := err.(awserr.Error); ok && aerr != nil {
+ return isCodeThrottle(aerr.Code())
}
return false
}
-// IsErrorExpiredCreds returns whether the error code is a credential expiry error.
-// Returns false if error is nil.
+// 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())
- }
+ if aerr, ok := err.(awserr.Error); ok && aerr != nil {
+ return isCodeExpiredCreds(aerr.Code())
}
return false
}
@@ -141,17 +248,58 @@ func IsErrorExpiredCreds(err error) bool {
//
// Alias for the utility function IsErrorRetryable
func (r *Request) IsErrorRetryable() bool {
+ if isErrCode(r.Error, r.RetryErrorCodes) {
+ return true
+ }
+
+ // HTTP response status code 501 should not be retried.
+ // 501 represents Not Implemented which means the request method is not
+ // supported by the server and cannot be handled.
+ if r.HTTPResponse != nil {
+ // HTTP response status code 500 represents internal server error and
+ // should be retried without any throttle.
+ if r.HTTPResponse.StatusCode == 500 {
+ return true
+ }
+ }
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
+// 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 {
+ if isErrCode(r.Error, r.ThrottleErrorCodes) {
+ return true
+ }
+
+ if r.HTTPResponse != nil {
+ switch r.HTTPResponse.StatusCode {
+ case
+ 429, // error caused due to too many requests
+ 502, // Bad Gateway error should be throttled
+ 503, // caused when service is unavailable
+ 504: // error occurred due to gateway timeout
+ return true
+ }
+ }
+
return IsErrorThrottle(r.Error)
}
+func isErrCode(err error, codes []string) bool {
+ if aerr, ok := err.(awserr.Error); ok && aerr != nil {
+ for _, code := range codes {
+ if code == aerr.Code() {
+ return true
+ }
+ }
+ }
+
+ return false
+}
+
// IsErrorExpired returns whether the error code is a credential expiry error.
// Returns false if the request has no Error set.
//