Skip to content

fix(jobs): atomic dot-path state_data writes (distribution + audio-edit races) - #974

Merged
beveradb merged 2 commits into
mainfrom
feat/sess-20260902-1603-harden-state-data-races
Sep 2, 2026
Merged

fix(jobs): atomic dot-path state_data writes (distribution + audio-edit races)#974
beveradb merged 2 commits into
mainfrom
feat/sess-20260902-1603-harden-state-data-races

Conversation

@beveradb

@beveradb beveradb commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator

Summary

Follow-up hardening sweep to the file_urls/state_data lost-update fix (#970). An audit found the same read-copy-mutate-write-whole-map pattern in 8 more sites; this converts them to atomic Firestore dot-path writes so a stale snapshot can't clobber a concurrent sibling key.

Changes

  • video_worker distribution / redistribution / finalize (3 sites) — the most dangerous: they rewrote the whole state_data map and .pop('visibility_change_in_progress'), so a stale write could revert the worker_generation supersession fence (an atomic Increment) or resurrect the visibility guard — the "visibility-recycle-dataloss" surface. Now dot-path writes, with DELETE_FIELD for the guard.
  • Audio-editor undo / redo / apply (review.py) — full-map rewrites that could race with rapid clicks or the idle-reminder scheduler active during audio-edit → dot-path writes.
  • Audio-editor upload-for-join — now writes only this upload's entry via FieldPath(...).to_api_repr() (backtick-escapes the hyphenated uuid, string key), so concurrent uploads merge instead of one dropping the other (which orphaned a GCS file and 404'd on join). (CodeRabbit finding Improve readme Windows instructions. #1.)
  • Deferred YouTube-URL write (youtube_queue_processor) → dot-path.

Out of scope (documented)

  • The two audio-edit list fields (audio_edit_stack / audio_edit_redo_stack) remain last-write-wins across concurrent edits to the same job — a list can't be dot-path-merged. Acceptable for the single-reviewer sequential audio-editor UI; true multi-writer safety needs a transaction/CAS. Documented inline. (CodeRabbit finding Major refactor, rename to Karaoke Gen, implement tests for everything #2 — deliberately deferred.)
  • Two low-risk fresh-dict-replace sites (create-from-search / admin re-search) — single-request, early-lifecycle — left as-is.

Testing

  • Full backend unit suite green (4266 passed) + emulator suite green (118 passed).
  • New emulator regression tests: distribution write preserves the worker_generation fence + clears the visibility guard (with a contrast test showing the old full-map write reverted the fence); concurrent audio-edit uploads both survive.
  • Updated the 2 unit tests that asserted the old full-map shape.
  • CodeRabbit CLI: finding Improve readme Windows instructions. #1 fixed, finding Major refactor, rename to Karaoke Gen, implement tests for everything #2 documented/deferred; no new findings on re-review.

Review

  • Local CodeRabbit review completed (2 cycles); feedback addressed

@coderabbitai ignore


🤖 Generated with Claude Code

beveradb and others added 2 commits September 2, 2026 16:15
…edit paths

Follow-up sweep to the file_urls/state_data lost-update fix (#970). Converts the
remaining read-copy-mutate-write-whole-map sites to atomic Firestore dot-path
writes so a stale snapshot can't clobber a concurrent sibling key:

- video_worker distribution/redistribution/finalize (3 sites): the most
  dangerous — they rewrote the whole state_data map and popped
  visibility_change_in_progress, so a stale write could revert the
  worker_generation supersession fence (an Increment) or resurrect the
  visibility guard. Now dot-path writes + DELETE_FIELD for the guard.
- audio-editor undo/redo/apply/upload (review.py): full-map rewrites that could
  race with rapid clicks or the idle-reminder scheduler active during audio-edit.
- deferred YouTube-URL write (youtube_queue_processor).

Left the two low-risk fresh-dict-replace sites (create-from-search / admin
re-search) — single-request, early-lifecycle — out of scope.

Tests: updated the 2 unit tests that asserted the old shape; added an emulator
regression test proving the distribution write preserves the worker_generation
supersession fence and clears the visibility guard (with a contrast test showing
the old full-map write reverted the fence). Full backend suite green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…abbit)

Address CodeRabbit findings on the audio-editor concurrency:
- Uploads: write only this upload's entry keyed by its uuid via
  FieldPath(...).to_api_repr() (backtick-escapes the hyphenated uuid, keeps the
  key a string so it doesn't clash with update_job's 'updated_at'). Concurrent
  uploads now merge instead of the last write dropping the other's entry (which
  orphaned a GCS file and 404'd on join_start/join_end). Emulator test added.
- Edit-stack lists: documented as a known limitation — they remain
  last-write-wins across concurrent edits to the same job (a list can't be
  dot-path-merged); acceptable for the single-reviewer sequential UI, a
  transaction/CAS would be needed for true multi-writer safety.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@beveradb
beveradb enabled auto-merge (squash) September 2, 2026 20:27
@beveradb
beveradb merged commit ff18ecb into main Sep 2, 2026
22 checks passed
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