← All docs changelog/2026-04-25.md

Apr 25, 2026

Commits

161dd64 — Add ETag CAS + per-key lock for race-safe location DB writes

Concurrent writes to the same location's SQLite DB silently lost rows: the download/modify/put_object cycle had no ETag check, so two writers (cross-container or same-container) would both download, both write, and the second put_object would clobber the first.

Two protection layers now guard writes:

  1. S3 conditional writes (ETag CAS) — cross-container. Each put_object includes IfMatch=etag (the ETag captured at download). A 412 PreconditionFailed triggers cache eviction, re-download, and retry (up to 5 attempts with jittered exponential backoff). New-location seed race fixed via IfNoneMatch='*' atomic create.

  2. threading.Lock keyed by S3 key — same-container serialization. Concurrent requests for the same location DB within one Modal container are serialized by a per-key lock, preventing wasted S3 round-trips and retry budget.

with_location_db(location, fn) is the new write entry point. All existing write call sites migrated: store, store_chunked_init, store_chunk, delete_asset, register_external_asset, sync_project_from_supabase. Read paths unchanged. Legacy _save_location_db retained with deprecation notice.

Testing:

  • 5 new unit tests: CAS conflict retry, CAS exhaustion, seed race, parallel writes, lock serialization
  • Gated real-S3 chaos suite (test_concurrency.py, RUN_S3_CONCURRENCY_TESTS=1): 20 parallel store() + 5 parallel chunk uploads against sparky-data dev prefix

Changed:

  • dev/core/data.py — +397/-105 lines: with_location_db, _download_location_db, _upload_location_db, _seed_location_db, _get_db_lock, _DBPreconditionFailed, _db_cache, _db_locks; all write call sites migrated
  • dev/data/sync.py — migrated sync_project_from_supabase to with_location_db
  • dev/tests/test_data_service/test_concurrency.py — new: gated S3 chaos tests (parallel store + chunk upload)
  • dev/tests/test_data_service/test_data_service.py — +279 lines: unit tests for CAS retry, exhaustion, seed race, lock serialization