From 9649266a7f8cb5ef52f2278b674a40c75a7f49bb Mon Sep 17 00:00:00 2001 From: Snigdha Date: Fri, 30 Oct 2026 12:24:00 -0400 Subject: [PATCH] Make Retry Behavior 2.1 the default retry behavior --- .../feature-retry-behavior-2-1-default.json | 6 + .../source/DynamoDBClientConfiguration.cpp | 6 + .../DynamoDBStreamsClientConfiguration.cpp | 6 + .../aws/core/client/AWSProtocolClient.h | 4 + .../aws/core/client/AdaptiveRetryStrategy.h | 1 + .../aws/core/client/ClientConfiguration.h | 6 +- .../aws/core/internal/RetryStrategyImpl.h | 21 +++ .../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 | 59 +++++---- .../source/client/CoreErrors.cpp | 27 ++-- .../source/client/RetryStrategy.cpp | 86 ++++++++---- .../defaults/ClientConfigurationDefaults.cpp | 11 +- .../smithy/client/AwsSmithyClientBase.cpp | 3 + .../aws/client/AWSClientTest.cpp | 4 - .../aws/client/AsyncAWSClientTest.cpp | 2 - .../aws/client/RetryStrategyTest.cpp | 124 ++++++++++++++++-- .../smithy/client/AsyncSmithyClientTest.cpp | 5 +- .../DynamoDBUnitTests.cpp | 42 ++++++ .../velocity/cpp/DefaultsSource.vm | 5 +- .../ServiceClientConfigurationSource.vm | 10 ++ 24 files changed, 350 insertions(+), 96 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..f56deae0d684 --- /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 to keep the previous DefaultRetryStrategy behavior." +} diff --git a/generated/src/aws-cpp-sdk-dynamodb/source/DynamoDBClientConfiguration.cpp b/generated/src/aws-cpp-sdk-dynamodb/source/DynamoDBClientConfiguration.cpp index afb004ed24c8..b83775bd770a 100644 --- a/generated/src/aws-cpp-sdk-dynamodb/source/DynamoDBClientConfiguration.cpp +++ b/generated/src/aws-cpp-sdk-dynamodb/source/DynamoDBClientConfiguration.cpp @@ -50,12 +50,18 @@ DynamoDBClientConfiguration::DynamoDBClientConfiguration(const Aws::Client::Clie DynamoDBClientConfiguration::DynamoDBClientConfiguration(const char* inputProfileName, bool shouldDisableIMDS) : BaseClientConfigClass(inputProfileName, shouldDisableIMDS), enableEndpointDiscovery(ClientConfiguration::enableEndpointDiscovery) { LoadDynamoDBSpecificConfig(Aws::String(inputProfileName)); + if (this->retryStrategy) { + this->retryStrategy = this->configFactories.retryStrategyCreateFn(); + } } DynamoDBClientConfiguration::DynamoDBClientConfiguration(bool useSmartDefaults, const char* defaultMode, bool shouldDisableIMDS) : BaseClientConfigClass(useSmartDefaults, defaultMode, shouldDisableIMDS), enableEndpointDiscovery(ClientConfiguration::enableEndpointDiscovery) { LoadDynamoDBSpecificConfig(this->profileName); + if (this->retryStrategy) { + this->retryStrategy = this->configFactories.retryStrategyCreateFn(); + } } DynamoDBClientConfiguration::DynamoDBClientConfiguration(const Aws::Client::ClientConfiguration& config) diff --git a/generated/src/aws-cpp-sdk-dynamodbstreams/source/DynamoDBStreamsClientConfiguration.cpp b/generated/src/aws-cpp-sdk-dynamodbstreams/source/DynamoDBStreamsClientConfiguration.cpp index 0a7aea97952c..b061ff6767cb 100644 --- a/generated/src/aws-cpp-sdk-dynamodbstreams/source/DynamoDBStreamsClientConfiguration.cpp +++ b/generated/src/aws-cpp-sdk-dynamodbstreams/source/DynamoDBStreamsClientConfiguration.cpp @@ -27,12 +27,18 @@ DynamoDBStreamsClientConfiguration::DynamoDBStreamsClientConfiguration(const Aws DynamoDBStreamsClientConfiguration::DynamoDBStreamsClientConfiguration(const char* inputProfileName, bool shouldDisableIMDS) : BaseClientConfigClass(inputProfileName, shouldDisableIMDS) { LoadDynamoDBStreamsSpecificConfig(Aws::String(inputProfileName)); + if (this->retryStrategy) { + this->retryStrategy = this->configFactories.retryStrategyCreateFn(); + } } DynamoDBStreamsClientConfiguration::DynamoDBStreamsClientConfiguration(bool useSmartDefaults, const char* defaultMode, bool shouldDisableIMDS) : BaseClientConfigClass(useSmartDefaults, defaultMode, shouldDisableIMDS) { LoadDynamoDBStreamsSpecificConfig(this->profileName); + if (this->retryStrategy) { + this->retryStrategy = this->configFactories.retryStrategyCreateFn(); + } } DynamoDBStreamsClientConfiguration::DynamoDBStreamsClientConfiguration(const Aws::Client::ClientConfiguration& config) 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..f6fde5e2de75 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,7 +652,7 @@ 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 = ""); 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..c12956856bb5 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,23 @@ namespace Aws mutable Aws::Utils::Threading::ReaderWriterLock m_retryQuotaLock; int m_retryQuota; }; + + namespace Internal + { + AWS_CORE_LOCAL std::shared_ptr CreateLegacyRetryStrategy(); + + AWS_CORE_LOCAL bool UsesRetryBehavior21(const RetryStrategy* retryStrategy); + + AWS_CORE_LOCAL std::shared_ptr InitRetryStrategyForDefaultsMode(const Aws::String& defaultsModeRetryMode); + + 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..e761f6e718a8 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 @@ -571,7 +572,7 @@ ClientConfiguration::ClientConfiguration(bool /*useSmartDefaults*/, const char* Aws::Config::Defaults::SetSmartDefaultsConfigurationParameters(*this, defaultMode, hasEc2MetadataRegion, ec2MetadataRegion); } -static Aws::String ResolveRetryMode(Aws::String retryMode) { +static Aws::String ResolveRetryMode(Aws::String retryMode, const Aws::String& defaultRetryMode = "standard") { if (retryMode.empty()) { retryMode = Aws::Environment::GetEnv("AWS_RETRY_MODE"); @@ -580,45 +581,49 @@ static Aws::String ResolveRetryMode(Aws::String retryMode) { { retryMode = Aws::Config::GetCachedConfigValue("retry_mode"); } - if (Aws::Utils::StringUtils::ToLower(Aws::Environment::GetEnv("AWS_NEW_RETRIES_2026").c_str()) == "true" && retryMode.empty()) + if (retryMode.empty()) { - retryMode = "standard"; + retryMode = defaultRetryMode; } - return retryMode; + return Aws::Utils::StringUtils::ToLower(retryMode.c_str()); } std::shared_ptr InitRetryStrategy(int maxAttempts, Aws::String retryMode) { retryMode = ResolveRetryMode(retryMode); std::shared_ptr retryStrategy; - if (retryMode == "standard") + if (retryMode == "legacy") + { + retryStrategy = Internal::CreateLegacyRetryStrategy(); + } + 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; } @@ -645,9 +650,10 @@ static int ResolveConfiguredMaxAttempts() } 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") + retryMode = ResolveRetryMode(retryMode); + if (retryMode == "legacy") { - // Gate is off: ignore service-specific tuning, use default behavior + // Legacy mode ignores service-specific tuning. return InitRetryStrategy(retryMode); } @@ -658,14 +664,15 @@ std::shared_ptr InitRetryStrategy(int maxAttempts, Aws::String re 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); } @@ -674,6 +681,14 @@ std::shared_ptr InitRetryStrategy(Aws::String retryMode) return InitRetryStrategy(ResolveConfiguredMaxAttempts(), retryMode); } +namespace Internal +{ +std::shared_ptr InitRetryStrategyForDefaultsMode(const Aws::String& defaultsModeRetryMode) +{ + return InitRetryStrategy(ResolveRetryMode("", defaultsModeRetryMode)); +} +} // namespace Internal + Aws::String ClientConfiguration::LoadConfigFromEnvOrProfile(const Aws::String& envKey, const Aws::String& profile, const Aws::String& profileProperty, 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/RetryStrategy.cpp b/src/aws-cpp-sdk-core/source/client/RetryStrategy.cpp index 1b2689ab8fdc..e1821173bcb3 100644 --- a/src/aws-cpp-sdk-core/source/client/RetryStrategy.cpp +++ b/src/aws-cpp-sdk-core/source/client/RetryStrategy.cpp @@ -5,9 +5,9 @@ #include #include +#include #include #include -#include #include #include #include @@ -34,22 +34,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 +71,75 @@ 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"); + } + + // Error names the core error mapper classifies as retryable only since Retry Behavior 2.1. + // Legacy retry mode did 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) { - return Aws::MakeShared("StandardRetryStrategy"); + const Aws::String& name = error.GetExceptionName(); + for (const char* retry21OnlyName : RETRY_21_ONLY_ERROR_NAMES) + { + if (name == retry21OnlyName) + { + return true; + } + } } - return Aws::MakeShared("StandardRetryStrategy"); + // Legacy retry mode did not retry a 429 naming an exception the SDK does not know. + return error.GetResponseCode() == Aws::Http::HttpResponseCode::TOO_MANY_REQUESTS && + errorType == CoreErrors::UNKNOWN && !error.GetExceptionName().empty(); } + + class LegacyRetryStrategy : public DefaultRetryStrategy + { + public: + bool ShouldRetry(const AWSError& error, long attemptedRetries) const override + { + if (IsRetry21OnlyError(error)) + { + return false; + } + return DefaultRetryStrategy::ShouldRetry(error, attemptedRetries); + } + }; } // anonymous namespace +namespace Aws +{ + namespace Client + { + namespace Internal + { + std::shared_ptr CreateLegacyRetryStrategy() + { + return Aws::MakeShared(RETRY_STRATEGY_TAG); + } + + 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..4dcb04bb0c1d 100644 --- a/src/aws-cpp-sdk-core/source/config/defaults/ClientConfigurationDefaults.cpp +++ b/src/aws-cpp-sdk-core/source/config/defaults/ClientConfigurationDefaults.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -117,31 +118,31 @@ namespace Aws void SetLegacyClientConfiguration(Aws::Client::ClientConfiguration& clientConfig) { - clientConfig.retryStrategy = Aws::Client::InitRetryStrategy("default"); + clientConfig.retryStrategy = Aws::Client::Internal::InitRetryStrategyForDefaultsMode("standard"); } void SetStandardClientConfiguration(Aws::Client::ClientConfiguration& clientConfig) { clientConfig.connectTimeoutMs = 3100; - clientConfig.retryStrategy = Aws::Client::InitRetryStrategy("standard"); + clientConfig.retryStrategy = Aws::Client::Internal::InitRetryStrategyForDefaultsMode("standard"); } void SetInRegionClientConfiguration(Aws::Client::ClientConfiguration& clientConfig) { clientConfig.connectTimeoutMs = 1100; - clientConfig.retryStrategy = Aws::Client::InitRetryStrategy("standard"); + clientConfig.retryStrategy = Aws::Client::Internal::InitRetryStrategyForDefaultsMode("standard"); } void SetCrossRegionClientConfiguration(Aws::Client::ClientConfiguration& clientConfig) { clientConfig.connectTimeoutMs = 3100; - clientConfig.retryStrategy = Aws::Client::InitRetryStrategy("standard"); + clientConfig.retryStrategy = Aws::Client::Internal::InitRetryStrategyForDefaultsMode("standard"); } void SetMobileClientConfiguration(Aws::Client::ClientConfiguration& clientConfig) { clientConfig.connectTimeoutMs = 30000; - clientConfig.retryStrategy = Aws::Client::InitRetryStrategy("standard"); + clientConfig.retryStrategy = Aws::Client::Internal::InitRetryStrategyForDefaultsMode("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/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..784deda338a5 100644 --- a/tests/aws-cpp-sdk-core-tests/aws/client/RetryStrategyTest.cpp +++ b/tests/aws-cpp-sdk-core-tests/aws/client/RetryStrategyTest.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -30,6 +31,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 +78,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 +139,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 +329,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 +356,109 @@ 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()); +} 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/DynamoDBUnitTests.cpp b/tests/aws-cpp-sdk-dynamodb-unit-tests/DynamoDBUnitTests.cpp index e2f376302b2e..fe073db471e2 100644 --- a/tests/aws-cpp-sdk-dynamodb-unit-tests/DynamoDBUnitTests.cpp +++ b/tests/aws-cpp-sdk-dynamodb-unit-tests/DynamoDBUnitTests.cpp @@ -833,3 +833,45 @@ 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"}}}; + + ExpectDynamoDBRetryStrategy(ResolvedRetryStrategy(DynamoDBClientConfiguration())); + ExpectDynamoDBRetryStrategy(ResolvedRetryStrategy(DynamoDBClientConfiguration(true))); + { + Environment::EnvironmentRAII adaptive{{{"AWS_RETRY_MODE", "adaptive"}}}; + auto retryStrategy = ResolvedRetryStrategy(DynamoDBClientConfiguration(true)); + EXPECT_STREQ("adaptive", retryStrategy->GetStrategyName()); + ExpectDynamoDBRetryStrategy(retryStrategy); + } +} + +TEST_F(DynamoDBUnitTest, ConfiguredRetrySettingsWinOverDynamoDBTuning) { + Environment::EnvironmentRAII envVars{ + {{"AWS_RETRY_MODE", ""}, {"AWS_MAX_ATTEMPTS", "2"}, {"AWS_REGION", "us-east-1"}, {"AWS_EC2_METADATA_DISABLED", "true"}}}; + EXPECT_EQ(2, ResolvedRetryStrategy(DynamoDBClientConfiguration(true))->GetMaxAttempts()); + + ClientConfiguration configuration; + configuration.retryStrategy = Aws::MakeShared(LOG_TAG, 7); + EXPECT_EQ(7, ResolvedRetryStrategy(DynamoDBClientConfiguration(configuration))->GetMaxAttempts()); +} 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..ca74fc736f98 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 @@ -14,6 +14,7 @@ \#include \#include \#include +\#include \#include \#include \#include @@ -108,7 +109,7 @@ namespace Aws void SetLegacyClientConfiguration(Aws::Client::ClientConfiguration& clientConfig) { - clientConfig.retryStrategy = Aws::Client::InitRetryStrategy("default"); + clientConfig.retryStrategy = Aws::Client::Internal::InitRetryStrategyForDefaultsMode("standard"); } #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()}); + clientConfig.retryStrategy = Aws::Client::Internal::InitRetryStrategyForDefaultsMode(${configEntry.value.getAsSourceString()}); #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..302d6ce685d3 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 @@ -170,11 +170,21 @@ ${metadata.classNamePrefix}ClientConfiguration::${metadata.classNamePrefix}Clien ${metadata.classNamePrefix}ClientConfiguration::${metadata.classNamePrefix}ClientConfiguration(const char* inputProfileName, bool shouldDisableIMDS) : BaseClientConfigClass(inputProfileName, shouldDisableIMDS)#if($metadata.hasEndpointDiscoveryTrait), enableEndpointDiscovery(ClientConfiguration::enableEndpointDiscovery)#end${nl}{ Load${serviceNamespace}SpecificConfig(Aws::String(inputProfileName)); +#if($serviceModel.metadata.serviceId == "DynamoDB" || $serviceModel.metadata.serviceId == "DynamoDB Streams") + if (this->retryStrategy) { + this->retryStrategy = this->configFactories.retryStrategyCreateFn(); + } +#end } ${metadata.classNamePrefix}ClientConfiguration::${metadata.classNamePrefix}ClientConfiguration(bool useSmartDefaults, const char* defaultMode, bool shouldDisableIMDS) : BaseClientConfigClass(useSmartDefaults, defaultMode, shouldDisableIMDS)#if($metadata.hasEndpointDiscoveryTrait), enableEndpointDiscovery(ClientConfiguration::enableEndpointDiscovery)#end${nl}{ Load${serviceNamespace}SpecificConfig(this->profileName); +#if($serviceModel.metadata.serviceId == "DynamoDB" || $serviceModel.metadata.serviceId == "DynamoDB Streams") + if (this->retryStrategy) { + this->retryStrategy = this->configFactories.retryStrategyCreateFn(); + } +#end } #set($classNamePrefix = $metadata.classNamePrefix)