Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changelog/feature-retry-behavior-2-1-default.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"type": "feature",
"category": "aws-cpp-sdk-core",
"contributor": "pulimsr",
"description": "Make Retry Behavior 2.1 the default retry behavior and remove AWS_NEW_RETRIES_2026. Set AWS_RETRY_MODE=legacy or retry_mode = legacy, or use DefaultRetryStrategy, to keep the previous behavior. Custom retry strategies now also get the Retry Behavior 2.1 request handling, and errors such as LimitExceededException or an HTTP 429 with an unknown error name now report ShouldRetry() as true."
}
4 changes: 3 additions & 1 deletion .cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,8 @@
"dtmp",
"ytmpe",
"dtmpe",
"isleapyear"
"isleapyear",
// ClassifyUnmodeledTooManyRequests
"Unmodeled"
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,13 @@ void DynamoDBClientConfiguration::LoadDynamoDBSpecificConfig(const Aws::String&
if (!enableEndpointDiscovery) {
enableEndpointDiscovery = IsEndpointDiscoveryEnabled(this->endpointOverride, inputProfileName);
}
this->configFactories.retryStrategyCreateFn = []() -> std::shared_ptr<Client::RetryStrategy> {
return Client::InitRetryStrategy(4, "", 0.025);
const Aws::String retryProfileName = this->profileName;
this->configFactories.retryStrategyCreateFn = [retryProfileName]() -> std::shared_ptr<Client::RetryStrategy> {
Client::InitRetryStrategyOptions options;
options.maxAttempts = 4;
options.transientBackoffBaseSec = 0.025;
options.profileName = retryProfileName;
return Client::InitRetryStrategy(options);
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,13 @@ namespace Aws {
namespace DynamoDBStreams {

void DynamoDBStreamsClientConfiguration::LoadDynamoDBStreamsSpecificConfig(const Aws::String& inputProfileName) {
this->configFactories.retryStrategyCreateFn = []() -> std::shared_ptr<Client::RetryStrategy> {
return Client::InitRetryStrategy(4, "", 0.025);
const Aws::String retryProfileName = this->profileName;
this->configFactories.retryStrategyCreateFn = [retryProfileName]() -> std::shared_ptr<Client::RetryStrategy> {
Client::InitRetryStrategyOptions options;
options.maxAttempts = 4;
options.transientBackoffBaseSec = 0.025;
options.profileName = retryProfileName;
return Client::InitRetryStrategy(options);
};
#if defined(_MSC_VER)
(&reinterpret_cast<const int&>(inputProfileName));
Expand Down
7 changes: 6 additions & 1 deletion src/aws-cpp-sdk-core/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion src/aws-cpp-sdk-core/include/aws/core/client/AWSClient.h
Original file line number Diff line number Diff line change
Expand Up @@ -394,7 +394,7 @@ namespace Aws
Aws::Client::RequestCompressionConfig m_requestCompressionConfig;
std::shared_ptr<smithy::client::UserAgentInterceptor> m_userAgentInterceptor;
Aws::Vector<std::shared_ptr<smithy::interceptor::Interceptor>> m_interceptors;
bool m_enableNewRetries;
bool m_isLegacyRetryStrategy;
bool m_disableExpectHeader;
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ class AWS_CORE_API AdaptiveRetryStrategy : public StandardRetryStrategy
*/
AdaptiveRetryStrategy(long maxAttempts = 3);
AdaptiveRetryStrategy(std::shared_ptr<RetryQuotaContainer> retryQuotaContainer, long maxAttempts = 3);
explicit AdaptiveRetryStrategy(const StandardRetryStrategyOptions& options);

/**
* Retrieve and consume a send token.
Expand Down
24 changes: 21 additions & 3 deletions src/aws-cpp-sdk-core/include/aws/core/client/ClientConfiguration.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<std::shared_ptr<RetryStrategy>()> retryStrategyCreateFn;
/**
Expand Down Expand Up @@ -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> retryStrategy = nullptr;
Expand Down Expand Up @@ -652,12 +652,30 @@ namespace Aws

/**
* A helper function to initialize a retry strategy.
* Default is DefaultRetryStrategy (i.e. exponential backoff)
* Default is StandardRetryStrategy. Retry mode "legacy" uses DefaultRetryStrategy.
*/
AWS_CORE_API std::shared_ptr<RetryStrategy> InitRetryStrategy(Aws::String retryMode = "");
AWS_CORE_API std::shared_ptr<RetryStrategy> InitRetryStrategy(int maxRetries, Aws::String retryMode = "");
AWS_CORE_API std::shared_ptr<RetryStrategy> InitRetryStrategy(int maxRetries, Aws::String retryMode, double transientBackoffBaseSec);

struct InitRetryStrategyOptions
{
/**
* Service default. AWS_MAX_ATTEMPTS or max_attempts from the config file override it.
*/
long maxAttempts = 3;
/**
* Empty uses AWS_RETRY_MODE, then retry_mode from the config file, then "standard".
*/
Aws::String retryMode;
double transientBackoffBaseSec = 0.05;
/**
* Config file profile to read retry_mode and max_attempts from. Empty uses the default profile.
*/
Aws::String profileName;
};
AWS_CORE_API std::shared_ptr<RetryStrategy> InitRetryStrategy(const InitRetryStrategyOptions& options);

/**
* A helper function to compute a user agent
* @return Aws::String with a user-agent
Expand Down
15 changes: 12 additions & 3 deletions src/aws-cpp-sdk-core/include/aws/core/client/RetryStrategy.h
Original file line number Diff line number Diff line change
Expand Up @@ -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> retryQuotaContainer;
};

class AWS_CORE_API StandardRetryStrategy : public RetryStrategy
{
public:
explicit StandardRetryStrategy(const StandardRetryStrategyOptions& options);
StandardRetryStrategy(long maxAttempts = 3);
StandardRetryStrategy(std::shared_ptr<RetryQuotaContainer> retryQuotaContainer, long maxAttempts = 3);
StandardRetryStrategy(long maxAttempts, double transientBackoffBaseSec);
Expand All @@ -138,14 +149,12 @@ namespace Aws

const char* GetStrategyName() const override { return "standard";}

class RetryImpl;

protected:
std::shared_ptr<RetryQuotaContainer> m_retryQuotaContainer;
long m_maxAttempts;

private:
Aws::UniquePtr<RetryImpl> m_impl;
double m_transientBackoffBaseSec;
};
} // namespace Client
} // namespace Aws
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/**
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0.
*/

#pragma once

#include <aws/core/Core_EXPORTS.h>
#include <aws/core/client/AWSError.h>
#include <aws/core/client/CoreErrors.h>

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<CoreErrors>& error);

AWS_CORE_LOCAL void ClassifyUnmodeledTooManyRequests(AWSError<CoreErrors>& error);
} // namespace Internal
} // namespace Client
} // namespace Aws
Original file line number Diff line number Diff line change
Expand Up @@ -73,5 +73,10 @@ namespace Aws
mutable Aws::Utils::Threading::ReaderWriterLock m_retryQuotaLock;
int m_retryQuota;
};

namespace Internal

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This header was supposed to be internal to the build (#3836 (comment)), but it's installed with everything else in include/aws/core/internal/. Nothing outside core should include it, so let's just stop installing it. Everything that includes it in this PR is a core .cpp file, so nothing breaks.

{
AWS_CORE_LOCAL bool IsLegacyRetryStrategy(const RetryStrategy* retryStrategy);
} // namespace Internal
} // namespace Client
} // namespace Aws
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,7 @@ namespace client
Aws::MakeShared<features::ChunkingInterceptor>("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();
Expand Down Expand Up @@ -267,7 +266,7 @@ namespace client
ResponseHandlerFunc&& responseHandler,
EndpointUpdateCallback&& endpointCallback
) const;
bool m_enableNewRetries = false;
bool m_isLegacyRetryStrategy = false;
};
} // namespace client
} // namespace smithy
22 changes: 14 additions & 8 deletions src/aws-cpp-sdk-core/source/client/AWSClient.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
#include <aws/core/client/ClientConfiguration.h>
#include <aws/core/client/CoreErrors.h>
#include <aws/core/client/RetryStrategy.h>
#include <aws/core/internal/RetryErrorClassification.h>
#include <aws/core/internal/RetryStrategyImpl.h>
#include <aws/core/client/RequestCompression.h>
#include <aws/core/http/HttpClient.h>
#include <aws/core/http/HttpClientFactory.h>
Expand Down Expand Up @@ -177,7 +179,7 @@ AWSClient::AWSClient(const Aws::Client::ClientConfiguration& configuration,
m_httpClient->IsDefaultAwsHttpClient() ? Aws::Client::HttpClientChunkedMode::DEFAULT : configuration.httpClientChunkedMode,
configuration.awsChunkedBufferSize),
m_userAgentInterceptor},
m_enableNewRetries{Aws::Utils::StringUtils::ToLower(Aws::Environment::GetEnv("AWS_NEW_RETRIES_2026").c_str()) == "true"},
m_isLegacyRetryStrategy{Aws::Client::Internal::IsLegacyRetryStrategy(m_retryStrategy.get())},
m_disableExpectHeader(configuration.disableExpectHeader)
{
}
Expand Down Expand Up @@ -209,7 +211,7 @@ AWSClient::AWSClient(const Aws::Client::ClientConfiguration& configuration,
m_httpClient->IsDefaultAwsHttpClient() ? Aws::Client::HttpClientChunkedMode::DEFAULT : configuration.httpClientChunkedMode,
configuration.awsChunkedBufferSize),
m_userAgentInterceptor},
m_enableNewRetries{Aws::Utils::StringUtils::ToLower(Aws::Environment::GetEnv("AWS_NEW_RETRIES_2026").c_str()) == "true"},
m_isLegacyRetryStrategy{Aws::Client::Internal::IsLegacyRetryStrategy(m_retryStrategy.get())},
m_disableExpectHeader(configuration.disableExpectHeader)
{
}
Expand Down Expand Up @@ -305,7 +307,7 @@ HttpResponseOutcome AWSClient::AttemptExhaustively(const Aws::Http::URI& uri,
Aws::String invocationId = Aws::Utils::UUID::PseudoRandomUUID();
RequestInfo requestInfo;
requestInfo.attempt = 1;
requestInfo.maxAttempts = m_enableNewRetries ? m_retryStrategy->GetMaxAttempts() : 0;
requestInfo.maxAttempts = m_isLegacyRetryStrategy ? 0 : m_retryStrategy->GetMaxAttempts();
httpRequest->SetHeaderValue(Http::SDK_INVOCATION_ID_HEADER, invocationId);
httpRequest->SetHeaderValue(Http::SDK_REQUEST_HEADER, requestInfo);
AppendRecursionDetectionHeader(httpRequest);
Expand Down Expand Up @@ -399,7 +401,7 @@ HttpResponseOutcome AWSClient::AttemptExhaustively(const Aws::Http::URI& uri,
{
const bool blockedByQuotaOnly = outcome.GetError().ShouldRetry() &&
retries + 1 < m_retryStrategy->GetMaxAttempts();
if (m_enableNewRetries && blockedByQuotaOnly && request.IsLongPollingOperation()) {
if (!m_isLegacyRetryStrategy && blockedByQuotaOnly && request.IsLongPollingOperation()) {
m_httpClient->RetryRequestSleep(std::chrono::milliseconds(sleepMillis));
}
break;
Expand Down Expand Up @@ -490,7 +492,7 @@ HttpResponseOutcome AWSClient::AttemptExhaustively(const Aws::Http::URI& uri,
Aws::String invocationId = Aws::Utils::UUID::PseudoRandomUUID();
RequestInfo requestInfo;
requestInfo.attempt = 1;
requestInfo.maxAttempts = m_enableNewRetries ? m_retryStrategy->GetMaxAttempts() : 0;
requestInfo.maxAttempts = m_isLegacyRetryStrategy ? 0 : m_retryStrategy->GetMaxAttempts();
httpRequest->SetHeaderValue(Http::SDK_INVOCATION_ID_HEADER, invocationId);
httpRequest->SetHeaderValue(Http::SDK_REQUEST_HEADER, requestInfo);
AppendRecursionDetectionHeader(httpRequest);
Expand Down Expand Up @@ -674,6 +676,7 @@ HttpResponseOutcome AWSClient::AttemptOneRequest(const std::shared_ptr<Aws::Http
{
AWS_LOGSTREAM_DEBUG(AWS_CLIENT_LOG_TAG, "Request returned error. Attempting to generate appropriate error codes from response");
auto error = BuildAWSError(httpResponse);
Aws::Client::Internal::ClassifyUnmodeledTooManyRequests(error);
return HttpResponseOutcome(std::move(error));
}
else if(request.HasEmbeddedError(httpResponse->GetResponseBody(), httpResponse->GetHeaders()))
Expand Down Expand Up @@ -728,6 +731,7 @@ HttpResponseOutcome AWSClient::AttemptOneRequest(const std::shared_ptr<Aws::Http
{
AWS_LOGSTREAM_DEBUG(AWS_CLIENT_LOG_TAG, "Request returned error. Attempting to generate appropriate error codes from response");
auto error = BuildAWSError(httpResponse);
Aws::Client::Internal::ClassifyUnmodeledTooManyRequests(error);
return HttpResponseOutcome(std::move(error));
}

Expand Down Expand Up @@ -772,7 +776,7 @@ void AWSClient::AttemptExhaustivelyAsync(const Aws::Http::URI& uri,
context->monitoringContexts = 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);
Expand Down Expand Up @@ -888,7 +892,9 @@ Http::HttpClient::AttemptOutcome AWSClient::OnResponseReceivedAsync(const std::s
if (DoesResponseGenerateError(httpResponse))
{
AWS_LOGSTREAM_DEBUG(AWS_CLIENT_LOG_TAG, "Request returned error. Attempting to generate appropriate error codes from response");
return OnAttemptCompleteAsync(context, HttpResponseOutcome(BuildAWSError(httpResponse)));
auto error = BuildAWSError(httpResponse);
Aws::Client::Internal::ClassifyUnmodeledTooManyRequests(error);
return OnAttemptCompleteAsync(context, HttpResponseOutcome(std::move(error)));
}

if (request.HasEmbeddedError(httpResponse->GetResponseBody(), httpResponse->GetHeaders()))
Expand Down Expand Up @@ -989,7 +995,7 @@ Http::HttpClient::AttemptOutcome AWSClient::OnAttemptCompleteAsync(const std::sh
const bool blockedByQuotaOnly = outcome.GetError().ShouldRetry() &&
context->retries + 1 < m_retryStrategy->GetMaxAttempts();
context->outcome = std::move(outcome);
if (m_enableNewRetries && blockedByQuotaOnly && request.IsLongPollingOperation()) {
if (!m_isLegacyRetryStrategy && blockedByQuotaOnly && request.IsLongPollingOperation()) {
return {false, std::chrono::milliseconds(sleepMillis)};
}
return {};
Expand Down
4 changes: 4 additions & 0 deletions src/aws-cpp-sdk-core/source/client/AWSErrorMarshaller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include <aws/core/client/AWSError.h>
#include <aws/core/client/CoreErrors.h>
#include <aws/core/utils/cbor/CborValue.h>
#include <aws/core/internal/RetryErrorClassification.h>

using namespace Aws::Utils::Logging;
using namespace Aws::Utils::Json;
Expand Down Expand Up @@ -104,6 +105,7 @@ AWSError<CoreErrors> JsonErrorMarshaller::BuildAWSError(const std::shared_ptr<Ht

error.SetResponseHeaders(httpResponse->GetHeaders());
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;
Expand Down Expand Up @@ -197,6 +199,7 @@ AWSError<CoreErrors> XmlErrorMarshaller::BuildAWSError(const std::shared_ptr<Htt

error.SetResponseHeaders(httpResponse->GetHeaders());
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;
Expand Down Expand Up @@ -522,6 +525,7 @@ AWSError<CoreErrors> RpcV2ErrorMarshaller::BuildAWSError(const std::shared_ptr<H

error.SetResponseHeaders(httpResponse->GetHeaders());
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;
Expand Down
4 changes: 4 additions & 0 deletions src/aws-cpp-sdk-core/source/client/AdaptiveRetryStrategy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,10 @@ namespace Aws
StandardRetryStrategy(retryQuotaContainer, maxAttempts)
{}

AdaptiveRetryStrategy::AdaptiveRetryStrategy(const StandardRetryStrategyOptions& options) :
StandardRetryStrategy(options)
{}

bool AdaptiveRetryStrategy::HasSendToken()
{
return m_retryTokenBucket.Acquire(1, m_fastFail);
Expand Down
Loading
Loading