Repository navigation
feat(packstore): reclaim unreferenced loose files before packing - #315
Conversation
A store whose maintenance runs under a short time budget can keep unreferenced loose files on disk for as long as verification or packing uses up that budget. `Pack` removes orphans only at its end, so a large abandoned upload can stay on disk across every pass. `SweepOrphans` runs only that removal. A caller can run it first, with its own budget, and reclaim space whether or not `Pack` finishes. It keeps the same safety rules as `Pack`: it waits for in-flight writes through the maintenance lease, and it removes nothing when the reference inventory is incomplete. Generated with Claude Code (claude-opus-5-5) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
roborev: Combined Review (
|
A sweep holds the exclusive maintenance lease while it deletes, so application writes wait until it returns. It checked its context only between shard directories. A canceled or expired sweep therefore kept deleting through the rest of a large shard and held the lease past the caller's budget. When this happened in the last shard, the sweep also reported success, so the caller could not tell that it had stopped late. The sweep now checks its context before each file. Orphan removal also uses the maintainer's identity pin opener, like the other maintenance removals, so a test can cancel the context in the middle of a deletion. Generated with Claude Code (claude-opus-5-5) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
roborev: Combined Review (
|
… stores The sweep is meant to run before Pack on every maintenance pass. On a new store that has had no writes, the storage root does not exist yet. Pack creates the root, but the sweep failed on it, so a caller that followed the documented order got an error on every pass until the first write. A missing root now means there is nothing to sweep. One orphan that the store has no permission to remove, such as a file in a read-only shard directory, stopped the whole sweep. Shards are visited in a fixed order, so that one file blocked reclaiming every orphan after it on every pass, and Pack failed at its end for the same reason. The sweep now skips such a file, as the redundant-copy sweep already does, and reports it in LooseOrphansDeferred so the caller can see that space was left behind. SweepStats becomes OrphanSweepStats so that it is not confused with PackStats.LooseSwept, which counts packed copies removed from loose storage. Generated with Claude Code (claude-opus-5-5) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
roborev: Combined Review (
|
The orphan sweep skipped a file it lacked permission to remove only when the denial came after the sweep pinned the file. On macOS, an unreadable file needs a new pin directory in its shard. On Windows, the pin needs delete access. Either can be denied first, and the sweep then stopped, so one such file still blocked every orphan after it. Pack failed at its end for the same reason. The sweep now defers a permission denial from pin acquisition too. It checks for permission errors only at that step. A permission error after pinning can come with failed cleanup, and that case still stops the sweep so the caller sees it. Generated with Claude Code (claude-opus-5-5) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
roborev: Combined Review (
|
The test for a sweep that cannot remove one orphan denied the orphan that the sweep visited second. A sweep that stopped right after the denied file therefore still passed, so the test did not show that the sweep goes on to reclaim later orphans. The test now denies whichever orphan sorts first, because the sweep visits file names in sorted order. Picking by order, not by fixed content, keeps the test valid if its contents change. Generated with Claude Code (claude-opus-5-5) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
roborev: Combined Review (
|
A store whose maintenance runs under a short time budget can keep unreferenced loose files on disk indefinitely.
Packremoves orphans only at its very end, after verification and packing, so when those steps use up the budget every pass, a large abandoned upload is never reclaimed.Maintainer.SweepOrphansruns only the orphan removal. A caller can run it first, with its own budget, and get the space back whether or notPackfinishes:It shares its removal code (
removeLooseOrphans) withPack, so both follow the same rules:Suppressed, when the reference inventory is incomplete.LooseOrphansDeferredinstead of failing the sweep. Before this change, one such file in an early shard blocked every orphan after it, and failedPackat its end, on every pass.PackStatsreports the same count.Pack.It does not verify, pack, or repack, so it costs much less than
Pack.🤖 Generated with Claude Code