cache

package
v0.0.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 22, 2026 License: MPL-2.0 Imports: 15 Imported by: 0

Documentation

Overview

Package cache is the host-wide, immutable, content-addressed object cache.

Layout (root defaults to <os.UserCacheDir()>/fxvcs, override with the FXVCS_CACHE_DIR environment variable or Open):

<root>/objects/<storageDomainID>/<hh>/<hh>/<64 hex>   object bytes
<root>/pins/<storageDomainID>/<64 hex>                pending-publication marker
<root>/published/<storageDomainID>/<64 hex>/<remote>  publication marker per remote
<root>/tmp/                                           in-flight writes

Objects are addressed by the sha256 of their bytes; a stored object is therefore immutable and can be shared by every clone and worktree on the host. Concurrency across processes relies only on atomic rename: a writer streams into tmp/, fsyncs, then renames into place. Two writers racing on the same digest both produce identical bytes, so whichever rename wins is correct and the loser discards its temp file. No lock files are needed.

Readers verify: Open returns a reader that recomputes the digest and fails at EOF on mismatch, so a corrupt cache entry can never be handed out as valid content.

Pins mark objects that were ingested locally (git add) but not yet published to every required remote. Prune never evicts a pinned object. Publication markers are the temporary stand-in for the publication ledger ($GIT_COMMON_DIR/fxvcs/publication.db); the pre-push hook consults them.

Index

Constants

View Source
const EnvDir = "FXVCS_CACHE_DIR"

EnvDir is the environment variable that overrides the cache location.

Variables

View Source
var (
	ErrNotFound      = errors.New("cache: object not found")
	ErrCorrupt       = errors.New("cache: object bytes do not match digest")
	ErrInvalidDigest = errors.New("cache: invalid digest")
	ErrInvalidDomain = errors.New("cache: invalid storage domain id")
)

Errors.

Functions

func DefaultDir

func DefaultDir() (string, error)

DefaultDir returns FXVCS_CACHE_DIR if set, else <UserCacheDir>/fxvcs.

func Digest

func Digest(sum []byte) string

Digest formats a raw sha256 sum as "sha256:<hex>".

Types

type Cache

type Cache struct {
	// contains filtered or unexported fields
}

Cache is a handle on one cache root. It holds no open files or locks; a zero-cost value that can be created per process.

func Open

func Open(root string) (*Cache, error)

Open creates (if needed) and returns the cache at root. root=="" uses DefaultDir.

func (*Cache) BytesWritten

func (c *Cache) BytesWritten() int64

BytesWritten reports how many object bytes this handle has stored since it was opened. It is a lower bound on growth, not a measurement of the cache: other processes share it, so a caller must still measure eventually.

func (*Cache) Has

func (c *Cache) Has(domain, digest string) bool

Has reports whether the object file exists (without verifying its bytes).

func (*Cache) HasSized

func (c *Cache) HasSized(domain, digest string, size int64) bool

HasSized reports that the object is cached and is exactly size bytes.

This is the cheap presence predicate for hot paths. It is not a substitute for Verify: it catches a truncated or replaced entry, not bytes that rotted in place. Callers rely on it only where the bytes are hashed again before anything observable happens to them — see the package doc on reader verification.

func (*Cache) IsPinned

func (c *Cache) IsPinned(domain, digest string) (bool, error)

IsPinned reports whether the object carries a pending-publication marker.

func (*Cache) List

func (c *Cache) List() ([]Entry, error)

List enumerates every cached object with its pin state, sorted by domain then digest. A missing objects directory is an empty cache.

func (*Cache) Open

func (c *Cache) Open(domain, digest string) (*Reader, error)

Open returns a verifying reader for the object; ErrNotFound if absent. Opening touches the object's mtime (best effort) so Prune treats it as recently used.

func (*Cache) Pin

func (c *Cache) Pin(domain, digest string) error

Pin marks an object as pending publication. Pinned objects survive Prune.

func (*Cache) PlanPrune

func (c *Cache) PlanPrune(targetBytes int64) (plan []Entry, kept int, err error)

PlanPrune returns the LRU eviction plan that Prune(targetBytes) would execute, without removing anything, plus the number of objects kept (pinned or within budget). Pinned objects never appear in the plan.

func (*Cache) Prune

func (c *Cache) Prune(targetBytes int64) (PruneResult, error)

Prune evicts least-recently-used unpinned objects until the total object bytes are at or below targetBytes. Pinned (pending publication) objects are never evicted, so the result may remain above target. Recency is the file mtime, which Open refreshes; atime is not portable enough to rely on.

func (*Cache) Put

func (c *Cache) Put(domain string, r io.Reader) (PutResult, error)

Put streams r into the cache under domain and returns its digest. Bytes are written to a temp file, fsynced, then atomically renamed to their final path; an interrupted Put leaves at most a temp file under tmp/ and never a visible object.

func (*Cache) PutBytes

func (c *Cache) PutBytes(domain string, data []byte) (PutResult, error)

PutBytes stores content already held in memory.

It exists because Put cannot know an object's identity until it has read the whole stream, so it writes and fsyncs a temporary file before discovering that the object was already cached. That is the wrong shape for a caller that holds the bytes: re-ingesting an unchanged file then rewrote every chunk it already had. Hashing first costs nothing extra — Put hashes anyway — and turns the common "already present" case into a stat.

func (*Cache) PutBytesChecked

func (c *Cache) PutBytesChecked(domain string, data []byte) (PutResult, error)

PutBytesChecked is PutBytes for an object small enough that confirming the stored copy is cheaper than being wrong about it.

PutBytes trusts identity and size, which is right for bulk content that is hashed again on the way out. A manifest is different: it is the map to everything else, it is a few kilobytes, and a damaged one makes its whole asset unreadable rather than one chunk. So the existing entry is read, and replaced when it does not match.

func (*Cache) PutExpecting

func (c *Cache) PutExpecting(domain, expect string, size int64, r io.Reader) (PutResult, error)

PutExpecting stores a stream whose digest the caller already knows, and skips the write when the cache already holds that object at that size.

The stream is still read and hashed in full: callers use this to prove that the bytes in hand are the bytes the cache claims to have — unload is about to destroy them — so skipping the read would defeat the purpose. What it skips is the temporary file, the fsync and the rename, which is the part that made re-deriving an unchanged file expensive.

A stream that does not hash to expect is ErrCorrupt and nothing is stored.

func (*Cache) PutReplacing

func (c *Cache) PutReplacing(domain, digest string, r io.Reader) (PutResult, error)

PutReplacing stores a stream the caller expects to be digest, overwriting a damaged copy of that object.

It exists so that repairing an object never destroys the only copy first. Put treats an existing key as success and writes nothing, so a repair had to delete the damaged entry before fetching — and a fetch that then failed left nothing at all, along with a dropped pin. Writing with replacement keeps the damaged bytes until the good ones are in hand, and swaps them atomically.

The expected digest is required, and content that does not match it is discarded rather than stored: a caller repairing one object must not be able to write, or replace, a different one on a remote's say-so. A mismatch is ErrCorrupt and the cache is untouched.

func (*Cache) PutVerified

func (c *Cache) PutVerified(domain, digest string, data []byte) error

PutVerified stores bytes whose identity the caller has already established, replacing whatever is at that key.

It exists for repair. Put treats an existing key as success without looking at it — objects are immutable, so identity implies content — but that reasoning only holds while the stored bytes are the bytes that were stored. A caller holding a proven copy of the content (unload, reading the working file it is about to destroy) needs to be able to put the cache right, not to be told the damaged entry is fine.

func (*Cache) Remove

func (c *Cache) Remove(domain, digest string, force bool) error

Remove deletes an object and its pin/publication markers. It refuses to remove a pinned object unless force is set.

func (*Cache) Root

func (c *Cache) Root() string

Root returns the absolute cache directory.

func (*Cache) Stat

func (c *Cache) Stat(domain, digest string) (Info, error)

Stat returns metadata for an object; ErrNotFound if absent.

func (*Cache) TotalBytes

func (c *Cache) TotalBytes() (int64, error)

TotalBytes sums the sizes of all stored objects.

func (*Cache) Unpin

func (c *Cache) Unpin(domain, digest string) error

Unpin removes the pending-publication marker (no error if absent).

func (*Cache) Verify

func (c *Cache) Verify(domain, digest string) error

Verify reads the whole object and returns nil only if its bytes match.

type CorruptError

type CorruptError struct {
	Digest string // the object that was expected
	Got    string // the digest its bytes actually hash to ("" if not computed)
	Size   int64  // bytes actually read
	Want   int64  // bytes expected
}

CorruptError names the object whose bytes did not match, so a caller that can repair the cache knows what to evict. Verification happens while bytes stream, and by then the digest under suspicion is several call frames away from whoever can act on it; carrying it in the error is what makes an automatic evict-and-refetch possible instead of failing the whole operation.

errors.Is(err, ErrCorrupt) matches it.

func (*CorruptError) Error

func (e *CorruptError) Error() string

func (*CorruptError) Unwrap

func (e *CorruptError) Unwrap() error

type Entry

type Entry struct {
	Domain  string
	Digest  string
	Size    int64
	ModTime time.Time
	Pinned  bool
}

Entry is one cached object as enumerated by List.

type Info

type Info struct {
	Digest  string
	Size    int64
	ModTime time.Time
	Pinned  bool
}

Info describes a cached object.

type PruneResult

type PruneResult struct {
	BytesBefore int64
	BytesAfter  int64
	Removed     int
	SkippedPins int
}

PruneResult reports what Prune did.

type PutResult

type PutResult struct {
	Digest string // sha256:<hex>
	Size   int64
	// Existed is true when an identical object was already present.
	Existed bool
}

PutResult describes a stored object.

type Reader

type Reader struct {
	// contains filtered or unexported fields
}

Reader streams an object and verifies its digest as bytes pass through. Read returns ErrCorrupt (instead of io.EOF) if the full content does not hash to the expected digest, and also if the file is shorter or longer than recorded. Callers must treat any error as "no valid content".

func (*Reader) Close

func (r *Reader) Close() error

Close closes the underlying file.

func (*Reader) Read

func (r *Reader) Read(p []byte) (int, error)

Read implements io.Reader with verify-at-EOF semantics.

func (*Reader) Size

func (r *Reader) Size() int64

Size returns the object size as recorded on disk.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL