diff --git a/.changelog/feature-retry-behavior-2-1-default.json b/.changelog/feature-retry-behavior-2-1-default.json new file mode 100644 index 000000000000..f4b7f982db7b --- /dev/null +++ b/.changelog/feature-retry-behavior-2-1-default.json @@ -0,0 +1,6 @@ +{ + "type": "feature", + "category": "aws-cpp-sdk-core", + "contributor": "pulimsr", + "description": "Make Retry Behavior 2.1 the default retry behavior and remove AWS_NEW_RETRIES_2026. Set AWS_RETRY_MODE=legacy or retry_mode = legacy, or use DefaultRetryStrategy, to keep the previous behavior. Custom retry strategies now also get the Retry Behavior 2.1 request handling, and errors such as LimitExceededException or an HTTP 429 with an unknown error name now report ShouldRetry() as true." +} diff --git a/.cspell.json b/.cspell.json index 34b701bfb9b1..43c20633cb53 100644 --- a/.cspell.json +++ b/.cspell.json @@ -337,6 +337,8 @@ "dtmp", "ytmpe", "dtmpe", - "isleapyear" + "isleapyear", + // ClassifyUnmodeledTooManyRequests + "Unmodeled" ] } diff --git a/generated/src/aws-cpp-sdk-dynamodb/source/DynamoDBClientConfiguration.cpp b/generated/src/aws-cpp-sdk-dynamodb/source/DynamoDBClientConfiguration.cpp index afb004ed24c8..b3b86ef6b43f 100644 --- a/generated/src/aws-cpp-sdk-dynamodb/source/DynamoDBClientConfiguration.cpp +++ b/generated/src/aws-cpp-sdk-dynamodb/source/DynamoDBClientConfiguration.cpp @@ -37,8 +37,13 @@ void DynamoDBClientConfiguration::LoadDynamoDBSpecificConfig(const Aws::String& if (!enableEndpointDiscovery) { enableEndpointDiscovery = IsEndpointDiscoveryEnabled(this->endpointOverride, inputProfileName); } - this->configFactories.retryStrategyCreateFn = []() -> std::shared_ptr { - return Client::InitRetryStrategy(4, "", 0.025); + const Aws::String retryProfileName = this->profileName; + this->configFactories.retryStrategyCreateFn = [retryProfileName]() -> std::shared_ptr { + Client::InitRetryStrategyOptions options; + options.maxAttempts = 4; + options.transientBackoffBaseSec = 0.025; + options.profileName = retryProfileName; + return Client::InitRetryStrategy(options); }; } diff --git a/generated/src/aws-cpp-sdk-dynamodbstreams/source/DynamoDBStreamsClientConfiguration.cpp b/generated/src/aws-cpp-sdk-dynamodbstreams/source/DynamoDBStreamsClientConfiguration.cpp index 0a7aea97952c..cd06eae45213 100644 --- a/generated/src/aws-cpp-sdk-dynamodbstreams/source/DynamoDBStreamsClientConfiguration.cpp +++ b/generated/src/aws-cpp-sdk-dynamodbstreams/source/DynamoDBStreamsClientConfiguration.cpp @@ -9,8 +9,13 @@ namespace Aws { namespace DynamoDBStreams { void DynamoDBStreamsClientConfiguration::LoadDynamoDBStreamsSpecificConfig(const Aws::String& inputProfileName) { - this->configFactories.retryStrategyCreateFn = []() -> std::shared_ptr { - return Client::InitRetryStrategy(4, "", 0.025); + const Aws::String retryProfileName = this->profileName; + this->configFactories.retryStrategyCreateFn = [retryProfileName]() -> std::shared_ptr { + Client::InitRetryStrategyOptions options; + options.maxAttempts = 4; + options.transientBackoffBaseSec = 0.025; + options.profileName = retryProfileName; + return Client::InitRetryStrategy(options); }; #if defined(_MSC_VER) (&reinterpret_cast(inputProfileName)); diff --git a/src/aws-cpp-sdk-core/CMakeLists.txt b/src/aws-cpp-sdk-core/CMakeLists.txt index 939d4e7f8fea..ab319efddcd6 100644 --- a/src/aws-cpp-sdk-core/CMakeLists.txt +++ b/src/aws-cpp-sdk-core/CMakeLists.txt @@ -708,7 +708,12 @@ install (FILES ${AWS_AUTH_SIGNER_HEADERS} DESTINATION ${INCLUDE_DIRECTORY}/aws/c install (FILES ${AWS_AUTH_SIGNER_PROVIDER_HEADERS} DESTINATION ${INCLUDE_DIRECTORY}/aws/core/auth/signer-provider) install (FILES ${AWS_AUTH_BEARER_TOKEN_PROVIDER_HEADERS} DESTINATION ${INCLUDE_DIRECTORY}/aws/core/auth/bearer-token-provider) install (FILES ${AWS_CLIENT_HEADERS} DESTINATION ${INCLUDE_DIRECTORY}/aws/core/client) -install (FILES ${AWS_INTERNAL_HEADERS} DESTINATION ${INCLUDE_DIRECTORY}/aws/core/internal) +set(AWS_INSTALLED_INTERNAL_HEADERS ${AWS_INTERNAL_HEADERS}) +# Used only to build core; not part of the installed SDK. +list(REMOVE_ITEM AWS_INSTALLED_INTERNAL_HEADERS + "${CMAKE_CURRENT_SOURCE_DIR}/include/aws/core/internal/RetryErrorClassification.h" + "${CMAKE_CURRENT_SOURCE_DIR}/include/aws/core/internal/RetryStrategyImpl.h") +install (FILES ${AWS_INSTALLED_INTERNAL_HEADERS} DESTINATION ${INCLUDE_DIRECTORY}/aws/core/internal) install (FILES ${NET_HEADERS} DESTINATION ${INCLUDE_DIRECTORY}/aws/core/net) install (FILES ${HTTP_HEADERS} DESTINATION ${INCLUDE_DIRECTORY}/aws/core/http) install (FILES ${HTTP_STANDARD_HEADERS} DESTINATION ${INCLUDE_DIRECTORY}/aws/core/http/standard) diff --git a/src/aws-cpp-sdk-core/include/aws/core/client/AWSClient.h b/src/aws-cpp-sdk-core/include/aws/core/client/AWSClient.h index a03fd99dac21..27fa6aeaf464 100644 --- a/src/aws-cpp-sdk-core/include/aws/core/client/AWSClient.h +++ b/src/aws-cpp-sdk-core/include/aws/core/client/AWSClient.h @@ -394,7 +394,7 @@ namespace Aws Aws::Client::RequestCompressionConfig m_requestCompressionConfig; std::shared_ptr m_userAgentInterceptor; Aws::Vector> m_interceptors; - bool m_enableNewRetries; + bool m_isLegacyRetryStrategy; bool m_disableExpectHeader; }; diff --git a/src/aws-cpp-sdk-core/include/aws/core/client/AdaptiveRetryStrategy.h b/src/aws-cpp-sdk-core/include/aws/core/client/AdaptiveRetryStrategy.h index 20ef87f400ce..63c4250df80c 100644 --- a/src/aws-cpp-sdk-core/include/aws/core/client/AdaptiveRetryStrategy.h +++ b/src/aws-cpp-sdk-core/include/aws/core/client/AdaptiveRetryStrategy.h @@ -127,6 +127,7 @@ class AWS_CORE_API AdaptiveRetryStrategy : public StandardRetryStrategy */ AdaptiveRetryStrategy(long maxAttempts = 3); AdaptiveRetryStrategy(std::shared_ptr retryQuotaContainer, long maxAttempts = 3); + explicit AdaptiveRetryStrategy(const StandardRetryStrategyOptions& options); /** * Retrieve and consume a send token. diff --git a/src/aws-cpp-sdk-core/include/aws/core/client/ClientConfiguration.h b/src/aws-cpp-sdk-core/include/aws/core/client/ClientConfiguration.h index 9799edfa69ab..a3503961856b 100644 --- a/src/aws-cpp-sdk-core/include/aws/core/client/ClientConfiguration.h +++ b/src/aws-cpp-sdk-core/include/aws/core/client/ClientConfiguration.h @@ -109,7 +109,7 @@ namespace Aws struct ProviderFactories { /** - * Retry Strategy factory method. Default is DefaultRetryStrategy (i.e. exponential backoff). + * Retry Strategy factory method. Default is StandardRetryStrategy. */ std::function()> retryStrategyCreateFn; /** @@ -229,7 +229,7 @@ namespace Aws */ unsigned long lowSpeedLimit = 1; /** - * Strategy to use in case of failed requests. Default is DefaultRetryStrategy (i.e. exponential backoff). + * Strategy to use in case of failed requests. Default is StandardRetryStrategy. * Provide retry strategy here or via a factory method. */ std::shared_ptr retryStrategy = nullptr; @@ -652,12 +652,30 @@ namespace Aws /** * A helper function to initialize a retry strategy. - * Default is DefaultRetryStrategy (i.e. exponential backoff) + * Default is StandardRetryStrategy. Retry mode "legacy" uses DefaultRetryStrategy. */ AWS_CORE_API std::shared_ptr InitRetryStrategy(Aws::String retryMode = ""); AWS_CORE_API std::shared_ptr InitRetryStrategy(int maxRetries, Aws::String retryMode = ""); AWS_CORE_API std::shared_ptr InitRetryStrategy(int maxRetries, Aws::String retryMode, double transientBackoffBaseSec); + struct InitRetryStrategyOptions + { + /** + * Service default. AWS_MAX_ATTEMPTS or max_attempts from the config file override it. + */ + long maxAttempts = 3; + /** + * Empty uses AWS_RETRY_MODE, then retry_mode from the config file, then "standard". + */ + Aws::String retryMode; + double transientBackoffBaseSec = 0.05; + /** + * Config file profile to read retry_mode and max_attempts from. Empty uses the default profile. + */ + Aws::String profileName; + }; + AWS_CORE_API std::shared_ptr InitRetryStrategy(const InitRetryStrategyOptions& options); + /** * A helper function to compute a user agent * @return Aws::String with a user-agent diff --git a/src/aws-cpp-sdk-core/include/aws/core/client/RetryStrategy.h b/src/aws-cpp-sdk-core/include/aws/core/client/RetryStrategy.h index 272817b287b4..95fbaca6015f 100644 --- a/src/aws-cpp-sdk-core/include/aws/core/client/RetryStrategy.h +++ b/src/aws-cpp-sdk-core/include/aws/core/client/RetryStrategy.h @@ -119,9 +119,20 @@ namespace Aws int m_retryQuota; }; + struct StandardRetryStrategyOptions + { + long maxAttempts = 3; + double transientBackoffBaseSec = 0.05; + /** + * When null, the strategy creates its own retry quota container. + */ + std::shared_ptr retryQuotaContainer; + }; + class AWS_CORE_API StandardRetryStrategy : public RetryStrategy { public: + explicit StandardRetryStrategy(const StandardRetryStrategyOptions& options); StandardRetryStrategy(long maxAttempts = 3); StandardRetryStrategy(std::shared_ptr retryQuotaContainer, long maxAttempts = 3); StandardRetryStrategy(long maxAttempts, double transientBackoffBaseSec); @@ -138,14 +149,12 @@ namespace Aws const char* GetStrategyName() const override { return "standard";} - class RetryImpl; - protected: std::shared_ptr m_retryQuotaContainer; long m_maxAttempts; private: - Aws::UniquePtr m_impl; + double m_transientBackoffBaseSec; }; } // namespace Client } // namespace Aws diff --git a/src/aws-cpp-sdk-core/include/aws/core/internal/RetryErrorClassification.h b/src/aws-cpp-sdk-core/include/aws/core/internal/RetryErrorClassification.h new file mode 100644 index 000000000000..4db652dd5cbf --- /dev/null +++ b/src/aws-cpp-sdk-core/include/aws/core/internal/RetryErrorClassification.h @@ -0,0 +1,32 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ + +#pragma once + +#include +#include +#include + +namespace Aws +{ + namespace Client + { + namespace Internal + { + // Error names the core error mapper registers for standard and adaptive retry modes. + // Legacy retry mode does not retry them. + static const char* const STANDARD_ONLY_THROTTLING_ERROR_NAMES[] = { + "TooManyRequestsException", "ProvisionedThroughputExceededException", "TransactionInProgressException", + "RequestLimitExceeded", "BandwidthLimitExceeded", "LimitExceededException", + "PriorRequestNotComplete", "EC2ThrottledException"}; + + static const char* const STANDARD_ONLY_TRANSIENT_ERROR_NAMES[] = {"IDPCommunicationError"}; + + AWS_CORE_LOCAL bool IsStandardOnlyRetryableError(const AWSError& error); + + AWS_CORE_LOCAL void ClassifyUnmodeledTooManyRequests(AWSError& error); + } // namespace Internal + } // namespace Client +} // namespace Aws diff --git a/src/aws-cpp-sdk-core/include/aws/core/internal/RetryStrategyImpl.h b/src/aws-cpp-sdk-core/include/aws/core/internal/RetryStrategyImpl.h index b845281f6e98..32b7e39ed264 100644 --- a/src/aws-cpp-sdk-core/include/aws/core/internal/RetryStrategyImpl.h +++ b/src/aws-cpp-sdk-core/include/aws/core/internal/RetryStrategyImpl.h @@ -73,5 +73,10 @@ namespace Aws mutable Aws::Utils::Threading::ReaderWriterLock m_retryQuotaLock; int m_retryQuota; }; + + namespace Internal + { + AWS_CORE_LOCAL bool IsLegacyRetryStrategy(const RetryStrategy* retryStrategy); + } // namespace Internal } // namespace Client } // namespace Aws diff --git a/src/aws-cpp-sdk-core/include/smithy/client/AwsSmithyClientBase.h b/src/aws-cpp-sdk-core/include/smithy/client/AwsSmithyClientBase.h index 761e9d7f1933..20b18446aa63 100644 --- a/src/aws-cpp-sdk-core/include/smithy/client/AwsSmithyClientBase.h +++ b/src/aws-cpp-sdk-core/include/smithy/client/AwsSmithyClientBase.h @@ -115,8 +115,7 @@ namespace client Aws::MakeShared("AwsSmithyClientBase", m_httpClient->IsDefaultAwsHttpClient() ? Aws::Client::HttpClientChunkedMode::DEFAULT : m_clientConfig->httpClientChunkedMode, m_clientConfig->awsChunkedBufferSize) - }), - m_enableNewRetries{Aws::Utils::StringUtils::ToLower(Aws::Environment::GetEnv("AWS_NEW_RETRIES_2026").c_str()) == "true"} + }) { baseInit(); @@ -267,7 +266,7 @@ namespace client ResponseHandlerFunc&& responseHandler, EndpointUpdateCallback&& endpointCallback ) const; - bool m_enableNewRetries = false; + bool m_isLegacyRetryStrategy = false; }; } // namespace client } // namespace smithy diff --git a/src/aws-cpp-sdk-core/source/client/AWSClient.cpp b/src/aws-cpp-sdk-core/source/client/AWSClient.cpp index ae6fb9365798..82b4486a1289 100644 --- a/src/aws-cpp-sdk-core/source/client/AWSClient.cpp +++ b/src/aws-cpp-sdk-core/source/client/AWSClient.cpp @@ -14,6 +14,8 @@ #include #include #include +#include +#include #include #include #include @@ -177,7 +179,7 @@ AWSClient::AWSClient(const Aws::Client::ClientConfiguration& configuration, m_httpClient->IsDefaultAwsHttpClient() ? Aws::Client::HttpClientChunkedMode::DEFAULT : configuration.httpClientChunkedMode, configuration.awsChunkedBufferSize), m_userAgentInterceptor}, - m_enableNewRetries{Aws::Utils::StringUtils::ToLower(Aws::Environment::GetEnv("AWS_NEW_RETRIES_2026").c_str()) == "true"}, + m_isLegacyRetryStrategy{Aws::Client::Internal::IsLegacyRetryStrategy(m_retryStrategy.get())}, m_disableExpectHeader(configuration.disableExpectHeader) { } @@ -209,7 +211,7 @@ AWSClient::AWSClient(const Aws::Client::ClientConfiguration& configuration, m_httpClient->IsDefaultAwsHttpClient() ? Aws::Client::HttpClientChunkedMode::DEFAULT : configuration.httpClientChunkedMode, configuration.awsChunkedBufferSize), m_userAgentInterceptor}, - m_enableNewRetries{Aws::Utils::StringUtils::ToLower(Aws::Environment::GetEnv("AWS_NEW_RETRIES_2026").c_str()) == "true"}, + m_isLegacyRetryStrategy{Aws::Client::Internal::IsLegacyRetryStrategy(m_retryStrategy.get())}, m_disableExpectHeader(configuration.disableExpectHeader) { } @@ -305,7 +307,7 @@ HttpResponseOutcome AWSClient::AttemptExhaustively(const Aws::Http::URI& uri, Aws::String invocationId = Aws::Utils::UUID::PseudoRandomUUID(); RequestInfo requestInfo; requestInfo.attempt = 1; - requestInfo.maxAttempts = m_enableNewRetries ? m_retryStrategy->GetMaxAttempts() : 0; + requestInfo.maxAttempts = m_isLegacyRetryStrategy ? 0 : m_retryStrategy->GetMaxAttempts(); httpRequest->SetHeaderValue(Http::SDK_INVOCATION_ID_HEADER, invocationId); httpRequest->SetHeaderValue(Http::SDK_REQUEST_HEADER, requestInfo); AppendRecursionDetectionHeader(httpRequest); @@ -399,7 +401,7 @@ HttpResponseOutcome AWSClient::AttemptExhaustively(const Aws::Http::URI& uri, { const bool blockedByQuotaOnly = outcome.GetError().ShouldRetry() && retries + 1 < m_retryStrategy->GetMaxAttempts(); - if (m_enableNewRetries && blockedByQuotaOnly && request.IsLongPollingOperation()) { + if (!m_isLegacyRetryStrategy && blockedByQuotaOnly && request.IsLongPollingOperation()) { m_httpClient->RetryRequestSleep(std::chrono::milliseconds(sleepMillis)); } break; @@ -490,7 +492,7 @@ HttpResponseOutcome AWSClient::AttemptExhaustively(const Aws::Http::URI& uri, Aws::String invocationId = Aws::Utils::UUID::PseudoRandomUUID(); RequestInfo requestInfo; requestInfo.attempt = 1; - requestInfo.maxAttempts = m_enableNewRetries ? m_retryStrategy->GetMaxAttempts() : 0; + requestInfo.maxAttempts = m_isLegacyRetryStrategy ? 0 : m_retryStrategy->GetMaxAttempts(); httpRequest->SetHeaderValue(Http::SDK_INVOCATION_ID_HEADER, invocationId); httpRequest->SetHeaderValue(Http::SDK_REQUEST_HEADER, requestInfo); AppendRecursionDetectionHeader(httpRequest); @@ -674,6 +676,7 @@ HttpResponseOutcome AWSClient::AttemptOneRequest(const std::shared_ptrGetResponseBody(), httpResponse->GetHeaders())) @@ -728,6 +731,7 @@ HttpResponseOutcome AWSClient::AttemptOneRequest(const std::shared_ptrmonitoringContexts = Aws::Monitoring::OnRequestStarted(this->GetServiceClientName(), request.GetServiceRequestName(), context->httpRequest); context->attemptSkew = m_clientSkew->Load(); context->requestInfo.attempt = 1; - context->requestInfo.maxAttempts = m_enableNewRetries ? m_retryStrategy->GetMaxAttempts() : 0; + context->requestInfo.maxAttempts = m_isLegacyRetryStrategy ? 0 : m_retryStrategy->GetMaxAttempts(); context->httpRequest->SetHeaderValue(Http::SDK_INVOCATION_ID_HEADER, context->invocationId); context->httpRequest->SetHeaderValue(Http::SDK_REQUEST_HEADER, context->requestInfo); AppendRecursionDetectionHeader(context->httpRequest); @@ -888,7 +892,9 @@ Http::HttpClient::AttemptOutcome AWSClient::OnResponseReceivedAsync(const std::s if (DoesResponseGenerateError(httpResponse)) { AWS_LOGSTREAM_DEBUG(AWS_CLIENT_LOG_TAG, "Request returned error. Attempting to generate appropriate error codes from response"); - return OnAttemptCompleteAsync(context, HttpResponseOutcome(BuildAWSError(httpResponse))); + auto error = BuildAWSError(httpResponse); + Aws::Client::Internal::ClassifyUnmodeledTooManyRequests(error); + return OnAttemptCompleteAsync(context, HttpResponseOutcome(std::move(error))); } if (request.HasEmbeddedError(httpResponse->GetResponseBody(), httpResponse->GetHeaders())) @@ -989,7 +995,7 @@ Http::HttpClient::AttemptOutcome AWSClient::OnAttemptCompleteAsync(const std::sh const bool blockedByQuotaOnly = outcome.GetError().ShouldRetry() && context->retries + 1 < m_retryStrategy->GetMaxAttempts(); context->outcome = std::move(outcome); - if (m_enableNewRetries && blockedByQuotaOnly && request.IsLongPollingOperation()) { + if (!m_isLegacyRetryStrategy && blockedByQuotaOnly && request.IsLongPollingOperation()) { return {false, std::chrono::milliseconds(sleepMillis)}; } return {}; diff --git a/src/aws-cpp-sdk-core/source/client/AWSErrorMarshaller.cpp b/src/aws-cpp-sdk-core/source/client/AWSErrorMarshaller.cpp index cb96216945b0..1768fb9423b1 100644 --- a/src/aws-cpp-sdk-core/source/client/AWSErrorMarshaller.cpp +++ b/src/aws-cpp-sdk-core/source/client/AWSErrorMarshaller.cpp @@ -11,6 +11,7 @@ #include #include #include +#include using namespace Aws::Utils::Logging; using namespace Aws::Utils::Json; @@ -104,6 +105,7 @@ AWSError JsonErrorMarshaller::BuildAWSError(const std::shared_ptrGetHeaders()); error.SetResponseCode(httpResponse->GetResponseCode()); + Aws::Client::Internal::ClassifyUnmodeledTooManyRequests(error); error.SetRemoteHostIpAddress(httpResponse->GetOriginatingRequest().GetResolvedRemoteHost()); AWS_LOGSTREAM_ERROR(AWS_ERROR_MARSHALLER_LOG_TAG, error); return error; @@ -197,6 +199,7 @@ AWSError XmlErrorMarshaller::BuildAWSError(const std::shared_ptrGetHeaders()); error.SetResponseCode(httpResponse->GetResponseCode()); + Aws::Client::Internal::ClassifyUnmodeledTooManyRequests(error); error.SetRemoteHostIpAddress(httpResponse->GetOriginatingRequest().GetResolvedRemoteHost()); AWS_LOGSTREAM_ERROR(AWS_ERROR_MARSHALLER_LOG_TAG, error); return error; @@ -522,6 +525,7 @@ AWSError RpcV2ErrorMarshaller::BuildAWSError(const std::shared_ptrGetHeaders()); error.SetResponseCode(httpResponse->GetResponseCode()); + Aws::Client::Internal::ClassifyUnmodeledTooManyRequests(error); error.SetRemoteHostIpAddress(httpResponse->GetOriginatingRequest().GetResolvedRemoteHost()); AWS_LOGSTREAM_ERROR(AWS_ERROR_MARSHALLER_LOG_TAG, error); return error; diff --git a/src/aws-cpp-sdk-core/source/client/AdaptiveRetryStrategy.cpp b/src/aws-cpp-sdk-core/source/client/AdaptiveRetryStrategy.cpp index 863d5dcf6894..e973baf94a1c 100644 --- a/src/aws-cpp-sdk-core/source/client/AdaptiveRetryStrategy.cpp +++ b/src/aws-cpp-sdk-core/source/client/AdaptiveRetryStrategy.cpp @@ -162,6 +162,10 @@ namespace Aws StandardRetryStrategy(retryQuotaContainer, maxAttempts) {} + AdaptiveRetryStrategy::AdaptiveRetryStrategy(const StandardRetryStrategyOptions& options) : + StandardRetryStrategy(options) + {} + bool AdaptiveRetryStrategy::HasSendToken() { return m_retryTokenBucket.Acquire(1, m_fastFail); diff --git a/src/aws-cpp-sdk-core/source/client/ClientConfiguration.cpp b/src/aws-cpp-sdk-core/source/client/ClientConfiguration.cpp index d4d831693782..7a20aec59bf3 100644 --- a/src/aws-cpp-sdk-core/source/client/ClientConfiguration.cpp +++ b/src/aws-cpp-sdk-core/source/client/ClientConfiguration.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -86,6 +87,91 @@ static T LoadEnumFromString(const std::array, N>& mapp return mapping->second; } +namespace +{ +Aws::String GetRetryConfigValue(const Aws::String& profileName, const Aws::String& key) +{ + return profileName.empty() ? Aws::Config::GetCachedConfigValue(key) : Aws::Config::GetCachedConfigValue(profileName, key); +} + +Aws::String ResolveRetryMode(Aws::String retryMode, const Aws::String& profileName) { + if (retryMode.empty()) + { + retryMode = Aws::Environment::GetEnv("AWS_RETRY_MODE"); + } + if (retryMode.empty()) + { + retryMode = GetRetryConfigValue(profileName, "retry_mode"); + } + if (retryMode.empty()) + { + retryMode = "standard"; + } + retryMode = Aws::Utils::StringUtils::ToLower(retryMode.c_str()); + return retryMode == "default" ? "legacy" : retryMode; +} + +int ResolveConfiguredMaxAttempts(const Aws::String& profileName) +{ + Aws::String maxAttemptsString = Aws::Environment::GetEnv("AWS_MAX_ATTEMPTS"); + if (maxAttemptsString.empty()) + { + maxAttemptsString = GetRetryConfigValue(profileName, "max_attempts"); + } + // In case users specify 0 explicitly to disable retry. + if (maxAttemptsString == "0") + { + return 0; + } + int maxAttempts = static_cast(Aws::Utils::StringUtils::ConvertToInt32(maxAttemptsString.c_str())); + if (maxAttempts == 0) + { + AWS_LOGSTREAM_INFO(CLIENT_CONFIG_TAG, "Retry Strategy will use the default max attempts."); + return -1; + } + return maxAttempts; +} + +std::shared_ptr CreateRetryStrategy(int maxAttempts, const Aws::String& retryMode) +{ + std::shared_ptr retryStrategy; + if (retryMode == "legacy") + { + retryStrategy = Aws::MakeShared(CLIENT_CONFIG_TAG); + } + else if (retryMode == "adaptive") + { + if (maxAttempts < 0) + { + // negative value set above force usage of default max attempts + retryStrategy = Aws::MakeShared(CLIENT_CONFIG_TAG); + } + else + { + retryStrategy = Aws::MakeShared(CLIENT_CONFIG_TAG, maxAttempts); + } + } + else + { + if (retryMode != "standard") + { + AWS_LOGSTREAM_WARN(CLIENT_CONFIG_TAG, "Unknown retry mode \"" << retryMode << "\", using standard retry mode."); + } + if (maxAttempts < 0) + { + // negative value set above force usage of default max attempts + retryStrategy = Aws::MakeShared(CLIENT_CONFIG_TAG); + } + else + { + retryStrategy = Aws::MakeShared(CLIENT_CONFIG_TAG, maxAttempts); + } + } + + return retryStrategy; +} +} // namespace + ClientConfiguration::ProviderFactories ClientConfiguration::ProviderFactories::defaultFactories = []() { ProviderFactories factories; @@ -496,6 +582,12 @@ ClientConfiguration::ClientConfiguration(const char* profile, bool shouldDisable this->profileName = Aws::String(profile); } this->credentialProviderConfig.profile = this->profileName; + const Aws::String retryProfileName = this->profileName; + this->configFactories.retryStrategyCreateFn = [retryProfileName]() { + InitRetryStrategyOptions options; + options.profileName = retryProfileName; + return InitRetryStrategy(options); + }; setLegacyClientConfigurationParameters(*this); setConfigFromEnvOrProfile(*this); // Call EC2 Instance Metadata service only once @@ -571,107 +663,46 @@ ClientConfiguration::ClientConfiguration(bool /*useSmartDefaults*/, const char* Aws::Config::Defaults::SetSmartDefaultsConfigurationParameters(*this, defaultMode, hasEc2MetadataRegion, ec2MetadataRegion); } -static Aws::String ResolveRetryMode(Aws::String retryMode) { - if (retryMode.empty()) - { - retryMode = Aws::Environment::GetEnv("AWS_RETRY_MODE"); - } - if (retryMode.empty()) - { - retryMode = Aws::Config::GetCachedConfigValue("retry_mode"); - } - if (Aws::Utils::StringUtils::ToLower(Aws::Environment::GetEnv("AWS_NEW_RETRIES_2026").c_str()) == "true" && retryMode.empty()) - { - retryMode = "standard"; - } - return retryMode; -} - std::shared_ptr InitRetryStrategy(int maxAttempts, Aws::String retryMode) { - retryMode = ResolveRetryMode(retryMode); - - std::shared_ptr retryStrategy; - if (retryMode == "standard") - { - if (maxAttempts < 0) - { - // negative value set above force usage of default max attempts - retryStrategy = Aws::MakeShared(CLIENT_CONFIG_TAG); - } - else - { - retryStrategy = Aws::MakeShared(CLIENT_CONFIG_TAG, maxAttempts); - } - } - else if (retryMode == "adaptive") - { - if (maxAttempts < 0) - { - // negative value set above force usage of default max attempts - retryStrategy = Aws::MakeShared(CLIENT_CONFIG_TAG); - } - else - { - retryStrategy = Aws::MakeShared(CLIENT_CONFIG_TAG, maxAttempts); - } - } - else - { - retryStrategy = Aws::MakeShared(CLIENT_CONFIG_TAG); - } - - return retryStrategy; + return CreateRetryStrategy(maxAttempts, ResolveRetryMode(retryMode, "")); } -static int ResolveConfiguredMaxAttempts() -{ - Aws::String maxAttemptsString = Aws::Environment::GetEnv("AWS_MAX_ATTEMPTS"); - if (maxAttemptsString.empty()) - { - maxAttemptsString = Aws::Config::GetCachedConfigValue("max_attempts"); - } - // In case users specify 0 explicitly to disable retry. - if (maxAttemptsString == "0") - { - return 0; - } - int maxAttempts = static_cast(Aws::Utils::StringUtils::ConvertToInt32(maxAttemptsString.c_str())); - if (maxAttempts == 0) - { - AWS_LOGSTREAM_INFO(CLIENT_CONFIG_TAG, "Retry Strategy will use the default max attempts."); - return -1; - } - return maxAttempts; +std::shared_ptr InitRetryStrategy(int maxAttempts, Aws::String retryMode, double transientBackoffBaseSec) { + InitRetryStrategyOptions options; + options.maxAttempts = maxAttempts; + options.retryMode = std::move(retryMode); + options.transientBackoffBaseSec = transientBackoffBaseSec; + return InitRetryStrategy(options); } -std::shared_ptr InitRetryStrategy(int maxAttempts, Aws::String retryMode, double transientBackoffBaseSec) { - if (Aws::Utils::StringUtils::ToLower(Aws::Environment::GetEnv("AWS_NEW_RETRIES_2026").c_str()) != "true") +std::shared_ptr InitRetryStrategy(const InitRetryStrategyOptions& options) { + const Aws::String retryMode = ResolveRetryMode(options.retryMode, options.profileName); + if (retryMode == "legacy") { - // Gate is off: ignore service-specific tuning, use default behavior - return InitRetryStrategy(retryMode); + return Aws::MakeShared(CLIENT_CONFIG_TAG); } + StandardRetryStrategyOptions strategyOptions; // maxAttempts is a service default. A max attempts value the customer configured wins. - const int configuredMaxAttempts = ResolveConfiguredMaxAttempts(); - if (configuredMaxAttempts >= 0) + const int configuredMaxAttempts = ResolveConfiguredMaxAttempts(options.profileName); + strategyOptions.maxAttempts = configuredMaxAttempts >= 0 ? configuredMaxAttempts : options.maxAttempts; + strategyOptions.transientBackoffBaseSec = options.transientBackoffBaseSec; + if (retryMode == "adaptive") { - maxAttempts = configuredMaxAttempts; + return Aws::MakeShared(CLIENT_CONFIG_TAG, strategyOptions); } - - // Only standard mode honors a service-specific backoff base; other modes are unaffected, - // so they go through the default-base path. - if (ResolveRetryMode(retryMode) != "standard") + if (retryMode != "standard") { - return InitRetryStrategy(maxAttempts, retryMode); + AWS_LOGSTREAM_WARN(CLIENT_CONFIG_TAG, "Unknown retry mode \"" << retryMode << "\", using standard retry mode."); } - - long attempts = static_cast(maxAttempts); - return Aws::MakeShared(CLIENT_CONFIG_TAG, attempts, transientBackoffBaseSec); + return Aws::MakeShared(CLIENT_CONFIG_TAG, strategyOptions); } std::shared_ptr InitRetryStrategy(Aws::String retryMode) { - return InitRetryStrategy(ResolveConfiguredMaxAttempts(), retryMode); + InitRetryStrategyOptions options; + options.retryMode = std::move(retryMode); + return InitRetryStrategy(options); } Aws::String ClientConfiguration::LoadConfigFromEnvOrProfile(const Aws::String& envKey, diff --git a/src/aws-cpp-sdk-core/source/client/CoreErrors.cpp b/src/aws-cpp-sdk-core/source/client/CoreErrors.cpp index 62114d4c3279..8260b9ae5c71 100644 --- a/src/aws-cpp-sdk-core/source/client/CoreErrors.cpp +++ b/src/aws-cpp-sdk-core/source/client/CoreErrors.cpp @@ -5,7 +5,7 @@ #include #include -#include +#include #include #include @@ -34,9 +34,7 @@ void CoreErrorsMapper::InitCoreErrorsMapper() } s_CoreErrorsMapper = Aws::New("InitCoreErrorsMapper"); - const bool newRetriesEnabled = Aws::Utils::StringUtils::ToLower(Aws::Environment::GetEnv("AWS_NEW_RETRIES_2026").c_str()) == "true"; - const RetryableType throttleType = - newRetriesEnabled ? RetryableType::RETRYABLE_THROTTLING : RetryableType::RETRYABLE; + const RetryableType throttleType = RetryableType::RETRYABLE_THROTTLING; s_CoreErrorsMapper->emplace("IncompleteSignature", AWSError(CoreErrors::INCOMPLETE_SIGNATURE, false)); s_CoreErrorsMapper->emplace("IncompleteSignatureException", AWSError(CoreErrors::INCOMPLETE_SIGNATURE, false)); @@ -94,16 +92,13 @@ void CoreErrorsMapper::InitCoreErrorsMapper() s_CoreErrorsMapper->emplace("RequestTimeTooSkewed", AWSError(CoreErrors::REQUEST_TIME_TOO_SKEWED, true)); s_CoreErrorsMapper->emplace("RequestTimeoutException", AWSError(CoreErrors::REQUEST_TIMEOUT, true)); s_CoreErrorsMapper->emplace("RequestTimeout", AWSError(CoreErrors::REQUEST_TIMEOUT, true)); - if (newRetriesEnabled) { - s_CoreErrorsMapper->emplace("TooManyRequestsException", AWSError(CoreErrors::THROTTLING, RetryableType::RETRYABLE_THROTTLING)); - s_CoreErrorsMapper->emplace("ProvisionedThroughputExceededException", AWSError(CoreErrors::THROTTLING, RetryableType::RETRYABLE_THROTTLING)); - s_CoreErrorsMapper->emplace("TransactionInProgressException", AWSError(CoreErrors::THROTTLING, RetryableType::RETRYABLE_THROTTLING)); - s_CoreErrorsMapper->emplace("RequestLimitExceeded", AWSError(CoreErrors::THROTTLING, RetryableType::RETRYABLE_THROTTLING)); - s_CoreErrorsMapper->emplace("BandwidthLimitExceeded", AWSError(CoreErrors::THROTTLING, RetryableType::RETRYABLE_THROTTLING)); - s_CoreErrorsMapper->emplace("LimitExceededException", AWSError(CoreErrors::THROTTLING, RetryableType::RETRYABLE_THROTTLING)); - s_CoreErrorsMapper->emplace("PriorRequestNotComplete", AWSError(CoreErrors::THROTTLING, RetryableType::RETRYABLE_THROTTLING)); - s_CoreErrorsMapper->emplace("EC2ThrottledException", AWSError(CoreErrors::THROTTLING, RetryableType::RETRYABLE_THROTTLING)); - s_CoreErrorsMapper->emplace("IDPCommunicationError", AWSError(CoreErrors::NETWORK_CONNECTION, true)); + for (const char* name : Aws::Client::Internal::STANDARD_ONLY_THROTTLING_ERROR_NAMES) + { + s_CoreErrorsMapper->emplace(name, AWSError(CoreErrors::THROTTLING, RetryableType::RETRYABLE_THROTTLING)); + } + for (const char* name : Aws::Client::Internal::STANDARD_ONLY_TRANSIENT_ERROR_NAMES) + { + s_CoreErrorsMapper->emplace(name, AWSError(CoreErrors::NETWORK_CONNECTION, true)); } } @@ -138,7 +133,7 @@ AWS_CORE_API AWSError CoreErrorsMapper::GetErrorForHttpResponseCode( error = AWSError(CoreErrors::RESOURCE_NOT_FOUND, retryable); break; case HttpResponseCode::TOO_MANY_REQUESTS: - error = AWSError(CoreErrors::SLOW_DOWN, retryable); + error = AWSError(CoreErrors::SLOW_DOWN, RetryableType::RETRYABLE_THROTTLING); break; case HttpResponseCode::INTERNAL_SERVER_ERROR: error = AWSError(CoreErrors::INTERNAL_FAILURE, retryable); @@ -165,6 +160,41 @@ AWS_CORE_API AWSError CoreErrorsMapper::GetErrorForHttpResponseCode( return error; } +bool Aws::Client::Internal::IsStandardOnlyRetryableError(const AWSError& error) +{ + const CoreErrors errorType = error.GetErrorType(); + const Aws::String& name = error.GetExceptionName(); + if (errorType == CoreErrors::THROTTLING) + { + for (const char* standardOnlyName : STANDARD_ONLY_THROTTLING_ERROR_NAMES) + { + if (name == standardOnlyName) + { + return true; + } + } + } + if (errorType == CoreErrors::NETWORK_CONNECTION) + { + for (const char* standardOnlyName : STANDARD_ONLY_TRANSIENT_ERROR_NAMES) + { + if (name == standardOnlyName) + { + return true; + } + } + } + return error.GetResponseCode() == HttpResponseCode::TOO_MANY_REQUESTS && errorType == CoreErrors::UNKNOWN && !name.empty(); +} + +void Aws::Client::Internal::ClassifyUnmodeledTooManyRequests(AWSError& error) +{ + if (error.GetResponseCode() == HttpResponseCode::TOO_MANY_REQUESTS && error.GetErrorType() == CoreErrors::UNKNOWN) + { + error.SetRetryableType(RetryableType::RETRYABLE_THROTTLING); + } +} + /** * Overload ostream operator<< for CoreErrors enum class for a prettier output such as "103" and not "<67-00 00-00>" */ diff --git a/src/aws-cpp-sdk-core/source/client/DefaultRetryStrategy.cpp b/src/aws-cpp-sdk-core/source/client/DefaultRetryStrategy.cpp index 405d7566cfb7..268cfc4d424e 100644 --- a/src/aws-cpp-sdk-core/source/client/DefaultRetryStrategy.cpp +++ b/src/aws-cpp-sdk-core/source/client/DefaultRetryStrategy.cpp @@ -6,6 +6,7 @@ #include #include +#include #include using namespace Aws; @@ -16,6 +17,9 @@ bool DefaultRetryStrategy::ShouldRetry(const AWSError& error, long a if (attemptedRetries >= m_maxRetries) return false; + if (Internal::IsStandardOnlyRetryableError(error)) + return false; + return error.ShouldRetry(); } diff --git a/src/aws-cpp-sdk-core/source/client/RetryStrategy.cpp b/src/aws-cpp-sdk-core/source/client/RetryStrategy.cpp index 1b2689ab8fdc..a4f94f19a995 100644 --- a/src/aws-cpp-sdk-core/source/client/RetryStrategy.cpp +++ b/src/aws-cpp-sdk-core/source/client/RetryStrategy.cpp @@ -7,7 +7,6 @@ #include #include #include -#include #include #include #include @@ -16,93 +15,44 @@ using namespace Aws::Utils::Threading; using namespace Aws::Client; -namespace Aws -{ - namespace Client - { - class StandardRetryStrategy::RetryImpl - { - public: - virtual ~RetryImpl() = default; - virtual long CalculateDelay(const AWSError& error, long attemptedRetries) const = 0; - }; - } -} - namespace { const char RETRY_STRATEGY_TAG[] = "StandardRetryStrategy"; - const double DEFAULT_TRANSIENT_BACKOFF_BASE_SEC = 0.05; - - bool IsNewRetriesEnabled() + std::shared_ptr QuotaContainerOrDefault(std::shared_ptr retryQuotaContainer) { - return Aws::Utils::StringUtils::ToLower(Aws::Environment::GetEnv("AWS_NEW_RETRIES_2026").c_str()) == "true"; + return retryQuotaContainer ? retryQuotaContainer : Aws::MakeShared(RETRY_STRATEGY_TAG); } - class LegacyRetryImpl : public StandardRetryStrategy::RetryImpl + StandardRetryStrategyOptions MakeOptions(long maxAttempts, std::shared_ptr retryQuotaContainer) { - public: - long CalculateDelay(const AWSError& error, long attemptedRetries) const override - { - AWS_UNREFERENCED_PARAM(error); - // Maximum left shift factor is capped by ceil(log2(max_delay)), to avoid wrap-around and overflow into negative values: - return (std::min)(static_cast(Aws::Utils::GetRandomValue() % 1000) * (1 << (std::min)(attemptedRetries, 15L)), 20000); - } - }; - - class NewRetriesImpl : public StandardRetryStrategy::RetryImpl - { - public: - explicit NewRetriesImpl(double transientBackoffBaseSec) : m_transientBackoffBaseSec(transientBackoffBaseSec) {} - - long CalculateDelay(const AWSError& error, long attemptedRetries) const override - { - double x = error.ShouldThrottle() ? 1.0 : m_transientBackoffBaseSec; - double exponentialPart = x * static_cast(1L << (std::min)(attemptedRetries, 30L)); - double cappedPart = (std::min)(exponentialPart, 20.0); - - double b = static_cast(Aws::Utils::GetRandomValue() % 10000) / 10000.0; - double t_i = b * cappedPart; - - const auto& headers = error.GetResponseHeaders(); - auto it = headers.find("x-amz-retry-after"); - if (it != headers.end()) - { - long long headerMs = Aws::Utils::StringUtils::ConvertToInt64(it->second.c_str()); - if (headerMs >= 0) - { - double headerSec = static_cast(headerMs) / 1000.0; - double clamped = (std::max)(t_i, (std::min)(headerSec, 5.0 + t_i)); - return static_cast(clamped * 1000.0); - } - AWS_LOGSTREAM_DEBUG(RETRY_STRATEGY_TAG, "Ignoring invalid x-amz-retry-after value: " << it->second); - } - - return static_cast(t_i * 1000.0); - } - - private: - double m_transientBackoffBaseSec; - }; + StandardRetryStrategyOptions options; + options.maxAttempts = maxAttempts; + options.retryQuotaContainer = std::move(retryQuotaContainer); + return options; + } - Aws::UniquePtr CreateRetryImpl(double transientBackoffBaseSec) + StandardRetryStrategyOptions MakeOptions(long maxAttempts, double transientBackoffBaseSec) { - if (IsNewRetriesEnabled()) - { - return Aws::MakeUnique("StandardRetryStrategy", transientBackoffBaseSec); - } - return Aws::MakeUnique("StandardRetryStrategy"); + StandardRetryStrategyOptions options; + options.maxAttempts = maxAttempts; + options.transientBackoffBaseSec = transientBackoffBaseSec; + return options; } +} // anonymous namespace - std::shared_ptr CreateQuotaContainer() +namespace Aws +{ + namespace Client { - if (IsNewRetriesEnabled()) + namespace Internal { - return Aws::MakeShared("StandardRetryStrategy"); - } - return Aws::MakeShared("StandardRetryStrategy"); - } -} // anonymous namespace + bool IsLegacyRetryStrategy(const RetryStrategy* retryStrategy) + { + return retryStrategy && Aws::String("default") == retryStrategy->GetStrategyName(); + } + } // namespace Internal + } // namespace Client +} // namespace Aws namespace Aws { @@ -112,17 +62,18 @@ namespace Aws static const int RETRY_COST = 5; static const int TIMEOUT_RETRY_COST = 10; + StandardRetryStrategy::StandardRetryStrategy(const StandardRetryStrategyOptions& options) + : m_retryQuotaContainer(QuotaContainerOrDefault(options.retryQuotaContainer)), m_maxAttempts(options.maxAttempts), + m_transientBackoffBaseSec(options.transientBackoffBaseSec) {} + StandardRetryStrategy::StandardRetryStrategy(long maxAttempts) - : m_retryQuotaContainer(CreateQuotaContainer()), m_maxAttempts(maxAttempts), - m_impl(CreateRetryImpl(DEFAULT_TRANSIENT_BACKOFF_BASE_SEC)) {} + : StandardRetryStrategy(MakeOptions(maxAttempts, nullptr)) {} StandardRetryStrategy::StandardRetryStrategy(std::shared_ptr retryQuotaContainer, long maxAttempts) - : m_retryQuotaContainer(retryQuotaContainer), m_maxAttempts(maxAttempts), - m_impl(CreateRetryImpl(DEFAULT_TRANSIENT_BACKOFF_BASE_SEC)) {} + : StandardRetryStrategy(MakeOptions(maxAttempts, std::move(retryQuotaContainer))) {} StandardRetryStrategy::StandardRetryStrategy(long maxAttempts, double transientBackoffBaseSec) - : m_retryQuotaContainer(CreateQuotaContainer()), m_maxAttempts(maxAttempts), - m_impl(CreateRetryImpl(transientBackoffBaseSec)) {} + : StandardRetryStrategy(MakeOptions(maxAttempts, transientBackoffBaseSec)) {} StandardRetryStrategy::~StandardRetryStrategy() = default; @@ -155,7 +106,28 @@ namespace Aws long StandardRetryStrategy::CalculateDelayBeforeNextRetry(const AWSError& error, long attemptedRetries) const { - return m_impl->CalculateDelay(error, attemptedRetries); + double x = error.ShouldThrottle() ? 1.0 : m_transientBackoffBaseSec; + double exponentialPart = x * static_cast(1L << (std::min)(attemptedRetries, 30L)); + double cappedPart = (std::min)(exponentialPart, 20.0); + + double b = static_cast(Aws::Utils::GetRandomValue() % 10000) / 10000.0; + double t_i = b * cappedPart; + + const auto& headers = error.GetResponseHeaders(); + auto it = headers.find("x-amz-retry-after"); + if (it != headers.end()) + { + long long headerMs = Aws::Utils::StringUtils::ConvertToInt64(it->second.c_str()); + if (headerMs >= 0) + { + double headerSec = static_cast(headerMs) / 1000.0; + double clamped = (std::max)(t_i, (std::min)(headerSec, 5.0 + t_i)); + return static_cast(clamped * 1000.0); + } + AWS_LOGSTREAM_DEBUG(RETRY_STRATEGY_TAG, "Ignoring invalid x-amz-retry-after value: " << it->second); + } + + return static_cast(t_i * 1000.0); } DefaultRetryQuotaContainer::DefaultRetryQuotaContainer() : m_retryQuota(INITIAL_RETRY_TOKENS) diff --git a/src/aws-cpp-sdk-core/source/config/defaults/ClientConfigurationDefaults.cpp b/src/aws-cpp-sdk-core/source/config/defaults/ClientConfigurationDefaults.cpp index 7b54066fb3fc..c71900542ded 100644 --- a/src/aws-cpp-sdk-core/source/config/defaults/ClientConfigurationDefaults.cpp +++ b/src/aws-cpp-sdk-core/source/config/defaults/ClientConfigurationDefaults.cpp @@ -115,33 +115,26 @@ namespace Aws return "standard"; } - void SetLegacyClientConfiguration(Aws::Client::ClientConfiguration& clientConfig) - { - clientConfig.retryStrategy = Aws::Client::InitRetryStrategy("default"); - } + void SetLegacyClientConfiguration(Aws::Client::ClientConfiguration& /*clientConfig*/) {} void SetStandardClientConfiguration(Aws::Client::ClientConfiguration& clientConfig) { clientConfig.connectTimeoutMs = 3100; - clientConfig.retryStrategy = Aws::Client::InitRetryStrategy("standard"); } void SetInRegionClientConfiguration(Aws::Client::ClientConfiguration& clientConfig) { clientConfig.connectTimeoutMs = 1100; - clientConfig.retryStrategy = Aws::Client::InitRetryStrategy("standard"); } void SetCrossRegionClientConfiguration(Aws::Client::ClientConfiguration& clientConfig) { clientConfig.connectTimeoutMs = 3100; - clientConfig.retryStrategy = Aws::Client::InitRetryStrategy("standard"); } void SetMobileClientConfiguration(Aws::Client::ClientConfiguration& clientConfig) { clientConfig.connectTimeoutMs = 30000; - clientConfig.retryStrategy = Aws::Client::InitRetryStrategy("standard"); } Aws::String ResolveDefaultModeName(const Aws::Client::ClientConfiguration& clientConfig, diff --git a/src/aws-cpp-sdk-core/source/smithy/client/AwsSmithyClientBase.cpp b/src/aws-cpp-sdk-core/source/smithy/client/AwsSmithyClientBase.cpp index 3cfa55bdd4d0..a1587ed1933c 100644 --- a/src/aws-cpp-sdk-core/source/smithy/client/AwsSmithyClientBase.cpp +++ b/src/aws-cpp-sdk-core/source/smithy/client/AwsSmithyClientBase.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -73,6 +74,7 @@ void AwsSmithyClientBase::baseInit() { AWS_CHECK_PTR(AWS_SMITHY_CLIENT_LOG, m_clientConfig); m_clientSkew = Aws::MakeShared(AWS_SMITHY_CLIENT_LOG, std::chrono::milliseconds(0)); createFromFactories(m_clientConfig->retryStrategy, m_clientConfig->configFactories.retryStrategyCreateFn); + m_isLegacyRetryStrategy = Aws::Client::Internal::IsLegacyRetryStrategy(m_clientConfig->retryStrategy.get()); createFromFactories(m_clientConfig->executor, m_clientConfig->configFactories.executorCreateFn); createFromFactories(m_clientConfig->writeRateLimiter, m_clientConfig->configFactories.writeRateLimiterCreateFn); createFromFactories(m_clientConfig->readRateLimiter, m_clientConfig->configFactories.readRateLimiterCreateFn); @@ -90,6 +92,7 @@ void AwsSmithyClientBase::baseCopyInit() { AWS_CHECK_PTR(AWS_SMITHY_CLIENT_LOG, m_clientConfig); m_clientSkew = Aws::MakeShared(AWS_SMITHY_CLIENT_LOG, std::chrono::milliseconds(0)); createFromFactoriesIfPresent(m_clientConfig->retryStrategy, m_clientConfig->configFactories.retryStrategyCreateFn); + m_isLegacyRetryStrategy = Aws::Client::Internal::IsLegacyRetryStrategy(m_clientConfig->retryStrategy.get()); createFromFactoriesIfPresent(m_clientConfig->executor, m_clientConfig->configFactories.executorCreateFn); createFromFactoriesIfPresent(m_clientConfig->writeRateLimiter, m_clientConfig->configFactories.writeRateLimiterCreateFn); createFromFactoriesIfPresent(m_clientConfig->readRateLimiter, m_clientConfig->configFactories.readRateLimiterCreateFn); @@ -108,7 +111,7 @@ void AwsSmithyClientBase::baseCopyAssign(const AwsSmithyClientBase& other, std::shared_ptr errorMarshaller) { m_serviceName = other.m_serviceName; m_serviceUserAgentName = other.m_serviceUserAgentName; - m_enableNewRetries = other.m_enableNewRetries; + m_isLegacyRetryStrategy = other.m_isLegacyRetryStrategy; m_httpClient = std::move(httpClient); m_errorMarshaller = std::move(errorMarshaller); @@ -130,7 +133,7 @@ void AwsSmithyClientBase::baseMoveAssign(AwsSmithyClientBase&& other) { m_errorMarshaller = std::move(other.m_errorMarshaller); m_interceptors = std::move(other.m_interceptors); m_userAgentInterceptor = std::move(other.m_userAgentInterceptor); - m_enableNewRetries = other.m_enableNewRetries; + m_isLegacyRetryStrategy = other.m_isLegacyRetryStrategy; } std::shared_ptr @@ -337,7 +340,7 @@ std::shared_ptr AwsSmithyClientBase::CreateR } pRequestCtx->m_requestInfo.attempt = 1; pRequestCtx->m_attemptSkew = m_clientSkew->Load(); - pRequestCtx->m_requestInfo.maxAttempts = m_enableNewRetries ? m_clientConfig->retryStrategy->GetMaxAttempts() : 0; + pRequestCtx->m_requestInfo.maxAttempts = m_isLegacyRetryStrategy ? 0 : m_clientConfig->retryStrategy->GetMaxAttempts(); pRequestCtx->m_interceptorContext = Aws::MakeShared(AWS_SMITHY_CLIENT_LOG, *request); pRequestCtx->m_responseHandler = std::move(responseHandler); pRequestCtx->m_authResolvedCallback = std::move(authCallback); @@ -711,7 +714,7 @@ Aws::Http::HttpClient::AttemptOutcome AwsSmithyClientBase::HandleAsyncReply(std: { const bool blockedByQuotaOnly = outcome.GetError().ShouldRetry() && static_cast(pRequestCtx->m_retryCount) + 1 < m_clientConfig->retryStrategy->GetMaxAttempts(); - if (m_enableNewRetries && blockedByQuotaOnly && pRequestCtx->m_pRequest && pRequestCtx->m_pRequest->IsLongPollingOperation()) { + if (!m_isLegacyRetryStrategy && blockedByQuotaOnly && pRequestCtx->m_pRequest && pRequestCtx->m_pRequest->IsLongPollingOperation()) { giveUpDelay = std::chrono::milliseconds(sleepMillis); } break; diff --git a/tests/aws-cpp-sdk-core-tests/aws/client/AWSClientTest.cpp b/tests/aws-cpp-sdk-core-tests/aws/client/AWSClientTest.cpp index f1c224f09a5b..d5f2f7935060 100644 --- a/tests/aws-cpp-sdk-core-tests/aws/client/AWSClientTest.cpp +++ b/tests/aws-cpp-sdk-core-tests/aws/client/AWSClientTest.cpp @@ -490,7 +490,6 @@ static AmazonWebServiceRequestMock MakeLongPollingRequest() // SEP Test Case 12: long-polling, retryable error, retry quota exhausted - stops without a normal retry. TEST_F(AWSClientTestSuite, LongPollingQuotaExhausted) { - Aws::Environment::EnvironmentRAII env{{{"AWS_NEW_RETRIES_2026", "true"}}}; ClientConfiguration config; auto quota = Aws::MakeShared(ALLOCATION_TAG); config.retryStrategy = Aws::MakeShared(ALLOCATION_TAG, quota); @@ -509,7 +508,6 @@ TEST_F(AWSClientTestSuite, LongPollingQuotaExhausted) // SEP Test Case 14: long-polling, retryable error, stops at max attempts. TEST_F(AWSClientTestSuite, LongPollingMaxAttemptsExceeded) { - Aws::Environment::EnvironmentRAII env{{{"AWS_NEW_RETRIES_2026", "true"}}}; ClientConfiguration config; auto quota = Aws::MakeShared(ALLOCATION_TAG); config.retryStrategy = Aws::MakeShared(ALLOCATION_TAG, quota); @@ -530,7 +528,6 @@ TEST_F(AWSClientTestSuite, LongPollingMaxAttemptsExceeded) // SEP Test Case 15: long-polling, retryable error then success. TEST_F(AWSClientTestSuite, LongPollingRetriesThenSucceeds) { - Aws::Environment::EnvironmentRAII env{{{"AWS_NEW_RETRIES_2026", "true"}}}; ClientConfiguration config; auto quota = Aws::MakeShared(ALLOCATION_TAG); config.retryStrategy = Aws::MakeShared(ALLOCATION_TAG, quota); @@ -549,7 +546,6 @@ TEST_F(AWSClientTestSuite, LongPollingRetriesThenSucceeds) // SEP Test Case 16: long-polling, non-retryable error, fails without retrying. TEST_F(AWSClientTestSuite, LongPollingNonRetryableError) { - Aws::Environment::EnvironmentRAII env{{{"AWS_NEW_RETRIES_2026", "true"}}}; ClientConfiguration config; auto quota = Aws::MakeShared(ALLOCATION_TAG); config.retryStrategy = Aws::MakeShared(ALLOCATION_TAG, quota); diff --git a/tests/aws-cpp-sdk-core-tests/aws/client/AWSErrorMashallerTest.cpp b/tests/aws-cpp-sdk-core-tests/aws/client/AWSErrorMashallerTest.cpp index a4f234474a89..e8ff8903cc08 100644 --- a/tests/aws-cpp-sdk-core-tests/aws/client/AWSErrorMashallerTest.cpp +++ b/tests/aws-cpp-sdk-core-tests/aws/client/AWSErrorMashallerTest.cpp @@ -6,6 +6,15 @@ #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include #include @@ -1375,4 +1384,70 @@ TEST_F(AWSErrorMarshallerTest, TestRpcV2ErrorMarshallerWithPrefix) ASSERT_EQ(message, error.GetMessage()); ASSERT_EQ(requestId, error.GetRequestId()); ASSERT_FALSE(error.ShouldRetry()); -} \ No newline at end of file +} + +namespace +{ + std::shared_ptr WithResponseCode(Aws::UniquePtr response, Aws::Http::HttpResponseCode code) + { + response->SetResponseCode(code); + return std::shared_ptr(std::move(response)); + } + + class JsonClientUnderTest : public AWSJsonClient + { + public: + explicit JsonClientUnderTest(const ClientConfiguration& configuration) + : AWSJsonClient(configuration, Aws::MakeShared(ERROR_MARSHALLER_TEST_ALLOC_TAG), + Aws::MakeShared(ERROR_MARSHALLER_TEST_ALLOC_TAG)) {} + using AWSJsonClient::MakeRequest; + }; +} + +TEST_F(AWSErrorMarshallerTest, RpcV2UnmodeledTooManyRequestsIsRetryableThrottling) +{ + RpcV2ErrorMarshaller awsErrorMarshaller; + + auto unknownName = awsErrorMarshaller.BuildAWSError(WithResponseCode( + BuildHttpCborResponse("SomeUnknownException", "slow down", "Request Id"), Aws::Http::HttpResponseCode::TOO_MANY_REQUESTS)); + ASSERT_TRUE(unknownName.ShouldRetry()); + ASSERT_TRUE(unknownName.ShouldThrottle()); + + // A modeled error keeps its own classification. + auto modeled = awsErrorMarshaller.BuildAWSError(WithResponseCode( + BuildHttpCborResponse("IncompleteSignatureException", "no", "Request Id"), Aws::Http::HttpResponseCode::TOO_MANY_REQUESTS)); + ASSERT_FALSE(modeled.ShouldRetry()); +} + +TEST_F(AWSErrorMarshallerTest, ProtocolClientUnmodeledTooManyRequestsIsRetryableThrottling) +{ + Aws::Environment::EnvironmentRAII env{{{"AWS_EC2_METADATA_DISABLED", "true"}, {"AWS_REGION", "us-east-1"}}}; + auto mockHttpClient = Aws::MakeShared(ERROR_MARSHALLER_TEST_ALLOC_TAG); + auto mockHttpClientFactory = Aws::MakeShared(ERROR_MARSHALLER_TEST_ALLOC_TAG); + mockHttpClientFactory->SetClient(mockHttpClient); + Aws::Http::SetHttpClientFactory(mockHttpClientFactory); + { + ClientConfiguration configuration; + configuration.retryStrategy = Aws::MakeShared(ERROR_MARSHALLER_TEST_ALLOC_TAG, 1); + JsonClientUnderTest client(configuration); + const Aws::Http::URI uri("http://www.uri.com"); + AmazonWebServiceRequestMock request; + + auto bodylessRequest = Aws::Http::CreateHttpRequest(uri, Aws::Http::HttpMethod::HTTP_POST, Aws::Utils::Stream::DefaultResponseStreamFactoryMethod); + auto bodyless = Aws::MakeShared(ERROR_MARSHALLER_TEST_ALLOC_TAG, bodylessRequest); + bodyless->SetResponseCode(Aws::Http::HttpResponseCode::TOO_MANY_REQUESTS); + mockHttpClient->AddResponseToReturn(bodyless); + auto bodylessOutcome = client.MakeRequest(uri, request, Aws::Http::HttpMethod::HTTP_POST, Aws::Auth::NULL_SIGNER); + ASSERT_FALSE(bodylessOutcome.IsSuccess()); + ASSERT_TRUE(bodylessOutcome.GetError().ShouldThrottle()); + + mockHttpClient->AddResponseToReturn(WithResponseCode( + BuildHttpResponse("SomeUnknownException", "slow down", "Request Id"), Aws::Http::HttpResponseCode::TOO_MANY_REQUESTS)); + auto unknownNameOutcome = client.MakeRequest(uri, request, Aws::Http::HttpMethod::HTTP_POST, Aws::Auth::NULL_SIGNER); + ASSERT_FALSE(unknownNameOutcome.IsSuccess()); + ASSERT_TRUE(unknownNameOutcome.GetError().ShouldRetry()); + ASSERT_TRUE(unknownNameOutcome.GetError().ShouldThrottle()); + } + Aws::Http::CleanupHttp(); + Aws::Http::InitHttp(); +} diff --git a/tests/aws-cpp-sdk-core-tests/aws/client/AsyncAWSClientTest.cpp b/tests/aws-cpp-sdk-core-tests/aws/client/AsyncAWSClientTest.cpp index 383461a2c08b..73e98e2ade35 100644 --- a/tests/aws-cpp-sdk-core-tests/aws/client/AsyncAWSClientTest.cpp +++ b/tests/aws-cpp-sdk-core-tests/aws/client/AsyncAWSClientTest.cpp @@ -170,7 +170,6 @@ namespace TEST_F(AsyncAWSClientTest, LongPollingQuotaExhaustedDelaysCompletion) { - Aws::Environment::EnvironmentRAII env{{{"AWS_NEW_RETRIES_2026", "true"}}}; ClientConfiguration config; auto quota = Aws::MakeShared(ALLOCATION_TAG); config.retryStrategy = Aws::MakeShared(ALLOCATION_TAG, quota, LONG_POLL_DELAY_MS); @@ -190,7 +189,6 @@ namespace TEST_F(AsyncAWSClientTest, NonLongPollingQuotaExhaustedCompletesImmediately) { - Aws::Environment::EnvironmentRAII env{{{"AWS_NEW_RETRIES_2026", "true"}}}; ClientConfiguration config; auto quota = Aws::MakeShared(ALLOCATION_TAG); config.retryStrategy = Aws::MakeShared(ALLOCATION_TAG, quota, LONG_POLL_DELAY_MS); diff --git a/tests/aws-cpp-sdk-core-tests/aws/client/RetryStrategyTest.cpp b/tests/aws-cpp-sdk-core-tests/aws/client/RetryStrategyTest.cpp index 8f8290e7bc40..e09d09564896 100644 --- a/tests/aws-cpp-sdk-core-tests/aws/client/RetryStrategyTest.cpp +++ b/tests/aws-cpp-sdk-core-tests/aws/client/RetryStrategyTest.cpp @@ -7,8 +7,10 @@ #include #include #include +#include #include #include +#include #include #include #include @@ -16,6 +18,7 @@ #include #include #include +#include #include using namespace Aws::Client; @@ -71,8 +74,8 @@ TEST_F(RetryStrategyTest, TestDefaultRetryQuotaContainer) TEST_F(RetryStrategyTest, TestStandardRetryStrategy) { - AWSError requestTimeoutError(CoreErrors::REQUEST_TIMEOUT, true); - AWSError retryableError(CoreErrors::NETWORK_CONNECTION, true); + AWSError transientError(CoreErrors::NETWORK_CONNECTION, true); + AWSError throttlingError(CoreErrors::THROTTLING, RetryableType::RETRYABLE_THROTTLING); AWSError nonRetryableError(CoreErrors::INCOMPLETE_SIGNATURE, false); MockStandardRetryStrategy retryStrategy; @@ -88,59 +91,55 @@ TEST_F(RetryStrategyTest, TestStandardRetryStrategy) // Non-retryable error. ASSERT_FALSE(retryStrategy.ShouldRetry(nonRetryableError, 0)); // Hit attempt limit. - ASSERT_FALSE(retryStrategy.ShouldRetry(retryableError, 2)); + ASSERT_FALSE(retryStrategy.ShouldRetry(transientError, 2)); - // Acquire 5 tokens, remains 495. - ASSERT_TRUE(retryStrategy.ShouldRetry(retryableError, 0)); - ASSERT_EQ(495, retryStrategy.GetRetryQuotaContainer()->GetRetryQuota()); + // Acquire 14 tokens, remains 486. + ASSERT_TRUE(retryStrategy.ShouldRetry(transientError, 0)); + ASSERT_EQ(486, retryStrategy.GetRetryQuotaContainer()->GetRetryQuota()); - // Acquire 10 tokens, remains 485. - ASSERT_TRUE(retryStrategy.ShouldRetry(requestTimeoutError, 0)); - ASSERT_EQ(485, retryStrategy.GetRetryQuotaContainer()->GetRetryQuota()); + // Acquire 5 tokens, remains 481. + ASSERT_TRUE(retryStrategy.ShouldRetry(throttlingError, 0)); + ASSERT_EQ(481, retryStrategy.GetRetryQuotaContainer()->GetRetryQuota()); - // Acquire 478 tokens, remains 7. - ASSERT_TRUE(retryStrategy.GetRetryQuotaContainer()->AcquireRetryQuota(478)); - ASSERT_EQ(7, retryStrategy.GetRetryQuotaContainer()->GetRetryQuota()); + // Acquire 470 tokens, remains 11. + ASSERT_TRUE(retryStrategy.GetRetryQuotaContainer()->AcquireRetryQuota(470)); + ASSERT_EQ(11, retryStrategy.GetRetryQuotaContainer()->GetRetryQuota()); - // Not able to retry with request timeout error with insufficient tokens. - ASSERT_FALSE(retryStrategy.ShouldRetry(requestTimeoutError, 0)); + // Not able to retry a transient error with insufficient tokens. + ASSERT_FALSE(retryStrategy.ShouldRetry(transientError, 0)); - // Still able to retry with other errors. Acquire 5 tokens, remains 2. - ASSERT_TRUE(retryStrategy.ShouldRetry(retryableError, 0)); - ASSERT_EQ(2, retryStrategy.GetRetryQuotaContainer()->GetRetryQuota()); + // Still able to retry a throttling error. Acquire 5 tokens, remains 6. + ASSERT_TRUE(retryStrategy.ShouldRetry(throttlingError, 0)); + ASSERT_EQ(6, retryStrategy.GetRetryQuotaContainer()->GetRetryQuota()); - // Release 5 tokens, remains 7. - retryStrategy.RequestBookkeeping(httpResponseOutcome, retryableError); - ASSERT_EQ(7, retryStrategy.GetRetryQuotaContainer()->GetRetryQuota()); + // Release 14 tokens, remains 20. + retryStrategy.RequestBookkeeping(httpResponseOutcome, transientError); + ASSERT_EQ(20, retryStrategy.GetRetryQuotaContainer()->GetRetryQuota()); - // Release 10 tokens, remains 17. - retryStrategy.RequestBookkeeping(httpResponseOutcome, requestTimeoutError); - ASSERT_EQ(17, retryStrategy.GetRetryQuotaContainer()->GetRetryQuota()); + // Release 5 tokens, remains 25. + retryStrategy.RequestBookkeeping(httpResponseOutcome, throttlingError); + ASSERT_EQ(25, retryStrategy.GetRetryQuotaContainer()->GetRetryQuota()); - // Release 1 token, remains 18. + // Release 1 token, remains 26. retryStrategy.RequestBookkeeping(httpResponseOutcome); - ASSERT_EQ(18, retryStrategy.GetRetryQuotaContainer()->GetRetryQuota()); + ASSERT_EQ(26, retryStrategy.GetRetryQuotaContainer()->GetRetryQuota()); - // Release 480 tokens, remains 498 - retryStrategy.GetRetryQuotaContainer()->ReleaseRetryQuota(480); - ASSERT_EQ(498, retryStrategy.GetRetryQuotaContainer()->GetRetryQuota()); + // Release 470 tokens, remains 496. + retryStrategy.GetRetryQuotaContainer()->ReleaseRetryQuota(470); + ASSERT_EQ(496, retryStrategy.GetRetryQuotaContainer()->GetRetryQuota()); - // Hit the max token capicity, remains 500. - retryStrategy.RequestBookkeeping(httpResponse, retryableError); + // Hit the max token capacity, remains 500. + retryStrategy.RequestBookkeeping(httpResponseOutcome, transientError); ASSERT_EQ(500, retryStrategy.GetRetryQuotaContainer()->GetRetryQuota()); - // Hit the max token capicity, not able to release more tokens. - retryStrategy.RequestBookkeeping(httpResponse, requestTimeoutError); + // Hit the max token capacity, not able to release more tokens. + retryStrategy.RequestBookkeeping(httpResponseOutcome, throttlingError); ASSERT_EQ(500, retryStrategy.GetRetryQuotaContainer()->GetRetryQuota()); } -class NewRetriesStrategyTest : public Aws::Testing::AwsCppSdkGTestSuite -{ - Aws::Environment::EnvironmentRAII m_env{{{"AWS_NEW_RETRIES_2026", "true"}}}; -}; // SEP Test Case 1 -TEST_F(NewRetriesStrategyTest, TransientRetryCost) +TEST_F(RetryStrategyTest, TransientRetryCost) { MockStandardRetryStrategy retryStrategy; AWSError transientError(CoreErrors::NETWORK_CONNECTION, true); @@ -155,7 +154,7 @@ TEST_F(NewRetriesStrategyTest, TransientRetryCost) } // SEP Test Case 3 -TEST_F(NewRetriesStrategyTest, QuotaExhaustionWithNewCosts) +TEST_F(RetryStrategyTest, QuotaExhaustionWithNewCosts) { MockStandardRetryStrategy retryStrategy; AWSError transientError(CoreErrors::NETWORK_CONNECTION, true); @@ -169,7 +168,7 @@ TEST_F(NewRetriesStrategyTest, QuotaExhaustionWithNewCosts) } // SEP Test Case 5 -TEST_F(NewRetriesStrategyTest, ExponentialBackoffTransient) +TEST_F(RetryStrategyTest, ExponentialBackoffTransient) { MockStandardRetryStrategy retryStrategy; AWSError transientError(CoreErrors::NETWORK_CONNECTION, true); @@ -185,7 +184,7 @@ TEST_F(NewRetriesStrategyTest, ExponentialBackoffTransient) } // SEP Test Case 6 -TEST_F(NewRetriesStrategyTest, MaxBackoffCap) +TEST_F(RetryStrategyTest, MaxBackoffCap) { MockStandardRetryStrategy retryStrategy; AWSError transientError(CoreErrors::NETWORK_CONNECTION, true); @@ -196,7 +195,7 @@ TEST_F(NewRetriesStrategyTest, MaxBackoffCap) } // SEP Test Case 8 -TEST_F(NewRetriesStrategyTest, QuotaRecoveryOnSuccess) +TEST_F(RetryStrategyTest, QuotaRecoveryOnSuccess) { MockStandardRetryStrategy retryStrategy; AWSError transientError(CoreErrors::NETWORK_CONNECTION, true); @@ -215,7 +214,7 @@ TEST_F(NewRetriesStrategyTest, QuotaRecoveryOnSuccess) } // SEP Test Case 9 -TEST_F(NewRetriesStrategyTest, ThrottlingRetryCost) +TEST_F(RetryStrategyTest, ThrottlingRetryCost) { MockStandardRetryStrategy retryStrategy; AWSError throttlingError(CoreErrors::THROTTLING, RetryableType::RETRYABLE_THROTTLING); @@ -227,7 +226,7 @@ TEST_F(NewRetriesStrategyTest, ThrottlingRetryCost) } // SEP Test Case 10 -TEST_F(NewRetriesStrategyTest, ExponentialBackoffThrottling) +TEST_F(RetryStrategyTest, ExponentialBackoffThrottling) { MockStandardRetryStrategy retryStrategy; AWSError throttlingError(CoreErrors::THROTTLING, RetryableType::RETRYABLE_THROTTLING); @@ -243,7 +242,7 @@ TEST_F(NewRetriesStrategyTest, ExponentialBackoffThrottling) } // SEP Test Case 17 -TEST_F(NewRetriesStrategyTest, RetryAfterHeaderHonored) +TEST_F(RetryStrategyTest, RetryAfterHeaderHonored) { MockStandardRetryStrategy retryStrategy; AWSError error(CoreErrors::NETWORK_CONNECTION, true); @@ -257,7 +256,7 @@ TEST_F(NewRetriesStrategyTest, RetryAfterHeaderHonored) } // SEP Test Case 18 -TEST_F(NewRetriesStrategyTest, RetryAfterFloorClamped) +TEST_F(RetryStrategyTest, RetryAfterFloorClamped) { MockStandardRetryStrategy retryStrategy; AWSError error(CoreErrors::NETWORK_CONNECTION, true); @@ -271,7 +270,7 @@ TEST_F(NewRetriesStrategyTest, RetryAfterFloorClamped) } // SEP Test Case 19 -TEST_F(NewRetriesStrategyTest, RetryAfterCeilingClamped) +TEST_F(RetryStrategyTest, RetryAfterCeilingClamped) { MockStandardRetryStrategy retryStrategy; AWSError error(CoreErrors::NETWORK_CONNECTION, true); @@ -285,7 +284,7 @@ TEST_F(NewRetriesStrategyTest, RetryAfterCeilingClamped) } // SEP Test Case 20 -TEST_F(NewRetriesStrategyTest, InvalidRetryAfterFallsBack) +TEST_F(RetryStrategyTest, InvalidRetryAfterFallsBack) { MockStandardRetryStrategy retryStrategy; AWSError error(CoreErrors::NETWORK_CONNECTION, true); @@ -299,7 +298,7 @@ TEST_F(NewRetriesStrategyTest, InvalidRetryAfterFallsBack) } // SEP Test Case 11 - DynamoDB tuning: 25ms non-throttling backoff base -TEST_F(NewRetriesStrategyTest, DynamoDBBackoffBase) +TEST_F(RetryStrategyTest, DynamoDBBackoffBase) { MockStandardRetryStrategy retryStrategy(0.025); AWSError transientError(CoreErrors::NETWORK_CONNECTION, true); @@ -327,26 +326,24 @@ class InitRetryStrategyTest : public Aws::Testing::AwsCppSdkGTestSuite void TearDown() override { Aws::Config::ReloadCachedConfigFile(); } }; -TEST_F(InitRetryStrategyTest, GateOffUnsetRetryModeUsesDefaultRetryStrategy) +TEST_F(InitRetryStrategyTest, UnsetRetryModeUsesStandardRetryStrategy) { - Aws::Environment::EnvironmentRAII env{{{"AWS_NEW_RETRIES_2026", ""}, - {"AWS_RETRY_MODE", ""}, + Aws::Environment::EnvironmentRAII env{{{"AWS_RETRY_MODE", ""}, {"AWS_MAX_ATTEMPTS", ""}, {"AWS_CONFIG_FILE", "/nonexistent/retry-strategy-test-config"}}}; Aws::Config::ReloadCachedConfigFile(); auto retryStrategy = InitRetryStrategy(); ASSERT_TRUE(retryStrategy); - ASSERT_STREQ("default", retryStrategy->GetStrategyName()); - ASSERT_EQ(11, retryStrategy->GetMaxAttempts()); + ASSERT_STREQ("standard", retryStrategy->GetStrategyName()); + ASSERT_EQ(3, retryStrategy->GetMaxAttempts()); } TEST_F(InitRetryStrategyTest, ConfiguredMaxAttemptsOverridesServiceDefault) { for (const auto& testCase : {std::make_pair("", 4L), std::make_pair("2", 2L), std::make_pair("0", 0L)}) { - Aws::Environment::EnvironmentRAII env{{{"AWS_NEW_RETRIES_2026", "true"}, - {"AWS_RETRY_MODE", ""}, + Aws::Environment::EnvironmentRAII env{{{"AWS_RETRY_MODE", ""}, {"AWS_MAX_ATTEMPTS", testCase.first}, {"AWS_CONFIG_FILE", "/nonexistent/retry-strategy-test-config"}}}; Aws::Config::ReloadCachedConfigFile(); @@ -356,3 +353,191 @@ TEST_F(InitRetryStrategyTest, ConfiguredMaxAttemptsOverridesServiceDefault) ASSERT_EQ(testCase.second, retryStrategy->GetMaxAttempts()) << "AWS_MAX_ATTEMPTS=" << testCase.first; } } + +namespace +{ + Aws::Vector> RetryEnv(const char* retryMode, const char* maxAttempts = "") + { + return {{"AWS_RETRY_MODE", retryMode}, + {"AWS_MAX_ATTEMPTS", maxAttempts}, + {"AWS_CONFIG_FILE", "/nonexistent/retry-strategy-test-config"}}; + } + + AWSError NamedError(CoreErrors type, const Aws::String& name, HttpResponseCode code = HttpResponseCode::BAD_REQUEST) + { + AWSError error(type, name, "", true); + error.SetResponseCode(code); + return error; + } + + std::shared_ptr JsonErrorResponse(HttpResponseCode code, const Aws::String& body) + { + auto request = CreateHttpRequest(URI("http://www.uri.com"), HttpMethod::HTTP_POST, Aws::Utils::Stream::DefaultResponseStreamFactoryMethod); + auto response = Aws::MakeShared(ALLOCATION_TAG, request); + response->SetResponseCode(code); + response->GetResponseBody() << body; + return response; + } +} + +TEST_F(InitRetryStrategyTest, LegacyRetryModeUsesDefaultRetryStrategy) +{ + for (const char* retryMode : {"legacy", "LEGACY"}) + { + Aws::Environment::EnvironmentRAII env{RetryEnv(retryMode, "5")}; + Aws::Config::ReloadCachedConfigFile(); + + auto retryStrategy = InitRetryStrategy(); + ASSERT_STREQ("default", retryStrategy->GetStrategyName()) << retryMode; + // Legacy mode keeps its previous max attempts. + ASSERT_EQ(11, retryStrategy->GetMaxAttempts()) << retryMode; + } +} + +TEST_F(InitRetryStrategyTest, UnknownRetryModeUsesStandardRetryStrategy) +{ + Aws::Environment::EnvironmentRAII env{RetryEnv("bogus")}; + Aws::Config::ReloadCachedConfigFile(); + + ASSERT_STREQ("standard", InitRetryStrategy()->GetStrategyName()); +} + +TEST_F(InitRetryStrategyTest, LegacyRetryModeDoesNotRetryRetry21OnlyErrors) +{ + Aws::Environment::EnvironmentRAII env{RetryEnv("legacy")}; + Aws::Config::ReloadCachedConfigFile(); + auto retryStrategy = InitRetryStrategy(); + + ASSERT_FALSE(retryStrategy->ShouldRetry(NamedError(CoreErrors::THROTTLING, "TooManyRequestsException"), 0)); + ASSERT_FALSE(retryStrategy->ShouldRetry(NamedError(CoreErrors::NETWORK_CONNECTION, "IDPCommunicationError"), 0)); + ASSERT_FALSE(retryStrategy->ShouldRetry(NamedError(CoreErrors::UNKNOWN, "SomeUnknownException", HttpResponseCode::TOO_MANY_REQUESTS), 0)); + + ASSERT_TRUE(retryStrategy->ShouldRetry(NamedError(CoreErrors::THROTTLING, "ThrottlingException"), 0)); + ASSERT_TRUE(retryStrategy->ShouldRetry(NamedError(CoreErrors::INTERNAL_FAILURE, "InternalFailure"), 0)); +} + +TEST_F(InitRetryStrategyTest, ServiceTunedAdaptiveStrategyUsesServiceBackoffBase) +{ + Aws::Environment::EnvironmentRAII env{RetryEnv("adaptive")}; + Aws::Config::ReloadCachedConfigFile(); + + auto retryStrategy = InitRetryStrategy(4, "", 0.025); + ASSERT_STREQ("adaptive", retryStrategy->GetStrategyName()); + ASSERT_EQ(4, retryStrategy->GetMaxAttempts()); + AWSError transientError(CoreErrors::SERVICE_UNAVAILABLE, true); + for (int i = 0; i < 100; ++i) + { + ASSERT_LE(retryStrategy->CalculateDelayBeforeNextRetry(transientError, 0), 25); + } +} + +TEST_F(InitRetryStrategyTest, ServiceTunedStrategyInLegacyModeUsesDefaultRetryStrategy) +{ + Aws::Environment::EnvironmentRAII env{RetryEnv("legacy")}; + Aws::Config::ReloadCachedConfigFile(); + + auto retryStrategy = InitRetryStrategy(4, "", 0.025); + ASSERT_STREQ("default", retryStrategy->GetStrategyName()); + ASSERT_EQ(11, retryStrategy->GetMaxAttempts()); +} + +TEST_F(RetryStrategyTest, UnmodeledTooManyRequestsIsRetryableThrottling) +{ + JsonErrorMarshaller marshaller; + + auto unknownName = marshaller.BuildAWSError( + JsonErrorResponse(HttpResponseCode::TOO_MANY_REQUESTS, R"({"__type":"SomeUnknownException","message":"slow down"})")); + ASSERT_TRUE(unknownName.ShouldRetry()); + ASSERT_TRUE(unknownName.ShouldThrottle()); + + auto noType = marshaller.BuildAWSError(JsonErrorResponse(HttpResponseCode::TOO_MANY_REQUESTS, R"({"message":"slow down"})")); + ASSERT_TRUE(noType.ShouldRetry()); + ASSERT_TRUE(noType.ShouldThrottle()); + + // A modeled error keeps its own classification. + auto modeled = marshaller.BuildAWSError( + JsonErrorResponse(HttpResponseCode::TOO_MANY_REQUESTS, R"({"__type":"AccessDeniedException","message":"no"})")); + ASSERT_FALSE(modeled.ShouldRetry()); +} + +TEST_F(InitRetryStrategyTest, DefaultRetryModeIsAnAliasForLegacy) +{ + Aws::Environment::EnvironmentRAII env{RetryEnv("")}; + Aws::Config::ReloadCachedConfigFile(); + + auto retryStrategy = InitRetryStrategy("default"); + ASSERT_STREQ("default", retryStrategy->GetStrategyName()); + ASSERT_EQ(11, retryStrategy->GetMaxAttempts()); +} + +TEST_F(InitRetryStrategyTest, SmartDefaultsConfigurationResolvesRetryStrategyFromFactory) +{ + Aws::Environment::EnvironmentRAII env{{{"AWS_RETRY_MODE", ""}, + {"AWS_MAX_ATTEMPTS", ""}, + {"AWS_DEFAULTS_MODE", ""}, + {"AWS_REGION", "us-east-1"}, + {"AWS_EC2_METADATA_DISABLED", "true"}, + {"AWS_CONFIG_FILE", "/nonexistent/retry-strategy-test-config"}}}; + Aws::Config::ReloadCachedConfigFile(); + + ClientConfiguration smartDefaults(true); + ASSERT_FALSE(smartDefaults.retryStrategy); + ASSERT_STREQ("standard", smartDefaults.configFactories.retryStrategyCreateFn()->GetStrategyName()); + + Aws::Environment::EnvironmentRAII legacy{{{"AWS_RETRY_MODE", "legacy"}}}; + ASSERT_STREQ("default", ClientConfiguration(true).configFactories.retryStrategyCreateFn()->GetStrategyName()); +} + +TEST_F(InitRetryStrategyTest, RetrySettingsFromConfigFile) +{ + Aws::Utils::TempFile configFile(std::ios_base::out | std::ios_base::trunc); + ASSERT_TRUE(configFile.good()); + configFile << "[default]" << std::endl; + configFile << "retry_mode = adaptive" << std::endl; + configFile << "[profile prod]" << std::endl; + configFile << "retry_mode = legacy" << std::endl; + configFile << "[profile tuned]" << std::endl; + configFile << "max_attempts = 2" << std::endl; + configFile.close(); + { + Aws::Environment::EnvironmentRAII env{{{"AWS_RETRY_MODE", ""}, + {"AWS_MAX_ATTEMPTS", ""}, + {"AWS_PROFILE", ""}, + {"AWS_DEFAULTS_MODE", ""}, + {"AWS_REGION", "us-east-1"}, + {"AWS_EC2_METADATA_DISABLED", "true"}, + {"AWS_CONFIG_FILE", configFile.GetFileName().c_str()}}}; + Aws::Config::ReloadCachedConfigFile(); + + ASSERT_STREQ("adaptive", InitRetryStrategy()->GetStrategyName()); + + ClientConfiguration prod("prod"); + ASSERT_STREQ("default", prod.configFactories.retryStrategyCreateFn()->GetStrategyName()); + + InitRetryStrategyOptions tunedOptions; + tunedOptions.maxAttempts = 4; + tunedOptions.transientBackoffBaseSec = 0.025; + tunedOptions.profileName = "tuned"; + ASSERT_EQ(2, InitRetryStrategy(tunedOptions)->GetMaxAttempts()); + } + Aws::Config::ReloadCachedConfigFile(); +} + +TEST_F(RetryStrategyTest, DefaultRetryStrategyDoesNotRetryRetry21OnlyErrors) +{ + DefaultRetryStrategy retryStrategy; + + ASSERT_FALSE(retryStrategy.ShouldRetry(NamedError(CoreErrors::THROTTLING, "LimitExceededException"), 0)); + ASSERT_FALSE(retryStrategy.ShouldRetry(NamedError(CoreErrors::UNKNOWN, "SomeUnknownException", HttpResponseCode::TOO_MANY_REQUESTS), 0)); + ASSERT_TRUE(retryStrategy.ShouldRetry(NamedError(CoreErrors::THROTTLING, "Throttling"), 0)); +} + +TEST_F(RetryStrategyTest, UnmodeledTooManyRequestsIsRetryableThrottlingForXml) +{ + XmlErrorMarshaller marshaller; + + auto unknownCode = marshaller.BuildAWSError(JsonErrorResponse( + HttpResponseCode::TOO_MANY_REQUESTS, "SomeUnknownCodeslow down")); + ASSERT_TRUE(unknownCode.ShouldRetry()); + ASSERT_TRUE(unknownCode.ShouldThrottle()); +} diff --git a/tests/aws-cpp-sdk-core-tests/smithy/client/AsyncSmithyClientTest.cpp b/tests/aws-cpp-sdk-core-tests/smithy/client/AsyncSmithyClientTest.cpp index 6ca7f9673779..ddebd029f497 100644 --- a/tests/aws-cpp-sdk-core-tests/smithy/client/AsyncSmithyClientTest.cpp +++ b/tests/aws-cpp-sdk-core-tests/smithy/client/AsyncSmithyClientTest.cpp @@ -257,7 +257,6 @@ namespace TEST_F(AsyncSmithyClientTest, LongPollingQuotaExhaustedDelaysCompletion) { - Aws::Environment::EnvironmentRAII env{{{"AWS_NEW_RETRIES_2026", "true"}}}; auto quota = Aws::MakeShared(ALLOCATION_TAG); ASSERT_TRUE(quota->AcquireRetryQuota(498)); client = MakeClient(Aws::MakeShared(ALLOCATION_TAG, quota, LONG_POLL_DELAY_MS)); @@ -281,7 +280,6 @@ namespace TEST_F(AsyncSmithyClientTest, NonLongPollingQuotaExhaustedCompletesImmediately) { - Aws::Environment::EnvironmentRAII env{{{"AWS_NEW_RETRIES_2026", "true"}}}; auto quota = Aws::MakeShared(ALLOCATION_TAG); ASSERT_TRUE(quota->AcquireRetryQuota(498)); client = MakeClient(Aws::MakeShared(ALLOCATION_TAG, quota, LONG_POLL_DELAY_MS)); @@ -302,9 +300,8 @@ namespace #endif } - TEST_F(AsyncSmithyClientTest, NewRetriesFlagIsCaseInsensitive) + TEST_F(AsyncSmithyClientTest, FirstRequestHeaderIncludesMaxAttempts) { - Aws::Environment::EnvironmentRAII env{{{"AWS_NEW_RETRIES_2026", "TRUE"}}}; client = MakeClient(Aws::MakeShared(ALLOCATION_TAG, 3)); mockHttpClient->AddResponseToReturn(MakeSuccessResponse()); diff --git a/tests/aws-cpp-sdk-dynamodb-unit-tests/CMakeLists.txt b/tests/aws-cpp-sdk-dynamodb-unit-tests/CMakeLists.txt index 5ffab84f9faa..e50edbc83c72 100644 --- a/tests/aws-cpp-sdk-dynamodb-unit-tests/CMakeLists.txt +++ b/tests/aws-cpp-sdk-dynamodb-unit-tests/CMakeLists.txt @@ -24,6 +24,11 @@ set_compiler_warnings(${PROJECT_NAME}) target_link_libraries(${PROJECT_NAME} ${PROJECT_LIBS}) +if(TARGET aws-cpp-sdk-dynamodbstreams) + target_link_libraries(${PROJECT_NAME} aws-cpp-sdk-dynamodbstreams) + target_compile_definitions(${PROJECT_NAME} PRIVATE HAS_DYNAMODB_STREAMS) +endif() + if(MSVC AND BUILD_SHARED_LIBS) set_target_properties(${PROJECT_NAME} PROPERTIES LINK_FLAGS "/DELAYLOAD:aws-cpp-sdk-dynamodb.dll /DELAYLOAD:aws-cpp-sdk-core.dll") target_link_libraries(${PROJECT_NAME} delayimp.lib) diff --git a/tests/aws-cpp-sdk-dynamodb-unit-tests/DynamoDBUnitTests.cpp b/tests/aws-cpp-sdk-dynamodb-unit-tests/DynamoDBUnitTests.cpp index e2f376302b2e..499eb3e6afa2 100644 --- a/tests/aws-cpp-sdk-dynamodb-unit-tests/DynamoDBUnitTests.cpp +++ b/tests/aws-cpp-sdk-dynamodb-unit-tests/DynamoDBUnitTests.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -18,6 +19,9 @@ #include #include #include +#ifdef HAS_DYNAMODB_STREAMS +#include +#endif #include #include #include @@ -833,3 +837,84 @@ TEST_F(DynamoDBUnitTest, PutItemDoesNotSendExpect100ContinueHeader) { const auto requestSeen = mock_http_client_->GetMostRecentHttpRequest(); EXPECT_FALSE(requestSeen.HasHeader("expect")); } + +namespace { +std::shared_ptr ResolvedRetryStrategy(const ClientConfiguration& configuration) { + return configuration.retryStrategy ? configuration.retryStrategy : configuration.configFactories.retryStrategyCreateFn(); +} + +void ExpectDynamoDBRetryStrategy(const std::shared_ptr& retryStrategy) { + ASSERT_TRUE(retryStrategy); + EXPECT_EQ(4, retryStrategy->GetMaxAttempts()); + AWSError transientError(CoreErrors::SERVICE_UNAVAILABLE, true); + for (int i = 0; i < 100; ++i) { + EXPECT_LE(retryStrategy->CalculateDelayBeforeNextRetry(transientError, 0), 25); + } +} +} // namespace + +TEST_F(DynamoDBUnitTest, DefaultRetryStrategyUsesDynamoDBTuning) { + Environment::EnvironmentRAII envVars{{{"AWS_RETRY_MODE", ""}, + {"AWS_MAX_ATTEMPTS", ""}, + {"AWS_DEFAULTS_MODE", ""}, + {"AWS_REGION", "us-east-1"}, + {"AWS_EC2_METADATA_DISABLED", "true"}, + {"AWS_CONFIG_FILE", "/nonexistent/dynamodb-retry-test-config"}}}; + Aws::Config::ReloadCachedConfigFile(); + + ExpectDynamoDBRetryStrategy(ResolvedRetryStrategy(DynamoDBClientConfiguration())); + ExpectDynamoDBRetryStrategy(ResolvedRetryStrategy(DynamoDBClientConfiguration(true))); + // The path behind DynamoDBClient(const ClientConfiguration&). + ExpectDynamoDBRetryStrategy(ResolvedRetryStrategy(DynamoDBClientConfiguration(ClientConfiguration(true)))); + { + Environment::EnvironmentRAII adaptive{{{"AWS_RETRY_MODE", "adaptive"}}}; + auto retryStrategy = ResolvedRetryStrategy(DynamoDBClientConfiguration(true)); + EXPECT_STREQ("adaptive", retryStrategy->GetStrategyName()); + ExpectDynamoDBRetryStrategy(retryStrategy); + } + Aws::Config::ReloadCachedConfigFile(); +} + +TEST_F(DynamoDBUnitTest, ConfiguredRetrySettingsWinOverDynamoDBTuning) { + Utils::TempFile configFile(std::ios_base::out | std::ios_base::trunc); + ASSERT_TRUE(configFile.good()); + configFile << "[profile tuned]" << std::endl; + configFile << "region = us-west-2" << std::endl; + configFile << "max_attempts = 3" << std::endl; + configFile.close(); + { + Environment::EnvironmentRAII envVars{{{"AWS_RETRY_MODE", ""}, + {"AWS_MAX_ATTEMPTS", "2"}, + {"AWS_REGION", "us-east-1"}, + {"AWS_EC2_METADATA_DISABLED", "true"}, + {"AWS_CONFIG_FILE", configFile.GetFileName().c_str()}}}; + Aws::Config::ReloadCachedConfigFile(); + EXPECT_EQ(2, ResolvedRetryStrategy(DynamoDBClientConfiguration(true))->GetMaxAttempts()); + + Environment::EnvironmentRAII noEnvMaxAttempts{{{"AWS_MAX_ATTEMPTS", ""}}}; + EXPECT_EQ(3, ResolvedRetryStrategy(DynamoDBClientConfiguration("tuned"))->GetMaxAttempts()); + + ClientConfiguration configuration; + configuration.retryStrategy = Aws::MakeShared(LOG_TAG, 7); + EXPECT_EQ(7, ResolvedRetryStrategy(DynamoDBClientConfiguration(configuration))->GetMaxAttempts()); + } + Aws::Config::ReloadCachedConfigFile(); +} + +#ifdef HAS_DYNAMODB_STREAMS +TEST_F(DynamoDBUnitTest, DynamoDBStreamsDefaultRetryStrategyUsesDynamoDBTuning) { + Environment::EnvironmentRAII envVars{{{"AWS_RETRY_MODE", ""}, + {"AWS_MAX_ATTEMPTS", ""}, + {"AWS_DEFAULTS_MODE", ""}, + {"AWS_REGION", "us-east-1"}, + {"AWS_EC2_METADATA_DISABLED", "true"}, + {"AWS_CONFIG_FILE", "/nonexistent/dynamodb-retry-test-config"}}}; + Aws::Config::ReloadCachedConfigFile(); + + using Aws::DynamoDBStreams::DynamoDBStreamsClientConfiguration; + ExpectDynamoDBRetryStrategy(ResolvedRetryStrategy(DynamoDBStreamsClientConfiguration())); + ExpectDynamoDBRetryStrategy(ResolvedRetryStrategy(DynamoDBStreamsClientConfiguration(true))); + ExpectDynamoDBRetryStrategy(ResolvedRetryStrategy(DynamoDBStreamsClientConfiguration(ClientConfiguration(true)))); + Aws::Config::ReloadCachedConfigFile(); +} +#endif diff --git a/tools/code-generation/generator/src/main/resources/com/amazonaws/util/awsclientgenerator/velocity/cpp/DefaultsSource.vm b/tools/code-generation/generator/src/main/resources/com/amazonaws/util/awsclientgenerator/velocity/cpp/DefaultsSource.vm index c9523f135e98..39eef3e5d6ae 100644 --- a/tools/code-generation/generator/src/main/resources/com/amazonaws/util/awsclientgenerator/velocity/cpp/DefaultsSource.vm +++ b/tools/code-generation/generator/src/main/resources/com/amazonaws/util/awsclientgenerator/velocity/cpp/DefaultsSource.vm @@ -106,9 +106,8 @@ namespace Aws return "standard"; } - void SetLegacyClientConfiguration(Aws::Client::ClientConfiguration& clientConfig) + void SetLegacyClientConfiguration(Aws::Client::ClientConfiguration& /*clientConfig*/) { - clientConfig.retryStrategy = Aws::Client::InitRetryStrategy("default"); } #foreach($defaultMode in $defaultsModel.defaultsModes) @@ -120,9 +119,7 @@ namespace Aws void Set${defaultModeNameCamel}ClientConfiguration(Aws::Client::ClientConfiguration& clientConfig) { #foreach ($configEntry in $defaultMode.configurationValues.entrySet()) -#if (${configEntry.key} == "retryMode") - clientConfig.retryStrategy = Aws::Client::InitRetryStrategy(${configEntry.value.getAsSourceString()}); -#else +#if (${configEntry.key} != "retryMode") clientConfig.${configEntry.key} = ${configEntry.value.getAsSourceString()}; #end #end diff --git a/tools/code-generation/generator/src/main/resources/com/amazonaws/util/awsclientgenerator/velocity/cpp/common/ServiceClientConfigurationSource.vm b/tools/code-generation/generator/src/main/resources/com/amazonaws/util/awsclientgenerator/velocity/cpp/common/ServiceClientConfigurationSource.vm index 11679bd614cb..6e38ad0a9838 100644 --- a/tools/code-generation/generator/src/main/resources/com/amazonaws/util/awsclientgenerator/velocity/cpp/common/ServiceClientConfigurationSource.vm +++ b/tools/code-generation/generator/src/main/resources/com/amazonaws/util/awsclientgenerator/velocity/cpp/common/ServiceClientConfigurationSource.vm @@ -132,8 +132,13 @@ void ${metadata.classNamePrefix}ClientConfiguration::Load${serviceNamespace}Spec #end ## DynamoDB and DynamoDB Streams tune retry 2.1: maxAttempts=4 and a 25ms non-throttling backoff base #if($serviceModel.metadata.serviceId == "DynamoDB" || $serviceModel.metadata.serviceId == "DynamoDB Streams") - this->configFactories.retryStrategyCreateFn = []() -> std::shared_ptr { - return Client::InitRetryStrategy(4, "", 0.025); + const Aws::String retryProfileName = this->profileName; + this->configFactories.retryStrategyCreateFn = [retryProfileName]() -> std::shared_ptr { + Client::InitRetryStrategyOptions options; + options.maxAttempts = 4; + options.transientBackoffBaseSec = 0.025; + options.profileName = retryProfileName; + return Client::InitRetryStrategy(options); }; #end ## Bedrock API key auth