Help us shape the future of Go! Take the 2026 Go Developer Survey

cache

package
v1.9.3 Latest Latest
Warning

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

Go to latest
Published: Oct 26, 2026 License: MIT Imports: 17 Imported by: 0

Documentation

Overview

Package cache is the worker-local staging cache for artifact inputs.

Staging exists because the native libraries that read Dispatch's heavy inputs — CAD kernels, mesh importers, PDF engines — want a file path they can seek and memory-map, not a stream. The cache materialises an artifact to local disk and hands back that path.

It is content-addressed. Entries live under their BLAKE3 hash, so two jobs consuming the same model share one copy, and re-running a job over an input it already staged costs nothing. The hash is computed during the download rather than by a separate pass: the bytes are already streaming to disk, so hashing them is free. That is what fills in the content_hash column an artifact carries as NULL after registration.

Three mechanisms keep concurrent staging honest:

  • Single-flight collapses concurrent stages of the same artifact into one download.
  • Leases pin an entry while a job is using it, so eviction cannot pull a file out from under a running handler.
  • A byte budget bounds total disk use, evicting unleased entries by least-recent-use and making a job wait when nothing can be freed.

That last mechanism is also the artifact plane's first piece of admission control: a job needing more staging space than is available waits instead of filling the disk.

The budget is a resource.Manager, not a counter. Each cached object holds one lease for its bytes and eviction releases it, so with WithManager the staged bytes sit in the same ledger the worker admits jobs against: the cache is registered as that manager's disk reclaimer, and a job short on disk gets it by evicting rather than by waiting for a cache that has no reason to shrink. With no manager supplied the cache builds a private single-key one, which is the private disk budget it always had.

The cache is a cache. Its index is an optimisation rebuilt from disk on startup, and a corrupt or missing index costs a re-download, never correctness.

Index

Constants

View Source
const DefaultBudget int64 = 20 << 30 // 20 GiB

DefaultBudget is the disk allowance when none is configured.

Variables

View Source
var ErrBudgetExceeded = errors.New("dispatch/artifact/cache: budget exceeded")

ErrBudgetExceeded means the cache could not free enough space for a stage request.

It is returned both when a single artifact is larger than the whole budget — which can never succeed and so fails immediately — and when every cached entry is currently leased and the caller's deadline elapsed while waiting for one to be released. The manager's own resource.ErrCapacityExceeded is kept in the chain, so a caller that wants the dimension rather than the layer can still find it.

Functions

func TrimHashPrefix

func TrimHashPrefix(hash string) string

TrimHashPrefix returns a digest without its algorithm label.

Types

type Cache

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

Cache stages artifacts to local disk, content-addressed and bounded by a byte budget. It is safe for concurrent use.

Every cached object holds one resource.Manager lease for its bytes, and eviction returns them by releasing that lease. That is what lets the same ledger that admits jobs see the disk the cache is sitting on: without it, staged bytes would be invisible to admission and a worker would offer capacity it had already spent.

func New

func New(dir string, backend artifact.Backend, opts ...Option) (*Cache, error)

New opens a cache rooted at dir.

Startup wipes the temp directory and rebuilds the entry table by walking the hash directories. The walk is the source of truth: there is no persisted index to corrupt, so a crash costs at most a re-download.

func (*Cache) Available

func (c *Cache) Available(key string) int64

Available reports the bytes eviction could free right now, satisfying resource.Reclaimer.

It reads the entry table and nothing else. The guarantee this cache keeps is that the table lock and the manager's lock are never held at the same time — not that one is always taken first; rebuild takes them in the opposite sequence and is safe for exactly that reason. Reclaim lets go of the table before releasing a lease. Totalling the leases here instead of the entry sizes would break the rule on the one call the manager makes while a caller is mid-Acquire, and hold the table under the manager's lock while Reclaim waits for the table. That is the deadlock.

func (*Cache) Budget

func (c *Cache) Budget() int64

Budget returns the configured disk allowance. The engine uses it to reject a job definition whose declared inputs could never be staged.

func (*Cache) Close

func (c *Cache) Close() error

Close releases the cache. Staged files survive so the next process can reuse them; only the temp directory is cleared.

func (*Cache) Dir

func (c *Cache) Dir() string

Dir returns the cache root.

func (*Cache) Purge

func (c *Cache) Purge() error

Purge removes every cached file and returns its bytes to the manager.

The table is drained under its own lock rather than replaced, so an eviction running at the same time cannot pick an entry this loop has already released: each entry leaves the table once and its hold goes back once. It still assumes no live stagers — Purge deletes files out from under anything holding one, which was always its contract.

func (*Cache) Reclaim

func (c *Cache) Reclaim(ctx context.Context, key string, need int64) (int64, error)

Reclaim frees up to need bytes by evicting least recently used entries, satisfying resource.Reclaimer.

The bytes return to the manager as each victim's lease is released, which is the only path that credits the ledger; the count returned here is the manager's "something changed, re-check" signal and is deliberately not added to anything.

It reclaims nothing for any key but disk. This cache holds bytes on one volume and nothing else, and a reclaimer that answered for memory it does not hold would have the manager admit work the box cannot run.

This runs on the admission path — Manager.Acquire calls it before it blocks, under whatever deadline the caller set — so it does the unlinks the shortfall needs and stops, rather than tidying up while a fetcher waits.

func (*Cache) Stage

func (c *Cache) Stage(ctx context.Context, ref artifact.Ref) (path, hash string, release func(), err error)

Stage materialises an artifact locally and returns its path, its content hash, and a release function the caller must invoke.

The returned path stays valid until release is called. Callers should `defer release()` immediately; releasing twice is safe.

func (*Cache) Used

func (c *Cache) Used() int64

Used returns the bytes currently held on disk.

type Option

type Option func(*Cache)

Option configures a Cache.

func WithBudget

func WithBudget(bytes int64) Option

WithBudget sets the maximum bytes the cache may hold on disk.

It configures the private manager the cache builds for itself, so it is ignored when WithManager supplies one: a shared ledger's disk capacity is the allowance, and a second ceiling underneath it would only be a place for the two to disagree.

func WithLogger

func WithLogger(l log.Logger) Option

WithLogger sets the logger.

func WithManager

func WithManager(m resource.Manager) Option

WithManager admits cached bytes against a shared resource manager rather than a private one.

This is what makes staged bytes visible to job admission. The cache registers itself as the manager's disk reclaimer and holds a lease per cached entry, so a worker sizing up a job is offered the disk that is free plus the disk the cache can evict, and redeems the second half by evicting. A nil manager leaves the private one in place, so a caller threading an optional dependency through does not have to branch.

Jump to

Keyboard shortcuts

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