Artifact 80c0f72ecda9ba24529eef3b4faa8217d0d066cc6e3c557d8d2d00f369cc31dc:
- File
docs/RELEASE-NOTES-0.3.32.md
— part of check-in
[5c445bb3f3]
at
2026-09-28 02:39:03
on branch trunk
— Take the writer lock inside LogStore.open and test the lock class
LogStore.open now takes the exclusive lock before it reads the file's length and scans for the end, and a read-only handle refuses to append. purge undo opens the store through the shared writer path.
A new DST sweep runs two to four writers interleaved against one log through a file that models the lock. It flags unlocked or misplaced appends as they happen, checks each writer sees everything committed before it locked and reads back its own writes, including deltas. A source check fails on any write-mode open of the store outside openStoreAppend. An e2e test races commits, tags and user-table writes on one store and counts what landed. (user: dmestas, size: 4092) [annotate] [blame] [check-ins using]
# zit 0.3.32 zit 0.3.32 makes `zit purge undo` take the repository's write lock before it restores anything, like every other command that writes the store. It also moves the lock into the storage layer, so no future writer can append without it, and adds the tests that should have caught the bug in the first place. No artifact, tag namespace, sync message, or on-disk repository format changed. ## `purge undo` waits its turn A zit repository is one append-only file, and every writer takes an exclusive lock on it before appending: commit, clone, pull, a server receiving a push, config and user-table writes, tags, tickets, wiki and forum posts. Two writers that are not serialised both scan the file for its end and both append there, so the second writes on top of the first. The overwriting frames are complete and checksum-valid, so neither `zit verify` nor crash recovery can see that anything happened: one of the two writes is simply gone. `zit purge undo` restores buried artifacts by appending them, but it opened the store without that lock. Run while a commit, a pull loop or a server push was writing to the same repository, from this checkout or another worktree of the same store, either the restored artifacts or the other writer's check-in could be lost without a trace. It now opens the store the way every other writer does, so it waits for a concurrent writer to finish rather than racing it. The same change makes it resolve a detached worktree's store, which the old direct open did not. The regression test holds the repository's write lock, starts `zit purge undo`, and checks that the store is byte-for-byte unchanged a second and a half later. It then releases the lock and checks that the undo completes and the restored store verifies. Against the old code, the store changes while the lock is held and the test fails. ## The lock now lives where the appends happen The `purge undo` bug was possible because the lock was taken by each command when it opened the file, and one command opened it a different way. The log itself now takes the lock: opening a store for appending takes the exclusive lock first, and only then reads the file's length and scans for its end. A store opened for reading has no append path at all, so a writer that opens the file the wrong way fails loudly instead of writing unlocked. A source check in the test suite also fails if any code outside the one shared opener opens the repository file for writing. ## Tests for the whole class, not the one bug The deterministic simulation already swept crashes, torn writes and faulty disks, but always with a single writer, and a lock that no one contends for is never exercised. A new sweep runs two to four writers against one simulated log, interleaved step by step, through a file that models the lock as the operating system provides it. Every writer follows a real command's order: find the file's length, open the log, append, commit, close. Another writer can run between any two of those steps. The simulated file reports an append by a handle that does not hold the lock, or an append that does not land at the file's end, at the moment it happens. Each writer must also see every artifact committed before it took the lock, and must read back what it just wrote, including deltas against other writers' artifacts. At the end, every committed artifact must be present and identical through a fresh reader. Removing the lock fails this sweep, and so does reading the file's length before taking the lock. An end-to-end test also runs commits from two worktrees, tag writes and user table writes against one store at once, and checks that every one of them landed and the store verifies. ## Compatibility - No on-disk repository or wire format changed; Fossil RFC revision 00 remains the target. - `zit purge undo` now waits, without a message, while another zit process is writing to the same repository. ## Qualification `zig fmt --check`, `zig build test`, `e2e`, `smoke`, `git-import-e2e`, `git-export-e2e`, and `dst`, plus the three-runner CI battery.