Documentation
¶
Overview ¶
Package cache provides the ONE shared persistent-cache mechanism (R3): a keyed Store with three interchangeable VALIDITY modes and one storage layout.
A Store is a directory of one JSON entry per key, keyed by a content key, with the entry recording the validity inputs so the READ decides freshness. This package's own consumers — spec/refs (submodule verdicts) and spec/container (image list, image labels) — are Stores today; the loader's materialized-tree cache (sdk/loaderkit) and a plugin HTTP client (plugin-gh) are folded onto the SAME Store in their own producer-ordered changes, so a feature never grows its own bespoke cache.
Validity lives IN the entry, never in the storage mechanism, so the same Store serves all three policies:
- TTL — Entry.Resolved within a window (the status hot path: the image list, labels, submodule verdicts).
- Components — Entry.Components equal the caller's current components (the content-addressed caches: the loader's materialized tree, a GitHub blob by SHA). No time validity: valid while the inputs are unchanged.
- Validator — Entry.Validator is an upstream revalidation token (an HTTP ETag / Last-Modified); the caller revalidates and refreshes on change (a GitHub PR/issues read that must not re-fetch while upstream is unchanged).
Layout + concurrency (the one mechanism, the spec/refs + loaderkit patterns): one file per key under the store directory, filenames a SHA-256 of the key (a key may carry '/', '|', '@' — never a safe filename). Reads are LOCK-FREE: a writer publishes atomically (tmp + rename), so a reader sees either the complete old state (miss) or the complete new one — never a torn entry. The MISS path takes a per-key blocking advisory flock (spec/lock) and double-checks under the lock, so concurrent first-missers compute ONCE and reuse the winner's entry. Every cache failure — a key error, a read error, lock contention timeout, a corrupt entry — is a MISS: the cache is an optimization, never a correctness dependency.
Index ¶
- Constants
- func HashHex(s string) string
- func KeyDigest(components map[string]string) string
- func StoreDir(name string) (string, error)
- type Entry
- type Store
- func (s *Store) Clear() error
- func (s *Store) Delete(key string) error
- func (s *Store) Dir() string
- func (s *Store) Fill(key string, fill func() (Entry, error)) (Entry, error)
- func (s *Store) Get(key string) (Entry, bool)
- func (s *Store) Keys() []string
- func (s *Store) Len() int
- func (s *Store) Put(key string, e Entry)
- func (s *Store) PutEntry(key string, e Entry)
- func (s *Store) Read(key string, out any) bool
- func (s *Store) ReadTTL(key string, ttl time.Duration, out any) bool
- func (s *Store) WriteValue(key string, value any)
Constants ¶
const DefaultMaxEntries = 4096
DefaultMaxEntries bounds a Store's size (the Docker builder --keep-storage analogue): beyond it the write path opportunistically reclaims the OLDEST entries. Reclamation is STORAGE-bound only — validity is decided by the entry (TTL / components / validator), never by age — so a pruned entry is only ever a stale-input orphan. 0 disables the cap.
Variables ¶
This section is empty.
Functions ¶
func HashHex ¶ added in v0.2026265.2108
HashHex is the hex SHA-256 of s — the content-addressed digest helper shared by every components-keyed caller (the loader's walk envelope, a GitHub blob).
func KeyDigest ¶ added in v0.2026265.2108
KeyDigest is the components digest of a component set: its keys sorted and joined with NUL separators around "k=v" pairs, SHA-256'd. One helper so every components-validity caller derives the same key from the same inputs (R3).
Types ¶
type Entry ¶
type Entry struct {
// Key is the ORIGINAL cache key (the filename is its hash, so the key is
// carried inside the entry). Set by the Store on Put; a caller need not set it.
Key string `json:"key,omitempty"`
// Value is the cached payload (opaque JSON).
Value json.RawMessage `json:"value"`
// Resolved is when the value was written.
Resolved time.Time `json:"resolved"`
// Components are the content/identity inputs a components-validity caller
// compares: the entry is valid iff these equal the caller's current set.
Components map[string]string `json:"components,omitempty"`
// Validator is an upstream revalidation token (HTTP ETag / Last-Modified) a
// caller uses to revalidate without a body re-fetch.
Validator string `json:"validator,omitempty"`
}
Entry is one cached value plus the inputs its validity is decided from.
Value is the caller's opaque payload. Resolved is the write time (RECLAMATION ordering — and the TTL input where a caller uses one). Components and Validator are the two non-time validity inputs; a caller sets exactly the one its policy uses (a TTL caller sets neither).
func (Entry) Decode ¶ added in v0.2026265.2108
Decode unmarshals the entry's Value into out; false on a decode error.
func (Entry) FreshComponents ¶ added in v0.2026265.2108
FreshComponents reports whether the entry's components equal want. Two nil component sets are equal; a nil/empty want matches only a nil/empty entry set.
type Store ¶ added in v0.2026265.2108
type Store struct {
// contains filtered or unexported fields
}
Store is a persistent keyed cache rooted at a directory. Construct with Open and share it: the on-disk state is the point.
func Open ¶ added in v0.2026265.2108
Open returns a Store rooted at dir (created lazily on first write), pruning to DefaultMaxEntries. An empty dir yields an inert store whose every operation is a miss/no-op — the cache is an optimization, so a caller never has to guard it.
func OpenLimited ¶ added in v0.2026265.2108
OpenLimited is Open with an explicit entry cap (0 disables pruning).
func OpenNamed ¶ added in v0.2026265.2108
OpenNamed opens the named store under the charly cache dir (~/.config/charly/cache/<name>/, CHARLY_CACHE_DIR overriding the root). A path-resolution failure yields an inert store, so a caller never guards it — the ONE idiom every named-store consumer shares (R3).
func (*Store) Clear ¶ added in v0.2026265.2108
Clear drops EVERY entry in the store (the `--invalidate` / `charly cache clear` semantics).
func (*Store) Fill ¶ added in v0.2026265.2108
Fill returns the entry for key, computing it with fill when absent. It serializes the miss under the per-key flock and DOUBLE-CHECKS under the lock, so concurrent first-missers compute once and reuse the winner's entry. A fill error is returned un-wrapped; nothing is written. fill takes no argument: the store-level policy is presence-or-absence, and a caller that must REVALIDATE a mutable upstream (the gh ETag path) reads the existing entry with Get, decides upstream itself, and Puts the refreshed entry — Fill is the compute-once primitive, not a revalidation hook.
Every failure short of fill itself — an inert store, a lock timeout — degrades to calling fill and returning its result WITHOUT persisting: the cache never fails a caller.
func (*Store) Get ¶ added in v0.2026265.2108
Get returns the entry for key and whether it was present (and well-formed). A corrupt or absent entry is a miss.
func (*Store) Keys ¶ added in v0.2026265.2108
Keys returns every stored key (the original key carried in each entry), in no particular order. A corrupt entry is skipped.
func (*Store) Put ¶ added in v0.2026265.2108
Put stores e under key, stamping Resolved to now, atomically (tmp + rename so a lock-free reader never sees a torn entry) and best-effort (a write failure is silent).
func (*Store) PutEntry ¶ added in v0.2026265.2108
PutEntry stores e under key EXACTLY as given — the caller controls Resolved (the backdating seam for TTL tests, and the revalidating caller that refreshes an unchanged entry's Resolved). Key is stamped. Atomic + best-effort like Put.
func (*Store) Read ¶ added in v0.2026265.2108
Read decodes the entry for key into out, returning whether it was present and well-formed (the caller applies its own TTL/components/validator policy to the returned Entry via a companion Get — Read is the pure decode convenience).
func (*Store) ReadTTL ¶ added in v0.2026265.2108
ReadTTL is Read with the TTL policy applied: it decodes the entry for key into out only when present AND within ttl. The common status-cache read.
func (*Store) WriteValue ¶ added in v0.2026265.2108
WriteValue is Put for a caller storing a plain value under a TTL (the common status-cache shape): the value is marshalled and stored with no components or validator.