Skip to content

Make Retry Behavior 2.1 the default retry behavior - #3974

Open
pulimsr wants to merge 2 commits into
mainfrom
feature/retries-2026-default
Open

pulimsr wants to merge 2 commits into
mainfrom
feature/retries-2026-default

Conversation

@pulimsr

@pulimsr pulimsr commented Oct 31, 2026 •

Copy link
Copy Markdown
Collaborator

Do not merge before November 2026. This is the "no sooner than November 2026" rollout announced in #3832

Makes Retry Behavior 2.1 the default retry behavior and removes the AWS_NEW_RETRIES_2026 opt-in.

  • Clients with no retry configuration use the standard retry strategy, including clients built with ClientConfiguration(true) or a profile. These configurations now leave retryStrategy unset, and the client resolves it when it is created.
  • AWS_RETRY_MODE=legacy / retry_mode = legacy (or default) keeps the previous DefaultRetryStrategy behavior unchanged. DefaultRetryStrategy also keeps that behavior when set directly.
  • retry_mode and max_attempts are read from the named profile of ClientConfiguration(profileName).
  • An HTTP 429 that doesn't match a modeled error is retried as a throttling error.
  • DynamoDB and DynamoDB Streams use 4 attempts and a 25 ms base on all default construction paths, including adaptive mode. AWS_MAX_ATTEMPTS / max_attempts still take precedence.
  • Custom retry strategies also get the Retry Behavior 2.1 request handling (max attempts in amz-sdk-request, long-polling backoff).
  • Adds AdaptiveRetryStrategy(long maxAttempts, double transientBackoffBaseSec) and InitRetryStrategy(int maxRetries, Aws::String retryMode, double transientBackoffBaseSec, const Aws::String& profileName).

Check all that applies:

  • Did a review by yourself.
  • Added proper tests to cover this PR. (If tests are not applicable, explain.)
  • Checked if this PR is a breaking (APIs have been changed) change.
  • Checked if this PR will not introduce cross-platform inconsistent behavior.
  • Checked if this PR would require a ReadMe/Wiki update.

Check which platforms you have built SDK on to verify the correctness of this PR.

  • Linux
  • Windows
  • Android
  • MacOS
  • IOS
  • Other Platforms

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

@pulimsr
pulimsr force-pushed the feature/retries-2026-default branch from 9649266 to 714da31 Compare November 2, 2026 18:55
@pulimsr
pulimsr requested a review from averyk22 November 2, 2026 19:12

namespace
{
// Error names the core error mapper classifies as retryable only since Retry Behavior 2.1.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This list has to stay in sync with the names CoreErrorsMapper registers in CoreErrors.cpp, and nothing ties the two together. If someone adds a new throttling name there later, legacy mode starts retrying it and nobody notices. Could we keep the list in one place, in a header next to the mapper that we don't install, and use it both to register the entries and here?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Done in 5ad7a0b. The names now live in one place, internal/RetryErrorClassification.h, which is excluded from install.

// Gate is off: ignore service-specific tuning, use default behavior
return InitRetryStrategy(retryMode);
// Legacy mode ignores service-specific tuning.
return CreateRetryStrategy(ResolveConfiguredMaxAttempts(profileName), retryMode);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Legacy ignores max attempts, so this resolves a value only for CreateRetryStrategy to throw it away. Same thing as the -1 on #3849: I'd rather not carry that sentinel through more call paths. Can this just build the DefaultRetryStrategy directly?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Legacy now builds DefaultRetryStrategy directly, before any max-attempts resolution.

The options path doesn't use the sentinel either: InitRetryStrategyOptions::maxAttempts defaults to 3. The only -1 handling left is in the existing public InitRetryStrategy(int maxRetries, Aws::String retryMode) overload, which behaves as before.

*/
AdaptiveRetryStrategy(long maxAttempts = 3);
AdaptiveRetryStrategy(std::shared_ptr<RetryQuotaContainer> retryQuotaContainer, long maxAttempts = 3);
AdaptiveRetryStrategy(long maxAttempts, double transientBackoffBaseSec);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We already pushed back on a constructor just for the backoff base on #3836 (#3836 (comment)), and now Standard and Adaptive each have three constructors that do almost the same thing. Can we do what we did with QuotaConfig: one options struct on StandardRetryStrategy, the same one taken by Adaptive, with the existing constructors calling it?

struct StandardRetryStrategyOptions {
    long maxAttempts = 3;
    double transientBackoffBaseSec = 0.05;
    std::shared_ptr<RetryQuotaContainer> retryQuotaContainer;
};
explicit StandardRetryStrategy(const StandardRetryStrategyOptions& options);

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Added StandardRetryStrategyOptions as suggested, with
explicit StandardRetryStrategy(const StandardRetryStrategyOptions&).

One side effect: a null retryQuotaContainer now gets the default container instead of being stored as null.

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);
AWS_CORE_API std::shared_ptr<RetryStrategy> InitRetryStrategy(int maxRetries, Aws::String retryMode, double transientBackoffBaseSec,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Same thing here: this is the fourth InitRetryStrategy overload, and each one adds a parameter to the last. Reading from the named profile is the right fix, but I'd rather this took one options struct (max attempts, retry mode, backoff base, profile name) than another overload.

int m_retryQuota;
};

namespace Internal

Copy link
Copy Markdown
Contributor

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.


namespace Internal
{
AWS_CORE_LOCAL bool UsesRetryBehavior21(const RetryStrategy* retryStrategy);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

UsesRetryBehavior21 is named after a spec version, which stops meaning anything once this is the default. All it checks is whether the strategy is DefaultRetryStrategy, so I'd name it for that, something like IsLegacyRetryStrategy, and rename m_enableNewRetries to match, since "new" goes stale the same way.

void SetLegacyClientConfiguration(Aws::Client::ClientConfiguration& clientConfig)
{
clientConfig.retryStrategy = Aws::Client::InitRetryStrategy("default");
// Leave the retry strategy unset so the client configuration factory resolves it.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

retryStrategy is already null here, so this line and the comment can go, same as the other modes. That also cleans up the generated ClientConfigurationDefaults.cpp. Same for the empty retryMode branch below: flip the condition to != instead of leaving a comment in it.

{
bool UsesRetryBehavior21(const RetryStrategy* retryStrategy)
{
// DefaultRetryStrategy is the legacy retry behavior, so it keeps the request handling from before Retry Behavior 2.1.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Same as on #3836, please drop the narration comments. This one, the two in DefaultRetryStrategy.cpp, the one in ResolveRetryMode, and the one on the legacy branch of InitRetryStrategy just restate what the code does.

AWSError<CoreErrors> nonRetryableError(CoreErrors::INCOMPLETE_SIGNATURE, false);

MockStandardRetryStrategy retryStrategy;
MockStandardRetryStrategy retryStrategy(Aws::MakeShared<DefaultRetryQuotaContainer>(ALLOCATION_TAG));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This now injects the old DefaultRetryQuotaContainer so the old numbers still pass, which means it tests a setup nobody gets by default anymore. The real standard behavior is already covered in NewRetriesStrategyTest. Can we rewrite this against the default container and merge the two, since "new retries" is just the standard strategy now?

return mapping->second;
}

static std::shared_ptr<RetryStrategy> InitRetryStrategyForProfile(const Aws::String& profileName);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit: same as on #3836, I'd rather these new helpers go in an anonymous namespace above the constructors than be static with a forward declaration.

@pulimsr
pulimsr marked this pull request as ready for review November 4, 2026 19:37

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants