From 714da31299e58a38d4b494070eafd51c84a9bfe6 Mon Sep 17 00:00:00 2001 From: Snigdha Date: Fri, 30 Oct 2026 12:24:00 -0400 Subject: [PATCH 1/2] Make Retry Behavior 2.1 the default retry behavior --- .../feature-retry-behavior-2-1-default.json | 6 + .../source/DynamoDBClientConfiguration.cpp | 5 +- .../DynamoDBStreamsClientConfiguration.cpp | 5 +- .../aws/core/client/AWSProtocolClient.h | 4 + .../aws/core/client/AdaptiveRetryStrategy.h | 1 + .../aws/core/client/ClientConfiguration.h | 8 +- .../aws/core/internal/RetryStrategyImpl.h | 17 ++ .../smithy/client/AwsSmithyClientBase.h | 3 +- .../source/client/AWSClient.cpp | 5 +- .../source/client/AWSErrorMarshaller.cpp | 4 + .../source/client/AWSXmlClient.cpp | 2 + .../source/client/AdaptiveRetryStrategy.cpp | 4 + .../source/client/ClientConfiguration.cpp | 123 +++++++---- .../source/client/CoreErrors.cpp | 27 +-- .../source/client/DefaultRetryStrategy.cpp | 33 +++ .../source/client/RetryStrategy.cpp | 44 ++-- .../defaults/ClientConfigurationDefaults.cpp | 7 +- .../smithy/client/AwsSmithyClientBase.cpp | 3 + .../aws/client/AWSClientTest.cpp | 4 - .../aws/client/AWSErrorMashallerTest.cpp | 68 +++++- .../aws/client/AsyncAWSClientTest.cpp | 2 - .../aws/client/RetryStrategyTest.cpp | 204 +++++++++++++++++- .../smithy/client/AsyncSmithyClientTest.cpp | 5 +- .../CMakeLists.txt | 5 + .../DynamoDBUnitTests.cpp | 85 ++++++++ .../velocity/cpp/DefaultsSource.vm | 5 +- .../ServiceClientConfigurationSource.vm | 5 +- 27 files changed, 555 insertions(+), 129 deletions(-) create mode 100644 .changelog/feature-retry-behavior-2-1-default.json 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..dc9330aa04c5 --- /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." +} diff --git a/generated/src/aws-cpp-sdk-dynamodb/source/DynamoDBClientConfiguration.cpp b/generated/src/aws-cpp-sdk-dynamodb/source/DynamoDBClientConfiguration.cpp index afb004ed24c8..9eca3d2e75e8 100644 --- a/generated/src/aws-cpp-sdk-dynamodb/source/DynamoDBClientConfiguration.cpp +++ b/generated/src/aws-cpp-sdk-dynamodb/source/DynamoDBClientConfiguration.cpp @@ -37,8 +37,9 @@ 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 { + return Client::InitRetryStrategy(4, "", 0.025, retryProfileName); }; } diff --git a/generated/src/aws-cpp-sdk-dynamodbstreams/source/DynamoDBStreamsClientConfiguration.cpp b/generated/src/aws-cpp-sdk-dynamodbstreams/source/DynamoDBStreamsClientConfiguration.cpp index 0a7aea97952c..cec36a16c2ae 100644 --- a/generated/src/aws-cpp-sdk-dynamodbstreams/source/DynamoDBStreamsClientConfiguration.cpp +++ b/generated/src/aws-cpp-sdk-dynamodbstreams/source/DynamoDBStreamsClientConfiguration.cpp @@ -9,8 +9,9 @@ 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 { + return Client::InitRetryStrategy(4, "", 0.025, retryProfileName); }; #if defined(_MSC_VER) (&reinterpret_cast(inputProfileName)); diff --git a/src/aws-cpp-sdk-core/include/aws/core/client/AWSProtocolClient.h b/src/aws-cpp-sdk-core/include/aws/core/client/AWSProtocolClient.h index 9878e7306648..488ffb8bf651 100644 --- a/src/aws-cpp-sdk-core/include/aws/core/client/AWSProtocolClient.h +++ b/src/aws-cpp-sdk-core/include/aws/core/client/AWSProtocolClient.h @@ -175,6 +175,10 @@ namespace Aws error.SetResponseHeaders(httpResponse->GetHeaders()); error.SetResponseCode(httpResponse->GetResponseCode()); + if (error.GetResponseCode() == Aws::Http::HttpResponseCode::TOO_MANY_REQUESTS && error.GetErrorType() == CoreErrors::UNKNOWN) + { + error.SetRetryableType(RetryableType::RETRYABLE_THROTTLING); + } error.SetRemoteHostIpAddress(httpResponse->GetOriginatingRequest().GetResolvedRemoteHost()); AWS_LOGSTREAM_ERROR(GetClientLogTag(), error); return error; 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..d67acad9cd71 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); + AdaptiveRetryStrategy(long maxAttempts, double transientBackoffBaseSec); /** * 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..7a520d6a7949 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,11 +652,13 @@ 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); + AWS_CORE_API std::shared_ptr InitRetryStrategy(int maxRetries, Aws::String retryMode, double transientBackoffBaseSec, + const Aws::String& profileName); /** * A helper function to compute a user agent 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..d06ea4e39914 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 @@ -9,8 +9,11 @@ #include #include #include +#include #include +#include + namespace Aws { namespace Client @@ -73,5 +76,19 @@ namespace Aws mutable Aws::Utils::Threading::ReaderWriterLock m_retryQuotaLock; int m_retryQuota; }; + + namespace Internal + { + AWS_CORE_LOCAL bool UsesRetryBehavior21(const RetryStrategy* retryStrategy); + + inline void ClassifyUnmodeledTooManyRequests(AWSError& error) + { + if (error.GetResponseCode() == Aws::Http::HttpResponseCode::TOO_MANY_REQUESTS && + error.GetErrorType() == CoreErrors::UNKNOWN) + { + error.SetRetryableType(RetryableType::RETRYABLE_THROTTLING); + } + } + } // 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..39d844d3e008 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(); diff --git a/src/aws-cpp-sdk-core/source/client/AWSClient.cpp b/src/aws-cpp-sdk-core/source/client/AWSClient.cpp index 8455b59847e2..5c165227cb59 100644 --- a/src/aws-cpp-sdk-core/source/client/AWSClient.cpp +++ b/src/aws-cpp-sdk-core/source/client/AWSClient.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -177,7 +178,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_enableNewRetries{Aws::Client::Internal::UsesRetryBehavior21(m_retryStrategy.get())}, m_disableExpectHeader(configuration.disableExpectHeader) { } @@ -209,7 +210,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_enableNewRetries{Aws::Client::Internal::UsesRetryBehavior21(m_retryStrategy.get())}, m_disableExpectHeader(configuration.disableExpectHeader) { } diff --git a/src/aws-cpp-sdk-core/source/client/AWSErrorMarshaller.cpp b/src/aws-cpp-sdk-core/source/client/AWSErrorMarshaller.cpp index cb96216945b0..8c137b851390 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/AWSXmlClient.cpp b/src/aws-cpp-sdk-core/source/client/AWSXmlClient.cpp index abbb7c5015cc..7aa25429d3ce 100644 --- a/src/aws-cpp-sdk-core/source/client/AWSXmlClient.cpp +++ b/src/aws-cpp-sdk-core/source/client/AWSXmlClient.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include using namespace Aws; @@ -230,6 +231,7 @@ AWSError AWSXMLClient::BuildAWSError(const std::shared_ptrGetHeaders()); error.SetResponseCode(httpResponse->GetResponseCode()); + Aws::Client::Internal::ClassifyUnmodeledTooManyRequests(error); error.SetRemoteHostIpAddress(httpResponse->GetOriginatingRequest().GetResolvedRemoteHost()); AWS_LOGSTREAM_ERROR(AWS_XML_CLIENT_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..9c372d405bd1 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(long maxAttempts, double transientBackoffBaseSec) : + StandardRetryStrategy(maxAttempts, transientBackoffBaseSec) + {} + 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 5449e28d8d14..fb895f5ea157 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,8 @@ static T LoadEnumFromString(const std::array, N>& mapp return mapping->second; } +static std::shared_ptr InitRetryStrategyForProfile(const Aws::String& profileName); + ClientConfiguration::ProviderFactories ClientConfiguration::ProviderFactories::defaultFactories = []() { ProviderFactories factories; @@ -496,6 +499,8 @@ 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]() { return InitRetryStrategyForProfile(retryProfileName); }; setLegacyClientConfigurationParameters(*this); setConfigFromEnvOrProfile(*this); // Call EC2 Instance Metadata service only once @@ -571,107 +576,133 @@ ClientConfiguration::ClientConfiguration(bool /*useSmartDefaults*/, const char* Aws::Config::Defaults::SetSmartDefaultsConfigurationParameters(*this, defaultMode, hasEc2MetadataRegion, ec2MetadataRegion); } -static Aws::String ResolveRetryMode(Aws::String retryMode) { +static Aws::String GetRetryConfigValue(const Aws::String& profileName, const Aws::String& key) +{ + return profileName.empty() ? Aws::Config::GetCachedConfigValue(key) : Aws::Config::GetCachedConfigValue(profileName, key); +} + +static Aws::String ResolveRetryMode(Aws::String retryMode, const Aws::String& profileName) { if (retryMode.empty()) { retryMode = Aws::Environment::GetEnv("AWS_RETRY_MODE"); } if (retryMode.empty()) { - retryMode = Aws::Config::GetCachedConfigValue("retry_mode"); + retryMode = GetRetryConfigValue(profileName, "retry_mode"); } - if (Aws::Utils::StringUtils::ToLower(Aws::Environment::GetEnv("AWS_NEW_RETRIES_2026").c_str()) == "true" && retryMode.empty()) + if (retryMode.empty()) { retryMode = "standard"; } - return retryMode; + retryMode = Aws::Utils::StringUtils::ToLower(retryMode.c_str()); + // "default" is the strategy name of DefaultRetryStrategy, the legacy retry behavior. + return retryMode == "default" ? "legacy" : retryMode; } -std::shared_ptr InitRetryStrategy(int maxAttempts, Aws::String retryMode) { - retryMode = ResolveRetryMode(retryMode); +static 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; +} +static std::shared_ptr CreateRetryStrategy(int maxAttempts, const Aws::String& retryMode) +{ std::shared_ptr retryStrategy; - if (retryMode == "standard") + 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); + retryStrategy = Aws::MakeShared(CLIENT_CONFIG_TAG); } else { - retryStrategy = Aws::MakeShared(CLIENT_CONFIG_TAG, maxAttempts); + retryStrategy = Aws::MakeShared(CLIENT_CONFIG_TAG, maxAttempts); } } - else if (retryMode == "adaptive") + 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); + retryStrategy = Aws::MakeShared(CLIENT_CONFIG_TAG); } else { - retryStrategy = Aws::MakeShared(CLIENT_CONFIG_TAG, maxAttempts); + retryStrategy = Aws::MakeShared(CLIENT_CONFIG_TAG, maxAttempts); } } - else - { - retryStrategy = Aws::MakeShared(CLIENT_CONFIG_TAG); - } return retryStrategy; } -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) { + return CreateRetryStrategy(maxAttempts, ResolveRetryMode(retryMode, "")); } 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") + return InitRetryStrategy(maxAttempts, retryMode, transientBackoffBaseSec, ""); +} + +std::shared_ptr InitRetryStrategy(int maxAttempts, Aws::String retryMode, double transientBackoffBaseSec, + const Aws::String& profileName) { + retryMode = ResolveRetryMode(retryMode, profileName); + if (retryMode == "legacy") { - // Gate is off: ignore service-specific tuning, use default behavior - return InitRetryStrategy(retryMode); + // Legacy mode ignores service-specific tuning. + return CreateRetryStrategy(ResolveConfiguredMaxAttempts(profileName), retryMode); } // maxAttempts is a service default. A max attempts value the customer configured wins. - const int configuredMaxAttempts = ResolveConfiguredMaxAttempts(); + const int configuredMaxAttempts = ResolveConfiguredMaxAttempts(profileName); if (configuredMaxAttempts >= 0) { maxAttempts = configuredMaxAttempts; } - // 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") + long attempts = static_cast(maxAttempts); + if (retryMode == "adaptive") { - return InitRetryStrategy(maxAttempts, retryMode); + return Aws::MakeShared(CLIENT_CONFIG_TAG, attempts, transientBackoffBaseSec); + } + if (retryMode != "standard") + { + 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); } std::shared_ptr InitRetryStrategy(Aws::String retryMode) { - return InitRetryStrategy(ResolveConfiguredMaxAttempts(), retryMode); + return InitRetryStrategy(ResolveConfiguredMaxAttempts(""), retryMode); +} + +static std::shared_ptr InitRetryStrategyForProfile(const Aws::String& profileName) +{ + return CreateRetryStrategy(ResolveConfiguredMaxAttempts(profileName), ResolveRetryMode("", profileName)); } 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..164ad3c190b4 100644 --- a/src/aws-cpp-sdk-core/source/client/CoreErrors.cpp +++ b/src/aws-cpp-sdk-core/source/client/CoreErrors.cpp @@ -5,7 +5,6 @@ #include #include -#include #include #include @@ -34,9 +33,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,17 +91,15 @@ 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)); - } + 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)); } void CoreErrorsMapper::CleanupCoreErrorsMapper() @@ -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); diff --git a/src/aws-cpp-sdk-core/source/client/DefaultRetryStrategy.cpp b/src/aws-cpp-sdk-core/source/client/DefaultRetryStrategy.cpp index 405d7566cfb7..fb189be4f04e 100644 --- a/src/aws-cpp-sdk-core/source/client/DefaultRetryStrategy.cpp +++ b/src/aws-cpp-sdk-core/source/client/DefaultRetryStrategy.cpp @@ -11,11 +11,44 @@ using namespace Aws; using namespace Aws::Client; +namespace +{ + // Error names the core error mapper classifies as retryable only since Retry Behavior 2.1. + // DefaultRetryStrategy is the legacy retry behavior and does not retry them. + const char* const RETRY_21_ONLY_ERROR_NAMES[] = {"TooManyRequestsException", "ProvisionedThroughputExceededException", + "TransactionInProgressException", "RequestLimitExceeded", + "BandwidthLimitExceeded", "LimitExceededException", + "PriorRequestNotComplete", "EC2ThrottledException", + "IDPCommunicationError"}; + + bool IsRetry21OnlyError(const AWSError& error) + { + const CoreErrors errorType = error.GetErrorType(); + if (errorType == CoreErrors::THROTTLING || errorType == CoreErrors::NETWORK_CONNECTION) + { + const Aws::String& name = error.GetExceptionName(); + for (const char* retry21OnlyName : RETRY_21_ONLY_ERROR_NAMES) + { + if (name == retry21OnlyName) + { + return true; + } + } + } + // A 429 naming an exception the SDK does not know was not retried. + return error.GetResponseCode() == Aws::Http::HttpResponseCode::TOO_MANY_REQUESTS && + errorType == CoreErrors::UNKNOWN && !error.GetExceptionName().empty(); + } +} + bool DefaultRetryStrategy::ShouldRetry(const AWSError& error, long attemptedRetries) const { if (attemptedRetries >= m_maxRetries) return false; + if (IsRetry21OnlyError(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..37b6f1555d03 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 @@ -34,22 +33,6 @@ namespace { const double DEFAULT_TRANSIENT_BACKOFF_BASE_SEC = 0.05; - bool IsNewRetriesEnabled() - { - return Aws::Utils::StringUtils::ToLower(Aws::Environment::GetEnv("AWS_NEW_RETRIES_2026").c_str()) == "true"; - } - - class LegacyRetryImpl : public StandardRetryStrategy::RetryImpl - { - 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: @@ -87,23 +70,30 @@ namespace { Aws::UniquePtr CreateRetryImpl(double transientBackoffBaseSec) { - if (IsNewRetriesEnabled()) - { - return Aws::MakeUnique("StandardRetryStrategy", transientBackoffBaseSec); - } - return Aws::MakeUnique("StandardRetryStrategy"); + return Aws::MakeUnique("StandardRetryStrategy", transientBackoffBaseSec); } std::shared_ptr CreateQuotaContainer() { - if (IsNewRetriesEnabled()) - { - return Aws::MakeShared("StandardRetryStrategy"); - } - return Aws::MakeShared("StandardRetryStrategy"); + return Aws::MakeShared("StandardRetryStrategy"); } } // anonymous namespace +namespace Aws +{ + namespace Client + { + namespace Internal + { + bool UsesRetryBehavior21(const RetryStrategy* retryStrategy) + { + // DefaultRetryStrategy is the legacy retry behavior, so it keeps the request handling from before Retry Behavior 2.1. + return retryStrategy && Aws::String("default") != retryStrategy->GetStrategyName(); + } + } // namespace Internal + } // namespace Client +} // namespace Aws + namespace Aws { namespace Client 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..b9217a93b0ec 100644 --- a/src/aws-cpp-sdk-core/source/config/defaults/ClientConfigurationDefaults.cpp +++ b/src/aws-cpp-sdk-core/source/config/defaults/ClientConfigurationDefaults.cpp @@ -117,31 +117,28 @@ namespace Aws void SetLegacyClientConfiguration(Aws::Client::ClientConfiguration& clientConfig) { - clientConfig.retryStrategy = Aws::Client::InitRetryStrategy("default"); + // Leave the retry strategy unset so the client configuration factory resolves it. + clientConfig.retryStrategy = nullptr; } 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 5893181de468..8b4c14d7cb54 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_enableNewRetries = Aws::Client::Internal::UsesRetryBehavior21(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_enableNewRetries = Aws::Client::Internal::UsesRetryBehavior21(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); 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 e5ff090eb727..88ce81b5af2b 100644 --- a/tests/aws-cpp-sdk-core-tests/aws/client/AWSClientTest.cpp +++ b/tests/aws-cpp-sdk-core-tests/aws/client/AWSClientTest.cpp @@ -447,7 +447,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); @@ -466,7 +465,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); @@ -487,7 +485,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); @@ -506,7 +503,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..7b86bd91a6a8 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,13 @@ #include #include +#include +#include +#include +#include +#include +#include +#include #include #include #include @@ -1375,4 +1382,63 @@ 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::BuildAWSError; + }; +} + +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 mockHttpClientFactory = Aws::MakeShared(ERROR_MARSHALLER_TEST_ALLOC_TAG); + mockHttpClientFactory->SetClient(Aws::MakeShared(ERROR_MARSHALLER_TEST_ALLOC_TAG)); + Aws::Http::SetHttpClientFactory(mockHttpClientFactory); + ClientConfiguration configuration; + JsonClientUnderTest client(configuration); + + auto request = Aws::Http::CreateHttpRequest(Aws::Http::URI("http://www.uri.com"), Aws::Http::HttpMethod::HTTP_POST, + Aws::Utils::Stream::DefaultResponseStreamFactoryMethod); + auto bodyless = Aws::MakeShared(ERROR_MARSHALLER_TEST_ALLOC_TAG, request); + bodyless->SetResponseCode(Aws::Http::HttpResponseCode::TOO_MANY_REQUESTS); + auto bodylessError = client.BuildAWSError(bodyless); + ASSERT_TRUE(bodylessError.ShouldRetry()); + ASSERT_TRUE(bodylessError.ShouldThrottle()); + + auto unknownName = client.BuildAWSError(WithResponseCode( + BuildHttpResponse("SomeUnknownException", "slow down", "Request Id"), Aws::Http::HttpResponseCode::TOO_MANY_REQUESTS)); + ASSERT_TRUE(unknownName.ShouldRetry()); + ASSERT_TRUE(unknownName.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..8b13047625db 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; @@ -30,6 +33,8 @@ class MockStandardRetryStrategy : public Aws::Client::StandardRetryStrategy MockStandardRetryStrategy() = default; explicit MockStandardRetryStrategy(double transientBackoffBaseSec) : StandardRetryStrategy(3, transientBackoffBaseSec) {} + explicit MockStandardRetryStrategy(std::shared_ptr retryQuotaContainer) + : StandardRetryStrategy(retryQuotaContainer) {} const std::shared_ptr& GetRetryQuotaContainer() const { @@ -75,7 +80,7 @@ TEST_F(RetryStrategyTest, TestStandardRetryStrategy) AWSError retryableError(CoreErrors::NETWORK_CONNECTION, true); AWSError nonRetryableError(CoreErrors::INCOMPLETE_SIGNATURE, false); - MockStandardRetryStrategy retryStrategy; + MockStandardRetryStrategy retryStrategy(Aws::MakeShared(ALLOCATION_TAG)); ASSERT_EQ(500, retryStrategy.GetRetryQuotaContainer()->GetRetryQuota()); ASSERT_EQ(3, retryStrategy.GetMaxAttempts()); @@ -136,7 +141,6 @@ TEST_F(RetryStrategyTest, TestStandardRetryStrategy) class NewRetriesStrategyTest : public Aws::Testing::AwsCppSdkGTestSuite { - Aws::Environment::EnvironmentRAII m_env{{{"AWS_NEW_RETRIES_2026", "true"}}}; }; // SEP Test Case 1 @@ -327,26 +331,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 +358,187 @@ 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()); + + ASSERT_EQ(2, InitRetryStrategy(4, "", 0.025, "tuned")->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..5fecb6f69258 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 @@ -108,7 +108,8 @@ namespace Aws void SetLegacyClientConfiguration(Aws::Client::ClientConfiguration& clientConfig) { - clientConfig.retryStrategy = Aws::Client::InitRetryStrategy("default"); + // Leave the retry strategy unset so the client configuration factory resolves it. + clientConfig.retryStrategy = nullptr; } #foreach($defaultMode in $defaultsModel.defaultsModes) @@ -121,7 +122,7 @@ namespace Aws { #foreach ($configEntry in $defaultMode.configurationValues.entrySet()) #if (${configEntry.key} == "retryMode") - clientConfig.retryStrategy = Aws::Client::InitRetryStrategy(${configEntry.value.getAsSourceString()}); +## The retry strategy is resolved by the client configuration factory. #else clientConfig.${configEntry.key} = ${configEntry.value.getAsSourceString()}; #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..d69dc18827af 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,9 @@ 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 { + return Client::InitRetryStrategy(4, "", 0.025, retryProfileName); }; #end ## Bedrock API key auth From 5ad7a0bd73c05589f8296dbd18f9b15b6cc65f61 Mon Sep 17 00:00:00 2001 From: Snigdha Date: Wed, 4 Nov 2026 10:39:31 -0500 Subject: [PATCH 2/2] Refactor retry strategy options and error classification --- .../feature-retry-behavior-2-1-default.json | 2 +- .cspell.json | 4 +- .../source/DynamoDBClientConfiguration.cpp | 6 +- .../DynamoDBStreamsClientConfiguration.cpp | 6 +- src/aws-cpp-sdk-core/CMakeLists.txt | 7 +- .../include/aws/core/client/AWSClient.h | 2 +- .../aws/core/client/AWSProtocolClient.h | 4 - .../aws/core/client/AdaptiveRetryStrategy.h | 2 +- .../aws/core/client/ClientConfiguration.h | 20 +- .../include/aws/core/client/RetryStrategy.h | 15 +- .../core/internal/RetryErrorClassification.h | 32 +++ .../aws/core/internal/RetryStrategyImpl.h | 14 +- .../smithy/client/AwsSmithyClientBase.h | 2 +- .../source/client/AWSClient.cpp | 21 +- .../source/client/AWSErrorMarshaller.cpp | 2 +- .../source/client/AWSXmlClient.cpp | 2 - .../source/client/AdaptiveRetryStrategy.cpp | 4 +- .../source/client/ClientConfiguration.cpp | 212 +++++++++--------- .../source/client/CoreErrors.cpp | 53 ++++- .../source/client/DefaultRetryStrategy.cpp | 33 +-- .../source/client/RetryStrategy.cpp | 106 ++++----- .../defaults/ClientConfigurationDefaults.cpp | 6 +- .../smithy/client/AwsSmithyClientBase.cpp | 12 +- .../aws/client/AWSErrorMashallerTest.cpp | 45 ++-- .../aws/client/RetryStrategyTest.cpp | 101 +++++---- .../velocity/cpp/DefaultsSource.vm | 8 +- .../ServiceClientConfigurationSource.vm | 6 +- 27 files changed, 389 insertions(+), 338 deletions(-) create mode 100644 src/aws-cpp-sdk-core/include/aws/core/internal/RetryErrorClassification.h diff --git a/.changelog/feature-retry-behavior-2-1-default.json b/.changelog/feature-retry-behavior-2-1-default.json index dc9330aa04c5..f4b7f982db7b 100644 --- a/.changelog/feature-retry-behavior-2-1-default.json +++ b/.changelog/feature-retry-behavior-2-1-default.json @@ -2,5 +2,5 @@ "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." + "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 07d085df6ce9..eb2826e48919 100644 --- a/.cspell.json +++ b/.cspell.json @@ -336,6 +336,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 9eca3d2e75e8..b3b86ef6b43f 100644 --- a/generated/src/aws-cpp-sdk-dynamodb/source/DynamoDBClientConfiguration.cpp +++ b/generated/src/aws-cpp-sdk-dynamodb/source/DynamoDBClientConfiguration.cpp @@ -39,7 +39,11 @@ void DynamoDBClientConfiguration::LoadDynamoDBSpecificConfig(const Aws::String& } const Aws::String retryProfileName = this->profileName; this->configFactories.retryStrategyCreateFn = [retryProfileName]() -> std::shared_ptr { - return Client::InitRetryStrategy(4, "", 0.025, retryProfileName); + 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 cec36a16c2ae..cd06eae45213 100644 --- a/generated/src/aws-cpp-sdk-dynamodbstreams/source/DynamoDBStreamsClientConfiguration.cpp +++ b/generated/src/aws-cpp-sdk-dynamodbstreams/source/DynamoDBStreamsClientConfiguration.cpp @@ -11,7 +11,11 @@ namespace DynamoDBStreams { void DynamoDBStreamsClientConfiguration::LoadDynamoDBStreamsSpecificConfig(const Aws::String& inputProfileName) { const Aws::String retryProfileName = this->profileName; this->configFactories.retryStrategyCreateFn = [retryProfileName]() -> std::shared_ptr { - return Client::InitRetryStrategy(4, "", 0.025, retryProfileName); + 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/AWSProtocolClient.h b/src/aws-cpp-sdk-core/include/aws/core/client/AWSProtocolClient.h index 488ffb8bf651..9878e7306648 100644 --- a/src/aws-cpp-sdk-core/include/aws/core/client/AWSProtocolClient.h +++ b/src/aws-cpp-sdk-core/include/aws/core/client/AWSProtocolClient.h @@ -175,10 +175,6 @@ namespace Aws error.SetResponseHeaders(httpResponse->GetHeaders()); error.SetResponseCode(httpResponse->GetResponseCode()); - if (error.GetResponseCode() == Aws::Http::HttpResponseCode::TOO_MANY_REQUESTS && error.GetErrorType() == CoreErrors::UNKNOWN) - { - error.SetRetryableType(RetryableType::RETRYABLE_THROTTLING); - } error.SetRemoteHostIpAddress(httpResponse->GetOriginatingRequest().GetResolvedRemoteHost()); AWS_LOGSTREAM_ERROR(GetClientLogTag(), error); return error; 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 d67acad9cd71..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,7 +127,7 @@ class AWS_CORE_API AdaptiveRetryStrategy : public StandardRetryStrategy */ AdaptiveRetryStrategy(long maxAttempts = 3); AdaptiveRetryStrategy(std::shared_ptr retryQuotaContainer, long maxAttempts = 3); - AdaptiveRetryStrategy(long maxAttempts, double transientBackoffBaseSec); + 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 7a520d6a7949..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 @@ -657,8 +657,24 @@ namespace Aws 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); - AWS_CORE_API std::shared_ptr InitRetryStrategy(int maxRetries, Aws::String retryMode, double transientBackoffBaseSec, - const Aws::String& profileName); + + 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 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 d06ea4e39914..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 @@ -9,11 +9,8 @@ #include #include #include -#include #include -#include - namespace Aws { namespace Client @@ -79,16 +76,7 @@ namespace Aws namespace Internal { - AWS_CORE_LOCAL bool UsesRetryBehavior21(const RetryStrategy* retryStrategy); - - inline void ClassifyUnmodeledTooManyRequests(AWSError& error) - { - if (error.GetResponseCode() == Aws::Http::HttpResponseCode::TOO_MANY_REQUESTS && - error.GetErrorType() == CoreErrors::UNKNOWN) - { - error.SetRetryableType(RetryableType::RETRYABLE_THROTTLING); - } - } + 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 39d844d3e008..20b18446aa63 100644 --- a/src/aws-cpp-sdk-core/include/smithy/client/AwsSmithyClientBase.h +++ b/src/aws-cpp-sdk-core/include/smithy/client/AwsSmithyClientBase.h @@ -266,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 5c165227cb59..444cb83e8242 100644 --- a/src/aws-cpp-sdk-core/source/client/AWSClient.cpp +++ b/src/aws-cpp-sdk-core/source/client/AWSClient.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -178,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::Client::Internal::UsesRetryBehavior21(m_retryStrategy.get())}, + m_isLegacyRetryStrategy{Aws::Client::Internal::IsLegacyRetryStrategy(m_retryStrategy.get())}, m_disableExpectHeader(configuration.disableExpectHeader) { } @@ -210,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::Client::Internal::UsesRetryBehavior21(m_retryStrategy.get())}, + m_isLegacyRetryStrategy{Aws::Client::Internal::IsLegacyRetryStrategy(m_retryStrategy.get())}, m_disableExpectHeader(configuration.disableExpectHeader) { } @@ -306,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 +400,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 +491,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); @@ -673,6 +674,7 @@ HttpResponseOutcome AWSClient::AttemptOneRequest(const std::shared_ptrGetResponseBody(), httpResponse->GetHeaders())) @@ -727,6 +729,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); @@ -886,7 +889,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())) @@ -987,7 +992,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 8c137b851390..1768fb9423b1 100644 --- a/src/aws-cpp-sdk-core/source/client/AWSErrorMarshaller.cpp +++ b/src/aws-cpp-sdk-core/source/client/AWSErrorMarshaller.cpp @@ -11,7 +11,7 @@ #include #include #include -#include +#include using namespace Aws::Utils::Logging; using namespace Aws::Utils::Json; diff --git a/src/aws-cpp-sdk-core/source/client/AWSXmlClient.cpp b/src/aws-cpp-sdk-core/source/client/AWSXmlClient.cpp index 7aa25429d3ce..abbb7c5015cc 100644 --- a/src/aws-cpp-sdk-core/source/client/AWSXmlClient.cpp +++ b/src/aws-cpp-sdk-core/source/client/AWSXmlClient.cpp @@ -22,7 +22,6 @@ #include #include #include -#include #include using namespace Aws; @@ -231,7 +230,6 @@ AWSError AWSXMLClient::BuildAWSError(const std::shared_ptrGetHeaders()); error.SetResponseCode(httpResponse->GetResponseCode()); - Aws::Client::Internal::ClassifyUnmodeledTooManyRequests(error); error.SetRemoteHostIpAddress(httpResponse->GetOriginatingRequest().GetResolvedRemoteHost()); AWS_LOGSTREAM_ERROR(AWS_XML_CLIENT_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 9c372d405bd1..e973baf94a1c 100644 --- a/src/aws-cpp-sdk-core/source/client/AdaptiveRetryStrategy.cpp +++ b/src/aws-cpp-sdk-core/source/client/AdaptiveRetryStrategy.cpp @@ -162,8 +162,8 @@ namespace Aws StandardRetryStrategy(retryQuotaContainer, maxAttempts) {} - AdaptiveRetryStrategy::AdaptiveRetryStrategy(long maxAttempts, double transientBackoffBaseSec) : - StandardRetryStrategy(maxAttempts, transientBackoffBaseSec) + AdaptiveRetryStrategy::AdaptiveRetryStrategy(const StandardRetryStrategyOptions& options) : + StandardRetryStrategy(options) {} bool AdaptiveRetryStrategy::HasSendToken() diff --git a/src/aws-cpp-sdk-core/source/client/ClientConfiguration.cpp b/src/aws-cpp-sdk-core/source/client/ClientConfiguration.cpp index fb895f5ea157..d86f713953a5 100644 --- a/src/aws-cpp-sdk-core/source/client/ClientConfiguration.cpp +++ b/src/aws-cpp-sdk-core/source/client/ClientConfiguration.cpp @@ -87,7 +87,90 @@ static T LoadEnumFromString(const std::array, N>& mapp return mapping->second; } -static std::shared_ptr InitRetryStrategyForProfile(const Aws::String& profileName); +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 = []() { @@ -500,7 +583,11 @@ ClientConfiguration::ClientConfiguration(const char* profile, bool shouldDisable } this->credentialProviderConfig.profile = this->profileName; const Aws::String retryProfileName = this->profileName; - this->configFactories.retryStrategyCreateFn = [retryProfileName]() { return InitRetryStrategyForProfile(retryProfileName); }; + this->configFactories.retryStrategyCreateFn = [retryProfileName]() { + InitRetryStrategyOptions options; + options.profileName = retryProfileName; + return InitRetryStrategy(options); + }; setLegacyClientConfigurationParameters(*this); setConfigFromEnvOrProfile(*this); // Call EC2 Instance Metadata service only once @@ -576,133 +663,46 @@ ClientConfiguration::ClientConfiguration(bool /*useSmartDefaults*/, const char* Aws::Config::Defaults::SetSmartDefaultsConfigurationParameters(*this, defaultMode, hasEc2MetadataRegion, ec2MetadataRegion); } -static Aws::String GetRetryConfigValue(const Aws::String& profileName, const Aws::String& key) -{ - return profileName.empty() ? Aws::Config::GetCachedConfigValue(key) : Aws::Config::GetCachedConfigValue(profileName, key); -} - -static 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()); - // "default" is the strategy name of DefaultRetryStrategy, the legacy retry behavior. - return retryMode == "default" ? "legacy" : retryMode; -} - -static 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; -} - -static 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; -} - std::shared_ptr InitRetryStrategy(int maxAttempts, Aws::String retryMode) { return CreateRetryStrategy(maxAttempts, ResolveRetryMode(retryMode, "")); } std::shared_ptr InitRetryStrategy(int maxAttempts, Aws::String retryMode, double transientBackoffBaseSec) { - return InitRetryStrategy(maxAttempts, retryMode, 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, - const Aws::String& profileName) { - retryMode = ResolveRetryMode(retryMode, profileName); +std::shared_ptr InitRetryStrategy(const InitRetryStrategyOptions& options) { + const Aws::String retryMode = ResolveRetryMode(options.retryMode, options.profileName); if (retryMode == "legacy") { - // Legacy mode ignores service-specific tuning. - return CreateRetryStrategy(ResolveConfiguredMaxAttempts(profileName), 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(profileName); - if (configuredMaxAttempts >= 0) - { - maxAttempts = configuredMaxAttempts; - } - - long attempts = static_cast(maxAttempts); + const int configuredMaxAttempts = ResolveConfiguredMaxAttempts(options.profileName); + strategyOptions.maxAttempts = configuredMaxAttempts >= 0 ? configuredMaxAttempts : options.maxAttempts; + strategyOptions.transientBackoffBaseSec = options.transientBackoffBaseSec; if (retryMode == "adaptive") { - return Aws::MakeShared(CLIENT_CONFIG_TAG, attempts, transientBackoffBaseSec); + return Aws::MakeShared(CLIENT_CONFIG_TAG, strategyOptions); } if (retryMode != "standard") { AWS_LOGSTREAM_WARN(CLIENT_CONFIG_TAG, "Unknown retry mode \"" << retryMode << "\", using standard retry mode."); } - 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); -} - -static std::shared_ptr InitRetryStrategyForProfile(const Aws::String& profileName) -{ - return CreateRetryStrategy(ResolveConfiguredMaxAttempts(profileName), ResolveRetryMode("", profileName)); + 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 164ad3c190b4..8260b9ae5c71 100644 --- a/src/aws-cpp-sdk-core/source/client/CoreErrors.cpp +++ b/src/aws-cpp-sdk-core/source/client/CoreErrors.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include @@ -91,15 +92,14 @@ 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)); - 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)); + } } void CoreErrorsMapper::CleanupCoreErrorsMapper() @@ -160,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 fb189be4f04e..268cfc4d424e 100644 --- a/src/aws-cpp-sdk-core/source/client/DefaultRetryStrategy.cpp +++ b/src/aws-cpp-sdk-core/source/client/DefaultRetryStrategy.cpp @@ -6,47 +6,18 @@ #include #include +#include #include using namespace Aws; using namespace Aws::Client; -namespace -{ - // Error names the core error mapper classifies as retryable only since Retry Behavior 2.1. - // DefaultRetryStrategy is the legacy retry behavior and does not retry them. - const char* const RETRY_21_ONLY_ERROR_NAMES[] = {"TooManyRequestsException", "ProvisionedThroughputExceededException", - "TransactionInProgressException", "RequestLimitExceeded", - "BandwidthLimitExceeded", "LimitExceededException", - "PriorRequestNotComplete", "EC2ThrottledException", - "IDPCommunicationError"}; - - bool IsRetry21OnlyError(const AWSError& error) - { - const CoreErrors errorType = error.GetErrorType(); - if (errorType == CoreErrors::THROTTLING || errorType == CoreErrors::NETWORK_CONNECTION) - { - const Aws::String& name = error.GetExceptionName(); - for (const char* retry21OnlyName : RETRY_21_ONLY_ERROR_NAMES) - { - if (name == retry21OnlyName) - { - return true; - } - } - } - // A 429 naming an exception the SDK does not know was not retried. - return error.GetResponseCode() == Aws::Http::HttpResponseCode::TOO_MANY_REQUESTS && - errorType == CoreErrors::UNKNOWN && !error.GetExceptionName().empty(); - } -} - bool DefaultRetryStrategy::ShouldRetry(const AWSError& error, long attemptedRetries) const { if (attemptedRetries >= m_maxRetries) return false; - if (IsRetry21OnlyError(error)) + 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 37b6f1555d03..a4f94f19a995 100644 --- a/src/aws-cpp-sdk-core/source/client/RetryStrategy.cpp +++ b/src/aws-cpp-sdk-core/source/client/RetryStrategy.cpp @@ -15,67 +15,28 @@ 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; - - class NewRetriesImpl : public StandardRetryStrategy::RetryImpl + std::shared_ptr QuotaContainerOrDefault(std::shared_ptr retryQuotaContainer) { - 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; - }; + return retryQuotaContainer ? retryQuotaContainer : Aws::MakeShared(RETRY_STRATEGY_TAG); + } - Aws::UniquePtr CreateRetryImpl(double transientBackoffBaseSec) + StandardRetryStrategyOptions MakeOptions(long maxAttempts, std::shared_ptr retryQuotaContainer) { - return Aws::MakeUnique("StandardRetryStrategy", transientBackoffBaseSec); + StandardRetryStrategyOptions options; + options.maxAttempts = maxAttempts; + options.retryQuotaContainer = std::move(retryQuotaContainer); + return options; } - std::shared_ptr CreateQuotaContainer() + StandardRetryStrategyOptions MakeOptions(long maxAttempts, double transientBackoffBaseSec) { - return Aws::MakeShared("StandardRetryStrategy"); + StandardRetryStrategyOptions options; + options.maxAttempts = maxAttempts; + options.transientBackoffBaseSec = transientBackoffBaseSec; + return options; } } // anonymous namespace @@ -85,10 +46,9 @@ namespace Aws { namespace Internal { - bool UsesRetryBehavior21(const RetryStrategy* retryStrategy) + bool IsLegacyRetryStrategy(const RetryStrategy* retryStrategy) { - // DefaultRetryStrategy is the legacy retry behavior, so it keeps the request handling from before Retry Behavior 2.1. - return retryStrategy && Aws::String("default") != retryStrategy->GetStrategyName(); + return retryStrategy && Aws::String("default") == retryStrategy->GetStrategyName(); } } // namespace Internal } // namespace Client @@ -102,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; @@ -145,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 b9217a93b0ec..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,11 +115,7 @@ namespace Aws return "standard"; } - void SetLegacyClientConfiguration(Aws::Client::ClientConfiguration& clientConfig) - { - // Leave the retry strategy unset so the client configuration factory resolves it. - clientConfig.retryStrategy = nullptr; - } + void SetLegacyClientConfiguration(Aws::Client::ClientConfiguration& /*clientConfig*/) {} void SetStandardClientConfiguration(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 8b4c14d7cb54..e07e3651f784 100644 --- a/src/aws-cpp-sdk-core/source/smithy/client/AwsSmithyClientBase.cpp +++ b/src/aws-cpp-sdk-core/source/smithy/client/AwsSmithyClientBase.cpp @@ -74,7 +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_enableNewRetries = Aws::Client::Internal::UsesRetryBehavior21(m_clientConfig->retryStrategy.get()); + 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); @@ -92,7 +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_enableNewRetries = Aws::Client::Internal::UsesRetryBehavior21(m_clientConfig->retryStrategy.get()); + 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); @@ -111,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); @@ -133,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 @@ -340,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); @@ -713,7 +713,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/AWSErrorMashallerTest.cpp b/tests/aws-cpp-sdk-core-tests/aws/client/AWSErrorMashallerTest.cpp index 7b86bd91a6a8..e8ff8903cc08 100644 --- a/tests/aws-cpp-sdk-core-tests/aws/client/AWSErrorMashallerTest.cpp +++ b/tests/aws-cpp-sdk-core-tests/aws/client/AWSErrorMashallerTest.cpp @@ -9,8 +9,10 @@ #include #include #include +#include #include #include +#include #include #include #include @@ -1398,7 +1400,7 @@ namespace explicit JsonClientUnderTest(const ClientConfiguration& configuration) : AWSJsonClient(configuration, Aws::MakeShared(ERROR_MARSHALLER_TEST_ALLOC_TAG), Aws::MakeShared(ERROR_MARSHALLER_TEST_ALLOC_TAG)) {} - using AWSJsonClient::BuildAWSError; + using AWSJsonClient::MakeRequest; }; } @@ -1420,25 +1422,32 @@ TEST_F(AWSErrorMarshallerTest, RpcV2UnmodeledTooManyRequestsIsRetryableThrottlin 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(Aws::MakeShared(ERROR_MARSHALLER_TEST_ALLOC_TAG)); + mockHttpClientFactory->SetClient(mockHttpClient); Aws::Http::SetHttpClientFactory(mockHttpClientFactory); - ClientConfiguration configuration; - JsonClientUnderTest client(configuration); - - auto request = Aws::Http::CreateHttpRequest(Aws::Http::URI("http://www.uri.com"), Aws::Http::HttpMethod::HTTP_POST, - Aws::Utils::Stream::DefaultResponseStreamFactoryMethod); - auto bodyless = Aws::MakeShared(ERROR_MARSHALLER_TEST_ALLOC_TAG, request); - bodyless->SetResponseCode(Aws::Http::HttpResponseCode::TOO_MANY_REQUESTS); - auto bodylessError = client.BuildAWSError(bodyless); - ASSERT_TRUE(bodylessError.ShouldRetry()); - ASSERT_TRUE(bodylessError.ShouldThrottle()); - - auto unknownName = client.BuildAWSError(WithResponseCode( - BuildHttpResponse("SomeUnknownException", "slow down", "Request Id"), Aws::Http::HttpResponseCode::TOO_MANY_REQUESTS)); - ASSERT_TRUE(unknownName.ShouldRetry()); - ASSERT_TRUE(unknownName.ShouldThrottle()); - + { + 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/RetryStrategyTest.cpp b/tests/aws-cpp-sdk-core-tests/aws/client/RetryStrategyTest.cpp index 8b13047625db..e09d09564896 100644 --- a/tests/aws-cpp-sdk-core-tests/aws/client/RetryStrategyTest.cpp +++ b/tests/aws-cpp-sdk-core-tests/aws/client/RetryStrategyTest.cpp @@ -33,8 +33,6 @@ class MockStandardRetryStrategy : public Aws::Client::StandardRetryStrategy MockStandardRetryStrategy() = default; explicit MockStandardRetryStrategy(double transientBackoffBaseSec) : StandardRetryStrategy(3, transientBackoffBaseSec) {} - explicit MockStandardRetryStrategy(std::shared_ptr retryQuotaContainer) - : StandardRetryStrategy(retryQuotaContainer) {} const std::shared_ptr& GetRetryQuotaContainer() const { @@ -76,11 +74,11 @@ 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(Aws::MakeShared(ALLOCATION_TAG)); + MockStandardRetryStrategy retryStrategy; ASSERT_EQ(500, retryStrategy.GetRetryQuotaContainer()->GetRetryQuota()); ASSERT_EQ(3, retryStrategy.GetMaxAttempts()); @@ -93,58 +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 -{ -}; // SEP Test Case 1 -TEST_F(NewRetriesStrategyTest, TransientRetryCost) +TEST_F(RetryStrategyTest, TransientRetryCost) { MockStandardRetryStrategy retryStrategy; AWSError transientError(CoreErrors::NETWORK_CONNECTION, true); @@ -159,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); @@ -173,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); @@ -189,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); @@ -200,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); @@ -219,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); @@ -231,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); @@ -247,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); @@ -261,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); @@ -275,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); @@ -289,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); @@ -303,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); @@ -519,7 +514,11 @@ TEST_F(InitRetryStrategyTest, RetrySettingsFromConfigFile) ClientConfiguration prod("prod"); ASSERT_STREQ("default", prod.configFactories.retryStrategyCreateFn()->GetStrategyName()); - ASSERT_EQ(2, InitRetryStrategy(4, "", 0.025, "tuned")->GetMaxAttempts()); + InitRetryStrategyOptions tunedOptions; + tunedOptions.maxAttempts = 4; + tunedOptions.transientBackoffBaseSec = 0.025; + tunedOptions.profileName = "tuned"; + ASSERT_EQ(2, InitRetryStrategy(tunedOptions)->GetMaxAttempts()); } Aws::Config::ReloadCachedConfigFile(); } 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 5fecb6f69258..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,10 +106,8 @@ namespace Aws return "standard"; } - void SetLegacyClientConfiguration(Aws::Client::ClientConfiguration& clientConfig) + void SetLegacyClientConfiguration(Aws::Client::ClientConfiguration& /*clientConfig*/) { - // Leave the retry strategy unset so the client configuration factory resolves it. - clientConfig.retryStrategy = nullptr; } #foreach($defaultMode in $defaultsModel.defaultsModes) @@ -121,9 +119,7 @@ namespace Aws void Set${defaultModeNameCamel}ClientConfiguration(Aws::Client::ClientConfiguration& clientConfig) { #foreach ($configEntry in $defaultMode.configurationValues.entrySet()) -#if (${configEntry.key} == "retryMode") -## The retry strategy is resolved by the client configuration factory. -#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 d69dc18827af..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 @@ -134,7 +134,11 @@ void ${metadata.classNamePrefix}ClientConfiguration::Load${serviceNamespace}Spec #if($serviceModel.metadata.serviceId == "DynamoDB" || $serviceModel.metadata.serviceId == "DynamoDB Streams") const Aws::String retryProfileName = this->profileName; this->configFactories.retryStrategyCreateFn = [retryProfileName]() -> std::shared_ptr { - return Client::InitRetryStrategy(4, "", 0.025, retryProfileName); + Client::InitRetryStrategyOptions options; + options.maxAttempts = 4; + options.transientBackoffBaseSec = 0.025; + options.profileName = retryProfileName; + return Client::InitRetryStrategy(options); }; #end ## Bedrock API key auth