Skip to content

Conversation

@bentsku
Copy link
Contributor

@bentsku bentsku commented Oct 22, 2025

Motivation

See #13294
The issue above is due to using the version-specific write lock to enforce write preconditions, which doesn't work as the new version already has its own lock which isn't the same as the previous version being overwritten, so the 2 new concurrent versions aren't sharing the lock thus both execute.

Current S3 locking is pretty complex as we need to lock for multiple, very different scenarios:

  • we have a per object version (in non-versioned bucket, just per object) reader-writer lock which is used to prevent writes during read, and read during writes: when we pass the file object back to the web server to be sent over the client, we do not want another client modifying the object content. We do not have a lot of control over the "file sending" process, so the data stream sent back to the web server is wrapped with a read-lock, and following convention, the web server closes the file, releasing the lock. This works pretty well and we don't have concurrency issues anymore
  • when S3 released conditional writes, my first initiative was to used the already existing locking mechanism we had, as we could use the writer lock to enforce only one concurrent write (which is already the case) and verify the conditions inside the lock. This also worked very well, except for versioned buckets. We can dive a bit deeper into it.

This is how S3 versioning works: https://docs.aws.amazon.com/AmazonS3/latest/userguide/versioning-workflows.html

Basically, an object key is now a reference to a stack of object versions (themselves regular S3 object, or delete markers). The problem is that concurrent writes on a versioned object are not overwriting the previous object, but only putting a new object on the stack, so the current "per object version" locking doesn't work, we're not accessing the existing object lock when doing a conditional write, but only the new, current object.

We need a way to lock "the whole stack" instead. I've tried adding this to the current storage layer, but it doesn't work: if we share the same write lock around an "object stack", we enter a deadlock when doing a in-place CopyObject: CopyObject can read the previous object version and write a new version on top of it. If it shares the same lock, it's pretty clear this cannot work.

So we need to use a specific lock for conditional writes, per object key (stack) when using versioned buckets. For regular objects, it already works correctly by design, so I decided to not add it for them and keep the changes contained to versioned buckets.

Side-note: AWS did some changes in DeleteObject to now allow IfMatch to be used which wasn't the case before, so maybe we can use the new lock for all keys or use open() instead to get the right lock. I'm not sure IfMatch can be used with a specific version id, so using open without the lock might just work fine on the latest object of the stack.

Changes

  • add locking mechanism for versioned buckets only for conditional writes, with clean ups

Testing

This is pretty hard to test in Python due to bad concurrency, but the reporter of the issue shared a small Go snippet that I could execute which would fail reliably before the fix, and is now properly working 👍

@bentsku bentsku added this to the 4.10 milestone Oct 22, 2025
@bentsku bentsku self-assigned this Oct 22, 2025
@bentsku bentsku added aws:s3 Amazon Simple Storage Service semver: patch Non-breaking changes which can be included in patch releases docs: skip Pull request does not require documentation changes notes: skip Pull request does not have to be mentioned in the release notes labels Oct 22, 2025
@github-actions
Copy link

github-actions bot commented Oct 22, 2025

Test Results - Preflight, Unit

22 377 tests  ±0   20 627 ✅ ±0   15m 56s ⏱️ +37s
     1 suites ±0    1 750 💤 ±0 
     1 files   ±0        0 ❌ ±0 

Results for commit 149f42b. ± Comparison against base commit afbdfeb.

♻️ This comment has been updated with latest results.

@github-actions
Copy link

github-actions bot commented Oct 22, 2025

Test Results (amd64) - Acceptance

7 tests  ±0   5 ✅ ±0   3m 23s ⏱️ +3s
1 suites ±0   2 💤 ±0 
1 files   ±0   0 ❌ ±0 

Results for commit 149f42b. ± Comparison against base commit afbdfeb.

♻️ This comment has been updated with latest results.

@github-actions
Copy link

github-actions bot commented Oct 23, 2025

S3 Image Test Results (AMD64 / ARM64)

    2 files    2 suites   8m 14s ⏱️
  533 tests 481 ✅  52 💤 0 ❌
1 066 runs  962 ✅ 104 💤 0 ❌

Results for commit 149f42b.

♻️ This comment has been updated with latest results.

@github-actions
Copy link

github-actions bot commented Oct 23, 2025

Test Results (amd64) - Integration, Bootstrap

    5 files      5 suites   1h 40m 53s ⏱️
2 008 tests 1 842 ✅ 166 💤 0 ❌
2 014 runs  1 842 ✅ 172 💤 0 ❌

Results for commit 149f42b.

♻️ This comment has been updated with latest results.

@bentsku bentsku force-pushed the fix-s3-write-precondition-versioned branch from 30014f1 to 4bb03c5 Compare October 23, 2025 09:09
@bentsku bentsku force-pushed the fix-s3-write-precondition-versioned branch from 4bb03c5 to 149f42b Compare October 23, 2025 10:47
@github-actions
Copy link

LocalStack Community integration with Pro

    2 files  ±    0      2 suites  ±0   1h 17m 43s ⏱️ - 43m 43s
1 984 tests  - 2 894  1 814 ✅  - 2 709  170 💤  - 185  0 ❌ ±0 
1 986 runs   - 2 894  1 814 ✅  - 2 709  172 💤  - 185  0 ❌ ±0 

Results for commit 149f42b. ± Comparison against base commit afbdfeb.

This pull request removes 2894 tests.
tests.aws.scenario.bookstore.test_bookstore.TestBookstoreApplication ‑ test_lambda_dynamodb
tests.aws.scenario.bookstore.test_bookstore.TestBookstoreApplication ‑ test_opensearch_crud
tests.aws.scenario.bookstore.test_bookstore.TestBookstoreApplication ‑ test_search_books
tests.aws.scenario.bookstore.test_bookstore.TestBookstoreApplication ‑ test_setup
tests.aws.scenario.kinesis_firehose.test_kinesis_firehose.TestKinesisFirehoseScenario ‑ test_kinesis_firehose_s3
tests.aws.scenario.lambda_destination.test_lambda_destination_scenario.TestLambdaDestinationScenario ‑ test_destination_sns
tests.aws.scenario.lambda_destination.test_lambda_destination_scenario.TestLambdaDestinationScenario ‑ test_infra
tests.aws.scenario.loan_broker.test_loan_broker.TestLoanBrokerScenario ‑ test_prefill_dynamodb_table
tests.aws.scenario.loan_broker.test_loan_broker.TestLoanBrokerScenario ‑ test_stepfunctions_input_recipient_list[step_function_input0-SUCCEEDED]
tests.aws.scenario.loan_broker.test_loan_broker.TestLoanBrokerScenario ‑ test_stepfunctions_input_recipient_list[step_function_input1-SUCCEEDED]
…

@bentsku bentsku marked this pull request as ready for review October 24, 2025 08:40
@bentsku bentsku requested a review from k-a-il as a code owner October 24, 2025 08:40
Copy link
Contributor

@k-a-il k-a-il left a comment

Choose a reason for hiding this comment

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

LGTM 👍

@bentsku bentsku merged commit 1efc7e8 into main Oct 28, 2025
53 checks passed
@bentsku bentsku deleted the fix-s3-write-precondition-versioned branch October 28, 2025 14:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

aws:s3 Amazon Simple Storage Service docs: skip Pull request does not require documentation changes notes: skip Pull request does not have to be mentioned in the release notes semver: patch Non-breaking changes which can be included in patch releases

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants