Skip to content

Fix multipart upload abort on transient part retry (#499) - #526

Open
hanabanaka wants to merge 1 commit into
mainfrom
s3ec-noretries-multipart-fix
Open

hanabanaka wants to merge 1 commit into
mainfrom
s3ec-noretries-multipart-fix

Conversation

@hanabanaka

Copy link
Copy Markdown

Issue #, if available: #499

Description of changes:

In the high-level multipart put path (enableMultipartPutObject(true)), each
already-encrypted ciphertext part was uploaded with its body wrapped in
NoRetriesAsyncRequestBody, which throws "Re-subscription is not supported!"
on any second subscribe(). When the async SDK retried a part after a transient
network failure, it re-subscribed to the body, the wrapper threw, and the entire
multipart upload aborted (issue #499).

That guard exists to stop re-subscription of live AES-GCM cipher streams,
where re-running the cipher would reuse the key/IV. It does not apply here: by
the time parts upload, the object has already been encrypted once and written to
temp ciphertext files on disk (MultipartUploadObjectPipeline.putLocalObject),
so each part is a static file. Re-reading it yields identical bytes with no
cipher re-run, and uploadPart is idempotent per uploadId/partNumber.

Fix: pass the file-based part body straight to the SDK in
UploadObjectObserver.onPartCreate so native per-part retry works. The three
call sites that wrap genuine live-cipher streams
(MultipartUploadObjectPipeline, S3AsyncEncryptionClient) are untouched, so
the GCM protection stays intact.

Tests: adds UploadObjectObserverTest (no AWS; mocks S3AsyncClient) covering
the three branches of onPartCreate — part-body re-subscription on retry (the
regression guard), temp-file cleanup and delete-notification after upload, and
unwrapping an upload failure into S3EncryptionClientException. The
re-subscription and unwrap tests are mutation-verified.

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

Check any applicable:

  • Were any files moved? Moving files changes their URL, which breaks all hyperlinks to the files.

Each ciphertext part was wrapped in NoRetriesAsyncRequestBody, which throws
on re-subscribe. When the SDK retried a part after a transient failure, the
whole upload aborted.

That guard is only needed for live AES-GCM cipher streams (re-running the
cipher would reuse the key/IV). Parts are static ciphertext files on disk, so
re-reading them is safe. Pass the file body straight through so the SDK can
retry the part natively; the live-cipher call sites keep the guard.

Adds UploadObjectObserverTest (no AWS; mocks S3AsyncClient) covering part-body
re-subscription on retry and temp-file cleanup after upload.
@hanabanaka
hanabanaka requested a review from a team as a code owner October 9, 2026 22:56

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.

1 participant