Skip to content
7 changes: 7 additions & 0 deletions src/aws-cpp-sdk-core/include/aws/core/client/RetryStrategy.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#pragma once

#include <aws/core/Core_EXPORTS.h>
#include <aws/core/utils/memory/AWSMemory.h>
#include <aws/core/utils/threading/ReaderWriterLock.h>
#include <memory>

Expand Down Expand Up @@ -123,6 +124,7 @@ namespace Aws
public:
StandardRetryStrategy(long maxAttempts = 3);
StandardRetryStrategy(std::shared_ptr<RetryQuotaContainer> retryQuotaContainer, long maxAttempts = 3);
virtual ~StandardRetryStrategy();

virtual void RequestBookkeeping(const HttpResponseOutcome& httpResponseOutcome) override;
virtual void RequestBookkeeping(const HttpResponseOutcome& httpResponseOutcome, const AWSError<CoreErrors>& lastError) override;
Expand All @@ -135,9 +137,14 @@ namespace Aws

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

struct RetryImpl;

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

private:
Aws::UniquePtr<RetryImpl> m_impl;
};
} // namespace Client
} // namespace Aws
71 changes: 71 additions & 0 deletions src/aws-cpp-sdk-core/include/aws/core/internal/RetryStrategyImpl.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/**
* 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>
#include <aws/core/client/RetryStrategy.h>
#include <aws/core/utils/threading/ReaderWriterLock.h>

namespace Aws
{
namespace Client
{
static const int THROTTLE_BASED_RETRY_COST = 14;
static const int THROTTLE_BASED_THROTTLING_COST = 5;
static const int THROTTLE_BASED_INITIAL_TOKENS = 500;

class AWS_CORE_LOCAL ThrottleBasedRetryQuotaContainer : public RetryQuotaContainer
{
public:
ThrottleBasedRetryQuotaContainer(int retryCost = THROTTLE_BASED_RETRY_COST, int throttlingRetryCost = THROTTLE_BASED_THROTTLING_COST)
: m_retryQuota(THROTTLE_BASED_INITIAL_TOKENS), m_retryCost(retryCost), m_throttlingRetryCost(throttlingRetryCost) {}

virtual ~ThrottleBasedRetryQuotaContainer() = default;

bool AcquireRetryQuota(int capacityAmount) override
{
Aws::Utils::Threading::WriterLockGuard guard(m_retryQuotaLock);
if (capacityAmount > m_retryQuota)
{
return false;
}
else
{
m_retryQuota -= capacityAmount;
return true;
}
}

bool AcquireRetryQuota(const AWSError<CoreErrors>& error) override
{
int capacityAmount = error.ShouldThrottle() ? m_throttlingRetryCost : m_retryCost;
return AcquireRetryQuota(capacityAmount);
}

void ReleaseRetryQuota(int capacityAmount) override
{
Aws::Utils::Threading::WriterLockGuard guard(m_retryQuotaLock);
m_retryQuota = (std::min)(m_retryQuota + capacityAmount, THROTTLE_BASED_INITIAL_TOKENS);
}

void ReleaseRetryQuota(const AWSError<CoreErrors>& error) override
{
int capacityAmount = error.ShouldThrottle() ? m_throttlingRetryCost : m_retryCost;
ReleaseRetryQuota(capacityAmount);
}

int GetRetryQuota() const override { return m_retryQuota; }

private:
mutable Aws::Utils::Threading::ReaderWriterLock m_retryQuotaLock;
int m_retryQuota;
int m_retryCost;
int m_throttlingRetryCost;
};
} // namespace Client
} // namespace Aws
4 changes: 4 additions & 0 deletions src/aws-cpp-sdk-core/source/client/ClientConfiguration.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -552,6 +552,10 @@ std::shared_ptr<RetryStrategy> InitRetryStrategy(int maxAttempts, Aws::String re
{
retryMode = Aws::Config::GetCachedConfigValue("retry_mode");
}
if (Aws::Utils::StringUtils::ToLower(Aws::Environment::GetEnv("AWS_NEW_RETRIES_2026").c_str()) == "true" && retryMode.empty())
{
retryMode = "standard";
}

std::shared_ptr<RetryStrategy> retryStrategy;
if (retryMode == "standard")
Expand Down
98 changes: 93 additions & 5 deletions src/aws-cpp-sdk-core/source/client/RetryStrategy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,96 @@
#include <aws/core/client/AWSError.h>
#include <aws/core/client/CoreErrors.h>
#include <aws/core/client/RetryStrategy.h>
#include <aws/core/internal/RetryStrategyImpl.h>
#include <aws/core/platform/Environment.h>
#include <aws/core/utils/Outcome.h>
#include <aws/core/utils/StringUtils.h>
#include <aws/core/utils/local/Random.h>
#include <aws/core/utils/logging/LogMacros.h>

using namespace Aws::Utils::Threading;
using namespace Aws::Client;

static const char RETRY_STRATEGY_TAG[] = "StandardRetryStrategy";

namespace Aws
{
namespace Client
{
class StandardRetryStrategy::RetryImpl
{
public:
virtual ~RetryImpl() = default;
virtual long CalculateDelay(const AWSError<CoreErrors>& error, long attemptedRetries) const = 0;
};
}
}

namespace {
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<CoreErrors>& 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<int>(Aws::Utils::GetRandomValue() % 1000) * (1 << std::min(attemptedRetries, 15L)), 20000);
}
};

class NewRetriesImpl : public StandardRetryStrategy::RetryImpl
{
public:
long CalculateDelay(const AWSError<CoreErrors>& error, long attemptedRetries) const override
{
double x = error.ShouldThrottle() ? 1.0 : 0.05;
double exponentialPart = x * static_cast<double>(1L << (std::min)(attemptedRetries, 30L));
double cappedPart = (std::min)(exponentialPart, 20.0);

double b = static_cast<double>(Aws::Utils::GetRandomValue() % 10000) / 10000.0;
double t_i = b * cappedPart;

const auto& headers = error.GetResponseHeaders();
auto it = headers.find("x-amz-retry-after");
if (it != headers.end())
{
long long headerMs = Aws::Utils::StringUtils::ConvertToInt64(it->second.c_str());
if (headerMs < 0)
{
AWS_LOGSTREAM_DEBUG(RETRY_STRATEGY_TAG, "Ignoring invalid x-amz-retry-after value: " << it->second);
}
double headerSec = static_cast<double>(headerMs) / 1000.0;
double clamped = (std::max)(t_i, (std::min)(headerSec, 5.0 + t_i));
return static_cast<long>(clamped * 1000.0);
}

return static_cast<long>(t_i * 1000.0);
}
};

Aws::UniquePtr<StandardRetryStrategy::RetryImpl> CreateRetryImpl()
{
if (IsNewRetriesEnabled())
{
return Aws::MakeUnique<NewRetriesImpl>("StandardRetryStrategy");
}
return Aws::MakeUnique<LegacyRetryImpl>("StandardRetryStrategy");
}

std::shared_ptr<RetryQuotaContainer> CreateQuotaContainer()
{
if (IsNewRetriesEnabled())
{
return Aws::MakeShared<ThrottleBasedRetryQuotaContainer>("StandardRetryStrategy");
}
return Aws::MakeShared<DefaultRetryQuotaContainer>("StandardRetryStrategy");
}
} // anonymous namespace

namespace Aws
{
Expand All @@ -20,10 +106,14 @@ namespace Aws
static const int TIMEOUT_RETRY_COST = 10;

StandardRetryStrategy::StandardRetryStrategy(long maxAttempts)
: m_retryQuotaContainer(Aws::MakeShared<DefaultRetryQuotaContainer>("StandardRetryStrategy")), m_maxAttempts(maxAttempts) {}
: m_retryQuotaContainer(CreateQuotaContainer()), m_maxAttempts(maxAttempts),
m_impl(CreateRetryImpl()) {}

StandardRetryStrategy::StandardRetryStrategy(std::shared_ptr<RetryQuotaContainer> retryQuotaContainer, long maxAttempts)
: m_retryQuotaContainer(retryQuotaContainer), m_maxAttempts(maxAttempts) {}
: m_retryQuotaContainer(retryQuotaContainer), m_maxAttempts(maxAttempts),
m_impl(CreateRetryImpl()) {}

StandardRetryStrategy::~StandardRetryStrategy() = default;

void StandardRetryStrategy::RequestBookkeeping(const HttpResponseOutcome& httpResponseOutcome)
{
Expand Down Expand Up @@ -54,9 +144,7 @@ namespace Aws

long StandardRetryStrategy::CalculateDelayBeforeNextRetry(const AWSError<CoreErrors>& error, long attemptedRetries) const
{
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<int>(Aws::Utils::GetRandomValue() % 1000) * (1 << std::min(attemptedRetries, 15L)), 20000);
return m_impl->CalculateDelay(error, attemptedRetries);
}

DefaultRetryQuotaContainer::DefaultRetryQuotaContainer() : m_retryQuota(INITIAL_RETRY_TOKENS)
Expand Down
Loading
Loading