Repository navigation
Fix Content-Length and retries for seekable streaming payloads - #853
Merged
Merged
Conversation
The HTTP serializers only set Content-Length for sized payloads, so files and BytesIO bodies were sent without one and aiohttp fell back to Transfer-Encoding: chunked, which S3 rejects. Synchronously seekable streams are now measured from their current position to the end without consuming them. Streams whose length can't be determined, including async streams, still get no Content-Length. A Content-Length the caller already set, such as S3's modeled ContentLength member, now takes precedence. The serializers used to append their own length too, sending and signing "3, 3". This also fixes @requiresLength request payloads. Their length could never be computed, because the request serializer measured the async wrapper rather than the caller's stream. An unset payload of such an operation is sent with a length of 0. The length is computed at serialization, before signing, as the Go v2, Java v2 and Rust SDKs do, so steps that need it before signing, such as aws-chunked encoding, have it.
Operations with a streaming input were never retried, so a throttled or failed upload from a file raised instead of being resent. A data stream that can be rewound is now retried, and each retry rewinds it to the position it had before the first attempt rather than to 0, so a caller that skipped the first bytes of a file doesn't have them sent. Event streams, and streams that can't be rewound or fail to seek, are still sent once.
Whether a request could be retried was split between retryable(), which reported every streaming operation as not retryable, and a check of the request future in the retry loop that told event streams apart. retryable() now returns False only for operations with an input event stream, and the retry loop checks only whether the body can be rewound. Event streams no longer depend on how the pipeline was invoked, and the rate-limiting tests no longer need a body that can't be rewound. If seeking a body back fails before a retry, the call now returns the attempt's error with a note, as when a send token times out, instead of failing with the seek error.
The file payload test went through the same code as the BytesIO one, and the @requiresLength modeled-length test repeated the duplicate-header test, so it's now a parameter of it.
The coroutine-returning tell and seek fixtures and the stream whose tell fails only at its end don't match any stream callers pass, and the async seek case already covers what _is_async is for. The two tests for data streams that can't be rewound are now one parametrized test.
stobrien89
approved these changes
Oct 19, 2026
Contributor
Author
|
The Build smithy-python / aws-sdk-pr-build (pull_request) failure is related to and will be addressed by: aws/aws-sdk-python#112. This failure is independent of this PR. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Streaming payloads given as a file or
BytesIO(for example the body of S3PutObject) were sent without aContent-Length. aiohttp then fell back toTransfer-Encoding: chunked, which S3 rejects. When the caller also set the modeledContentLength, the header was sent and signed twice.@requiresLengthpayloads always failed to serialize, and operations with a streaming input were never retried.This PR measures seekable streams from their current position when the request is serialized, lets a modeled
Content-Lengthtake precedence, and retries seekable streaming uploads by rewinding them to where they started. Streams whose length can't be determined are still sent without aContent-Length, and event streams are still never retried.Before / After
Before
After
Repro
By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.