diff --git a/generated/src/aws-cpp-sdk-dynamodb/source/DynamoDBClientConfiguration.cpp b/generated/src/aws-cpp-sdk-dynamodb/source/DynamoDBClientConfiguration.cpp index 0b726b1b5a4d..afb004ed24c8 100644 --- a/generated/src/aws-cpp-sdk-dynamodb/source/DynamoDBClientConfiguration.cpp +++ b/generated/src/aws-cpp-sdk-dynamodb/source/DynamoDBClientConfiguration.cpp @@ -38,10 +38,7 @@ void DynamoDBClientConfiguration::LoadDynamoDBSpecificConfig(const Aws::String& enableEndpointDiscovery = IsEndpointDiscoveryEnabled(this->endpointOverride, inputProfileName); } this->configFactories.retryStrategyCreateFn = []() -> std::shared_ptr { - // TODO: renable once default retries are evaluated - // Align with other SDKs to default retry to 10 times for dynamodb. - // return Client::InitRetryStrategy(10); - return Client::InitRetryStrategy(); + return Client::InitRetryStrategy(4, "", 0.025); }; } diff --git a/generated/src/aws-cpp-sdk-dynamodbstreams/include/aws/dynamodbstreams/DynamoDBStreamsClientConfiguration.h b/generated/src/aws-cpp-sdk-dynamodbstreams/include/aws/dynamodbstreams/DynamoDBStreamsClientConfiguration.h new file mode 100644 index 000000000000..0afdbbef7b33 --- /dev/null +++ b/generated/src/aws-cpp-sdk-dynamodbstreams/include/aws/dynamodbstreams/DynamoDBStreamsClientConfiguration.h @@ -0,0 +1,43 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ + +#pragma once + +#include +#include + +namespace Aws { +namespace DynamoDBStreams { +struct AWS_DYNAMODBSTREAMS_API DynamoDBStreamsClientConfiguration : public Aws::Client::GenericClientConfiguration { + using BaseClientConfigClass = Aws::Client::GenericClientConfiguration; + + DynamoDBStreamsClientConfiguration(const Aws::Client::ClientConfigurationInitValues& configuration = {}); + + /** + * Create a configuration based on settings in the aws configuration file for the given profile name. + * The configuration file location can be set via the environment variable AWS_CONFIG_FILE + * @param profileName the aws profile name. + * @param shouldDisableIMDS whether or not to disable IMDS calls. + */ + DynamoDBStreamsClientConfiguration(const char* profileName, bool shouldDisableIMDS = false); + + /** + * Create a configuration with a predefined smart defaults + * @param useSmartDefaults, required to differentiate c-tors + * @param defaultMode, default mode to use + * @param shouldDisableIMDS whether or not to disable IMDS calls. + */ + DynamoDBStreamsClientConfiguration(bool useSmartDefaults, const char* defaultMode = "legacy", bool shouldDisableIMDS = false); + + /** + * Converting constructors for compatibility with a legacy code + */ + DynamoDBStreamsClientConfiguration(const Aws::Client::ClientConfiguration& config); + + private: + void LoadDynamoDBStreamsSpecificConfig(const Aws::String& profileName); +}; +} // namespace DynamoDBStreams +} // namespace Aws \ No newline at end of file diff --git a/generated/src/aws-cpp-sdk-dynamodbstreams/include/aws/dynamodbstreams/DynamoDBStreamsEndpointProvider.h b/generated/src/aws-cpp-sdk-dynamodbstreams/include/aws/dynamodbstreams/DynamoDBStreamsEndpointProvider.h index 93802fc9a918..d990fb7b2e87 100644 --- a/generated/src/aws-cpp-sdk-dynamodbstreams/include/aws/dynamodbstreams/DynamoDBStreamsEndpointProvider.h +++ b/generated/src/aws-cpp-sdk-dynamodbstreams/include/aws/dynamodbstreams/DynamoDBStreamsEndpointProvider.h @@ -4,25 +4,30 @@ */ #pragma once -#include #include #include #include #include +#include #include #include namespace Aws { namespace DynamoDBStreams { namespace Endpoint { +using DynamoDBStreamsClientConfiguration = Aws::DynamoDBStreams::DynamoDBStreamsClientConfiguration; using EndpointParameters = Aws::Endpoint::EndpointParameters; using Aws::Endpoint::DefaultEndpointProvider; using Aws::Endpoint::EndpointProviderBase; using DynamoDBStreamsClientContextParameters = Aws::Endpoint::ClientContextParameters; -using DynamoDBStreamsClientConfiguration = Aws::Client::GenericClientConfiguration; -using DynamoDBStreamsBuiltInParameters = Aws::Endpoint::BuiltInParameters; +class AWS_DYNAMODBSTREAMS_API DynamoDBStreamsBuiltInParameters : public Aws::Endpoint::BuiltInParameters { + public: + virtual ~DynamoDBStreamsBuiltInParameters() {}; + using Aws::Endpoint::BuiltInParameters::SetFromClientConfiguration; + virtual void SetFromClientConfiguration(const DynamoDBStreamsClientConfiguration& config); +}; /** * The type for the DynamoDBStreams Client Endpoint Provider. @@ -35,6 +40,24 @@ using DynamoDBStreamsEndpointProviderBase = using DynamoDBStreamsDefaultEpProviderBase = DefaultEndpointProvider; +} // namespace Endpoint +} // namespace DynamoDBStreams + +namespace Endpoint { +/** + * Export endpoint provider symbols for Windows DLL, otherwise declare as extern + */ +AWS_DYNAMODBSTREAMS_EXTERN template class AWS_DYNAMODBSTREAMS_API Aws::Endpoint::EndpointProviderBase< + DynamoDBStreams::Endpoint::DynamoDBStreamsClientConfiguration, DynamoDBStreams::Endpoint::DynamoDBStreamsBuiltInParameters, + DynamoDBStreams::Endpoint::DynamoDBStreamsClientContextParameters>; + +AWS_DYNAMODBSTREAMS_EXTERN template class AWS_DYNAMODBSTREAMS_API Aws::Endpoint::DefaultEndpointProvider< + DynamoDBStreams::Endpoint::DynamoDBStreamsClientConfiguration, DynamoDBStreams::Endpoint::DynamoDBStreamsBuiltInParameters, + DynamoDBStreams::Endpoint::DynamoDBStreamsClientContextParameters>; +} // namespace Endpoint + +namespace DynamoDBStreams { +namespace Endpoint { /** * Default endpoint provider used for this service */ diff --git a/generated/src/aws-cpp-sdk-dynamodbstreams/include/aws/dynamodbstreams/DynamoDBStreamsServiceClientModel.h b/generated/src/aws-cpp-sdk-dynamodbstreams/include/aws/dynamodbstreams/DynamoDBStreamsServiceClientModel.h index 2f792da67e33..a400a2df65a3 100644 --- a/generated/src/aws-cpp-sdk-dynamodbstreams/include/aws/dynamodbstreams/DynamoDBStreamsServiceClientModel.h +++ b/generated/src/aws-cpp-sdk-dynamodbstreams/include/aws/dynamodbstreams/DynamoDBStreamsServiceClientModel.h @@ -51,7 +51,6 @@ class RetryStrategy; } // namespace Client namespace DynamoDBStreams { -using DynamoDBStreamsClientConfiguration = Aws::Client::GenericClientConfiguration; using DynamoDBStreamsEndpointProviderBase = Aws::DynamoDBStreams::Endpoint::DynamoDBStreamsEndpointProviderBase; using DynamoDBStreamsEndpointProvider = Aws::DynamoDBStreams::Endpoint::DynamoDBStreamsEndpointProvider; diff --git a/generated/src/aws-cpp-sdk-dynamodbstreams/source/DynamoDBStreamsClientConfiguration.cpp b/generated/src/aws-cpp-sdk-dynamodbstreams/source/DynamoDBStreamsClientConfiguration.cpp new file mode 100644 index 000000000000..0a7aea97952c --- /dev/null +++ b/generated/src/aws-cpp-sdk-dynamodbstreams/source/DynamoDBStreamsClientConfiguration.cpp @@ -0,0 +1,44 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ + +#include + +namespace Aws { +namespace DynamoDBStreams { + +void DynamoDBStreamsClientConfiguration::LoadDynamoDBStreamsSpecificConfig(const Aws::String& inputProfileName) { + this->configFactories.retryStrategyCreateFn = []() -> std::shared_ptr { + return Client::InitRetryStrategy(4, "", 0.025); + }; +#if defined(_MSC_VER) + (&reinterpret_cast(inputProfileName)); +#else + (void)(inputProfileName); +#endif +} + +DynamoDBStreamsClientConfiguration::DynamoDBStreamsClientConfiguration(const Aws::Client::ClientConfigurationInitValues& configuration) + : BaseClientConfigClass(configuration) { + LoadDynamoDBStreamsSpecificConfig(this->profileName); +} + +DynamoDBStreamsClientConfiguration::DynamoDBStreamsClientConfiguration(const char* inputProfileName, bool shouldDisableIMDS) + : BaseClientConfigClass(inputProfileName, shouldDisableIMDS) { + LoadDynamoDBStreamsSpecificConfig(Aws::String(inputProfileName)); +} + +DynamoDBStreamsClientConfiguration::DynamoDBStreamsClientConfiguration(bool useSmartDefaults, const char* defaultMode, + bool shouldDisableIMDS) + : BaseClientConfigClass(useSmartDefaults, defaultMode, shouldDisableIMDS) { + LoadDynamoDBStreamsSpecificConfig(this->profileName); +} + +DynamoDBStreamsClientConfiguration::DynamoDBStreamsClientConfiguration(const Aws::Client::ClientConfiguration& config) + : BaseClientConfigClass(config) { + LoadDynamoDBStreamsSpecificConfig(this->profileName); +} + +} // namespace DynamoDBStreams +} // namespace Aws diff --git a/generated/src/aws-cpp-sdk-dynamodbstreams/source/DynamoDBStreamsEndpointProvider.cpp b/generated/src/aws-cpp-sdk-dynamodbstreams/source/DynamoDBStreamsEndpointProvider.cpp index 065f00f2b992..d9070a748417 100644 --- a/generated/src/aws-cpp-sdk-dynamodbstreams/source/DynamoDBStreamsEndpointProvider.cpp +++ b/generated/src/aws-cpp-sdk-dynamodbstreams/source/DynamoDBStreamsEndpointProvider.cpp @@ -6,7 +6,27 @@ #include namespace Aws { +#ifndef AWS_DYNAMODBSTREAMS_EXPORTS // Except for Windows DLL +namespace Endpoint { +/** + * Instantiate endpoint providers + */ +template class Aws::Endpoint::EndpointProviderBase; + +template class Aws::Endpoint::DefaultEndpointProvider; +} // namespace Endpoint +#endif + namespace DynamoDBStreams { -namespace Endpoint {} // namespace Endpoint +namespace Endpoint { +void DynamoDBStreamsBuiltInParameters::SetFromClientConfiguration(const DynamoDBStreamsClientConfiguration& config) { + SetFromClientConfiguration(static_cast(config)); +} + +} // namespace Endpoint } // namespace DynamoDBStreams } // namespace Aws diff --git a/generated/src/aws-cpp-sdk-sqs/include/aws/sqs/model/ReceiveMessageRequest.h b/generated/src/aws-cpp-sdk-sqs/include/aws/sqs/model/ReceiveMessageRequest.h index f26fcc154d01..bdd85790b6f9 100644 --- a/generated/src/aws-cpp-sdk-sqs/include/aws/sqs/model/ReceiveMessageRequest.h +++ b/generated/src/aws-cpp-sdk-sqs/include/aws/sqs/model/ReceiveMessageRequest.h @@ -32,6 +32,7 @@ class ReceiveMessageRequest : public SQSRequest { // so we can not get operation's name from response. inline virtual const char* GetServiceRequestName() const override { return "ReceiveMessage"; } + inline virtual bool IsLongPollingOperation() const override { return true; } AWS_SQS_API Aws::String SerializePayload() const override; AWS_SQS_API Aws::Http::HeaderValueCollection GetRequestSpecificHeaders() const override; diff --git a/generated/src/aws-cpp-sdk-states/include/aws/states/model/GetActivityTaskRequest.h b/generated/src/aws-cpp-sdk-states/include/aws/states/model/GetActivityTaskRequest.h index 2764abe02c1d..a75013a70308 100644 --- a/generated/src/aws-cpp-sdk-states/include/aws/states/model/GetActivityTaskRequest.h +++ b/generated/src/aws-cpp-sdk-states/include/aws/states/model/GetActivityTaskRequest.h @@ -26,6 +26,7 @@ class GetActivityTaskRequest : public SFNRequest { // so we can not get operation's name from response. inline virtual const char* GetServiceRequestName() const override { return "GetActivityTask"; } + inline virtual bool IsLongPollingOperation() const override { return true; } AWS_SFN_API Aws::String SerializePayload() const override; AWS_SFN_API Aws::Http::HeaderValueCollection GetRequestSpecificHeaders() const override; diff --git a/generated/src/aws-cpp-sdk-swf/include/aws/swf/model/PollForActivityTaskRequest.h b/generated/src/aws-cpp-sdk-swf/include/aws/swf/model/PollForActivityTaskRequest.h index d8d71bc9d785..509fb6bdc37d 100644 --- a/generated/src/aws-cpp-sdk-swf/include/aws/swf/model/PollForActivityTaskRequest.h +++ b/generated/src/aws-cpp-sdk-swf/include/aws/swf/model/PollForActivityTaskRequest.h @@ -27,6 +27,7 @@ class PollForActivityTaskRequest : public SWFRequest { // so we can not get operation's name from response. inline virtual const char* GetServiceRequestName() const override { return "PollForActivityTask"; } + inline virtual bool IsLongPollingOperation() const override { return true; } AWS_SWF_API Aws::String SerializePayload() const override; AWS_SWF_API Aws::Http::HeaderValueCollection GetRequestSpecificHeaders() const override; diff --git a/generated/src/aws-cpp-sdk-swf/include/aws/swf/model/PollForDecisionTaskRequest.h b/generated/src/aws-cpp-sdk-swf/include/aws/swf/model/PollForDecisionTaskRequest.h index b359db083672..fb4709024c46 100644 --- a/generated/src/aws-cpp-sdk-swf/include/aws/swf/model/PollForDecisionTaskRequest.h +++ b/generated/src/aws-cpp-sdk-swf/include/aws/swf/model/PollForDecisionTaskRequest.h @@ -27,6 +27,7 @@ class PollForDecisionTaskRequest : public SWFRequest { // so we can not get operation's name from response. inline virtual const char* GetServiceRequestName() const override { return "PollForDecisionTask"; } + inline virtual bool IsLongPollingOperation() const override { return true; } AWS_SWF_API Aws::String SerializePayload() const override; AWS_SWF_API Aws::Http::HeaderValueCollection GetRequestSpecificHeaders() const override; diff --git a/generated/tests/dynamodbstreams-gen-tests/DynamoDBStreamsIncludeTests.cpp b/generated/tests/dynamodbstreams-gen-tests/DynamoDBStreamsIncludeTests.cpp index 6e73813d35c3..1aebf64a1380 100644 --- a/generated/tests/dynamodbstreams-gen-tests/DynamoDBStreamsIncludeTests.cpp +++ b/generated/tests/dynamodbstreams-gen-tests/DynamoDBStreamsIncludeTests.cpp @@ -7,6 +7,7 @@ #include #include +#include #include #include #include diff --git a/src/aws-cpp-sdk-core/include/aws/core/AmazonWebServiceRequest.h b/src/aws-cpp-sdk-core/include/aws/core/AmazonWebServiceRequest.h index 77b8e9d72aa7..33e80b9779cf 100644 --- a/src/aws-cpp-sdk-core/include/aws/core/AmazonWebServiceRequest.h +++ b/src/aws-cpp-sdk-core/include/aws/core/AmazonWebServiceRequest.h @@ -114,6 +114,12 @@ namespace Aws */ virtual bool IsChunked() const { return false; } + /** + * Whether this operation is a long-polling operation (e.g. SQS ReceiveMessage). + * Long-polling operations apply a backoff delay before returning when retry quota is exhausted. + */ + virtual bool IsLongPollingOperation() const { return false; } + /** * Register closure for request signed event. */ 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 b7a691af2d7e..a5c164384a7b 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 @@ -653,6 +653,7 @@ 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); /** * 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 3e6d59f78b9a..272817b287b4 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 @@ -124,6 +124,7 @@ namespace Aws public: StandardRetryStrategy(long maxAttempts = 3); StandardRetryStrategy(std::shared_ptr retryQuotaContainer, long maxAttempts = 3); + StandardRetryStrategy(long maxAttempts, double transientBackoffBaseSec); virtual ~StandardRetryStrategy(); virtual void RequestBookkeeping(const HttpResponseOutcome& httpResponseOutcome) override; diff --git a/src/aws-cpp-sdk-core/include/aws/core/internal/LongPollingRetryHelper.h b/src/aws-cpp-sdk-core/include/aws/core/internal/LongPollingRetryHelper.h new file mode 100644 index 000000000000..5d992c2f125b --- /dev/null +++ b/src/aws-cpp-sdk-core/include/aws/core/internal/LongPollingRetryHelper.h @@ -0,0 +1,40 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ + +#pragma once + +#include +#include +#include +#include + +namespace Aws { +namespace Client { +namespace Internal { + +/** + * For long-polling operations (e.g. SQS ReceiveMessage), when the retry strategy + * returns ShouldRetry=false due to quota exhaustion (not max attempts), apply a + * backoff delay before returning the error to the caller. This prevents all blocked + * callers from slamming the service simultaneously after quota is restored. + * + * Returns the sleep duration in milliseconds, or 0 if no sleep is needed. + */ +inline long ComputeLongPollingSleepMs( + const AWSError& error, + long retries, + const std::shared_ptr& retryStrategy, + bool isLongPollingOperation) +{ + if (!isLongPollingOperation) return 0; + if (!error.ShouldRetry()) return 0; + if (retries + 1 >= retryStrategy->GetMaxAttempts()) return 0; + + return retryStrategy->CalculateDelayBeforeNextRetry(error, retries); +} + +} // namespace Internal +} // namespace Client +} // namespace Aws diff --git a/src/aws-cpp-sdk-core/source/client/AWSClient.cpp b/src/aws-cpp-sdk-core/source/client/AWSClient.cpp index cf3dcf867f25..2d09351a67f0 100644 --- a/src/aws-cpp-sdk-core/source/client/AWSClient.cpp +++ b/src/aws-cpp-sdk-core/source/client/AWSClient.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -362,6 +363,12 @@ HttpResponseOutcome AWSClient::AttemptExhaustively(const Aws::Http::URI& uri, if (!retryWithCorrectRegion && !m_retryStrategy->ShouldRetry(outcome.GetError(), retries)) { + long lpSleepMs = Internal::ComputeLongPollingSleepMs( + outcome.GetError(), retries, m_retryStrategy, request.IsLongPollingOperation()); + if (lpSleepMs > 0) + { + m_httpClient->RetryRequestSleep(std::chrono::milliseconds(lpSleepMs)); + } break; } if (request.IsEventStreamRequest() && diff --git a/src/aws-cpp-sdk-core/source/client/ClientConfiguration.cpp b/src/aws-cpp-sdk-core/source/client/ClientConfiguration.cpp index 3198214e8d1e..b43dab2906aa 100644 --- a/src/aws-cpp-sdk-core/source/client/ClientConfiguration.cpp +++ b/src/aws-cpp-sdk-core/source/client/ClientConfiguration.cpp @@ -543,7 +543,7 @@ ClientConfiguration::ClientConfiguration(bool /*useSmartDefaults*/, const char* Aws::Config::Defaults::SetSmartDefaultsConfigurationParameters(*this, defaultMode, hasEc2MetadataRegion, ec2MetadataRegion); } -std::shared_ptr InitRetryStrategy(int maxAttempts, Aws::String retryMode) { +static Aws::String ResolveRetryMode(Aws::String retryMode) { if (retryMode.empty()) { retryMode = Aws::Environment::GetEnv("AWS_RETRY_MODE"); @@ -556,6 +556,11 @@ std::shared_ptr InitRetryStrategy(int maxAttempts, Aws::String re { retryMode = "standard"; } + return retryMode; +} + +std::shared_ptr InitRetryStrategy(int maxAttempts, Aws::String retryMode) { + retryMode = ResolveRetryMode(retryMode); std::shared_ptr retryStrategy; if (retryMode == "standard") @@ -590,6 +595,24 @@ std::shared_ptr InitRetryStrategy(int maxAttempts, Aws::String re return retryStrategy; } +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") + { + // Gate is off: ignore service-specific tuning, use default behavior + return InitRetryStrategy(-1, retryMode); + } + + // 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") + { + return InitRetryStrategy(maxAttempts, retryMode); + } + + long attempts = (maxAttempts < 0) ? 3 : static_cast(maxAttempts); + return Aws::MakeShared(CLIENT_CONFIG_TAG, attempts, transientBackoffBaseSec); +} + std::shared_ptr InitRetryStrategy(Aws::String retryMode) { int maxAttempts = 0; diff --git a/src/aws-cpp-sdk-core/source/client/RetryStrategy.cpp b/src/aws-cpp-sdk-core/source/client/RetryStrategy.cpp index 3720fd0474d4..1b2689ab8fdc 100644 --- a/src/aws-cpp-sdk-core/source/client/RetryStrategy.cpp +++ b/src/aws-cpp-sdk-core/source/client/RetryStrategy.cpp @@ -32,6 +32,8 @@ namespace Aws namespace { const char RETRY_STRATEGY_TAG[] = "StandardRetryStrategy"; + 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"; @@ -51,9 +53,11 @@ namespace { class NewRetriesImpl : public StandardRetryStrategy::RetryImpl { public: + explicit NewRetriesImpl(double transientBackoffBaseSec) : m_transientBackoffBaseSec(transientBackoffBaseSec) {} + long CalculateDelay(const AWSError& error, long attemptedRetries) const override { - double x = error.ShouldThrottle() ? 1.0 : 0.05; + 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); @@ -76,13 +80,16 @@ namespace { return static_cast(t_i * 1000.0); } + + private: + double m_transientBackoffBaseSec; }; - Aws::UniquePtr CreateRetryImpl() + Aws::UniquePtr CreateRetryImpl(double transientBackoffBaseSec) { if (IsNewRetriesEnabled()) { - return Aws::MakeUnique("StandardRetryStrategy"); + return Aws::MakeUnique("StandardRetryStrategy", transientBackoffBaseSec); } return Aws::MakeUnique("StandardRetryStrategy"); } @@ -107,11 +114,15 @@ namespace Aws StandardRetryStrategy::StandardRetryStrategy(long maxAttempts) : m_retryQuotaContainer(CreateQuotaContainer()), m_maxAttempts(maxAttempts), - m_impl(CreateRetryImpl()) {} + m_impl(CreateRetryImpl(DEFAULT_TRANSIENT_BACKOFF_BASE_SEC)) {} StandardRetryStrategy::StandardRetryStrategy(std::shared_ptr retryQuotaContainer, long maxAttempts) : m_retryQuotaContainer(retryQuotaContainer), m_maxAttempts(maxAttempts), - m_impl(CreateRetryImpl()) {} + m_impl(CreateRetryImpl(DEFAULT_TRANSIENT_BACKOFF_BASE_SEC)) {} + + StandardRetryStrategy::StandardRetryStrategy(long maxAttempts, double transientBackoffBaseSec) + : m_retryQuotaContainer(CreateQuotaContainer()), m_maxAttempts(maxAttempts), + m_impl(CreateRetryImpl(transientBackoffBaseSec)) {} StandardRetryStrategy::~StandardRetryStrategy() = default; 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 a324b1d8b119..a454322510ec 100644 --- a/src/aws-cpp-sdk-core/source/smithy/client/AwsSmithyClientBase.cpp +++ b/src/aws-cpp-sdk-core/source/smithy/client/AwsSmithyClientBase.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -613,6 +614,13 @@ void AwsSmithyClientBase::HandleAsyncReply(std::shared_ptrretryStrategy->ShouldRetry(outcome.GetError(), static_cast(pRequestCtx->m_retryCount))) { + bool isLongPoll = pRequestCtx->m_pRequest && pRequestCtx->m_pRequest->IsLongPollingOperation(); + long lpSleepMs = Aws::Client::Internal::ComputeLongPollingSleepMs( + outcome.GetError(), static_cast(pRequestCtx->m_retryCount), m_clientConfig->retryStrategy, isLongPoll); + if (lpSleepMs > 0) + { + m_httpClient->RetryRequestSleep(std::chrono::milliseconds(lpSleepMs)); + } break; } diff --git a/tests/aws-cpp-sdk-core-tests/aws/client/AWSClientTest.cpp b/tests/aws-cpp-sdk-core-tests/aws/client/AWSClientTest.cpp index 846e27c6d666..1f2fb6bdd960 100644 --- a/tests/aws-cpp-sdk-core-tests/aws/client/AWSClientTest.cpp +++ b/tests/aws-cpp-sdk-core-tests/aws/client/AWSClientTest.cpp @@ -421,6 +421,92 @@ TEST_F(AWSClientTestSuite, TestStandardRetryStrategy) ASSERT_EQ(3, clientWithStandardRetryStrategy.GetRetryQuotaContainer()->GetRetryQuota()); } +static AmazonWebServiceRequestMock MakeLongPollingRequest() +{ + AmazonWebServiceRequestMock request; + auto params = Aws::MakeShared(ALLOCATION_TAG); + params->parameterMap.emplace("isLongPollingOperation", "true"); + request.SetServiceSpecificParameters(params); + return request; +} + +// 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); + MockAWSClientWithStandardRetryStrategy client(config); + ASSERT_TRUE(client.GetRetryQuotaContainer()->AcquireRetryQuota(498)); + + HeaderValueCollection responseHeaders; + QueueMockResponse(AWSError(CoreErrors::NETWORK_CONNECTION, true), responseHeaders); + + auto request = MakeLongPollingRequest(); + auto outcome = client.MakeRequest(request); + ASSERT_FALSE(outcome.IsSuccess()); + ASSERT_EQ(0, client.GetRequestAttemptedRetries()); +} + +// 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); + MockAWSClientWithStandardRetryStrategy client(config); + + HeaderValueCollection responseHeaders; + AWSError transientError(CoreErrors::NETWORK_CONNECTION, true); + QueueMockResponse(transientError, responseHeaders); + QueueMockResponse(transientError, responseHeaders); + QueueMockResponse(transientError, responseHeaders); + + auto request = MakeLongPollingRequest(); + auto outcome = client.MakeRequest(request); + ASSERT_FALSE(outcome.IsSuccess()); + ASSERT_EQ(2, client.GetRequestAttemptedRetries()); +} + +// 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); + MockAWSClientWithStandardRetryStrategy client(config); + + HeaderValueCollection responseHeaders; + QueueMockResponse(AWSError(CoreErrors::NETWORK_CONNECTION, true), responseHeaders); + QueueMockResponse(HttpResponseCode::OK, responseHeaders); + + auto request = MakeLongPollingRequest(); + auto outcome = client.MakeRequest(request); + AWS_ASSERT_SUCCESS(outcome); + ASSERT_EQ(1, client.GetRequestAttemptedRetries()); +} + +// 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); + MockAWSClientWithStandardRetryStrategy client(config); + + HeaderValueCollection responseHeaders; + QueueMockResponse(HttpResponseCode::BAD_REQUEST, responseHeaders); + + auto request = MakeLongPollingRequest(); + auto outcome = client.MakeRequest(request); + ASSERT_FALSE(outcome.IsSuccess()); + ASSERT_EQ(0, client.GetRequestAttemptedRetries()); +} + TEST_F(AWSClientTestSuite, TestRecursionDetection) { struct AWSClientTestSuite_TestRecursionDetection_TestCase 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 18fe1f7570a5..27b57994acf9 100644 --- a/tests/aws-cpp-sdk-core-tests/aws/client/RetryStrategyTest.cpp +++ b/tests/aws-cpp-sdk-core-tests/aws/client/RetryStrategyTest.cpp @@ -25,6 +25,10 @@ static const char ALLOCATION_TAG[] = "RetryStrategyTest"; class MockStandardRetryStrategy : public Aws::Client::StandardRetryStrategy { public: + MockStandardRetryStrategy() = default; + explicit MockStandardRetryStrategy(double transientBackoffBaseSec) + : StandardRetryStrategy(3, transientBackoffBaseSec) {} + const std::shared_ptr& GetRetryQuotaContainer() const { return m_retryQuotaContainer; @@ -290,4 +294,26 @@ TEST_F(NewRetriesStrategyTest, InvalidRetryAfterFallsBack) long delay = retryStrategy.CalculateDelayBeforeNextRetry(error, 0); ASSERT_GE(delay, 0); ASSERT_LE(delay, 50); -} \ No newline at end of file +} + +// SEP Test Case 11 - DynamoDB tuning: 25ms non-throttling backoff base +TEST_F(NewRetriesStrategyTest, DynamoDBBackoffBase) +{ + MockStandardRetryStrategy retryStrategy(0.025); + AWSError transientError(CoreErrors::NETWORK_CONNECTION, true); + + // Backoff with 25ms base: [0, 25ms] at i=0, [0, 50ms] at i=1, etc. + for (int i = 0; i < 4; ++i) + { + long delay = retryStrategy.CalculateDelayBeforeNextRetry(transientError, i); + long maxDelay = static_cast(0.025 * (1L << i) * 1000.0); + ASSERT_GE(delay, 0) << "Retry " << i; + ASSERT_LE(delay, maxDelay) << "Retry " << i; + } + + // Throttling base is unchanged at 1s regardless of the transient base. + AWSError throttlingError(CoreErrors::THROTTLING, RetryableType::RETRYABLE_THROTTLING); + long delay = retryStrategy.CalculateDelayBeforeNextRetry(throttlingError, 0); + ASSERT_GE(delay, 0); + ASSERT_LE(delay, 1000); +} diff --git a/tools/code-generation/generator/src/main/java/com/amazonaws/util/awsclientgenerator/domainmodels/codegeneration/Operation.java b/tools/code-generation/generator/src/main/java/com/amazonaws/util/awsclientgenerator/domainmodels/codegeneration/Operation.java index c69ebf9772c8..3eb73526a9f1 100644 --- a/tools/code-generation/generator/src/main/java/com/amazonaws/util/awsclientgenerator/domainmodels/codegeneration/Operation.java +++ b/tools/code-generation/generator/src/main/java/com/amazonaws/util/awsclientgenerator/domainmodels/codegeneration/Operation.java @@ -94,6 +94,9 @@ public class Operation { // For Requestless Defaults private boolean requestlessDefault = false; + // Long-polling operations that must back off even when retry quota is exhausted + private boolean longPolling = false; + public boolean hasRequest() { return this.request != null && !this.phonyRequest; } diff --git a/tools/code-generation/generator/src/main/java/com/amazonaws/util/awsclientgenerator/domainmodels/codegeneration/ServiceModel.java b/tools/code-generation/generator/src/main/java/com/amazonaws/util/awsclientgenerator/domainmodels/codegeneration/ServiceModel.java index a9bfe814188f..af2f34f524b8 100644 --- a/tools/code-generation/generator/src/main/java/com/amazonaws/util/awsclientgenerator/domainmodels/codegeneration/ServiceModel.java +++ b/tools/code-generation/generator/src/main/java/com/amazonaws/util/awsclientgenerator/domainmodels/codegeneration/ServiceModel.java @@ -91,6 +91,7 @@ public boolean hasServiceSpecificClientConfig() { return metadata.getServiceId().equalsIgnoreCase("S3") || metadata.getServiceId().equalsIgnoreCase("S3-CRT") || metadata.getServiceId().equalsIgnoreCase("S3 Control") || + metadata.getServiceId().equalsIgnoreCase("DynamoDB Streams") || metadata.getSigningName().equalsIgnoreCase("bedrock") || metadata.isHasEndpointDiscoveryTrait() || endpointRuleSetModel.getParameters().containsKey("AccountId") || endpointRuleSetModel.getParameters().containsKey("AccountIdEndpointMode"); diff --git a/tools/code-generation/generator/src/main/java/com/amazonaws/util/awsclientgenerator/transform/C2jModelToGeneratorModelTransformer.java b/tools/code-generation/generator/src/main/java/com/amazonaws/util/awsclientgenerator/transform/C2jModelToGeneratorModelTransformer.java index 89ed4e05ab95..13ac9be1437a 100644 --- a/tools/code-generation/generator/src/main/java/com/amazonaws/util/awsclientgenerator/transform/C2jModelToGeneratorModelTransformer.java +++ b/tools/code-generation/generator/src/main/java/com/amazonaws/util/awsclientgenerator/transform/C2jModelToGeneratorModelTransformer.java @@ -170,6 +170,7 @@ public ServiceModel convert() { convertShapes(); convertOperations(); + applyLongPollingTrait(); removeIgnoredOperations(); removeUnreferencedShapes(); postProcessShapes(); @@ -589,6 +590,24 @@ void convertOperations() { } } + private static final Map> LONG_POLLING_OPERATIONS = Map.of( + "SQS", Set.of("ReceiveMessage"), + "SFN", Set.of("GetActivityTask"), + "SWF", Set.of("PollForActivityTask", "PollForDecisionTask") + ); + + void applyLongPollingTrait() { + Optional.ofNullable(c2jServiceModel.getMetadata().getServiceId()) + .map(LONG_POLLING_OPERATIONS::get) + .ifPresent(longPollOps -> { + for (Map.Entry entry : operations.entrySet()) { + if (longPollOps.contains(entry.getKey())) { + entry.getValue().setLongPolling(true); + } + } + }); + } + Operation convertOperation(C2jOperation c2jOperation) { Operation operation = new Operation(); diff --git a/tools/code-generation/generator/src/main/resources/com/amazonaws/util/awsclientgenerator/velocity/cpp/RequestHeader.vm b/tools/code-generation/generator/src/main/resources/com/amazonaws/util/awsclientgenerator/velocity/cpp/RequestHeader.vm index 4516a24155af..1e0c8e945d04 100644 --- a/tools/code-generation/generator/src/main/resources/com/amazonaws/util/awsclientgenerator/velocity/cpp/RequestHeader.vm +++ b/tools/code-generation/generator/src/main/resources/com/amazonaws/util/awsclientgenerator/velocity/cpp/RequestHeader.vm @@ -57,6 +57,9 @@ namespace Model #if($shape.hasEventStreamMembers()) inline virtual bool IsEventStreamRequest() const override { return true; } #end +#if($operation.longPolling) + inline virtual bool IsLongPollingOperation() const override { return true; } +#end #if($hasEventStreamResponse) inline virtual bool HasEventStreamResponse() const override { return true; } #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 6331feba91f7..11679bd614cb 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 @@ -130,13 +130,10 @@ void ${metadata.classNamePrefix}ClientConfiguration::Load${serviceNamespace}Spec enableEndpointDiscovery = IsEndpointDiscoveryEnabled(this->endpointOverride, inputProfileName); } #end -## DyanmoDB historically requires 10 retries for backwards compatibility -#if($serviceModel.metadata.serviceId == "DynamoDB") +## 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 { - // TODO: renable once default retries are evaluated - // Align with other SDKs to default retry to 10 times for dynamodb. - // return Client::InitRetryStrategy(10); - return Client::InitRetryStrategy(); + return Client::InitRetryStrategy(4, "", 0.025); }; #end ## Bedrock API key auth diff --git a/tools/code-generation/generator/src/main/resources/com/amazonaws/util/awsclientgenerator/velocity/cpp/smithy/SmithyServiceInvokeOperationMethod.vm b/tools/code-generation/generator/src/main/resources/com/amazonaws/util/awsclientgenerator/velocity/cpp/smithy/SmithyServiceInvokeOperationMethod.vm index 881e801cd0a8..641a9c835cb2 100644 --- a/tools/code-generation/generator/src/main/resources/com/amazonaws/util/awsclientgenerator/velocity/cpp/smithy/SmithyServiceInvokeOperationMethod.vm +++ b/tools/code-generation/generator/src/main/resources/com/amazonaws/util/awsclientgenerator/velocity/cpp/smithy/SmithyServiceInvokeOperationMethod.vm @@ -1,7 +1,6 @@ #set($emitGenericOperation = true) #parse("com/amazonaws/util/awsclientgenerator/velocity/cpp/common/ServiceClientOperationRequestRequiredMemberValidate.vm") #set($emitGenericOperation = false) - #if($metadata.protocol == "rest-json" || $metadata.protocol == "rest-xml") auto result = InvokeServiceOperation(request, [&](Aws::Endpoint::AWSEndpoint& resolvedEndpoint) { #parse("com/amazonaws/util/awsclientgenerator/velocity/cpp/smithy/SmithyUriRequestQueryParams.vm")