libfetchers: index tarball-cache thin packs with a real ODB, stop disabling pack refresh - #7
Open
jld-adriano wants to merge 1 commit into
Open
Conversation
Pass the repository ODB to libgit2 when indexing tarball-cache packfiles so thin-pack deltas can be resolved, and stop disabling pack-backend refresh on pooled repos. Assisted-by: Devin:claude-sonnet-4-5 Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation
Exa CI intermittently fails Nix evaluation with a missing path inside an already-cached tarball input, e.g. on
exapkgs-eval-prewarmforexa-labs/monorepomaster:Sibling runs fail the same way on other pins (
«fcc13c0…»/default.nix,«be9e214…»/default.nix), on both x86_64 and aarch64 hosts. The mirror tarballs are complete and hash-valid, and when the runner caches are inspected afterwards the objects are present andgit fsck --connectivity-onlyis clean — so this is a transient, self-healing fault in how the tarball cache is written/read, not bad upstream content.tarball-ttlis set to a year in that CI, anddownloadTarball_only re-validates a cache entry viahasObject(treeHash)on the top tree, so a tree whose descendants are not resolvable stays poisoned.Two things in
libfetchers/git-utils.ccare unsound for that setup, where up to 16 runner slots share oneXDG_CACHE_HOMEand therefore onetarball-cache-v2:GitRepoImpl::flush()writes a thin pack (git_mempack_write_thin_pack) but indexes it withgit_indexer_new(..., odb = nullptr, ...). libgit2 documents that argument as "object database from which to read base objects when fixing thin packs. Pass NULL if no thin pack is expected";fix_thin_pack()bails with "cannot fix a thin pack without an ODB". Delta bases legitimately live in other packs here — different nixpkgs revisions share the overwhelming majority of blobs and trees, and real runner caches do contain cross-pack deltas (git verify-packon a runner:deltas=2800 cross-base-seen=7) — so telling the indexer no thin pack is expected contradicts what the writer just produced.getPool()monkey-patchedrefresh = nullptronto each pooled repo's pack backend. Its comment grants that the optimization is wrong if "another process happens to be unpacking a similar tarball to the cache at the same time", dismissing it as "a very unrealistic scenario" — on this CI that is the steady state, so pooled repos can hold a stale view of the pack directory and fail to resolve objects another writer has since packed.Context
packfilesOnlyODB, i.e. pack backend + mempack, installed viagit_repository_set_odbin the constructor) togit_indexer_new, kept alive by an RAIIObjectDbthroughgit_indexer_commit. Thin-pack bases in existing packs are now resolvable instead of being an error case the writer cannot hit only by luck.refresh = nullptrmonkey-patch, restoring libgit2's refresh-on-miss for pooled repos. It was introduced in d1f9fe9 and moved to the pool in f7c3f02 as a syscall optimization; measured on a monorepo dev-shell eval it is noise (cold: 16.57s patched vs 16.69s baseline wall; warm mean over interleaved A/B runs: 19.554s vs 19.542s wall, 15.354s vs 15.444s Nix CPU), so correctness under a shared cache is worth more than the readdir it saved.Honest scoping: I could not reproduce the CI failure, either sequentially or under 16-way and 32-way concurrent fetches into a shared cache, so this is not a proven fix for that specific error — it removes two unsound API usages that are the only plausible mechanisms found for it. The failure's own evidence (healthy-on-inspection caches, multiple revisions, both arches, no corruption, no cleanup job touching the cache) is consistent with a write/visibility race of this kind.
Verification:
nix build .#nix-cli;nix build .#checks.x86_64-linux.{nix-fetchers-tests-run,nix-functional-tests,nix-store-tests-run};nix flake check --no-buildgreen (incl.pre-commit);clang-format --dry-run --Werrorclean. Sequential (4 nixpkgs revisions, re-reading every earlier revision's paths from fresh processes) plus 16-way and 4×32-way concurrent shared-cache stress with the patched binary: all fetches and reads succeeded,git fsck --connectivity-onlyandgit verify-pack -vclean with no unresolved bases.Link to Devin session: https://app.devin.ai/sessions/4a8d0a6cdb9046d9906f0d9d22d638c3
Requested by: @jld-adriano