aws-sdk-core 3.46.0 → 3.94.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.
Files changed (86) hide show
  1. checksums.yaml +5 -5
  2. data/VERSION +1 -1
  3. data/lib/aws-sdk-core.rb +7 -0
  4. data/lib/aws-sdk-core/arn.rb +77 -0
  5. data/lib/aws-sdk-core/arn_parser.rb +38 -0
  6. data/lib/aws-sdk-core/assume_role_web_identity_credentials.rb +102 -0
  7. data/lib/aws-sdk-core/async_client_stubs.rb +80 -0
  8. data/lib/aws-sdk-core/binary.rb +3 -0
  9. data/lib/aws-sdk-core/binary/decode_handler.rb +9 -1
  10. data/lib/aws-sdk-core/binary/encode_handler.rb +32 -0
  11. data/lib/aws-sdk-core/binary/event_builder.rb +122 -0
  12. data/lib/aws-sdk-core/binary/event_parser.rb +48 -18
  13. data/lib/aws-sdk-core/binary/event_stream_decoder.rb +5 -2
  14. data/lib/aws-sdk-core/binary/event_stream_encoder.rb +53 -0
  15. data/lib/aws-sdk-core/client_side_monitoring/publisher.rb +9 -1
  16. data/lib/aws-sdk-core/client_stubs.rb +10 -9
  17. data/lib/aws-sdk-core/credential_provider.rb +0 -31
  18. data/lib/aws-sdk-core/credential_provider_chain.rb +79 -39
  19. data/lib/aws-sdk-core/deprecations.rb +16 -10
  20. data/lib/aws-sdk-core/ecs_credentials.rb +12 -8
  21. data/lib/aws-sdk-core/endpoint_cache.rb +14 -11
  22. data/lib/aws-sdk-core/errors.rb +94 -6
  23. data/lib/aws-sdk-core/event_emitter.rb +42 -0
  24. data/lib/aws-sdk-core/instance_profile_credentials.rb +120 -38
  25. data/lib/aws-sdk-core/json.rb +13 -14
  26. data/lib/aws-sdk-core/json/error_handler.rb +19 -2
  27. data/lib/aws-sdk-core/json/handler.rb +19 -1
  28. data/lib/aws-sdk-core/log/formatter.rb +7 -1
  29. data/lib/aws-sdk-core/log/param_filter.rb +3 -3
  30. data/lib/aws-sdk-core/pageable_response.rb +34 -20
  31. data/lib/aws-sdk-core/param_validator.rb +11 -5
  32. data/lib/aws-sdk-core/plugins/client_metrics_plugin.rb +26 -1
  33. data/lib/aws-sdk-core/plugins/endpoint_discovery.rb +1 -1
  34. data/lib/aws-sdk-core/plugins/event_stream_configuration.rb +14 -0
  35. data/lib/aws-sdk-core/plugins/invocation_id.rb +33 -0
  36. data/lib/aws-sdk-core/plugins/regional_endpoint.rb +8 -1
  37. data/lib/aws-sdk-core/plugins/retries/client_rate_limiter.rb +137 -0
  38. data/lib/aws-sdk-core/plugins/retries/clock_skew.rb +98 -0
  39. data/lib/aws-sdk-core/plugins/retries/error_inspector.rb +142 -0
  40. data/lib/aws-sdk-core/plugins/retries/retry_quota.rb +57 -0
  41. data/lib/aws-sdk-core/plugins/retry_errors.rb +290 -106
  42. data/lib/aws-sdk-core/plugins/signature_v4.rb +13 -2
  43. data/lib/aws-sdk-core/plugins/stub_responses.rb +20 -7
  44. data/lib/aws-sdk-core/plugins/transfer_encoding.rb +51 -0
  45. data/lib/aws-sdk-core/plugins/user_agent.rb +4 -8
  46. data/lib/aws-sdk-core/process_credentials.rb +9 -3
  47. data/lib/aws-sdk-core/shared_config.rb +95 -125
  48. data/lib/aws-sdk-core/structure.rb +1 -2
  49. data/lib/aws-sdk-core/stubbing/protocols/rest.rb +19 -0
  50. data/lib/aws-sdk-core/stubbing/stub_data.rb +13 -4
  51. data/lib/aws-sdk-core/util.rb +4 -0
  52. data/lib/aws-sdk-core/waiters/waiter.rb +2 -2
  53. data/lib/aws-sdk-core/xml/error_handler.rb +26 -3
  54. data/lib/aws-sdk-sts.rb +7 -4
  55. data/lib/aws-sdk-sts/client.rb +1109 -459
  56. data/lib/aws-sdk-sts/client_api.rb +67 -0
  57. data/lib/aws-sdk-sts/customizations.rb +2 -0
  58. data/lib/aws-sdk-sts/errors.rb +150 -0
  59. data/lib/aws-sdk-sts/plugins/sts_regional_endpoints.rb +32 -0
  60. data/lib/aws-sdk-sts/presigner.rb +67 -0
  61. data/lib/aws-sdk-sts/resource.rb +1 -0
  62. data/lib/aws-sdk-sts/types.rb +736 -176
  63. data/lib/seahorse.rb +9 -0
  64. data/lib/seahorse/client/async_base.rb +50 -0
  65. data/lib/seahorse/client/async_response.rb +62 -0
  66. data/lib/seahorse/client/base.rb +4 -2
  67. data/lib/seahorse/client/configuration.rb +4 -2
  68. data/lib/seahorse/client/events.rb +1 -1
  69. data/lib/seahorse/client/h2/connection.rb +246 -0
  70. data/lib/seahorse/client/h2/handler.rb +151 -0
  71. data/lib/seahorse/client/handler_list_entry.rb +2 -2
  72. data/lib/seahorse/client/http/async_response.rb +42 -0
  73. data/lib/seahorse/client/http/response.rb +13 -8
  74. data/lib/seahorse/client/logging/formatter.rb +4 -2
  75. data/lib/seahorse/client/net_http/connection_pool.rb +19 -20
  76. data/lib/seahorse/client/net_http/handler.rb +7 -1
  77. data/lib/seahorse/client/net_http/patches.rb +7 -1
  78. data/lib/seahorse/client/networking_error.rb +28 -0
  79. data/lib/seahorse/client/plugin.rb +5 -4
  80. data/lib/seahorse/client/plugins/content_length.rb +5 -2
  81. data/lib/seahorse/client/plugins/h2.rb +64 -0
  82. data/lib/seahorse/client/response.rb +3 -5
  83. data/lib/seahorse/model/api.rb +4 -0
  84. data/lib/seahorse/model/operation.rb +4 -0
  85. data/lib/seahorse/model/shapes.rb +2 -2
  86. metadata +43 -10
@@ -0,0 +1,98 @@
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
+ # clock_corrections are recorded only on errors
13
+ # and only when time difference is greater than the
14
+ # CLOCK_SKEW_THRESHOLD
15
+ @endpoint_clock_corrections = Hash.new(0)
16
+
17
+ # estimated_skew is calculated on every request
18
+ # and is used to estimate a TTL for requests
19
+ @endpoint_estimated_skews = Hash.new(nil)
20
+ end
21
+
22
+ # Gets the clock_correction in seconds to apply to a given endpoint
23
+ # @param endpoint [URI / String]
24
+ def clock_correction(endpoint)
25
+ @mutex.synchronize { @endpoint_clock_corrections[endpoint.to_s] }
26
+ end
27
+
28
+ # The estimated skew factors in any clock skew from
29
+ # the service along with any network latency.
30
+ # This provides a more accurate value for the ttl,
31
+ # which should represent when the client will stop
32
+ # waiting for a request.
33
+ # Estimated Skew should not be used to correct clock skew errors
34
+ # it should only be used to estimate TTL for a request
35
+ def estimated_skew(endpoint)
36
+ @mutex.synchronize { @endpoint_estimated_skews[endpoint.to_s] }
37
+ end
38
+
39
+ # Determines whether a request has clock skew by comparing
40
+ # the current time against the server's time in the response
41
+ # @param context [Seahorse::Client::RequestContext]
42
+ def clock_skewed?(context)
43
+ server_time = server_time(context.http_response)
44
+ !!server_time &&
45
+ (Time.now.utc - server_time).abs > CLOCK_SKEW_THRESHOLD
46
+ end
47
+
48
+ # Called only on clock skew related errors
49
+ # Update the stored clock skew correction value for an endpoint
50
+ # from the server's time in the response
51
+ # @param context [Seahorse::Client::RequestContext]
52
+ def update_clock_correction(context)
53
+ endpoint = context.http_request.endpoint
54
+ now_utc = Time.now.utc
55
+ server_time = server_time(context.http_response)
56
+ if server_time && (now_utc - server_time).abs > CLOCK_SKEW_THRESHOLD
57
+ set_clock_correction(endpoint, server_time - now_utc)
58
+ end
59
+ end
60
+
61
+ # Called for every request
62
+ # Update our estimated clock skew for the endpoint
63
+ # from the servers time in the response
64
+ # @param context [Seahorse::Client::RequestContext]
65
+ def update_estimated_skew(context)
66
+ endpoint = context.http_request.endpoint
67
+ now_utc = Time.now.utc
68
+ server_time = server_time(context.http_response)
69
+ return unless server_time
70
+ @mutex.synchronize do
71
+ @endpoint_estimated_skews[endpoint.to_s] = server_time - now_utc
72
+ end
73
+ end
74
+
75
+ private
76
+
77
+ # @param response [Seahorse::Client::Http::Response:]
78
+ def server_time(response)
79
+ begin
80
+ Time.parse(response.headers['date']).utc
81
+ rescue
82
+ nil
83
+ end
84
+ end
85
+
86
+ # Sets the clock correction for an endpoint
87
+ # @param endpoint [URI / String]
88
+ # @param correction [Number]
89
+ def set_clock_correction(endpoint, correction)
90
+ @mutex.synchronize do
91
+ @endpoint_clock_corrections[endpoint.to_s] = correction
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
98
+
@@ -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
@@ -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 = lambda { |delay| (delay / 2) + Kernel.rand(0..(delay/2))}
9
- FULL_JITTER= lambda { |delay| Kernel.rand(0..delay) }
10
- NO_JITTER = lambda { |delay| delay }
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,162 +19,346 @@ module Aws
15
19
  full: FULL_JITTER
16
20
  }
17
21
 
18
- JITTERS.default_proc = lambda { |h,k|
19
- raise KeyError, "#{k} is not a named jitter function. Must be one of #{h.keys}"
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 ** c.retries * c.config.retry_base_delay
24
- delay = [delay, c.config.retry_max_delay].min if (c.config.retry_max_delay || 0) > 0
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 === jitter
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(:retry_limit,
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 and auth
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(:retry_max_delay,
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) used by the default backoff function.
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(:retry_base_delay,
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(:retry_jitter,
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. Some predefined functions can be referenced by name - :none, :equal, :full, otherwise a Proc that takes and returns a number.
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(:retry_backoff, DEFAULT_BACKOFF)
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
- # @api private
68
- class ErrorInspector
92
+ # END LEGACY OPTIONS
69
93
 
70
- EXPIRED_CREDS = Set.new([
71
- 'InvalidClientTokenId', # query services
72
- 'UnrecognizedClientException', # json services
73
- 'InvalidAccessKeyId', # s3
74
- 'AuthFailure', # ec2
75
- ])
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:
76
100
 
77
- THROTTLING_ERRORS = Set.new([
78
- 'Throttling', # query services
79
- 'ThrottlingException', # json services
80
- 'RequestThrottled', # sqs
81
- 'ProvisionedThroughputExceededException', # dynamodb
82
- 'TransactionInProgressException', # dynamodb
83
- 'RequestLimitExceeded', # ec2
84
- 'BandwidthLimitExceeded', # cloud search
85
- 'LimitExceededException', # kinesis
86
- 'TooManyRequestsException', # batch
87
- ])
101
+ * `legacy` - The pre-existing retry behavior. This is default value if
102
+ no retry mode is provided.
88
103
 
89
- CHECKSUM_ERRORS = Set.new([
90
- 'CRC32CheckFailed', # dynamodb
91
- ])
104
+ * `standard` - A standardized set of retry rules across the AWS SDKs.
105
+ This includes support for retry quotas, which limit the number of
106
+ unsuccessful retries a client can make.
92
107
 
93
- NETWORKING_ERRORS = Set.new([
94
- 'RequestTimeout', # s3
95
- ])
108
+ * `adaptive` - An experimental retry mode that includes all the
109
+ functionality of `standard` mode along with automatic client side
110
+ throttling. This is a provisional mode that may change behavior
111
+ in the future.
96
112
 
97
- def initialize(error, http_status_code)
98
- @error = error
99
- @name = extract_name(error)
100
- @http_status_code = http_status_code
101
- end
113
+ DOCS
114
+ resolve_retry_mode(cfg)
115
+ end
116
+
117
+ option(
118
+ :max_attempts,
119
+ default: 3,
120
+ doc_type: Integer,
121
+ docstring: <<-DOCS) do |cfg|
122
+ An integer representing the maximum number attempts that will be made for
123
+ a single request, including the initial attempt. For example,
124
+ setting this value to 5 will result in a request being retried up to
125
+ 4 times. Used in `standard` and `adaptive` retry modes.
126
+ DOCS
127
+ resolve_max_attempts(cfg)
128
+ end
129
+
130
+ option(
131
+ :adaptive_retry_wait_to_fill,
132
+ default: true,
133
+ doc_type: 'Boolean',
134
+ docstring: <<-DOCS) do |cfg|
135
+ Used only in `adaptive` retry mode. When true, the request will sleep
136
+ until there is sufficent client side capacity to retry the request.
137
+ When false, the request will raise a `RetryCapacityNotAvailableError` and will
138
+ not retry instead of sleeping.
139
+ DOCS
140
+ resolve_adaptive_retry_wait_to_fill(cfg)
141
+ end
142
+
143
+ option(
144
+ :correct_clock_skew,
145
+ default: true,
146
+ doc_type: 'Boolean',
147
+ docstring: <<-DOCS) do |cfg|
148
+ Used only in `standard` and adaptive retry modes. Specifies whether to apply
149
+ a clock skew correction and retry requests with skewed client clocks.
150
+ DOCS
151
+ resolve_correct_clock_skew(cfg)
152
+ end
153
+
154
+ # @api private undocumented
155
+ option(:client_rate_limiter) { Retries::ClientRateLimiter.new }
102
156
 
103
- def expired_credentials?
104
- !!(EXPIRED_CREDS.include?(@name) || @name.match(/expired/i))
157
+ # @api private undocumented
158
+ option(:retry_quota) { Retries::RetryQuota.new }
159
+
160
+ # @api private undocumented
161
+ option(:clock_skew) { Retries::ClockSkew.new }
162
+
163
+ def self.resolve_retry_mode(cfg)
164
+ value = ENV['AWS_RETRY_MODE'] ||
165
+ Aws.shared_config.retry_mode(profile: cfg.profile) ||
166
+ 'legacy'
167
+ # Raise if provided value is not one of the retry modes
168
+ if value != 'legacy' && value != 'standard' && value != 'adaptive'
169
+ raise ArgumentError,
170
+ 'Must provide either `legacy`, `standard`, or `adaptive` for '\
171
+ 'retry_mode profile option or for ENV[\'AWS_RETRY_MODE\']'
105
172
  end
173
+ value
174
+ end
106
175
 
107
- def throttling_error?
108
- !!(THROTTLING_ERRORS.include?(@name) || @name.match(/throttl/i) || @http_status_code == 429)
176
+ def self.resolve_max_attempts(cfg)
177
+ value = ENV['AWS_MAX_ATTEMPTS'] ||
178
+ Aws.shared_config.max_attempts(profile: cfg.profile) ||
179
+ 3
180
+ # Raise if provided value is not a positive integer
181
+ if !value.is_a?(Integer) || value <= 0
182
+ raise ArgumentError,
183
+ 'Must provide a positive integer for max_attempts profile '\
184
+ 'option or for ENV[\'AWS_MAX_ATTEMPTS\']'
109
185
  end
186
+ value
187
+ end
110
188
 
111
- def checksum?
112
- CHECKSUM_ERRORS.include?(@name) || @error.is_a?(Errors::ChecksumError)
189
+ def self.resolve_adaptive_retry_wait_to_fill(cfg)
190
+ value = ENV['AWS_ADAPTIVE_RETRY_WAIT_TO_FILL'] ||
191
+ Aws.shared_config.adaptive_retry_wait_to_fill(profile: cfg.profile) ||
192
+ 'true'
193
+
194
+ # Raise if provided value is not true or false
195
+ if value != 'true' && value != 'false'
196
+ raise ArgumentError,
197
+ 'Must provide either `true` or `false` for '\
198
+ 'adaptive_retry_wait_to_fill profile option or for '\
199
+ 'ENV[\'AWS_ADAPTIVE_RETRY_WAIT_TO_FILL\']'
113
200
  end
114
201
 
115
- def networking?
116
- @error.is_a?(Seahorse::Client::NetworkingError) ||
117
- NETWORKING_ERRORS.include?(@name)
202
+ value == 'true'
203
+ end
204
+
205
+ def self.resolve_correct_clock_skew(cfg)
206
+ value = ENV['AWS_CORRECT_CLOCK_SKEW'] ||
207
+ Aws.shared_config.correct_clock_skew(profile: cfg.profile) ||
208
+ 'true'
209
+
210
+ # Raise if provided value is not true or false
211
+ if value != 'true' && value != 'false'
212
+ raise ArgumentError,
213
+ 'Must provide either `true` or `false` for '\
214
+ 'correct_clock_skew profile option or for '\
215
+ 'ENV[\'AWS_CORRECT_CLOCK_SKEW\']'
118
216
  end
119
217
 
120
- def server?
121
- (500..599).include?(@http_status_code)
218
+ value == 'true'
219
+ end
220
+
221
+ class Handler < Seahorse::Client::Handler
222
+ # Max backoff (in seconds)
223
+ MAX_BACKOFF = 20
224
+
225
+ def call(context)
226
+ context.metadata[:retries] ||= {}
227
+ config = context.config
228
+
229
+ get_send_token(config)
230
+ add_retry_headers(context)
231
+ response = @handler.call(context)
232
+ error_inspector = Retries::ErrorInspector.new(
233
+ response.error, response.context.http_response.status_code
234
+ )
235
+
236
+ request_bookkeeping(context, response, error_inspector)
237
+
238
+ if error_inspector.endpoint_discovery?(context)
239
+ key = config.endpoint_cache.extract_key(context)
240
+ config.endpoint_cache.delete(key)
241
+ end
242
+
243
+ # Clock correction needs to be updated from the response even when
244
+ # the request is not retryable but should only be updated
245
+ # in the case of clock skew errors
246
+ if error_inspector.clock_skew?(context)
247
+ config.clock_skew.update_clock_correction(context)
248
+ end
249
+
250
+ # Estimated skew needs to be updated on every request
251
+ config.clock_skew.update_estimated_skew(context)
252
+
253
+ return response unless retryable?(context, response, error_inspector)
254
+
255
+ return response if context.retries >= config.max_attempts - 1
256
+
257
+ context.metadata[:retries][:capacity_amount] =
258
+ config.retry_quota.checkout_capacity(error_inspector)
259
+ return response unless context.metadata[:retries][:capacity_amount] > 0
260
+
261
+ delay = exponential_backoff(context.retries)
262
+ Kernel.sleep(delay)
263
+ retry_request(context, error_inspector)
122
264
  end
123
265
 
124
- def endpoint_discovery?(context)
125
- return false unless context.operation.endpoint_discovery
266
+ private
126
267
 
127
- if @http_status_code == 421 ||
128
- extract_name(@error) == 'InvalidEndpointException'
129
- @error = Errors::EndpointDiscoveryError.new
268
+ def get_send_token(config)
269
+ # either fail fast or block until a token becomes available
270
+ # must be configurable
271
+ # need a maximum rate at which we can send requests (max_send_rate)
272
+ # is unset until a throttle is seen
273
+ if config.retry_mode == 'adaptive'
274
+ config.client_rate_limiter.token_bucket_acquire(
275
+ 1,
276
+ config.adaptive_retry_wait_to_fill
277
+ )
130
278
  end
279
+ end
131
280
 
132
- # When endpoint discovery error occurs
133
- # evict the endpoint from cache
134
- if @error.is_a?(Errors::EndpointDiscoveryError)
135
- key = context.config.endpoint_cache.extract_key(context)
136
- context.config.endpoint_cache.delete(key)
137
- true
138
- else
139
- false
281
+ # maxsendrate is updated if on adaptive mode and based on response
282
+ # retry quota is updated if the request is successful (both modes)
283
+ def request_bookkeeping(context, response, error_inspector)
284
+ config = context.config
285
+ if response.successful?
286
+ config.retry_quota.release(
287
+ context.metadata[:retries][:capacity_amount]
288
+ )
289
+ end
290
+
291
+ if config.retry_mode == 'adaptive'
292
+ is_throttling_error = error_inspector.throttling_error?
293
+ config.client_rate_limiter.update_sending_rate(is_throttling_error)
140
294
  end
141
295
  end
142
-
143
- def retryable?(context)
144
- (expired_credentials? and refreshable_credentials?(context)) or
145
- throttling_error? or
146
- checksum? or
147
- networking? or
148
- server? or
149
- endpoint_discovery?(context)
296
+
297
+ def retryable?(context, response, error_inspector)
298
+ return false if response.successful?
299
+
300
+ error_inspector.retryable?(context) &&
301
+ context.http_response.body.respond_to?(:truncate)
150
302
  end
151
303
 
152
- private
304
+ def exponential_backoff(retries)
305
+ # for a transient error, use backoff
306
+ [Kernel.rand * 2**retries, MAX_BACKOFF].min
307
+ end
153
308
 
154
- def refreshable_credentials?(context)
155
- context.config.credentials.respond_to?(:refresh!)
309
+ def retry_request(context, error)
310
+ context.retries += 1
311
+ context.config.credentials.refresh! if error.expired_credentials?
312
+ context.http_request.body.rewind
313
+ context.http_response.reset
314
+ call(context)
156
315
  end
157
316
 
158
- def extract_name(error)
159
- if error.is_a?(Errors::ServiceError)
160
- error.class.code
161
- else
162
- error.class.name.to_s
317
+ def add_retry_headers(context)
318
+ request_pairs = {
319
+ 'attempt' => context.retries,
320
+ 'max' => context.config.max_attempts
321
+ }
322
+ if (ttl = compute_request_ttl(context))
323
+ request_pairs['ttl'] = ttl
163
324
  end
325
+
326
+ # create the request header
327
+ formatted_header = request_pairs.map { |k, v| "#{k}=#{v}" }.join('; ')
328
+ context.http_request.headers['amz-sdk-request'] = formatted_header
164
329
  end
165
330
 
331
+ def compute_request_ttl(context)
332
+ return if context.operation.async
333
+
334
+ endpoint = context.http_request.endpoint
335
+ estimated_skew = context.config.clock_skew.estimated_skew(endpoint)
336
+ if context.config.respond_to?(:http_read_timeout)
337
+ read_timeout = context.config.http_read_timeout
338
+ end
339
+
340
+ if estimated_skew && read_timeout
341
+ (Time.now.utc + read_timeout + estimated_skew)
342
+ .strftime('%Y%m%dT%H%M%SZ')
343
+ end
344
+ end
166
345
  end
167
346
 
168
- class Handler < Seahorse::Client::Handler
347
+ class LegacyHandler < Seahorse::Client::Handler
169
348
 
170
349
  def call(context)
171
350
  response = @handler.call(context)
172
351
  if response.error
173
- retry_if_possible(response)
352
+ error_inspector = Retries::ErrorInspector.new(
353
+ response.error, response.context.http_response.status_code
354
+ )
355
+
356
+ if error_inspector.endpoint_discovery?(context)
357
+ key = context.config.endpoint_cache.extract_key(context)
358
+ context.config.endpoint_cache.delete(key)
359
+ end
360
+
361
+ retry_if_possible(response, error_inspector)
174
362
  else
175
363
  response
176
364
  end
@@ -178,21 +366,15 @@ A delay randomiser function used by the default backoff function. Some predefine
178
366
 
179
367
  private
180
368
 
181
- def retry_if_possible(response)
369
+ def retry_if_possible(response, error_inspector)
182
370
  context = response.context
183
- error = error_for(response)
184
- if should_retry?(context, error)
185
- retry_request(context, error)
371
+ if should_retry?(context, error_inspector)
372
+ retry_request(context, error_inspector)
186
373
  else
187
374
  response
188
375
  end
189
376
  end
190
377
 
191
- def error_for(response)
192
- status_code = response.context.http_response.status_code
193
- ErrorInspector.new(response.error, status_code)
194
- end
195
-
196
378
  def retry_request(context, error)
197
379
  delay_retry(context)
198
380
  context.retries += 1
@@ -207,9 +389,9 @@ A delay randomiser function used by the default backoff function. Some predefine
207
389
  end
208
390
 
209
391
  def should_retry?(context, error)
210
- error.retryable?(context) and
211
- context.retries < retry_limit(context) and
212
- response_truncatable?(context)
392
+ error.retryable?(context) &&
393
+ context.retries < retry_limit(context) &&
394
+ response_truncatable?(context)
213
395
  end
214
396
 
215
397
  def retry_limit(context)
@@ -219,15 +401,17 @@ A delay randomiser function used by the default backoff function. Some predefine
219
401
  def response_truncatable?(context)
220
402
  context.http_response.body.respond_to?(:truncate)
221
403
  end
222
-
223
404
  end
224
405
 
225
406
  def add_handlers(handlers, config)
226
- if config.retry_limit > 0
407
+ if config.retry_mode == 'legacy'
408
+ if config.retry_limit > 0
409
+ handlers.add(LegacyHandler, step: :sign, priority: 99)
410
+ end
411
+ else
227
412
  handlers.add(Handler, step: :sign, priority: 99)
228
413
  end
229
414
  end
230
-
231
415
  end
232
416
  end
233
417
  end