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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: '018176486b2589c3e5643f0517d600048b788239'
|
4
|
+
data.tar.gz: bf23afa4bc903911cc3a571b318eb38911a4cb62
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8c5827b6dfca9a435419df7ec67f006179fe4656e909769e7545dbd4803b6723bdfedc7a9e89f23bff052d3e93428dcd39c9b803a1f35aba3ac26cfc3c33fb87
|
7
|
+
data.tar.gz: a244072123870020dbca0dee7bbe8f7057367b1ce81e18c7078b994e79a5f48c5ca5639e0df4113d4e68fdbc12627097751d21e45a65c4359e7d0abd4a548474
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
3.
|
1
|
+
3.91.0
|
@@ -28,7 +28,11 @@ module Aws
|
|
28
28
|
[:assume_role_credentials, {}],
|
29
29
|
[:shared_credentials, {}],
|
30
30
|
[:process_credentials, {}],
|
31
|
-
[:instance_profile_credentials, {
|
31
|
+
[:instance_profile_credentials, {
|
32
|
+
retries: @config ? @config.instance_profile_credentials_retries : 0,
|
33
|
+
http_open_timeout: @config ? @config.instance_profile_credentials_timeout : 1,
|
34
|
+
http_read_timeout: @config ? @config.instance_profile_credentials_timeout : 1
|
35
|
+
}]
|
32
36
|
]
|
33
37
|
end
|
34
38
|
|
data/lib/aws-sdk-core/errors.rb
CHANGED
@@ -36,6 +36,16 @@ module Aws
|
|
36
36
|
attr_accessor :code
|
37
37
|
|
38
38
|
end
|
39
|
+
|
40
|
+
# @api private undocumented
|
41
|
+
def retryable?
|
42
|
+
false
|
43
|
+
end
|
44
|
+
|
45
|
+
# @api private undocumented
|
46
|
+
def throttling?
|
47
|
+
false
|
48
|
+
end
|
39
49
|
end
|
40
50
|
|
41
51
|
# Raised when InstanceProfileCredentialsProvider or
|
@@ -255,6 +265,15 @@ Known AWS regions include (not specific to this service):
|
|
255
265
|
|
256
266
|
end
|
257
267
|
|
268
|
+
# Raised when attempting to retry a request
|
269
|
+
# and no capacity is available to retry (See adaptive retry_mode)
|
270
|
+
class RetryCapacityNotAvailableError < RuntimeError
|
271
|
+
def initialize(*args)
|
272
|
+
msg = 'Insufficient client side capacity available to retry request.'
|
273
|
+
super(msg)
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
258
277
|
# This module is mixed into another module, providing dynamic
|
259
278
|
# error classes. Error classes all inherit from {ServiceError}.
|
260
279
|
#
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'date'
|
2
|
+
require_relative 'retries/error_inspector'
|
2
3
|
|
3
4
|
module Aws
|
4
5
|
module Plugins
|
@@ -141,7 +142,7 @@ all generated client side metrics. Defaults to an empty string.
|
|
141
142
|
@handler.call(context)
|
142
143
|
rescue StandardError => e
|
143
144
|
# Handle SDK Exceptions
|
144
|
-
inspector =
|
145
|
+
inspector = Retries::ErrorInspector.new(
|
145
146
|
e,
|
146
147
|
context.http_response.status_code
|
147
148
|
)
|
@@ -0,0 +1,137 @@
|
|
1
|
+
module Aws
|
2
|
+
module Plugins
|
3
|
+
module Retries
|
4
|
+
# @api private
|
5
|
+
# Used only in 'adaptive' retry mode
|
6
|
+
class ClientRateLimiter
|
7
|
+
MIN_CAPACITY = 1
|
8
|
+
MIN_FILL_RATE = 0.5
|
9
|
+
SMOOTH = 0.8
|
10
|
+
# How much to scale back after a throttling response
|
11
|
+
BETA = 0.7
|
12
|
+
# Controls how aggressively we scale up after being throttled
|
13
|
+
SCALE_CONSTANT = 0.4
|
14
|
+
|
15
|
+
def initialize
|
16
|
+
@mutex = Mutex.new
|
17
|
+
@fill_rate = nil
|
18
|
+
@max_capacity = nil
|
19
|
+
@current_capacity = 0
|
20
|
+
@last_timestamp = nil
|
21
|
+
@enabled = false
|
22
|
+
@measured_tx_rate = 0
|
23
|
+
@last_tx_rate_bucket = Aws::Util.monotonic_seconds
|
24
|
+
@request_count = 0
|
25
|
+
@last_max_rate = 0
|
26
|
+
@last_throttle_time = Aws::Util.monotonic_seconds
|
27
|
+
@calculated_rate = nil
|
28
|
+
end
|
29
|
+
|
30
|
+
def token_bucket_acquire(amount, wait_to_fill = true)
|
31
|
+
# Client side throttling is not enabled until we see a
|
32
|
+
# throttling error
|
33
|
+
return unless @enabled
|
34
|
+
|
35
|
+
@mutex.synchronize do
|
36
|
+
token_bucket_refill
|
37
|
+
|
38
|
+
# Next see if we have enough capacity for the requested amount
|
39
|
+
while @current_capacity < amount
|
40
|
+
raise Aws::Errors::RetryCapacityNotAvailableError unless wait_to_fill
|
41
|
+
@mutex.sleep((amount - @current_capacity) / @fill_rate)
|
42
|
+
token_bucket_refill
|
43
|
+
end
|
44
|
+
@current_capacity -= amount
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def update_sending_rate(is_throttling_error)
|
49
|
+
@mutex.synchronize do
|
50
|
+
update_measured_rate
|
51
|
+
|
52
|
+
if is_throttling_error
|
53
|
+
rate_to_use = if @enabled
|
54
|
+
[@measured_tx_rate, @fill_rate].min
|
55
|
+
else
|
56
|
+
@measured_tx_rate
|
57
|
+
end
|
58
|
+
|
59
|
+
# The fill_rate is from the token bucket
|
60
|
+
@last_max_rate = rate_to_use
|
61
|
+
calculate_time_window
|
62
|
+
@last_throttle_time = Aws::Util.monotonic_seconds
|
63
|
+
@calculated_rate = cubic_throttle(rate_to_use)
|
64
|
+
enable_token_bucket
|
65
|
+
else
|
66
|
+
calculate_time_window
|
67
|
+
@calculated_rate = cubic_success(Aws::Util.monotonic_seconds)
|
68
|
+
end
|
69
|
+
|
70
|
+
new_rate = [@calculated_rate, 2 * @measured_tx_rate].min
|
71
|
+
token_bucket_update_rate(new_rate)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
def token_bucket_refill
|
78
|
+
timestamp = Aws::Util.monotonic_seconds
|
79
|
+
unless @last_timestamp
|
80
|
+
@last_timestamp = timestamp
|
81
|
+
return
|
82
|
+
end
|
83
|
+
|
84
|
+
fill_amount = (timestamp - @last_timestamp) * @fill_rate
|
85
|
+
@current_capacity = [
|
86
|
+
@max_capacity, @current_capacity + fill_amount
|
87
|
+
].min
|
88
|
+
|
89
|
+
@last_timestamp = timestamp
|
90
|
+
end
|
91
|
+
|
92
|
+
def token_bucket_update_rate(new_rps)
|
93
|
+
# Refill based on our current rate before we update to the
|
94
|
+
# new fill rate
|
95
|
+
token_bucket_refill
|
96
|
+
@fill_rate = [new_rps, MIN_FILL_RATE].max
|
97
|
+
@max_capacity = [new_rps, MIN_CAPACITY].max
|
98
|
+
# When we scale down we can't have a current capacity that exceeds our
|
99
|
+
# max_capacity.
|
100
|
+
@current_capacity = [@current_capacity, @max_capacity].min
|
101
|
+
end
|
102
|
+
|
103
|
+
def enable_token_bucket
|
104
|
+
@enabled = true
|
105
|
+
end
|
106
|
+
|
107
|
+
def update_measured_rate
|
108
|
+
t = Aws::Util.monotonic_seconds
|
109
|
+
time_bucket = (t * 2).floor / 2.0
|
110
|
+
@request_count += 1
|
111
|
+
if time_bucket > @last_tx_rate_bucket
|
112
|
+
current_rate = @request_count / (time_bucket - @last_tx_rate_bucket)
|
113
|
+
@measured_tx_rate = (current_rate * SMOOTH) +
|
114
|
+
(@measured_tx_rate * (1 - SMOOTH))
|
115
|
+
@request_count = 0
|
116
|
+
@last_tx_rate_bucket = time_bucket
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def calculate_time_window
|
121
|
+
# This is broken out into a separate calculation because it only
|
122
|
+
# gets updated when @last_max_rate changes so it can be cached.
|
123
|
+
@time_window = ((@last_max_rate * (1 - BETA)) / SCALE_CONSTANT)**(1.0 / 3)
|
124
|
+
end
|
125
|
+
|
126
|
+
def cubic_success(timestamp)
|
127
|
+
dt = timestamp - @last_throttle_time
|
128
|
+
(SCALE_CONSTANT * ((dt - @time_window)**3)) + @last_max_rate
|
129
|
+
end
|
130
|
+
|
131
|
+
def cubic_throttle(rate_to_use)
|
132
|
+
rate_to_use * BETA
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module Aws
|
2
|
+
module Plugins
|
3
|
+
module Retries
|
4
|
+
|
5
|
+
# @api private
|
6
|
+
class ClockSkew
|
7
|
+
|
8
|
+
CLOCK_SKEW_THRESHOLD = 5 * 60 # five minutes
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@mutex = Mutex.new
|
12
|
+
@endpoint_clock_corrections = Hash.new(0)
|
13
|
+
end
|
14
|
+
|
15
|
+
# Gets the clock_correction in seconds to apply to a given endpoint
|
16
|
+
# @param endpoint [URI / String]
|
17
|
+
def clock_correction(endpoint)
|
18
|
+
@mutex.synchronize { @endpoint_clock_corrections[endpoint.to_s] }
|
19
|
+
end
|
20
|
+
|
21
|
+
# Sets the clock correction for an endpoint
|
22
|
+
# @param endpoint [URI / String]
|
23
|
+
# @param correction [Number]
|
24
|
+
def set_clock_correction(endpoint, correction)
|
25
|
+
@mutex.synchronize { @endpoint_clock_corrections[endpoint.to_s] = correction }
|
26
|
+
end
|
27
|
+
|
28
|
+
# Determines whether a request has clock skew by comparing
|
29
|
+
# the current time against the server's time in the response
|
30
|
+
# @param context [Seahorse::Client::RequestContext]
|
31
|
+
def clock_skewed?(context)
|
32
|
+
server_time = server_time(context.http_response)
|
33
|
+
!!server_time && (Time.now.utc - server_time).abs > CLOCK_SKEW_THRESHOLD
|
34
|
+
end
|
35
|
+
|
36
|
+
# Update the stored clock skew value for an endpoint
|
37
|
+
# from the server's time in the response
|
38
|
+
# @param context [Seahorse::Client::RequestContext]
|
39
|
+
def update_clock_skew(context)
|
40
|
+
endpoint = context.http_request.endpoint
|
41
|
+
now_utc = Time.now.utc
|
42
|
+
server_time = server_time(context.http_response)
|
43
|
+
if server_time && (now_utc - server_time).abs > CLOCK_SKEW_THRESHOLD
|
44
|
+
set_clock_correction(endpoint, server_time - now_utc)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
# @param response [Seahorse::Client::Http::Response:]
|
51
|
+
def server_time(response)
|
52
|
+
begin
|
53
|
+
Time.parse(response.headers['date']).utc
|
54
|
+
rescue
|
55
|
+
nil
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
@@ -0,0 +1,142 @@
|
|
1
|
+
module Aws
|
2
|
+
module Plugins
|
3
|
+
module Retries
|
4
|
+
# @api private
|
5
|
+
# This class will be obsolete when APIs contain modeled exceptions
|
6
|
+
class ErrorInspector
|
7
|
+
EXPIRED_CREDS = Set.new(
|
8
|
+
[
|
9
|
+
'InvalidClientTokenId', # query services
|
10
|
+
'UnrecognizedClientException', # json services
|
11
|
+
'InvalidAccessKeyId', # s3
|
12
|
+
'AuthFailure', # ec2
|
13
|
+
'InvalidIdentityToken', # sts
|
14
|
+
'ExpiredToken' # route53
|
15
|
+
]
|
16
|
+
)
|
17
|
+
|
18
|
+
THROTTLING_ERRORS = Set.new(
|
19
|
+
[
|
20
|
+
'Throttling', # query services
|
21
|
+
'ThrottlingException', # json services
|
22
|
+
'ThrottledException', # sns
|
23
|
+
'RequestThrottled', # sqs
|
24
|
+
'RequestThrottledException', # generic service
|
25
|
+
'ProvisionedThroughputExceededException', # dynamodb
|
26
|
+
'TransactionInProgressException', # dynamodb
|
27
|
+
'RequestLimitExceeded', # ec2
|
28
|
+
'BandwidthLimitExceeded', # cloud search
|
29
|
+
'LimitExceededException', # kinesis
|
30
|
+
'TooManyRequestsException', # batch
|
31
|
+
'PriorRequestNotComplete', # route53
|
32
|
+
'SlowDown', # s3
|
33
|
+
'EC2ThrottledException' # ec2
|
34
|
+
]
|
35
|
+
)
|
36
|
+
|
37
|
+
CHECKSUM_ERRORS = Set.new(
|
38
|
+
[
|
39
|
+
'CRC32CheckFailed' # dynamodb
|
40
|
+
]
|
41
|
+
)
|
42
|
+
|
43
|
+
NETWORKING_ERRORS = Set.new(
|
44
|
+
[
|
45
|
+
'RequestTimeout', # s3
|
46
|
+
'RequestTimeoutException', # glacier
|
47
|
+
'IDPCommunicationError' # sts
|
48
|
+
]
|
49
|
+
)
|
50
|
+
|
51
|
+
# See: https://github.com/aws/aws-sdk-net/blob/5810dfe401e0eac2e59d02276d4b479224b4538e/sdk/src/Core/Amazon.Runtime/Pipeline/RetryHandler/RetryPolicy.cs#L78
|
52
|
+
CLOCK_SKEW_ERRORS = Set.new(
|
53
|
+
[
|
54
|
+
'RequestTimeTooSkewed',
|
55
|
+
'RequestExpired',
|
56
|
+
'InvalidSignatureException',
|
57
|
+
'SignatureDoesNotMatch',
|
58
|
+
'AuthFailure',
|
59
|
+
'RequestInTheFuture'
|
60
|
+
]
|
61
|
+
)
|
62
|
+
|
63
|
+
def initialize(error, http_status_code)
|
64
|
+
@error = error
|
65
|
+
@name = extract_name(@error)
|
66
|
+
@http_status_code = http_status_code
|
67
|
+
end
|
68
|
+
|
69
|
+
def expired_credentials?
|
70
|
+
!!(EXPIRED_CREDS.include?(@name) || @name.match(/expired/i))
|
71
|
+
end
|
72
|
+
|
73
|
+
def throttling_error?
|
74
|
+
!!(THROTTLING_ERRORS.include?(@name) ||
|
75
|
+
@name.match(/throttl/i) ||
|
76
|
+
@http_status_code == 429) ||
|
77
|
+
modeled_throttling?
|
78
|
+
end
|
79
|
+
|
80
|
+
def checksum?
|
81
|
+
CHECKSUM_ERRORS.include?(@name) || @error.is_a?(Errors::ChecksumError)
|
82
|
+
end
|
83
|
+
|
84
|
+
def networking?
|
85
|
+
@error.is_a?(Seahorse::Client::NetworkingError) ||
|
86
|
+
@error.is_a?(Errors::NoSuchEndpointError) ||
|
87
|
+
NETWORKING_ERRORS.include?(@name)
|
88
|
+
end
|
89
|
+
|
90
|
+
def server?
|
91
|
+
(500..599).cover?(@http_status_code)
|
92
|
+
end
|
93
|
+
|
94
|
+
def endpoint_discovery?(context)
|
95
|
+
return false unless context.operation.endpoint_discovery
|
96
|
+
|
97
|
+
@http_status_code == 421 ||
|
98
|
+
@name == 'InvalidEndpointException' ||
|
99
|
+
@error.is_a?(Errors::EndpointDiscoveryError)
|
100
|
+
end
|
101
|
+
|
102
|
+
def modeled_retryable?
|
103
|
+
@error.is_a?(Errors::ServiceError) && @error.retryable?
|
104
|
+
end
|
105
|
+
|
106
|
+
def modeled_throttling?
|
107
|
+
@error.is_a?(Errors::ServiceError) && @error.throttling?
|
108
|
+
end
|
109
|
+
|
110
|
+
def clock_skew?(context)
|
111
|
+
CLOCK_SKEW_ERRORS.include?(@name) &&
|
112
|
+
context.config.clock_skew.clock_skewed?(context)
|
113
|
+
end
|
114
|
+
|
115
|
+
def retryable?(context)
|
116
|
+
server? ||
|
117
|
+
modeled_retryable? ||
|
118
|
+
throttling_error? ||
|
119
|
+
networking? ||
|
120
|
+
checksum? ||
|
121
|
+
endpoint_discovery?(context) ||
|
122
|
+
(expired_credentials? && refreshable_credentials?(context)) ||
|
123
|
+
clock_skew?(context)
|
124
|
+
end
|
125
|
+
|
126
|
+
private
|
127
|
+
|
128
|
+
def refreshable_credentials?(context)
|
129
|
+
context.config.credentials.respond_to?(:refresh!)
|
130
|
+
end
|
131
|
+
|
132
|
+
def extract_name(error)
|
133
|
+
if error.is_a?(Errors::ServiceError)
|
134
|
+
error.class.code || error.class.name.to_s
|
135
|
+
else
|
136
|
+
error.class.name.to_s
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module Aws
|
2
|
+
module Plugins
|
3
|
+
module Retries
|
4
|
+
|
5
|
+
# @api private
|
6
|
+
# Used in 'standard' and 'adaptive' retry modes.
|
7
|
+
class RetryQuota
|
8
|
+
INITIAL_RETRY_TOKENS = 500
|
9
|
+
RETRY_COST = 5
|
10
|
+
NO_RETRY_INCREMENT = 1
|
11
|
+
TIMEOUT_RETRY_COST = 10
|
12
|
+
|
13
|
+
def initialize(opts = {})
|
14
|
+
@mutex = Mutex.new
|
15
|
+
@max_capacity = opts.fetch(:max_capacity, INITIAL_RETRY_TOKENS)
|
16
|
+
@available_capacity = @max_capacity
|
17
|
+
end
|
18
|
+
|
19
|
+
# check if there is sufficient capacity to retry
|
20
|
+
# and return it. If there is insufficient capacity
|
21
|
+
# return 0
|
22
|
+
# @return [Integer] The amount of capacity checked out
|
23
|
+
def checkout_capacity(error_inspector)
|
24
|
+
@mutex.synchronize do
|
25
|
+
capacity_amount = if error_inspector.networking?
|
26
|
+
TIMEOUT_RETRY_COST
|
27
|
+
else
|
28
|
+
RETRY_COST
|
29
|
+
end
|
30
|
+
|
31
|
+
# unable to acquire capacity
|
32
|
+
return 0 if capacity_amount > @available_capacity
|
33
|
+
|
34
|
+
@available_capacity -= capacity_amount
|
35
|
+
capacity_amount
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# capacity_amount refers to the amount of capacity requested from
|
40
|
+
# the last retry. It can either be RETRY_COST, TIMEOUT_RETRY_COST,
|
41
|
+
# or unset.
|
42
|
+
def release(capacity_amount)
|
43
|
+
# Implementation note: The release() method is called for
|
44
|
+
# every API call. In the common case where the request is
|
45
|
+
# successful and we're at full capacity, we can avoid locking.
|
46
|
+
# We can't exceed max capacity so there's no work we have to do.
|
47
|
+
return if @available_capacity == @max_capacity
|
48
|
+
|
49
|
+
@mutex.synchronize do
|
50
|
+
@available_capacity += capacity_amount || NO_RETRY_INCREMENT
|
51
|
+
@available_capacity = [@available_capacity, @max_capacity].min
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|