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
- func HashHex(s string) string
- func KeyDigest(components map[string]string) string
- func NamedStores() ([]string, error)
- func StoreDir(name string) (string, error)
- func StoreRoot() (string, error)
- type ArtifactStore
- type Entry
- type GCReclaim
- type Layout
- func (l *Layout) Clear() error
- func (l *Layout) Delete(key string) error
- func (l *Layout) Dir() string
- func (l *Layout) Fill(key string, fill func() (Entry, error)) (Entry, error)
- func (l *Layout) GC() error
- func (l *Layout) GCStats(dryRun bool) (GCReclaim, error)
- func (l *Layout) Get(key string) (Entry, bool)
- func (l *Layout) Keys() []string
- func (l *Layout) Len() int
- func (l *Layout) Put(key string, e Entry) error
- func (l *Layout) PutEntry(key string, e Entry) error
Constants ¶
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.
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
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
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
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.
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
Decode unmarshals the entry's Payload 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.
func (Entry) FreshTTL ¶ added in v0.2026265.2108
FreshTTL reports whether the entry is within ttl of its resolution.
func (Entry) FreshValidator ¶ added in v0.2026266.1044
FreshValidator reports whether the entry's validator equals the caller's current upstream token.
type GCReclaim ¶ added in v0.2026266.2126
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
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
OpenLayoutLimited is OpenLayout with an explicit entry cap (0 disables pruning).
func OpenNamedLayout ¶ added in v0.2026266.1044
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
Clear drops the whole layout (`--invalidate` / `charly cache clear`).
func (*Layout) Fill ¶ added in v0.2026266.1044
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
GC reclaims blobs no live manifest references, then enforces the entry cap.
func (*Layout) GCStats ¶ added in v0.2026266.2126
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
Get returns the entry for key and whether it was present (and well-formed).
func (*Layout) Keys ¶ added in v0.2026266.1044
Keys returns every stored key (read from the manifest annotations).