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 }