Skip to content

Implement updated standard retry behavior#733

Open
Alan4506 wants to merge 1 commit into
smithy-lang:developfrom
Alan4506:retries-2.1-sep-update-v2
Open

Implement updated standard retry behavior#733
Alan4506 wants to merge 1 commit into
smithy-lang:developfrom
Alan4506:retries-2.1-sep-update-v2

Conversation

@Alan4506

@Alan4506 Alan4506 commented Jul 2, 2026

Copy link
Copy Markdown
Collaborator

Description of changes:

Updates the standard retry behavior:

  • Throttling backoff: throttling errors use a larger base backoff (1000ms) than other errors (50ms), based on error.is_throttling_error.
  • Retry quota: RETRY_COST is now 14, throttling retries cost 5, and the timeout-specific cost is removed.
  • DynamoDB defaults: DynamoDB / DynamoDB Streams clients default to 4 max attempts and a 25ms base backoff, applied per-value (a value configured by the customer always wins). The generic runtime stays AWS-agnostic; the DynamoDB knowledge lives in an AWS codegen integration.
  • Long-polling: long-polling operations (SQS ReceiveMessage, SFN GetActivityTask, SWF PollForActivityTask/PollForDecisionTask) back off before giving up even when the quota is exhausted. Operation detection uses a per-operation codegen hook so no AWS service names leak into generic codegen.
  • x-amz-retry-after: honored when returned by the service, capped at 5 seconds above the normal backoff; invalid/missing values fall back to normal backoff and the standard HTTP Retry-After is ignored.

Testing:

Added unit tests for each change (throttling vs non-throttling backoff, quota costs, DynamoDB per-value defaults, the long-polling backoff matrix, and x-amz-retry-after parsing/clamping/wiring). Also updated affected functional tests. All Python checks pass (make check-py, make test-py) and the Java codegen builds successfully.

Follow-up:

The awsJson protocol's _create_error is not on develop yet (it lives on the json-rpc branch). Once that merges, it needs the same parse_retry_after(response) wiring that awsQuery already has, so x-amz-retry-after is honored for awsJson services (e.g. DynamoDB) too.

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

@Alan4506 Alan4506 requested a review from a team as a code owner July 2, 2026 16:07
*/
public final class AwsLongPollingIntegration implements PythonIntegration {

private static final Map<String, Set<String>> LONG_POLLING_OPERATIONS = Map.of(

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.

[Question, maybe blocking?]

Does it really make sense for us to do this now before the trait is ready? I'd rather not have to track the separate work of updating this generator to function based on the trait and just update it when the rest of the SDKs do.

If we are already committed to shipping this, it's fine, but it is extra effort on our part for no customer benefit.

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.

Here's what the SEP says:

IsLongPollingOperation      

This step determines whether the service operation in use is a long-polling operation. Long-polling operations MUST always back off when Retryable is true, even if HasRetryQuota is false.      

An operation is considered long-polling if it includes the aws.api#longPoll Smithy trait. (longPoll in C2J: "longPoll": {...}").      

Work to enable this trait is currently in progress, so until the trait is available, SDKs SHOULD hard-code the following combinations of service and operation as having this trait unless it requires significant wasted effort to do so.      

I took a look at other smithy SDKs and almost all of them are hardcoding the table now. I also found the trait has since shipped in smithy-lang/smithy#3019, though as smithy.api#longPoll rather than aws.api#longPoll.

However, I checked the service models: none of the operations carry the trait yet.

Also, given that:

  • long polling is a "SHOULD" rather than a "MUST" in SEP, and
  • the SEP also allows an exception: "... unless it requires significant wasted effort to do so" (I assume we fall into this case a bit?), and
  • we haven't released these clients yet,

I don't mind punting long polling to a follow-up. I agree that it is extra effort on our part for no customer benefit. If it also makes sense to you, then I'll drop the changes for long polling in this PR. Let me know what you think!

endpoint_resolver=config.endpoint_resolver,
retry_strategy=retry_strategy,
retry_strategy=retry_strategy,${?isLongPolling}
is_long_polling=True,${/isLongPolling}

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.

[blocking, change requested]

This information is retry specific. Plumbing it through every operation call works fine in this instance, but this type of solution isn't scalable. This method signature would grow very quickly if we needed to update the pipeline method's signature for every branching decision that we make as part of the pipeline call.

We should talk about the right way to plumb through the information. We already have the APIOperation object in scope. Since this will eventually be a modeled trait, doesn't it make more sense for us to add it to the operation object as opposed to growing the pipeline method's signature?

retry_delay = self.backoff_strategy.compute_next_backoff_delay(0)
return StandardRetryToken(retry_count=0, retry_delay=retry_delay)
return StandardRetryToken(
retry_count=0, retry_delay=retry_delay, is_long_polling=is_long_polling

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.

[Change requested, blocking but open to discussion]

The concept of a token represents the state of a given attempt. This concept is unrelated to whether or not the operation is used for long polling.

Plumbing this through the retry token means that refresh_retry_token_for_retry needs to copy it forward unchanged. This is more boilerplate and more complex than it needs to be.

I'm trying to think of a better alternative. Off the top of my head, we could add the operation's schema to these calls, maybe via a retries_context like concept? Or just put this long_polling in a context so that it can grow without growing the method signature?

non-throttling errors when the customer did not configure one. Only applies
to standard mode.
"""
self._default_max_attempts = default_max_attempts

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.

[Question/discussion]

Did you consider making this a service-level interceptor instead of passing it in here? The interceptor could set these values if they're unset and only apply to DDB. I think that's more inline with the intention of the library's original authors.

You may need to wait until we have provenance tracking to support this though.


_LOGGER = logging.getLogger(__name__)

_RETRY_AFTER_HEADER = "x-amz-retry-after"

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.

[Blocking]

We should not add aws-specific logic to smithy-http or other generic non-AWS specific code. We need to find a way to hook into this from the AWS specific code.

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