cache

package
v0.2026269.1619 Latest Latest
Warning

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

Go to latest
Published: Sep 26, 2026 License: MIT Imports: 13 Imported by: 6

Documentation

Overview

Package cache provides the ONE shared persistent-cache mechanism (R3): a content-addressed ArtifactStore whose on-disk form is a standard OCI Image Layout, so every cached artifact is a real OCI manifest — and a whole named cache can be pushed to / pulled from an OCI registry unchanged.

The model

A Store is one OCI Image Layout directory: blobs/<alg>/<digest> (content), index.json (the entry point), oci-layout (the version marker). Each cached artifact is ONE OCI image manifest:

  • config blob — the entry's validity metadata (resolved / components / validator). Validity lives IN the entry, never in the storage mechanism.
  • layers[0] — the cached payload bytes (JSON, a tar tree, …).
  • annotations — the cache KEY (ai.opencharly.cache.key) and the TAG (org.opencontainers.image.ref.name), both indexed by index.json.

A NAMED cache is one such layout. Because it is a valid OCI layout, the registry transport (candy/plugin-oci's verb:oci cache-push/cache-pull) reads and writes it verbatim with a standard registry client — no bespoke format.

Docker cache principles, applied

Content addressing (blob name = digest), a manifest/index entry point, tags for lookup, and no in-place mutation: a write publishes NEW blobs and swaps index.json atomically (tmp + rename), so a lock-free reader sees either the complete old index or the complete new one — never a torn entry. GC reclaims blobs no live manifest references.

Validity

  • TTL — Entry.Resolved within a window (the status hot path).
  • Components — Entry.Components equal the caller's current components (the content-addressed caches: the materialized tree, a GitHub blob by SHA).
  • Validator — Entry.Validator is an upstream revalidation token (an HTTP ETag / Last-Modified); the caller revalidates and refreshes on change.

Every cache failure — a read error, an unreadable blob, lock contention timeout, a corrupt manifest — is a MISS: the cache is an optimization, never a correctness dependency.

Index

Constants

View Source
const (
	// MediaTypeCacheConfig is the entry's validity-metadata config blob.
	MediaTypeCacheConfig = "application/vnd.opencharly.cache.config.v1+json"
	// MediaTypeCachePayload is the default media type of the cached payload.
	MediaTypeCachePayload = "application/vnd.opencharly.cache.payload.v1+json"
	// AnnotationCacheKey records the ORIGINAL cache key on the manifest (the
	// tag is a digest, so the key must be carried to be recoverable).
	AnnotationCacheKey = "ai.opencharly.cache.key"
)

Media types for the two blob roles an entry carries. They are namespaced under opencharly so the layout is self-describing without claiming to be a runnable container image.

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

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 NamedStores added in v0.2026266.2126

func NamedStores() ([]string, error)

NamedStores returns every named store under the cache root, sorted by name. A store is a directory that looks like an OCI Image Layout (carries an oci-layout marker) — a stray non-layout file/dir under the root is skipped, so a caller never mistakes unrelated cache state for an ArtifactStore. A missing root is an empty list, never an error.

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

func StoreRoot added in v0.2026266.2126

func StoreRoot() (string, error)

StoreRoot returns the ROOT directory every named store lives under (~/.config/charly/cache/), honoring the CHARLY_CACHE_DIR override.

Types

type ArtifactStore added in v0.2026266.1044

type ArtifactStore interface {
	// Put stores e under key (stamping Resolved and publishing new blobs).
	Put(key string, e Entry) error
	// Get returns the entry for key and whether it was present (and well-formed).
	Get(key string) (Entry, bool)
	// Delete removes key's entry (a no-op when absent).
	Delete(key string) error
	// Keys returns every stored key.
	Keys() []string
	// Len reports the entry count.
	Len() int
	// Clear drops every entry (the `charly cache clear` semantics).
	Clear() error
	// GC reclaims blobs no live entry references and enforces the entry cap.
	GC() error
	// Dir reports the store root ("" for an inert store).
	Dir() string
}

ArtifactStore is the ONE content-addressed cache contract. The file-based Layout implements it today; the registry transport (verb:oci cache-push/pull) moves a whole named store to and from a registry.

type Entry

type Entry struct {
	// Key is the ORIGINAL cache key (the manifest tag is its digest, so the key
	// is carried in an annotation). Get reconstructs it for the caller; a caller
	// need not set it on Put.
	Key string `json:"key,omitempty"`
	// Payload is the cached value's bytes (JSON, a tar tree, …).
	Payload []byte `json:"payload,omitempty"`
	// MediaType is the payload's media type. Empty means MediaTypeCachePayload.
	MediaType string `json:"media_type,omitempty"`
	// 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.

func (Entry) Decode added in v0.2026265.2108

func (e Entry) Decode(out any) bool

Decode unmarshals the entry's Payload 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.

func (Entry) FreshValidator added in v0.2026266.1044

func (e Entry) FreshValidator(current string) bool

FreshValidator reports whether the entry's validator equals the caller's current upstream token.

type GCReclaim added in v0.2026266.2126

type GCReclaim struct {
	RemovedBlobs int
	RemovedBytes int64
}

GCReclaim reports the outcome of one GC: the number of unreferenced blobs reclaimed and their summed size in bytes. dryRun counts the would-remove set without touching disk.

type Layout added in v0.2026266.1044

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

Layout is the simple file-based ArtifactStore: an OCI Image Layout directory.

func OpenLayout added in v0.2026266.1044

func OpenLayout(dir string) *Layout

OpenLayout returns a Layout 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.

func OpenLayoutLimited added in v0.2026266.1044

func OpenLayoutLimited(dir string, maxEntries int) *Layout

OpenLayoutLimited is OpenLayout with an explicit entry cap (0 disables pruning).

func OpenNamedLayout added in v0.2026266.1044

func OpenNamedLayout(name string) *Layout

OpenNamedLayout opens the named layout 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.

func (*Layout) Clear added in v0.2026266.1044

func (l *Layout) Clear() error

Clear drops the whole layout (`--invalidate` / `charly cache clear`).

func (*Layout) Delete added in v0.2026266.1044

func (l *Layout) Delete(key string) error

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

func (*Layout) Dir added in v0.2026266.1044

func (l *Layout) Dir() string

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

func (*Layout) Fill added in v0.2026266.1044

func (l *Layout) 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. fill takes no argument: a caller that must REVALIDATE a mutable upstream reads the existing entry with Get, decides upstream itself, and Puts the refreshed entry.

Fill STAMPS Resolved to now, exactly like Put: the compute-once path is a fresh computation, so its write time is now — a fill callback need not (and must not have to) set Resolved itself. A callback returning a zero Resolved (e.g. an empty degrade entry) would otherwise be instantly TTL-stale and sort as the OLDEST entry for reclamation.

func (*Layout) GC added in v0.2026266.1044

func (l *Layout) GC() error

GC reclaims blobs no live manifest references, then enforces the entry cap.

func (*Layout) GCStats added in v0.2026266.2126

func (l *Layout) GCStats(dryRun bool) (GCReclaim, error)

GCStats runs GC and reports what it reclaimed. With dryRun it computes the same set but removes nothing — the `--dry-run` probe. A missing/inert store is a zero reclaim, never an error.

The reported set INCLUDES cap eviction: the live-blob computation uses the entries a real GC would KEEP (the cap-pruned subset), so a dry-run predicts exactly what a real run removes — including the blobs a cap-evicted entry alone referenced. (Computing `live` from the un-pruned index would under-report.)

func (*Layout) Get added in v0.2026266.1044

func (l *Layout) Get(key string) (Entry, bool)

Get returns the entry for key and whether it was present (and well-formed).

func (*Layout) Keys added in v0.2026266.1044

func (l *Layout) Keys() []string

Keys returns every stored key (read from the manifest annotations).

func (*Layout) Len added in v0.2026266.1044

func (l *Layout) Len() int

Len reports the entry count.

func (*Layout) Put added in v0.2026266.1044

func (l *Layout) Put(key string, e Entry) error

Put stores e under key, stamping Resolved to now and publishing new blobs.

func (*Layout) PutEntry added in v0.2026266.1044

func (l *Layout) PutEntry(key string, e Entry) error

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

Jump to

Keyboard shortcuts

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