]>
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. | |
15c0b25d | 11 | // The default implementation used by most services is the client.DefaultRetryer |
bae9f6d2 JC |
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": {}, | |
107c1cdb | 41 | "RequestThrottledException": {}, |
bae9f6d2 JC |
42 | "TooManyRequestsException": {}, // Lambda functions |
43 | "PriorRequestNotComplete": {}, // Route53 | |
107c1cdb | 44 | "TransactionInProgressException": {}, |
bae9f6d2 JC |
45 | } |
46 | ||
47 | // credsExpiredCodes is a collection of error codes which signify the credentials | |
48 | // need to be refreshed. Expired tokens require refreshing of credentials, and | |
49 | // resigning before the request can be retried. | |
50 | var credsExpiredCodes = map[string]struct{}{ | |
51 | "ExpiredToken": {}, | |
52 | "ExpiredTokenException": {}, | |
53 | "RequestExpired": {}, // EC2 Only | |
54 | } | |
55 | ||
56 | func isCodeThrottle(code string) bool { | |
57 | _, ok := throttleCodes[code] | |
58 | return ok | |
59 | } | |
60 | ||
61 | func isCodeRetryable(code string) bool { | |
62 | if _, ok := retryableCodes[code]; ok { | |
63 | return true | |
64 | } | |
65 | ||
66 | return isCodeExpiredCreds(code) | |
67 | } | |
68 | ||
69 | func isCodeExpiredCreds(code string) bool { | |
70 | _, ok := credsExpiredCodes[code] | |
71 | return ok | |
72 | } | |
73 | ||
74 | var validParentCodes = map[string]struct{}{ | |
15c0b25d AP |
75 | ErrCodeSerialization: {}, |
76 | ErrCodeRead: {}, | |
bae9f6d2 JC |
77 | } |
78 | ||
9b12e4fe JC |
79 | type temporaryError interface { |
80 | Temporary() bool | |
81 | } | |
82 | ||
bae9f6d2 JC |
83 | func isNestedErrorRetryable(parentErr awserr.Error) bool { |
84 | if parentErr == nil { | |
85 | return false | |
86 | } | |
87 | ||
88 | if _, ok := validParentCodes[parentErr.Code()]; !ok { | |
89 | return false | |
90 | } | |
91 | ||
92 | err := parentErr.OrigErr() | |
93 | if err == nil { | |
94 | return false | |
95 | } | |
96 | ||
97 | if aerr, ok := err.(awserr.Error); ok { | |
98 | return isCodeRetryable(aerr.Code()) | |
99 | } | |
100 | ||
9b12e4fe | 101 | if t, ok := err.(temporaryError); ok { |
15c0b25d | 102 | return t.Temporary() || isErrConnectionReset(err) |
9b12e4fe JC |
103 | } |
104 | ||
bae9f6d2 JC |
105 | return isErrConnectionReset(err) |
106 | } | |
107 | ||
108 | // IsErrorRetryable returns whether the error is retryable, based on its Code. | |
109 | // Returns false if error is nil. | |
110 | func IsErrorRetryable(err error) bool { | |
111 | if err != nil { | |
112 | if aerr, ok := err.(awserr.Error); ok { | |
113 | return isCodeRetryable(aerr.Code()) || isNestedErrorRetryable(aerr) | |
114 | } | |
115 | } | |
116 | return false | |
117 | } | |
118 | ||
119 | // IsErrorThrottle returns whether the error is to be throttled based on its code. | |
120 | // Returns false if error is nil. | |
121 | func IsErrorThrottle(err error) bool { | |
122 | if err != nil { | |
123 | if aerr, ok := err.(awserr.Error); ok { | |
124 | return isCodeThrottle(aerr.Code()) | |
125 | } | |
126 | } | |
127 | return false | |
128 | } | |
129 | ||
130 | // IsErrorExpiredCreds returns whether the error code is a credential expiry error. | |
131 | // Returns false if error is nil. | |
132 | func IsErrorExpiredCreds(err error) bool { | |
133 | if err != nil { | |
134 | if aerr, ok := err.(awserr.Error); ok { | |
135 | return isCodeExpiredCreds(aerr.Code()) | |
136 | } | |
137 | } | |
138 | return false | |
139 | } | |
140 | ||
141 | // IsErrorRetryable returns whether the error is retryable, based on its Code. | |
142 | // Returns false if the request has no Error set. | |
143 | // | |
144 | // Alias for the utility function IsErrorRetryable | |
145 | func (r *Request) IsErrorRetryable() bool { | |
146 | return IsErrorRetryable(r.Error) | |
147 | } | |
148 | ||
149 | // IsErrorThrottle returns whether the error is to be throttled based on its code. | |
150 | // Returns false if the request has no Error set | |
151 | // | |
152 | // Alias for the utility function IsErrorThrottle | |
153 | func (r *Request) IsErrorThrottle() bool { | |
154 | return IsErrorThrottle(r.Error) | |
155 | } | |
156 | ||
157 | // IsErrorExpired returns whether the error code is a credential expiry error. | |
158 | // Returns false if the request has no Error set. | |
159 | // | |
160 | // Alias for the utility function IsErrorExpiredCreds | |
161 | func (r *Request) IsErrorExpired() bool { | |
162 | return IsErrorExpiredCreds(r.Error) | |
163 | } |