aws-sdk-core 3.90.1 → 3.91.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/VERSION +1 -1
- data/lib/aws-sdk-core/credential_provider_chain.rb +5 -1
- data/lib/aws-sdk-core/errors.rb +19 -0
- data/lib/aws-sdk-core/plugins/client_metrics_plugin.rb +2 -1
- data/lib/aws-sdk-core/plugins/retries/client_rate_limiter.rb +137 -0
- data/lib/aws-sdk-core/plugins/retries/clock_skew.rb +63 -0
- data/lib/aws-sdk-core/plugins/retries/error_inspector.rb +142 -0
- data/lib/aws-sdk-core/plugins/retries/retry_quota.rb +57 -0
- data/lib/aws-sdk-core/plugins/retry_errors.rb +252 -112
- data/lib/aws-sdk-core/plugins/signature_v4.rb +13 -2
- data/lib/aws-sdk-core/plugins/stub_responses.rb +1 -0
- data/lib/aws-sdk-core/shared_config.rb +8 -4
- data/lib/aws-sdk-core/util.rb +4 -0
- data/lib/aws-sdk-sts.rb +7 -4
- data/lib/aws-sdk-sts/client.rb +61 -10
- data/lib/aws-sdk-sts/errors.rb +30 -8
- data/lib/aws-sdk-sts/resource.rb +7 -0
- data/lib/seahorse/client/response.rb +3 -5
- metadata +6 -2
@@ -1,13 +1,17 @@
|
|
1
1
|
require 'set'
|
2
|
+
require_relative 'retries/error_inspector'
|
3
|
+
require_relative 'retries/retry_quota'
|
4
|
+
require_relative 'retries/client_rate_limiter'
|
5
|
+
require_relative 'retries/clock_skew'
|
2
6
|
|
3
7
|
module Aws
|
4
8
|
module Plugins
|
5
9
|
# @api private
|
6
10
|
class RetryErrors < Seahorse::Client::Plugin
|
7
|
-
|
8
|
-
EQUAL_JITTER =
|
9
|
-
FULL_JITTER =
|
10
|
-
NO_JITTER =
|
11
|
+
# BEGIN LEGACY OPTIONS
|
12
|
+
EQUAL_JITTER = ->(delay) { (delay / 2) + Kernel.rand(0..(delay / 2)) }
|
13
|
+
FULL_JITTER = ->(delay) { Kernel.rand(0..delay) }
|
14
|
+
NO_JITTER = ->(delay) { delay }
|
11
15
|
|
12
16
|
JITTERS = {
|
13
17
|
none: NO_JITTER,
|
@@ -15,168 +19,308 @@ module Aws
|
|
15
19
|
full: FULL_JITTER
|
16
20
|
}
|
17
21
|
|
18
|
-
JITTERS.default_proc = lambda { |h,k|
|
19
|
-
raise KeyError,
|
22
|
+
JITTERS.default_proc = lambda { |h, k|
|
23
|
+
raise KeyError,
|
24
|
+
"#{k} is not a named jitter function. Must be one of #{h.keys}"
|
20
25
|
}
|
21
26
|
|
22
27
|
DEFAULT_BACKOFF = lambda do |c|
|
23
|
-
delay = 2
|
24
|
-
|
28
|
+
delay = 2**c.retries * c.config.retry_base_delay
|
29
|
+
if (c.config.retry_max_delay || 0) > 0
|
30
|
+
delay = [delay, c.config.retry_max_delay].min
|
31
|
+
end
|
25
32
|
jitter = c.config.retry_jitter
|
26
|
-
jitter = JITTERS[jitter] if Symbol
|
33
|
+
jitter = JITTERS[jitter] if jitter.is_a?(Symbol)
|
27
34
|
delay = jitter.call(delay) if jitter
|
28
35
|
Kernel.sleep(delay)
|
29
36
|
end
|
30
37
|
|
31
|
-
option(
|
38
|
+
option(
|
39
|
+
:retry_limit,
|
32
40
|
default: 3,
|
33
41
|
doc_type: Integer,
|
34
42
|
docstring: <<-DOCS)
|
35
43
|
The maximum number of times to retry failed requests. Only
|
36
44
|
~ 500 level server errors and certain ~ 400 level client errors
|
37
45
|
are retried. Generally, these are throttling errors, data
|
38
|
-
checksum errors, networking errors, timeout errors
|
39
|
-
errors from expired credentials.
|
46
|
+
checksum errors, networking errors, timeout errors, auth errors,
|
47
|
+
endpoint discovery, and errors from expired credentials.
|
48
|
+
This option is only used in the `legacy` retry mode.
|
40
49
|
DOCS
|
41
50
|
|
42
|
-
option(
|
51
|
+
option(
|
52
|
+
:retry_max_delay,
|
43
53
|
default: 0,
|
44
54
|
doc_type: Integer,
|
45
55
|
docstring: <<-DOCS)
|
46
|
-
The maximum number of seconds to delay between retries (0 for no limit)
|
56
|
+
The maximum number of seconds to delay between retries (0 for no limit)
|
57
|
+
used by the default backoff function. This option is only used in the
|
58
|
+
`legacy` retry mode.
|
47
59
|
DOCS
|
48
60
|
|
49
|
-
option(
|
61
|
+
option(
|
62
|
+
:retry_base_delay,
|
50
63
|
default: 0.3,
|
51
64
|
doc_type: Float,
|
52
65
|
docstring: <<-DOCS)
|
53
|
-
The base delay in seconds used by the default backoff function.
|
66
|
+
The base delay in seconds used by the default backoff function. This option
|
67
|
+
is only used in the `legacy` retry mode.
|
54
68
|
DOCS
|
55
69
|
|
56
|
-
option(
|
70
|
+
option(
|
71
|
+
:retry_jitter,
|
57
72
|
default: :none,
|
58
73
|
doc_type: Symbol,
|
59
74
|
docstring: <<-DOCS)
|
60
|
-
A delay randomiser function used by the default backoff function.
|
75
|
+
A delay randomiser function used by the default backoff function.
|
76
|
+
Some predefined functions can be referenced by name - :none, :equal, :full,
|
77
|
+
otherwise a Proc that takes and returns a number. This option is only used
|
78
|
+
in the `legacy` retry mode.
|
61
79
|
|
62
80
|
@see https://www.awsarchitectureblog.com/2015/03/backoff.html
|
63
81
|
DOCS
|
64
82
|
|
65
|
-
option(
|
83
|
+
option(
|
84
|
+
:retry_backoff,
|
85
|
+
default: DEFAULT_BACKOFF,
|
86
|
+
doc_type: Proc,
|
87
|
+
docstring: <<-DOCS)
|
88
|
+
A proc or lambda used for backoff. Defaults to 2**retries * retry_base_delay.
|
89
|
+
This option is only used in the `legacy` retry mode.
|
90
|
+
DOCS
|
66
91
|
|
67
|
-
#
|
68
|
-
|
92
|
+
# END LEGACY OPTIONS
|
93
|
+
|
94
|
+
option(
|
95
|
+
:retry_mode,
|
96
|
+
default: 'legacy',
|
97
|
+
doc_type: String,
|
98
|
+
docstring: <<-DOCS) do |cfg|
|
99
|
+
Specifies which retry algorithm to use. Values are:
|
100
|
+
* `legacy` - The pre-existing retry behavior. This is default value if
|
101
|
+
no retry mode is provided.
|
102
|
+
* `standard` - A standardized set of retry rules across the AWS SDKs.
|
103
|
+
This includes support for retry quotas, which limit the number of
|
104
|
+
unsuccessful retries a client can make.
|
105
|
+
* `adaptive` - An experimental retry mode that includes all the
|
106
|
+
functionality of `standard` mode along with automatic client side
|
107
|
+
throttling. This is a provisional mode that may change behavior
|
108
|
+
in the future.
|
109
|
+
DOCS
|
110
|
+
resolve_retry_mode(cfg)
|
111
|
+
end
|
69
112
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
113
|
+
option(
|
114
|
+
:max_attempts,
|
115
|
+
default: 3,
|
116
|
+
doc_type: Integer,
|
117
|
+
docstring: <<-DOCS) do |cfg|
|
118
|
+
An integer representing the maximum number attempts that will be made for
|
119
|
+
a single request, including the initial attempt. For example,
|
120
|
+
setting this value to 5 will result in a request being retried up to
|
121
|
+
4 times. Used in `standard` and `adaptive` retry modes.
|
122
|
+
DOCS
|
123
|
+
resolve_max_attempts(cfg)
|
124
|
+
end
|
78
125
|
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
])
|
126
|
+
option(
|
127
|
+
:adaptive_retry_wait_to_fill,
|
128
|
+
default: true,
|
129
|
+
doc_type: 'Boolean',
|
130
|
+
docstring: <<-DOCS) do |cfg|
|
131
|
+
Used only in `adaptive` retry mode. When true, the request will sleep
|
132
|
+
until there is sufficent client side capacity to retry the request.
|
133
|
+
When false, the request will raise a `RetryCapacityNotAvailableError` and will
|
134
|
+
not retry instead of sleeping.
|
135
|
+
DOCS
|
136
|
+
resolve_adaptive_retry_wait_to_fill(cfg)
|
137
|
+
end
|
92
138
|
|
93
|
-
|
94
|
-
|
95
|
-
|
139
|
+
option(
|
140
|
+
:correct_clock_skew,
|
141
|
+
default: true,
|
142
|
+
doc_type: 'Boolean',
|
143
|
+
docstring: <<-DOCS) do |cfg|
|
144
|
+
Used only in `standard` and adaptive retry modes. Specifies whether to apply
|
145
|
+
a clock skew correction and retry requests with skewed client clocks.
|
146
|
+
DOCS
|
147
|
+
resolve_correct_clock_skew(cfg)
|
148
|
+
end
|
96
149
|
|
97
|
-
|
98
|
-
|
99
|
-
'IDPCommunicationError', # sts
|
100
|
-
])
|
150
|
+
# @api private undocumented
|
151
|
+
option(:client_rate_limiter) { Retries::ClientRateLimiter.new }
|
101
152
|
|
102
|
-
|
103
|
-
|
104
|
-
@name = extract_name(error)
|
105
|
-
@http_status_code = http_status_code
|
106
|
-
end
|
153
|
+
# @api private undocumented
|
154
|
+
option(:retry_quota) { Retries::RetryQuota.new }
|
107
155
|
|
108
|
-
|
109
|
-
|
110
|
-
end
|
156
|
+
# @api private undocumented
|
157
|
+
option(:clock_skew) { Retries::ClockSkew.new }
|
111
158
|
|
112
|
-
|
113
|
-
|
159
|
+
def self.resolve_retry_mode(cfg)
|
160
|
+
value = ENV['AWS_RETRY_MODE'] ||
|
161
|
+
Aws.shared_config.retry_mode(profile: cfg.profile) ||
|
162
|
+
'legacy'
|
163
|
+
# Raise if provided value is not one of the retry modes
|
164
|
+
if value != 'legacy' && value != 'standard' && value != 'adaptive'
|
165
|
+
raise ArgumentError,
|
166
|
+
'Must provide either `legacy`, `standard`, or `adaptive` for '\
|
167
|
+
'retry_mode profile option or for ENV[\'AWS_RETRY_MODE\']'
|
114
168
|
end
|
169
|
+
value
|
170
|
+
end
|
115
171
|
|
116
|
-
|
117
|
-
|
172
|
+
def self.resolve_max_attempts(cfg)
|
173
|
+
value = ENV['AWS_MAX_ATTEMPTS'] ||
|
174
|
+
Aws.shared_config.max_attempts(profile: cfg.profile) ||
|
175
|
+
3
|
176
|
+
# Raise if provided value is not a positive integer
|
177
|
+
if !value.is_a?(Integer) || value <= 0
|
178
|
+
raise ArgumentError,
|
179
|
+
'Must provide a positive integer for max_attempts profile '\
|
180
|
+
'option or for ENV[\'AWS_MAX_ATTEMPTS\']'
|
118
181
|
end
|
182
|
+
value
|
183
|
+
end
|
119
184
|
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
185
|
+
def self.resolve_adaptive_retry_wait_to_fill(cfg)
|
186
|
+
value = ENV['AWS_ADAPTIVE_RETRY_WAIT_TO_FILL'] ||
|
187
|
+
Aws.shared_config.adaptive_retry_wait_to_fill(profile: cfg.profile) ||
|
188
|
+
'true'
|
189
|
+
|
190
|
+
# Raise if provided value is not true or false
|
191
|
+
if value != 'true' && value != 'false'
|
192
|
+
raise ArgumentError,
|
193
|
+
'Must provide either `true` or `false` for '\
|
194
|
+
'adaptive_retry_wait_to_fill profile option or for '\
|
195
|
+
'ENV[\'AWS_ADAPTIVE_RETRY_WAIT_TO_FILL\']'
|
124
196
|
end
|
125
197
|
|
126
|
-
|
127
|
-
|
198
|
+
value == 'true'
|
199
|
+
end
|
200
|
+
|
201
|
+
def self.resolve_correct_clock_skew(cfg)
|
202
|
+
value = ENV['AWS_CORRECT_CLOCK_SKEW'] ||
|
203
|
+
Aws.shared_config.correct_clock_skew(profile: cfg.profile) ||
|
204
|
+
'true'
|
205
|
+
|
206
|
+
# Raise if provided value is not true or false
|
207
|
+
if value != 'true' && value != 'false'
|
208
|
+
raise ArgumentError,
|
209
|
+
'Must provide either `true` or `false` for '\
|
210
|
+
'correct_clock_skew profile option or for '\
|
211
|
+
'ENV[\'AWS_CORRECT_CLOCK_SKEW\']'
|
128
212
|
end
|
129
213
|
|
130
|
-
|
131
|
-
|
214
|
+
value == 'true'
|
215
|
+
end
|
216
|
+
|
217
|
+
class Handler < Seahorse::Client::Handler
|
218
|
+
# Max backoff (in seconds)
|
219
|
+
MAX_BACKOFF = 20
|
220
|
+
|
221
|
+
def call(context)
|
222
|
+
context.metadata[:retries] ||= {}
|
223
|
+
config = context.config
|
132
224
|
|
133
|
-
|
134
|
-
|
135
|
-
|
225
|
+
get_send_token(config)
|
226
|
+
response = @handler.call(context)
|
227
|
+
error_inspector = Retries::ErrorInspector.new(
|
228
|
+
response.error, response.context.http_response.status_code
|
229
|
+
)
|
230
|
+
|
231
|
+
request_bookkeeping(context, response, error_inspector)
|
232
|
+
|
233
|
+
if error_inspector.endpoint_discovery?(context)
|
234
|
+
key = config.endpoint_cache.extract_key(context)
|
235
|
+
config.endpoint_cache.delete(key)
|
136
236
|
end
|
137
237
|
|
138
|
-
#
|
139
|
-
#
|
140
|
-
if
|
141
|
-
|
142
|
-
context.config.endpoint_cache.delete(key)
|
143
|
-
true
|
144
|
-
else
|
145
|
-
false
|
238
|
+
# Clock skew needs to be updated from the response even when
|
239
|
+
# the request is not retryable
|
240
|
+
if error_inspector.clock_skew?(context)
|
241
|
+
config.clock_skew.update_clock_skew(context)
|
146
242
|
end
|
147
|
-
end
|
148
243
|
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
244
|
+
return response unless retryable?(context, response, error_inspector)
|
245
|
+
|
246
|
+
return response if context.retries >= config.max_attempts - 1
|
247
|
+
|
248
|
+
context.metadata[:retries][:capacity_amount] =
|
249
|
+
config.retry_quota.checkout_capacity(error_inspector)
|
250
|
+
return response unless context.metadata[:retries][:capacity_amount] > 0
|
251
|
+
|
252
|
+
delay = exponential_backoff(context.retries)
|
253
|
+
Kernel.sleep(delay)
|
254
|
+
retry_request(context, error_inspector)
|
156
255
|
end
|
157
256
|
|
158
257
|
private
|
159
258
|
|
160
|
-
def
|
161
|
-
|
259
|
+
def get_send_token(config)
|
260
|
+
# either fail fast or block until a token becomes available
|
261
|
+
# must be configurable
|
262
|
+
# need a maximum rate at which we can send requests (max_send_rate)
|
263
|
+
# is unset until a throttle is seen
|
264
|
+
if config.retry_mode == 'adaptive'
|
265
|
+
config.client_rate_limiter.token_bucket_acquire(
|
266
|
+
1,
|
267
|
+
config.adaptive_retry_wait_to_fill
|
268
|
+
)
|
269
|
+
end
|
162
270
|
end
|
163
271
|
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
272
|
+
# maxsendrate is updated if on adaptive mode and based on response
|
273
|
+
# retry quota is updated if the request is successful (both modes)
|
274
|
+
def request_bookkeeping(context, response, error_inspector)
|
275
|
+
config = context.config
|
276
|
+
if response.successful?
|
277
|
+
config.retry_quota.release(
|
278
|
+
context.metadata[:retries][:capacity_amount]
|
279
|
+
)
|
280
|
+
end
|
281
|
+
|
282
|
+
if config.retry_mode == 'adaptive'
|
283
|
+
is_throttling_error = error_inspector.throttling_error?
|
284
|
+
config.client_rate_limiter.update_sending_rate(is_throttling_error)
|
169
285
|
end
|
170
286
|
end
|
171
287
|
|
288
|
+
def retryable?(context, response, error_inspector)
|
289
|
+
return false if response.successful?
|
290
|
+
|
291
|
+
error_inspector.retryable?(context) &&
|
292
|
+
context.http_response.body.respond_to?(:truncate)
|
293
|
+
end
|
294
|
+
|
295
|
+
def exponential_backoff(retries)
|
296
|
+
# for a transient error, use backoff
|
297
|
+
[Kernel.rand * 2**retries, MAX_BACKOFF].min
|
298
|
+
end
|
299
|
+
|
300
|
+
def retry_request(context, error)
|
301
|
+
context.retries += 1
|
302
|
+
context.config.credentials.refresh! if error.expired_credentials?
|
303
|
+
context.http_request.body.rewind
|
304
|
+
context.http_response.reset
|
305
|
+
call(context)
|
306
|
+
end
|
172
307
|
end
|
173
308
|
|
174
|
-
class
|
309
|
+
class LegacyHandler < Seahorse::Client::Handler
|
175
310
|
|
176
311
|
def call(context)
|
177
312
|
response = @handler.call(context)
|
178
313
|
if response.error
|
179
|
-
|
314
|
+
error_inspector = Retries::ErrorInspector.new(
|
315
|
+
response.error, response.context.http_response.status_code
|
316
|
+
)
|
317
|
+
|
318
|
+
if error_inspector.endpoint_discovery?(context)
|
319
|
+
key = context.config.endpoint_cache.extract_key(context)
|
320
|
+
context.config.endpoint_cache.delete(key)
|
321
|
+
end
|
322
|
+
|
323
|
+
retry_if_possible(response, error_inspector)
|
180
324
|
else
|
181
325
|
response
|
182
326
|
end
|
@@ -184,21 +328,15 @@ A delay randomiser function used by the default backoff function. Some predefine
|
|
184
328
|
|
185
329
|
private
|
186
330
|
|
187
|
-
def retry_if_possible(response)
|
331
|
+
def retry_if_possible(response, error_inspector)
|
188
332
|
context = response.context
|
189
|
-
|
190
|
-
|
191
|
-
retry_request(context, error)
|
333
|
+
if should_retry?(context, error_inspector)
|
334
|
+
retry_request(context, error_inspector)
|
192
335
|
else
|
193
336
|
response
|
194
337
|
end
|
195
338
|
end
|
196
339
|
|
197
|
-
def error_for(response)
|
198
|
-
status_code = response.context.http_response.status_code
|
199
|
-
ErrorInspector.new(response.error, status_code)
|
200
|
-
end
|
201
|
-
|
202
340
|
def retry_request(context, error)
|
203
341
|
delay_retry(context)
|
204
342
|
context.retries += 1
|
@@ -213,9 +351,9 @@ A delay randomiser function used by the default backoff function. Some predefine
|
|
213
351
|
end
|
214
352
|
|
215
353
|
def should_retry?(context, error)
|
216
|
-
error.retryable?(context)
|
217
|
-
|
218
|
-
|
354
|
+
error.retryable?(context) &&
|
355
|
+
context.retries < retry_limit(context) &&
|
356
|
+
response_truncatable?(context)
|
219
357
|
end
|
220
358
|
|
221
359
|
def retry_limit(context)
|
@@ -225,15 +363,17 @@ A delay randomiser function used by the default backoff function. Some predefine
|
|
225
363
|
def response_truncatable?(context)
|
226
364
|
context.http_response.body.respond_to?(:truncate)
|
227
365
|
end
|
228
|
-
|
229
366
|
end
|
230
367
|
|
231
368
|
def add_handlers(handlers, config)
|
232
|
-
if config.
|
369
|
+
if config.retry_mode == 'legacy'
|
370
|
+
if config.retry_limit > 0
|
371
|
+
handlers.add(LegacyHandler, step: :sign, priority: 99)
|
372
|
+
end
|
373
|
+
else
|
233
374
|
handlers.add(Handler, step: :sign, priority: 99)
|
234
375
|
end
|
235
376
|
end
|
236
|
-
|
237
377
|
end
|
238
378
|
end
|
239
379
|
end
|