mirror of
https://github.com/kvcache-ai/AgentENV.git
synced 2026-11-05 01:17:36 +00:00
[PR #344] [MERGED] fix(overlaybd): reclaim cache capacity pinned by open entries #363
Labels
No labels
bug
documentation
enhancement
enhancement
pull-request
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
kvcache-ai/AgentENV#363
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
📋 Pull Request Information
Original PR: https://github.com/kvcache-ai/AgentENV/pull/344
Author: @huajq
Created: 10/8/2026
Status: ✅ Merged
Merged: 10/10/2026
Merged by: @yingdi-shan
Base:
main← Head:fix/overlaybd-open-entry-cache-eviction📝 Commits (1)
0973730fix(overlaybd): reclaim cache capacity pinned by open entries📊 Changes
10 files changed (+565 additions, -43 deletions)
View changed files
📝
docs/src/configuration/reference.md(+1 -0)📝
src/cfg/image.rs(+20 -0)📝
src/image/cache/service.rs(+1 -0)📝
src/orchestrator/tests.rs(+1 -0)📝
src/setup/deps.rs(+22 -0)📝
storage/overlaybd/src/backend/cache/full_file_cache/cache_entry.rs(+54 -10)📝
storage/overlaybd/src/backend/cache/full_file_cache/cache_pool.rs(+99 -1)📝
storage/overlaybd/src/backend/cache/full_file_cache/cache_store.rs(+142 -32)📝
storage/overlaybd/src/backend/cache/tests.rs(+220 -0)📝
storage/overlaybd/src/config.rs(+5 -0)📄 Description
What
Fix the OverlayBD full-file cache eviction cliff described in #320. Entries held open by long-lived sandboxes are no longer pinned forever: under capacity pressure the cache now reclaims blocks of open entries that have been idle past a threshold, so new templates keep being cached even when the pool is at its watermark. Concurrent uncached reads of the same block now share one source fetch. The idle threshold is configurable via
image.cache.remote_blocks.open_evict_idle_secs(default 600s,0disables).Why
On a node serving many templates with long-lived sandboxes, every template with a live sandbox kept its memory-snapshot and rootfs layers pinned: eviction only considered entries with
open_count == 0, so once pinned bytes reached the watermark there was nothing evictable,is_fulllatched, and every new template degraded to uncached per-block source reads (no sharing between concurrent readers) until creates exceededenvd.init_timeout_secs. The failure is a cliff: cached templates keep working while every new template degrades at once.Related issue
Closes #320
Scope and non-goals
Included:
refill_eviction_barrierfor the lifetime of the returnedBytes(safety prerequisite for evicting open entries).Excluded / non-goals (mirrors the issue): no change to default cache sizes, no per-block LRU, no cross-node placement or P2P changes, no change to
evict_by_size/evict_globalsemantics. Unrelated pre-existing finding (reported separately, not fixed here):reference.mddocuments[image.cache.remote_blocks].max_size_gbdefault as100while code defaults to10.Design and behavior changes
cache_entry.rs,cache_store.rs):read_blockacquires anOwnedRwLockReadGuardviatry_read_ownedbefore checking the bitmap, and the returnedBytesowns the guard (newCachedBlockBytesowner). A failed try-read (eviction pending/running) is reported as a miss, so readers fall back to refill/source instead of observing hole-punched pages. The refill path also usestry_read_ownedand skips to source fallback rather than queueing behind a writer.cache_pool.rs): tier 1 evicts closed entries whole (unchanged). If pressure remains, tier 2 reclaims blocks of open entries whoselast_accessis older thanopen_evict_idle, keeping the entry (evict_all_blocks); open handles keep working and refill on demand. Tier 2 skips entries with in-flight refills. Because tier 2 only runs when over the watermark, idle blocks are not reclaimed in normal operation, and active sandboxes are protected by the idle threshold (every hit/miss/refill refresheslast_access).cache_store.rs): previously a full pool made refill fail without triggering synchronous eviction (only the 1s periodic worker ran). Refill now runseviction_inneronce and retries the block once on ENOSPC, so an incoming template gets its blocks cached promptly.cache_entry.rs,cache_store.rs): the fallback source read publishes afutures_util::future::Sharedfuture in the entry'smiss_fanoutsmap. Any awaiter's poll drives the fetch (cancellation-safe, no designated loader), every awaiter gets a clone of the result (errors Arc-wrapped, fail-fast to all waiters). No caching or capacity accounting is touched on this path.Concurrency invariants: eviction's write side of the barrier spans bitmap clear + meta removal + hole punch, so it cannot interleave with refills or with any live read
Bytes. Tier-2 vsrename_store_key/evict_entryare mutually exclusive byopen_countand theEvictingslot state. Double eviction cannot double-subtract capacity (bytes are computed under the write barrier).Compatibility and operations
image.cache.remote_blocks.open_evict_idle_secs(default600), written to generated overlaybdcacheConfig.openEvictIdleSecsfor bothremote-blocksandmemory-blockscaches. Previously generated/hand-written overlaybd configs load unchanged (serde default applies). Behavior change: blocks of open entries idle longer than the threshold may be reclaimed under capacity pressure; a sandbox touching them again pays a source read. Set to0to restore the previous behavior.meta.bin/bitmap format unchanged.futures-utilwas already a dependency of the overlaybd crate.Validation
make fmtmake clippymake test-unitmake -C services test(required whenservices/changes)maketargetCommands and results:
Skipped checks and reasons:
make test-unit: not run as a whole; the touched crates' suites above plusmake test-ublkcover the change. Full VM-dependent suites need KVM and are outside this change's blast radius.make -C services test: noservices/changes.openapi.yml, protobuf, firecracker/envd clients untouched).concurrent_resumebenchmark (50 concurrent VMs): not measurable on the test host — the 50-VM burst fails envd timeout identically on baseline and fixed builds (pre-existing bench/host limitation, pool pre-warming tried). Cold/hot resume above covers the read-path cost.Risks and reviewer notes
test_idle_open_entries_are_block_evicted_under_pressure), but the 800-sandbox workload from #320 has not been rerun. @nblintao offered to test/review — a verification run with a small cache (e.g.max_size_gb = 2) on their node would be valuable.Byteswhile awaitingevict_all/evict_rangeon the same entry (self-deadlock; documented on those methods). No production call site does this.storage/overlaybd/src/backend/cache/full_file_cache/cache_entry.rs(barrier/owner),cache_pool.rs(tier-2 eviction),cache_store.rs(ENOSPC retry,pread_from_source_dedup),storage/overlaybd/src/config.rs+src/cfg/image.rs+src/setup/deps.rs(config plumbing).Checklist
🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.