]>
Commit | Line | Data |
---|---|---|
bae9f6d2 JC |
1 | package request |
2 | ||
3 | import ( | |
4 | "time" | |
5 | ||
6 | "github.com/aws/aws-sdk-go/aws" | |
7 | "github.com/aws/aws-sdk-go/aws/awserr" | |
8 | ) | |
9 | ||
10 | // Retryer is an interface to control retry logic for a given service. | |
11 | // The default implementation used by most services is the service.DefaultRetryer | |
12 | // structure, which contains basic retry logic using exponential backoff. | |
13 | type Retryer interface { | |
14 | RetryRules(*Request) time.Duration | |
15 | ShouldRetry(*Request) bool | |
16 | MaxRetries() int | |
17 | } | |
18 | ||
19 | // WithRetryer sets a config Retryer value to the given Config returning it | |
20 | // for chaining. | |
21 | func WithRetryer(cfg *aws.Config, retryer Retryer) *aws.Config { | |
22 | cfg.Retryer = retryer | |
23 | return cfg | |
24 | } | |
25 | ||
26 | // retryableCodes is a collection of service response codes which are retry-able | |
27 | // without any further action. | |
28 | var retryableCodes = map[string]struct{}{ | |
29 | "RequestError": {}, | |
30 | "RequestTimeout": {}, | |
31 | ErrCodeResponseTimeout: {}, | |
32 | "RequestTimeoutException": {}, // Glacier's flavor of RequestTimeout | |
33 | } | |
34 | ||
35 | var throttleCodes = map[string]struct{}{ | |
36 | "ProvisionedThroughputExceededException": {}, | |
37 | "Throttling": {}, | |
38 | "ThrottlingException": {}, | |
39 | "RequestLimitExceeded": {}, | |
40 | "RequestThrottled": {}, | |
41 | "LimitExceededException": {}, // Deleting 10+ DynamoDb tables at once | |
42 | "TooManyRequestsException": {}, // Lambda functions | |
43 | "PriorRequestNotComplete": {}, // Route53 | |
44 | } | |
45 | ||
46 | // credsExpiredCodes is a collection of error codes which signify the credentials | |
47 | // need to be refreshed. Expired tokens require refreshing of credentials, and | |
48 | // resigning before the request can be retried. | |
49 | var credsExpiredCodes = map[string]struct{}{ | |
50 | "ExpiredToken": {}, | |
51 | "ExpiredTokenException": {}, | |
52 | "RequestExpired": {}, // EC2 Only | |
53 | } | |
54 | ||
55 | func isCodeThrottle(code string) bool { | |
56 | _, ok := throttleCodes[code] | |
57 | return ok | |
58 | } | |
59 | ||
60 | func isCodeRetryable(code string) bool { | |
61 | if _, ok := retryableCodes[code]; ok { | |
62 | return true | |
63 | } | |
64 | ||
65 | return isCodeExpiredCreds(code) | |
66 | } | |
67 | ||
68 | func isCodeExpiredCreds(code string) bool { | |
69 | _, ok := credsExpiredCodes[code] | |
70 | return ok | |
71 | } | |
72 | ||
73 | var validParentCodes = map[string]struct{}{ | |
74 | ErrCodeSerialization: struct{}{}, | |
75 | ErrCodeRead: struct{}{}, | |
76 | } | |
77 | ||
78 | func isNestedErrorRetryable(parentErr awserr.Error) bool { | |
79 | if parentErr == nil { | |
80 | return false | |
81 | } | |
82 | ||
83 | if _, ok := validParentCodes[parentErr.Code()]; !ok { | |
84 | return false | |
85 | } | |
86 | ||
87 | err := parentErr.OrigErr() | |
88 | if err == nil { | |
89 | return false | |
90 | } | |
91 | ||
92 | if aerr, ok := err.(awserr.Error); ok { | |
93 | return isCodeRetryable(aerr.Code()) | |
94 | } | |
95 | ||
96 | return isErrConnectionReset(err) | |
97 | } | |
98 | ||
99 | // IsErrorRetryable returns whether the error is retryable, based on its Code. | |
100 | // Returns false if error is nil. | |
101 | func IsErrorRetryable(err error) bool { | |
102 | if err != nil { | |
103 | if aerr, ok := err.(awserr.Error); ok { | |
104 | return isCodeRetryable(aerr.Code()) || isNestedErrorRetryable(aerr) | |
105 | } | |
106 | } | |
107 | return false | |
108 | } | |
109 | ||
110 | // IsErrorThrottle returns whether the error is to be throttled based on its code. | |
111 | // Returns false if error is nil. | |
112 | func IsErrorThrottle(err error) bool { | |
113 | if err != nil { | |
114 | if aerr, ok := err.(awserr.Error); ok { | |
115 | return isCodeThrottle(aerr.Code()) | |
116 | } | |
117 | } | |
118 | return false | |
119 | } | |
120 | ||
121 | // IsErrorExpiredCreds returns whether the error code is a credential expiry error. | |
122 | // Returns false if error is nil. | |
123 | func IsErrorExpiredCreds(err error) bool { | |
124 | if err != nil { | |
125 | if aerr, ok := err.(awserr.Error); ok { | |
126 | return isCodeExpiredCreds(aerr.Code()) | |
127 | } | |
128 | } | |
129 | return false | |
130 | } | |
131 | ||
132 | // IsErrorRetryable returns whether the error is retryable, based on its Code. | |
133 | // Returns false if the request has no Error set. | |
134 | // | |
135 | // Alias for the utility function IsErrorRetryable | |
136 | func (r *Request) IsErrorRetryable() bool { | |
137 | return IsErrorRetryable(r.Error) | |
138 | } | |
139 | ||
140 | // IsErrorThrottle returns whether the error is to be throttled based on its code. | |
141 | // Returns false if the request has no Error set | |
142 | // | |
143 | // Alias for the utility function IsErrorThrottle | |
144 | func (r *Request) IsErrorThrottle() bool { | |
145 | return IsErrorThrottle(r.Error) | |
146 | } | |
147 | ||
148 | // IsErrorExpired returns whether the error code is a credential expiry error. | |
149 | // Returns false if the request has no Error set. | |
150 | // | |
151 | // Alias for the utility function IsErrorExpiredCreds | |
152 | func (r *Request) IsErrorExpired() bool { | |
153 | return IsErrorExpiredCreds(r.Error) | |
154 | } |