Skip to content

Retry 409 Conflict for PutMode::Create conditional puts in the S3 backend - #852

Open
adriancaruana wants to merge 1 commit into
apache:mainfrom
adriancaruana:retry-on-conflict-conditional-create
Open

adriancaruana wants to merge 1 commit into
apache:mainfrom
adriancaruana:retry-on-conflict-conditional-create

Conversation

@adriancaruana

Copy link
Copy Markdown

Which issue does this PR close?

Closes #.

Rationale for this change

The If-Match branch of AmazonS3::put_opts already sets .retry_on_conflict(true). The If-None-Match branch does not, although AWS documents a 409 for it too: a conditional write returns 409 Conflict when a concurrent delete to the key succeeds before the write completes, and the docs state that "uploads may be retried after receiving a 409 Conflict error". Concurrent conditional creates on the same key return 412, which this branch already maps to AlreadyExists.

Without the retry, that 409 reaches the caller as Error::AlreadyExists, via the CONFLICT mapping in client/retry.rs. That is the same error the Create branch deliberately returns for the settled 412/304 case, so a caller cannot distinguish "the object exists" from "a competing create was in flight and nothing landed". Callers using PutMode::Create as a mutual-exclusion primitive are all told a holder exists when no create landed, so nothing claims the work.

What changes are included in this PR?

One line: .retry_on_conflict(true) on the (PutMode::Create, S3ConditionalPut::ETagMatch) branch in src/aws/mod.rs, with a comment matching the sibling branch.

Are there any user-facing changes?

No API change. A conditional create that previously failed with AlreadyExists on a transient conflict is now retried under the existing retry policy.

Testing

No unit test added. The sibling If-Match retry has none either, and the 409 path is only exercised by the credentials-gated test_conditional_put. I did not want to add mock-server infrastructure for a one-line change, but happy to if you would prefer it.

Verified with cargo check, clippy and fmt --check under --no-default-features --features aws-base. I could not build --all-features locally (ring/aws-lc-rs need a C compiler I do not have on this machine).

I have not reproduced the 409 against real S3. The behaviour is from AWS documentation and from the sibling branch's comment, not from an observed failure.

AI disclosure

Written with AI assistance (Claude).

The If-Match (Update) branch already retries on 409. The If-None-Match
(Create) branch did not.

AWS documents a 409 for If-None-Match when a concurrent delete of the
key completes before the write does, and states that PutObject uploads
may be retried after a 409. Concurrent conditional creates on the same
key instead return 412, which this branch already maps to
AlreadyExists. A 409 means the request reached S3 and nothing was
created, so the retry cannot turn a successful create into a reported
conflict.

This does not set `.idempotent(true)`, so transport-level retry
behavior is unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@alamb

alamb commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

Cna you explain what the end user visible issue / problem this PR addresses? Like what operation are you doing that fails?

@adriancaruana

Copy link
Copy Markdown
Author

@alamb I am trying to use PutMode::Create to say whether we (the caller) created the object, and a transient 409 coms back as AlreadyExists just like a real conflict does, which is ambiguous. Retrying the 409 (the way the If-Match branch already) makes AlreadyExists unambiguity.

what operation are you doing that fails?

fyi, I haven't actually encountered this kind off failure. But I don't prefer if I didn't. 😄

@alamb

alamb commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

fyi, I haven't actually encountered this kind off failure. But I don't prefer if I didn't. 😄

Make sense. What I am trying to figure out is if this error is actually possible. Otherwise we are just making out code more complex for no reason

@adriancaruana

Copy link
Copy Markdown
Author

Fair criticism. Sorry, I should have provided this in the PR description.

S3 docs say:

If a conflicting operation occurs during the upload, S3 returns a 409 ConditionalRequestConflict response. On a 409 failure, retry the upload.

Here's a similar issue on opendal apache/opendal#6741

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