Artifact bf662eac57f0c9b958ff5c7cd002e2b9db6faf3dac5b74445f8678fdf1185ad3:
- File
docs/RELEASE-NOTES-0.3.33.md
— part of check-in
[d6cf7bf59c]
at
2026-09-28 11:59:39
on branch trunk
— Lock the store with exclusive-create lock files instead of flock
Writers create .zit.lock with O_CREAT|O_EXCL before opening the store. A waiter names the holder and gives up after a minute; a dead holder's lock is broken; pack and shun --force hold the writer lock across their final check and rename. Release 0.3.33. (user: dmestas, size: 4618) [annotate] [blame] [check-ins using]
# zit 0.3.33 zit 0.3.33 changes how a repository is locked for writing: the lock is now a file created with `O_CREAT|O_EXCL`, the way git and most Unix programs lock, instead of `flock`. A writer that has to wait now says what it is waiting for, and gives up with a message instead of waiting forever. It also gives repositories made by `zit import-git` the project code every sync peer requires. No artifact, tag namespace, sync message, or on-disk repository format changed. ## The write lock is a lock file Every command that writes a repository first creates `.zit.lock` beside the store's `.zit` file, exclusively, and deletes it when the write has committed. Only one process can create the file, which the filesystem guarantees. The file records the holder's process id and host. The old lock was `flock` on `.zit` itself. `flock` is not part of POSIX, and on network filesystems it ranges from correct to silently locking nothing. Exclusive create is defined by POSIX and is atomic on NFSv3 and later. A blocked `flock` also waited without a word and without end, which is how a hung writer could stall every other writer to a repository with no sign of why. Now a writer that finds the lock held waits for it, and after a second says: ``` zit: waiting for the repository lock, held by process 4242 on build-host ``` If the holder keeps it for a minute, the writer gives up, names the holder, and names the file to delete if no zit process is really using it. A lock file outlives a crashed holder, which `flock` did not. A lock that names a process on this machine that no longer exists is stale, and the next writer breaks it without asking. Breaking is serialised by a second lock and removes the file only if it still names the dead process, so a writer can never remove a lock a live process has just taken. A lock held from another machine cannot be checked from here, so it is only ever waited for. A process that already holds the lock and asks for it again is refused at once instead of waiting for itself forever. `zit pack` and `zit shun --force` exclude each other with the same kind of lock, `.zit-tmp-pack.lock`. They now also hold the writer lock across their final check and the rename that installs the new store. Before, a commit that landed between the check and the rename went into the store the rename replaced, and was lost. Keep a repository on a network filesystem only if one process at a time writes it. Lock files are sound there, but network filesystems cache aggressively, and zit makes no attempt to work around that. ## `import-git` records a project code Every sync peer checks the repository's project code in its handshake and refuses a repository without one. `zit import-git` wrote none, so the store it made verified cleanly and could not be served, cloned by `fossil`, or adopted by a forge. It now records a code derived from the new repository's path, exactly as `zit init` does. A store imported by an earlier release still has no code. Import it again to give it one. `zit verify` now says when a repository has no project code, without counting it as damage. ## Tests - **Lock unit tests** cover exclusion, a refused second acquire in the same process, breaking a dead holder's lock, waiting for and naming a live one, never breaking another host's lock, and a break that finds a newer lock in place of the stale one and leaves it alone. - **The DST lock sweep** now follows the new order: take the lock, read the file's length, open, append, commit, release. Writers can die at any step while holding the lock, and the next writer must break a dead holder's lock and never a live one's. - **The shun sweep** crashes the rewrite at every filesystem operation, including each lock operation. The next writer and the next rewrite must both get past whatever locks the crash left behind. - **End to end:** a commit breaks a lock left by a killed writer. `purge undo` waits for a held lock and names the holder while it waits. An import of a two-commit git repository has a 40-hex project code, and two imports get different codes. ## Compatibility - No on-disk repository or wire format changed; Fossil RFC revision 00 remains the target. - A writer now gives up after waiting a minute for a live lock holder, instead of waiting indefinitely. - `.zit.lock` and `.zit.lock.break` are reserved names, like `.zit-cache`: they are never tracked and never listed as extras. ## Qualification `zig fmt --check`, `zig build test`, `e2e`, `smoke`, `git-import-e2e`, `git-export-e2e`, and `dst`, plus the three-runner CI battery.