cache

package
v0.2026266.545 Latest Latest
Warning

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

Go to latest
Published: Sep 23, 2026 License: MIT Imports: 10 Imported by: 6

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

View Source
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

func HashHex(s string) string

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

func KeyDigest(components map[string]string) string

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).

func StoreDir added in v0.2026265.2108

func StoreDir(name string) (string, error)

StoreDir returns the cache DIRECTORY for a named store under the charly dir (~/.config/charly/cache/<name>/). CHARLY_CACHE_DIR overrides the root (the one override every store honors, so a caller can relocate the whole cache).

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

func (e Entry) Decode(out any) bool

Decode unmarshals the entry's Value into out; false on a decode error.

func (Entry) FreshComponents added in v0.2026265.2108

func (e Entry) FreshComponents(want map[string]string) bool

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.

func (Entry) FreshTTL added in v0.2026265.2108

func (e Entry) FreshTTL(ttl time.Duration) bool

FreshTTL reports whether the entry is within ttl of its resolution.

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

func Open(dir string) *Store

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

func OpenLimited(dir string, maxEntries int) *Store

OpenLimited is Open with an explicit entry cap (0 disables pruning).

func OpenNamed added in v0.2026265.2108

func OpenNamed(name string) *Store

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

func (s *Store) Clear() error

Clear drops EVERY entry in the store (the `--invalidate` / `charly cache clear` semantics).

func (*Store) Delete added in v0.2026265.2108

func (s *Store) Delete(key string) error

Delete removes key's entry (a no-op when absent).

func (*Store) Dir added in v0.2026265.2108

func (s *Store) Dir() string

Dir reports the store root ("" for an inert store).

func (*Store) Fill added in v0.2026265.2108

func (s *Store) Fill(key string, fill func() (Entry, error)) (Entry, error)

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

func (s *Store) Get(key string) (Entry, bool)

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

func (s *Store) Keys() []string

Keys returns every stored key (the original key carried in each entry), in no particular order. A corrupt entry is skipped.

func (*Store) Len added in v0.2026265.2108

func (s *Store) Len() int

Len reports the entry count.

func (*Store) Put added in v0.2026265.2108

func (s *Store) Put(key string, e Entry)

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

func (s *Store) PutEntry(key string, e Entry)

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

func (s *Store) Read(key string, out any) bool

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

func (s *Store) ReadTTL(key string, ttl time.Duration, out any) bool

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

func (s *Store) WriteValue(key string, value any)

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.

Jump to

Keyboard shortcuts

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