publish-stable: atomic swap, no 404 window, stale staging cleaned #27

Merged
aqua merged 1 commit from aqua/publish-atomic into main 2026-11-10 18:21:55 +00:00
Member

Follow-up chat-s6259-6261. The #26 swap was rename(live, live.old); rename(stage, live); rmtree(old): a window where the URL is missing (reader got FileNotFoundError in the new test against the old code), a kill inside it left it missing until the next run, and killed runs leaked .stage-* dirs. Fix: renameat2(RENAME_EXCHANGE) (refuses to run non-atomically if unavailable), flock per target, stale staging removed at the start of the next run. test_interruption.py (4 tests): kill at every counted filesystem mutation over an existing page and over a first publish, a raising step, 4 concurrent readers over 30 swaps; run against the old code it FAILS (reader FileNotFoundError; stale staging after a kill), against the new code it passes with the 6 existing tests. Inherent: a reader opening two files may straddle a swap (old index, new audio); no swap fixes that. Linux/ext4-xfs-tmpfs only. Not deployed; run of the real publish not repeated.

Generated with Claude Code (https://claude.com/claude-code)

Follow-up chat-s6259-6261. The #26 swap was rename(live, live.old); rename(stage, live); rmtree(old): a window where the URL is missing (reader got FileNotFoundError in the new test against the old code), a kill inside it left it missing until the next run, and killed runs leaked .stage-* dirs. Fix: renameat2(RENAME_EXCHANGE) (refuses to run non-atomically if unavailable), flock per target, stale staging removed at the start of the next run. test_interruption.py (4 tests): kill at every counted filesystem mutation over an existing page and over a first publish, a raising step, 4 concurrent readers over 30 swaps; run against the old code it FAILS (reader FileNotFoundError; stale staging after a kill), against the new code it passes with the 6 existing tests. Inherent: a reader opening two files may straddle a swap (old index, new audio); no swap fixes that. Linux/ext4-xfs-tmpfs only. Not deployed; run of the real publish not repeated. Generated with Claude Code (https://claude.com/claude-code)
The old swap renamed live aside and staging in: between the two renames /aqua/dings/ answered 404 (a
concurrent reader got FileNotFoundError), a kill there left it missing until the next run, and a killed
run leaked .stage-* dirs. Now renameat2(RENAME_EXCHANGE) swaps live and staging in one syscall, a
per-target flock serialises publishers, and the next run deletes stale staging. test_interruption.py kills
the publisher at every filesystem mutation (audit-hook counted), races readers against swaps, and fails
against the old code.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
aqua merged commit 9c9407b19c into main 2026-11-10 18:21:55 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
GameCult/AquaSynth!27
No description provided.