Artifact a22177c499471953cd7eca7e7d8c37c6f8be758282baf80ccadda32636a8b332:
- File
repo_content_cache.go
— part of check-in
[7156492179]
at
2026-10-25 23:53:19
on branch main
— feat: list a check-in's files narrowed to a cone
Repo.ListFilesIn passes the public Cone to the enumeration seam as the range predicate (draft-fossil-sparse-cone-00 §4.1). On a tree manifest the walk skips sub-manifests the cone cannot reach (§4.5), so over a sparse store it succeeds where ListFiles fails on an elided node. It reads through the attached content cache, via a new internal manifest.ListFilesFilteredCached.
Tracked in #491. (user: [email protected], size: 3948) [annotate] [blame] [check-ins using] [more...]
package libfossil import ( "fmt" "github.com/danmestas/go-libfossil/internal/blob" "github.com/danmestas/go-libfossil/internal/content" "github.com/danmestas/go-libfossil/internal/hash" ) // contentCache is the cache SetContentCache attached, valid for the store as // it stood after writes finished writes (db.DB.Writes). // // Content named by hash never changes, but a write can take it away: a purge // deletes the row, and SQLite gives a deleted newest rid to the next blob // stored; compaction and horizon or cone changes turn held blobs into // phantoms. What the cache keeps under a rid, and the manifests parsed from // it, are therefore good only until the next write. Rather than have every // such write know about the cache, a read that sees the write count moved // replaces the cache with an empty one. Reads in flight keep the cache they // started with, so nothing they store lands in its successor. type contentCache struct { cache *content.Cache bytes int64 writes int64 } // SetContentCache attaches a cache of expanded artifact content, bounded by // bytes, that ReadFile, ReadFileAt, ReadBlob, ListFiles, ListFilesIn, Diff and // Annotate share. It replaces any cache already attached; bytes <= 0 detaches // it. The default is no cache. It is safe to call while other goroutines read. // // The bound covers expanded content; the parses of the few check-in // manifests read most recently are kept beside it. Any write through this // Repo empties the cache. A write by another process, or through another // Repo opened on the same file, does not: attach a cache only where this // Repo is the store's one writer, or where no other writer purges, compacts // or narrows it. func (r *Repo) SetContentCache(bytes int64) { if bytes <= 0 { r.cache.Store(nil) return } r.cache.Store(newContentCache(bytes, r.DB().Writes())) } func newContentCache(bytes, writes int64) *contentCache { if bytes <= 0 { panic("libfossil.newContentCache: bytes must be > 0") } return &contentCache{cache: content.NewCache(bytes), bytes: bytes, writes: writes} } // liveCache returns the cache a read may use now, or nil to read uncached. // A cache that predates the latest finished write is replaced by an empty // one first. Losing that swap to another reader or to SetContentCache leaves // this read uncached, which is always correct. func (r *Repo) liveCache() *content.Cache { cc := r.cache.Load() if cc == nil { return nil } writes := r.DB().Writes() if cc.writes == writes { return cc.cache } fresh := newContentCache(cc.bytes, writes) if !r.cache.CompareAndSwap(cc, fresh) { return nil } return fresh.cache } // ReadBlob returns the content of the artifact named uuid, a full SHA1 or // SHA3-256 hash in lowercase hex, such as the UUID ListFiles reports for a // file. An unknown hash returns ErrArtifactNotFound (wrapped). An artifact // the store knows but does not hold, such as one elided below a horizon or // outside a sparse cone, fails as ReadFile of a path naming it does. func (r *Repo) ReadBlob(uuid string) ([]byte, error) { if uuid == "" { return nil, fmt.Errorf("libfossil: read blob: uuid is required") } if !hash.IsValidHash(uuid) { return nil, fmt.Errorf("libfossil: read blob: %q is not a full lowercase artifact hash", uuid) } if _, known := blob.Exists(r.DB(), uuid); !known { return nil, fmt.Errorf("libfossil: read blob %s: %w", uuid, ErrArtifactNotFound) } data, err := readHeld(r, r.liveCache(), uuid) if err != nil { return nil, fmt.Errorf("libfossil: read blob: %w", err) } return data, nil } // readHeld expands the artifact named uuid through cache (nil reads uncached) // once the store proves it holds it, as every read by hash here does. func readHeld(r *Repo, cache *content.Cache, uuid string) ([]byte, error) { rid, err := content.RequireAvailableByUUID(r.DB(), uuid) if err != nil { return nil, err } return cache.Expand(r.DB(), rid) }