Skip to content

imagecache: record layer sizes at unpack and image last-use on cache hit - #650

Merged
Dmitry Berkovich (dberkov) merged 3 commits into
agent-substrate:mainfrom
igooch:imagecache-size-lastuse
Aug 1, 2026
Merged

imagecache: record layer sizes at unpack and image last-use on cache hit#650
Dmitry Berkovich (dberkov) merged 3 commits into
agent-substrate:mainfrom
igooch:imagecache-size-lastuse

Conversation

@igooch

@igooch igooch commented Jul 30, 2026

Copy link
Copy Markdown
Collaborator

First slice of #463 Phase 2 (GC): the two facts eviction will need, persisted in the
filesystem so they survive atelet restarts. Inert on its own — no behavior change for
any current caller, and nothing deletes anything.

  • Layer sizes: unpack counts the uncompressed tar stream and writes a per-layer
    size file before the atomic rename (best-effort; a missing file is healed by a
    lazy backfill). Backfill covers layers unpacked by older atelets: one walk, once
    ever, skipping unreadable dirs (atelet has no CAP_DAC_READ_SEARCH) and preserving
    the dir mtime.
  • Last use: cache hits touch the image record's mtime — the persisted LRU
    timestamp — under a new hitMu (shared side only; the eviction pass will be its
    exclusive holder).
  • Store.CacheSize(): sums recorded sizes; the accounting behind the future
    --image-cache-max-bytes.

On-disk changes are purely additive, so no cache-layout version bump and no
migration: an older atelet reads this cache dir fine (ignores size files), and
this atelet reads a pre-upgrade dir fine (backfill). Rollback-safe.

Testing: unit tests for size recording, backfill (incl. the unreadable-dir
contract and mtime preservation), hit-path touch, and CacheSize; go test -race
green standalone; the demo e2e suite passes unchanged on Kind against this build,
with size files and record-mtime updates verified on the node.

Data foundations for eviction (agent-substrate#463 Phase 2), inert on their own:

- unpack counts the uncompressed tar stream and writes a per-layer
  'size' file before the atomic rename, so sizing the pool never walks
  a layer tree; the write is best-effort (a missing size file is
  recovered by the lazy backfill); layers from older atelets are
  backfilled on first sizing (one walk, once ever), skipping unreadable
  dirs (atelet has no CAP_DAC_READ_SEARCH) and preserving the dir mtime
- the cache-hit path touches the image record's mtime, persisting
  last-use for LRU ordering across atelet restarts; the touch happens
  under a new hitMu (shared side only — the eviction pass will be its
  exclusive holder)
- Store.CacheSize sums recorded sizes, the accounting behind the
  future --image-cache-max-bytes

Extracted from the phase2-gc-record-first prototype branch. This slice:
go test -race green standalone; gofmt/vet clean; no behavior change for
any current caller.
Comments now describe the mechanism's purpose (eviction, the GC
root-set scan) without pointing at agent-substrate#463 phases, which would go stale
once the work lands.
Comment thread internal/imagecache/sizes.go Outdated
Comment thread internal/imagecache/imagecache.go Outdated
Comment thread internal/imagecache/sizes.go
@dberkov
Dmitry Berkovich (dberkov) merged commit 51235e6 into agent-substrate:main Aug 1, 2026
11 checks passed
Dmitry Berkovich (dberkov) pushed a commit that referenced this pull request Aug 1, 2026
…ep (#658)

Third foundation slice of #463 Phase 2 (GC), independent of #650 and
#656. The
primitives are uncalled until the eviction engine PR; the only live
behavior is
the startup sweep, which only ever sees `.rm-*` dirs the engine will
create.

- **`retireLayer`**: evicts a layer with one atomic rename to a `.rm-*`
name
inside the layer singleflight; the slow `RemoveAll` is the caller's job,
outside all locks. Existence/mtime pre-flight runs *outside* the flight
(a
retire must never block behind an in-progress download) and both checks
are
re-run inside it before the rename. Returns gone/vetoed/retired so the
engine
  can distinguish "nothing there" from "must keep".
- **Reuse interlock**: `ensureLayer` now refreshes the layer dir mtime
inside
the same flight, so a retirement and a reuse cannot interleave — either
the
retire wins (the pull re-unpacks) or the touch wins (the retire vetoes).
- **Startup sweep**: `.rm-*` dirs (a crash between rename and removal)
are
reclaimed alongside the existing `.tmp-*` dirs, via `RemoveAllWritable`
since
  layer trees legitimately contain read-only content.
- **`isLayerDirName`** validates names read from disk or records.

Divergence from the prototype: `retireLayer` reports no freed size —
size
crediting belongs to the engine's call sites and arrives with it.

**Testing**: unit tests for the status contract, startup-sweep recovery
and
non-interference, and a retire-vs-pull race test; `go test -race` green.
Dmitry Berkovich (dberkov) pushed a commit that referenced this pull request Aug 1, 2026
…Spec (#656)

Second foundation slice of #463 Phase 2 (GC), independent of #650 and
inert on its
own: nothing reads the new field and nothing deletes anything.

- **`OverlaySpec.ImageDigest`** (optional, `omitempty`): the manifest
digest the
bundle's image ref resolved to, populated in `prepareOCIDirectory`. The
GC
root-set scan will use it to root the whole image record while the
bundle
exists. Compatible in both directions: ateom consumers never read the
field
(old binaries drop the unknown JSON key), and specs written by older
atelets
keep parsing with the field empty — so there is no deploy-ordering
constraint.
- **Atomic `WriteSpec`** (temp file + rename in the bundle dir): the
root-set scan
will read specs concurrently with bundle preparation, and a torn spec
would
  under-report the layers an actor is using — an error in the dangerous
direction (toward deleting in-use layers). With the rename, a reader
sees the
  complete old spec or the complete new one, never a fragment.

Landing early matters: every bundle written from now on carries the
digest,
shrinking the digestless-spec population the GC's compatibility fallback
has to
cover.

**Testing**: round-trip + old-spec compat and no-temp-files-left unit
tests;
`go test -race` green on both affected packages.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants