Skip to content

fix: lru local cache eviction #71 - #1481

Open
asmyshlyaev177 wants to merge 1 commit into
google:mainfrom
asmyshlyaev177:local_cache_gc_lru
Open

fix: lru local cache eviction #71#1481
asmyshlyaev177 wants to merge 1 commit into
google:mainfrom
asmyshlyaev177:local_cache_gc_lru

Conversation

@asmyshlyaev177

Copy link
Copy Markdown

Limit local cache size with per-script LRU eviction

Fixes #71

Problem

.wireit/<script>/cache/<fingerprint>/ gains a full copy of the script's
output per fingerprint, and nothing removes them. The README told users to
rm -rf .wireit/*/cache by hand.

Cheap when output is dist/, expensive when it isn't. A repo declaring
node_modules as output reached 73 GB at ~540 MB per entry.

Change

LocalCache keeps the N most recently used entries per script — written by
set() or read by get(). Eviction runs after each successful set().

  • Default N = 2. WIREIT_CACHE_MAX_ENTRIES overrides: positive integer, or
    infinity for the old behavior. Parsed like the existing WIREIT_PARALLEL.
  • Default is small because an entry is a full copy of output, so N multiplies
    a script's disk usage, and large-output scripts are the painful case. 2 covers
    bouncing between the current state and one other (a branch, an undo).

Recency = directory mtime

No index file, no consistency problem. get() stamps the entry it returns, so a
frequently restored entry isn't evicted for being old — that's LRU rather than
FIFO.

atime is unusable: filesystems are commonly mounted noatime or relatime.

Locking

None added. Eviction touches only the calling script's own cache folder, and
StandardScriptExecution#acquireSystemLockIfNeeded already holds that script's
lock across get() and set(). It skips the lock only for an empty output,
where entries are empty directories.

Housekeeping never fails a script

Stamping and eviction swallow errors:

  • unstampable directory (read-only mount, foreign owner) must still return a hit
  • undeletable entry (EBUSY on Windows) must not fail a script already cached

Degraded behavior is "cache larger than requested", not a failed build.

Entries are ranked with lstat, not stat, so a broken symlink in the folder is
evicted instead of throwing on every future eviction.

Tests

src/test/local-cache.test.ts, unit-testing LocalCache with explicit mtimes so
ordering doesn't depend on timestamp resolution:

case asserts
under limit nothing evicted
over limit trimmed to limit
get() on older entry it survives, newer one evicted (LRU, not FIFO)
infinity nothing evicted
post-eviction survivor still restorable
evicted entry get() returns undefined
broken symlink in folder eviction not wedged
stray file in folder evicted like an entry

Plus an end-to-end CLI test — 5 runs under WIREIT_CACHE_MAX_ENTRIES=2, 2
entries left — and a cli-options parse test. All run under
npm run test:cache-local.

Compatibility

Previously unbounded caches are now trimmed; set
WIREIT_CACHE_MAX_ENTRIES=infinity to opt out. Existing over-limit folders trim
on that script's next cache write.

@google-cla

google-cla Bot commented Aug 26, 2026

Copy link
Copy Markdown

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

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.

Garbage collection for cache directory

1 participant