Files
vercel__next.js/turbopack
Luke Sandberg 228df5fd74 [backport] Retain fewer stale cache versions and use a TTL, plus the mtime fallback (#97304)
Backports the following to `next-16-3`, as a pair:

- #96941 — "[turbopack] Retain fewer stale cache versions and use a TTL"
(`ae1e53a`)
- #97268 — "Add the mtime fallback back" (`52a3410`)

The second fixes a bug in the first: #96941 changes the `CURRENT` file
format from a bare
big-endian `u32` to a JSON object, and an existing cache directory in
the old format could no
longer be aged. #97268 adds the mtime fallback so an unparsable
`CURRENT` is aged by its mtime
instead of failing. They only make sense together, hence one branch.

Both cherry-picked with `-x`, no conflicts. Every touched source file is
byte-identical to canary.

### Verification on this branch

- `cargo check`, `cargo clippy --all-targets`, `cargo fmt --check` on
`turbo-persistence` and `turbo-tasks-backend` — clean
- `cargo test -p turbo-persistence -p turbo-tasks-backend` — all pass, 0
failures
- includes the `db_versioning` tests, notably
`test_unparsable_current_falls_back_to_mtime::case_1_old_u32_format`
(the old-format compatibility case #97268 restores)
- `cargo metadata --locked` — `Cargo.lock` consistent with the
manifests, no lockfile update needed
- `pnpm --filter=next types`, prettier, eslint — clean
- `pnpm build-all` — 18/18 tasks
- `test/production/next-server-nft` — 5/5 tests, 5/5 snapshots pass
(covers the traced `turbopack-cache-seed.js`)

### End-to-end verification against a real old-format cache

`create-next-app@16.3.0` + `next build` (with
`turbopackFileSystemCacheForBuild`) on released
`next@16.3.0` writes a `CURRENT` in the old pre-JSON format — a bare
big-endian `u32`
(`0000 0008`), 30M cache. This branch was then applied with
`pnpm pack-next -p <app> -- --release` (release binary confirmed to
contain
`TURBO_ENGINE_VERSION_TTL_DAYS` and the new `CURRENT` field names), and
two arms were run from
an identical starting state — same cache, same code, only the env var
differing:

| Arm | Build | Cache dirs after |
| --- | --- | --- |
| `TURBO_ENGINE_VERSION_TTL_DAYS=0` | exit 0 | old dir **deleted**, 1
remains |
| default TTL (3 days) | exit 0 | old dir **retained**, intact at 30M, 2
remain |

The retention arm is the meaningful control: without #97268's fallback,
`read_current_version`
would error on the bare-`u32` file, the age would become
`Duration::MAX`, and the directory would
be evicted in *both* arms. Retention under the default TTL is direct
evidence the mtime fallback
works.

Also confirmed:

- new `CURRENT` is written as JSON:
`{"max_sequence_number":8,"commit_time":"..."}`
- retention cap holds: seeded 4 version dirs → build left exactly 2
(current + one most-recently-used other)
- clean deletion, no `DELETION_PREFIX` leftovers in any run
- `TURBO_ENGINE_VERSION_TTL_DAYS=notanumber` warns (`expected a whole
number of days`) and falls back to the 3-day default rather than failing

<!-- NEXT_JS_LLM -->
2026-08-13 21:43:26 +00:00
..