## Summary
Rename `NEXT_DEV_WAIT_FOR_TURBOPACK_SHUTDOWN` to
`__NEXT_DEV_WAIT_FOR_TURBOPACK_SHUTDOWN` to mark the environment
variable as internal and subject to change.
## Verification
- `pnpm --filter=next types`
- `pnpm prettier --with-node-modules --ignore-path .prettierignore
--check packages/next/src/cli/next-dev.ts
packages/next/src/server/dev/hot-reloader-turbopack.ts
test/e2e/filesystem-cache/warm-restart-task-stats.test.ts`
- `npx eslint --config eslint.config.mjs
packages/next/src/cli/next-dev.ts
packages/next/src/server/dev/hot-reloader-turbopack.ts
test/e2e/filesystem-cache/warm-restart-task-stats.test.ts`
- Focused test attempted: `pnpm test-dev-turbo
test/e2e/filesystem-cache/warm-restart-task-stats.test.ts` (failed
because the fixture did not emit
`.next/dev/server/pages/_app/build-manifest.json`, causing the page
request to return 500 before the assertion)
<!-- NEXT_JS_LLM -->
### What?
Migrate the five local isolated test installs that explicitly used npm
to pnpm. The Nx fixture now has pnpm workspace metadata, while
filesystem-layout-sensitive fixtures use pnpm's hoisted linker with
copied package files.
### Why?
The npm-based Nx install bypassed the repository's centralized
supply-chain protections and could select a package immediately after
publication, including temporarily incomplete multi-package releases.
Using pnpm makes isolated installs inherit the repository's
`minimumReleaseAge`, exclusions, and exotic-subdependency policy.
The other npm installs depended on npm-style real package directories.
On Node versions affected by nodejs/node#65113, hoisted/copy mode
preserves that layout without leaving these fixtures outside the shared
pnpm security configuration; fixed Node releases use normal pnpm
linking.
### How?
- Use normal pnpm workspace resolution for the Nx fixture.
- Use `node-linker=hoisted` and `package-import-method=copy` for
filesystem tests only on affected Node releases; Node 24.21+ and 26.8+
use normal linking. Node 20 CI keeps the workaround because no fixed
Node 20 release exists.
- Validate local `@next/env` tarballs through the lockfile when hoisted
installs do not expose pnpm's virtual-store path marker.
- Keep the deployment-environment npm install unchanged.
### Verification
- `pnpm build-all`
- `pnpm types`
- A 9-version throwaway assertion verified the affected/fixed Node
release matrix
- `pnpm test-dev-turbo test/e2e/app-dir/nx-handling/nx-handling.test.ts
test/e2e/handle-non-hoisted-swc-helpers/index.test.ts
test/e2e/filesystem-cache/filesystem-cache.test.ts
test/e2e/filesystem-cache/warm-restart-task-stats.test.ts
test/e2e/filesystem-cache/evict-after-snapshot.test.ts` — all 25 tests
passed after installing the sandbox's missing Playwright browser
- Production Turbopack: Nx, non-hoisted SWC helper, build-cache-default,
and warm restart passed (9/9)
- `filesystem-cache.test.ts` production baseline: 15/17 passed; the same
two cache-growth bounds fail under both the unchanged npm fixture and
the pnpm fixture at nearly identical percentages, so they are
pre-existing sandbox-specific failures
- Generated-layout inspection: no package symlinks outside expected
`.bin` command shims; package files are copied; `node_modules/.pnpm` is
metadata-only
<!-- NEXT_JS_LLM -->
<!-- fleet 81cd457d-6956-4cf9-b6f6-9ebf9d95f285 -->
---------
Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com>
Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com>
## Summary
Add `NEXT_DEV_WAIT_FOR_TURBOPACK_SHUTDOWN` to make `next dev` wait for
Turbopack's full project shutdown before exiting. This waits for active
TurboTasks work and cache persistence instead of relying on fixed delays
or the parent's normal 100 ms child-exit timeout.
Use the option in the warm-restart task statistics test. Link documented
`Project` interface methods to their native binding documentation.
## Verification
- `pnpm --filter=next types`
- `npx eslint --config eslint.config.mjs
packages/next/src/cli/next-dev.ts
packages/next/src/server/dev/hot-reloader-turbopack.ts
test/e2e/filesystem-cache/warm-restart-task-stats.test.ts`
- Pre-commit lint-staged checks
- CI tests passed
<!-- NEXT_JS_LLM -->
## Summary
- migrate Cache Components, PPR, prefetching, prerendering,
revalidation, and SSG deployment exclusions to `@force-gate`
- remove the corresponding `skipDeployment` options and `skipped`
control flow while preserving each suite's rationale
This keeps caching-related exclusions together so their deployed
semantics can be reviewed by the same owners.
## Verification
- verified on the combined top-of-stack tree with `pnpm typescript`
- compared ordinary-mode collection before and after: all 4,670 existing
test names matched
- collected all 510 affected files in deploy mode: 4,578 skipped tests,
zero failures
<!-- NEXT_JS_LLM -->
## Summary
Enable `experimental.turbopackFileSystemCacheForBuild` by default in all
environments, including generic CI. Explicitly setting the option to
`false` remains the opt-out. Update the focused coverage and
documentation to match.
## Verification
- `IS_TURBOPACK_TEST=1 pnpm test-start-turbo
test/e2e/filesystem-cache/build-cache-default.test.ts`
- `pnpm prettier --with-node-modules --ignore-path .prettierignore
--check packages/next/src/server/config-shared.ts
test/e2e/filesystem-cache/build-cache-default.test.ts
docs/01-app/03-api-reference/08-turbopack.mdx
docs/01-app/03-api-reference/05-config/01-next-config-js/turbopackFileSystemCache.mdx`
- Not run: `pnpm --filter=next build` (the checkout has 11 pre-existing
TypeScript errors in unrelated files)
<!-- NEXT_JS_LLM -->
### What?
Makes `experimental.turbopackFileSystemCacheForBuild` (Turbopack's
on-disk
filesystem cache for `next build`) enabled by default, matching the
dev-mode
flag. It stays disabled by default in non-Vercel CI environments, and
can
always be turned off explicitly with
`experimental.turbopackFileSystemCacheForBuild: false`.
### Why?
The build filesystem cache greatly speeds up warm builds, but was
previously
opt-in (only auto-enabled for canary builds on the Vercel builder).
Defaulting
it on gives everyone faster subsequent builds, while keeping it off in
non-Vercel CI where the cache is unlikely to persist between builds.
### How?
- `turbopackFileSystemCacheForBuildDefault()` now returns `true` except
when
`isCI && !NOW_BUILDER` (removed the `isStableBuild()` gate).
- The explicit `false` opt-out already works via the existing
`config.experimental?.turbopackFileSystemCacheForBuild || false` read.
- Adds an e2e test (`test/e2e/app-dir/turbopack-fs-cache-build-default`)
asserting `.next/cache/turbopack` is written by default, empty when the
flag
is `false`, and empty in non-Vercel CI.
- Updates the `turbopackFileSystemCache` / Turbopack docs.
<!-- NEXT_JS_LLM -->
---------
Co-authored-by: vercel-fleet[bot] <308483924+vercel-fleet[bot]@users.noreply.github.com>
Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com>
Currently, importing a new module causes a complete eviction (`clear()`)
and re-evaluation of server chunks, both in the Turbopack runtime's
module cache and Node's `require.cache`.
Here's what happens today:
- A new module is imported into a chunk's graph
- This changes its `availability_info`, which in dev for non-entry
chunks is encoded into the chunk's filepath
- `VersionedContentMap` works entirely on chunk paths. When we construct
instructions to transition a chunk into its new state, we fail to find
the prior state and fall back to `clear()` as described above.
An ideal version of this is a refactor that implements
`VersionedContentMap` on a per-module basis, not a per-chunk one. This
PR doesn't do that, but achieves consistent module-level updates when a
chunk's availability info changes.
It does this by implementing chunk lists for server entry chunks the
same way the client hmr implementation does: an entry chunk's version
aggregates the versions of its dependent chunks (including dynamically
imported ones), keyed by merger rather than path. Entry chunks do not
encode availability info in their paths, so they are insulated from
missing versions. Updates are applied in Node.js through the same shared
merged-update machinery in the unified hmr runtime that the client
already uses, plus a new `ChunkListUpdate` branch in the server hmr
client to unwrap the merged updates.
Details:
- The merged-update wire format (`EcmascriptMergedUpdate` etc.) moves
from turbopack-browser into shared `turbopack_ecmascript::chunk_list`,
along with runtime-agnostic `ChunkListVersion` and `update_chunk_list`
implementations.
- `turbopack-nodejs` gains a chunk content merger mirroring the browser
one. `EcmascriptBuildNodeEntryChunk`'s versioned content is now a
chunk-list content over its sync and async chunks. It is version-only
and never emitted; the entry chunk still inlines its loader calls.
- The aggregate server hmr subscription now tracks only entry chunks.
Shared chunks under `server/chunks/` ride the entry's `ChunkListUpdate`
as module deltas, so their content-hash paths no longer matter.
- The Node hmr client applies `ChunkListUpdate` by feeding each nested
merged update through the shared apply path. Modules that appear in an
"added" chunk but already exist in the module cache were moved by a
chunk rename and are treated as modified.
- On a successful partial apply, the hot reloader clears the manifest
cache for updated chunks and notifies browsers to refetch RSC, without
clearing `require.cache`.
### Test Plan
Adds a series of e2e tests to catch the described manual test plan in
both entry and dynamic chunks.
This replaces per-chunk Server HMR turbo tasks with a single firehose
subscription that diffs every HMR chunk. This significantly cuts the
number of tokio task churn on projects with many server chunks and
centralizes the diff/clear logic. It leads to a multi-second saving in
both cold and warm builds in a large app.
This PR also renames `clear()` to `reEvaluateAllModulesExpensive()` to
label directly that this is a costly operation and should only be done
in exceptional circumstances.
A following PR will bring this to client chunks.
Rust:
- New `aggregate_hmr` module: `AggregateHmrVersion` keyed by chunk path,
`merged_partial_update` builder, and `is_hmr_eligible_chunk` (excludes
`.map` files, which would force every diff to `Total`).
- `Project::all_hmr_version_state` / `all_hmr_update` aggregate over the
whole `hmr_root_path`. The seed transition emits an empty `Partial` so
the JS consumer doesn't treat it as a restart and wipe handlers the
triggering request just populated. Any chunk requiring `Total`/`Missing`
escalates the batch.
- `VersionedContentMap::hmr_chunks_in_path` lists eligible chunks with
their `VersionedContent`.
NAPI:
- `projectAllHmrEvents(target)` returns a single subscription.
JS:
- `setupServerHmr` subscribes once via `allHmrEvents` instead of fanning
out over `hmrChunkNamesSubscribe`.
- `reEvaluateAllModulesExpensive()` evicts every chunk under
`server/chunks/` from `require.cache` directly rather than tracking
subscriptions. This is a bit fragile as it relies on the path prefix and
scanning require.cache.
Currently we skip a persist cycle when there are no dirty tasks. This PR
extends that to skip persist cycles when little compilation work has
happened since the last snapshot, avoiding small writes and expensive
compaction for little benefit.
Compilation time is a better proxy for "how much work a snapshot would
save" than a raw dirty-task count. The background snapshot loop
accumulates active (non-idle) wall-clock time and skips a cycle when
less than a threshold has elapsed since the last persisted snapshot.
Default is 1s, overridable via
`TURBO_ENGINE_SNAPSHOT_MIN_ACTIVE_TIME_MILLIS` (set to `0` in tests).
Shutdown and test snapshots bypass the gate.
The active-time bookkeeping is encapsulated in a small `Stopwatch`
helper.
<!-- NEXT_JS_LLM_PR -->
(#93807)
## What
Stop pinning compiled chunk source on `EcmascriptBuildNodeChunkVersion`.
The struct previously held a `Vec<ReadRef<CodeAndIds>>` for every module
in the chunk, even though the HMR update path only needs the bytes for
the *changed* subset and the bytes are already on disk. That field was
also forcing the upstream `CodeAndIds` / `BatchGroupCodeAndIds` tasks to
stay `serialization = "skip"`, so warm restarts had to re-walk every
module and re-hash its source to rebuild `entries_hashes`.
This change mirrors the browser-side pattern: a new
`EcmascriptBuildNodeChunkContentEntries` task lives on the chunk content
and holds `ResolvedVc<Code>` + `ResolvedVc<u64>` per module. The version
struct shrinks to `{ chunk_path, minify_type, entries_hashes }`, drops
`serialization = "skip"`, and switches `chunk_path` from `String` to
`RcStr`.
`update_ecmascript_node_chunk_content` now resolves entries lazily, only
when an added or modified module actually needs its code shipped.
## Why
Two wins, both for dev sessions starting against a warm filesystem
cache:
- **Memory.** The version no longer transitively pins every module's
compiled `Rope` in heap — those bytes can stay on disk until HMR
actually needs them.
- **Warm-restart CPU.** `entries_hashes` is sourced from the per-module
`Code::source_code_hash()` task (already cached) and the version itself
now round-trips through the persistent cache, so we don't re-hash
anything on warm start.
The HMR payload shape is unchanged.
## Perf
This should speed up warm builds a bit but the major benefit is not
recomputing node outputs and keeping them in ram
measuring v0 after loading the main route
Branch: Cold: 12.3G Warm: 7.34G
Canary: Cold 12.3G Warm: 8.5G
The trace file confirms the recomputations are gone and the heap
measurements confirm we trimming ~1.1g of ram
Using the devlow benchmarks i was able to confirm a possible small
progression
```
# canary
chat dev startup build=warm: root page = 23.96 s (from root page/start)
chat dev startup build=warm: root page = 21.21 s (from root page/start)
chat dev startup build=warm: root page = 22.70 s (from root page/start)
# branch
chat dev startup build=warm: root page = 20.94 s (from root page/start)
chat dev startup build=warm: root page = 19.43 s (from root page/start)
chat dev startup build=warm: root page = 23.49 s (from root page/start)
```
## Tests
I added a new integration test to ensure we don't accidentally regress
here. Which confirms that 'clean warm builds' run nothing and 'clean
warm dev sessions' just set up HMR session infra
<!-- NEXT_JS_LLM_PR -->
> **Note:** This is a **proof of concept** implementation. It is not yet
ready for production use.
## Summary
Implements memory eviction for the turbo-tasks engine. After a
persistence snapshot completes, tasks that are safe to remove are
evicted from in-memory storage and transparently restored from disk on
next access.
### Eviction levels
- **Full eviction**: Entire task removed from the in-memory map
(restored from disk on access). Only possible when the task has no
meaningful transient state (and other state is already on disk)
- **DataAndMeta eviction**: Both data and meta categories cleared, but
the task stays in the map to preserve transient state (e.g.
`current_session_clean`, aggregated session-clean counts).
- **DataOnly eviction**: Only data-category fields cleared; meta (graph
structure, output, dirty state) stays in memory.
- **MetaOnly eviction**: Only meta-category fields cleared; data stays
in memory.
Data and meta evictability are computed independently — if one category
is modified but the other is clean, the clean category can still be
dropped.
Eviction is gated behind `BackendOptions::evict_after_snapshot` (off by
default), and can be enabled in Next.js via the
`TURBO_ENGINE_EVICT_AFTER_SNAPSHOT=1` env var for testing.
## Key changes
- **Orthogonal eviction decision tree** (`storage_schema.rs`): Data and
meta evictability are computed independently. Full eviction additionally
requires no meaningful transient state (session-clean flags, aggregated
session-clean counts). Replaces the previous sequential bail-out
approach which was too aggressive on full eviction (losing transient
session state on leaf tasks) and not aggressive enough on partial
eviction (blocking all eviction when only one category was modified).
- **`drop_partial()` codegen** (`task_storage_macro.rs`): New generated
methods to drop data
- **`restore_from_*()` codegen changes** (`task_storage_macro.rs`): New
semantics for merging persistent data from the backend with transient
data stored in memory.
- **`task_cache` moved into `Storage`** (`storage.rs`): The
`CachedTaskType → TaskId` deduplication map was previously a separate
field on `TurboTasksBackendInner`. It is now owned by `Storage` so
eviction can remove entries when a task is fully evicted. Because
`task_cache` is a pure performance cache (entries are re-populated by
`task_by_type()` on miss once the task type is persisted to backing
storage), evicting entries is safe. After bulk eviction the map is
shrunk when it is less than half full.
- **Parallel shard eviction** (`storage.rs`): Eviction iterates all
storage shards in parallel after snapshot, applying the appropriate
eviction level per task. Each shard is shrunk after bulk eviction to
reclaim slack capacity.
- In principle this is O(N) work to scan, but because each pass drops
>98% of tasks there isn't wasted work and the logic is fast, taking
<100ms for even the largest applications.
## Design notes
- **SessionDependent tasks**: SessionDependent tasks can still be
evicted but if `current_session_clean` is set we prevent full eviction
to avoid rechecking. Within a session the file-watchers are responsible
for invalidations after setting `current_session_clean`.
## Known limitations (proof of concept)
- No LRU or access-frequency tracking — all eligible tasks are evicted
on every snapshot cycle
- No memory pressure feedback — eviction runs on a timer, not in
response to actual memory pressure
- Only runs after snapshotting which tends to be a high point in memory
- Future work will explore interleaving this logic with snapshotting to
trim the peak
<!-- NEXT_JS_LLM_PR -->
## Summary
Reworks how the turbo-tasks backend tracks modified tasks for persistence snapshots, reducing overhead and simplifying the snapshot lifecycle.
**Key changes:**
- **Replace `modified` DashMap with per-shard atomic counters + inline flags.** Instead of maintaining a separate `FxDashMap<TaskId, ModifiedState>` that mirrors every modification, track modifications via flags already on `TaskStorage` and use per-shard `AtomicU64` counters to skip unmodified shards during snapshot iteration. This eliminates a major source of memory overhead.
- The downside here is needing to scan the map. per shard counters enable early exits but we will still need to scan entire shards. For a large site that means scanning thousands of
- **Merge task cache writes into the snapshot pipeline.** New tasks now carry a `new_task` flag and their type hash in `SnapshotItem`, so task cache entries are written in the same batch as task data/meta — removing the separate `persisted_task_cache_log` (`Sharded<ChunkedVec<...>>`) and its associated locking.
- **Remove `local_is_partial` optimization.** The backing storage layer already short-circuits on empty databases, and new tasks eagerly set `restored` flags at allocation time, making this redundant.
- **Simplify `end_snapshot`.** Instead of a multi-pass retain/iterate/update cycle over the `modified` map, `end_snapshot` now just drains the small `snapshots` map (only tasks concurrently accessed during snapshot mode) and promotes their `modified_during_snapshot` flags.
- **Delete unused utilities.** Removes `Sharded`, `ChunkedVec` (from backing_storage), and `swap_retain` import now that they're no longer needed.
**Other cleanups:**
- `initialize_new_task` sets restored + new_task flags at allocation time for both persistent and transient tasks
- Fuzz test updated to use `active_tracking: true` and `StorageMode::ReadWrite`
- New KV storage test for batch write+flush+reopen pattern
- Minor fix: `SmallVec::into_boxed_slice()` instead of `into_vec().into_boxed_slice()`
## Build Benchmark Results
Measured over 9 runs (1 warm-up discarded), macOS, `TURBOPACK_PERSISTENT_CACHE=1`.
### Cold build (`rm -rf .next/`)
| | Time (avg) | Time (stddev) | MaxRSS (avg) |
|---|---|---|---|
| HEAD | 75.48s | 0.72s | 24,231 MiB |
| This PR | 75.20s | 1.98s | 23,829 MiB |
| **Delta** | −0.28s | — | **−402 MiB (−1.7%)** |
### Warm build (single file edit)
| | Time (avg) | MaxRSS (avg) |
|---|---|---|
| HEAD | 23.79s | 8,764 MiB |
| This PR | 23.67s | 8,766 MiB |
| **Delta** | −0.12s | flat |
Cold build time difference is within noise (< 1 stddev). The meaningful improvement is a **~400 MiB reduction in peak memory on cold builds**, consistent with the fixed overhead this PR targets. Warm builds are unaffected as expected.
### What?
Two fixes for the Turbopack build tracing introduced in #90397:
1. **Don't block SSG on Turbopack shutdown**: `workerMain()` no longer awaits the shutdown promise before returning. Trace event collection is deferred to `waitForShutdown()`, which the parent process awaits *after* SSG completes. This allows static generation and Turbopack persistence/cache-flush to run in parallel.
2. **Add persistence spans to `trace-build` allowlist**: `turbopack-build-events`, `turbopack-persistence`, and `turbopack-compaction` are now included in the `to-json-build.ts` allowlist so they appear in `.next/trace-build`.
### Why?
- The `await shutdownPromise` in `workerMain()` was too eager — it prevented the caller from acknowledging the build as complete and starting SSG until Turbopack persistence finished flushing to disk.
- The persistence/compaction spans emitted by Rust (`turbopack-persistence`, `turbopack-compaction`) were not in the `to-json-build.ts` allowlist, so they were silently filtered out of `.next/trace-build`.
### How?
**`impl.ts` (worker)**:
- Removed `await shutdownPromise` from `workerMain()` — it now returns build results immediately
- `waitForShutdown()` now returns `{ debugTraceEvents }` after awaiting shutdown, so trace events are collected only after all compilation events (including persistence spans) have been processed
**`index.ts` (parent)**:
- Moved `recordTraceEvents(debugTraceEvents)` from the `workerMain` result handler into the `shutdownPromise` `.then()` chain, so events are replayed into the parent reporter after shutdown completes
**`to-json-build.ts`**:
- Added `turbopack-build-events`, `turbopack-persistence`, `turbopack-compaction` to the allowlist
**Test updates**:
- Enabled `turbopackFileSystemCacheForBuild: true` in the trace-build test fixture
- Updated the Turbopack inline snapshot to include `turbopack-build-events`
## Summary
- Add a generic `TraceEvent` compilation event type that carries a name, wall-clock timing, and arbitrary attributes
- Emit `TraceEvent` from Turbopack's Rust backend for cache persistence and compaction operations
- Expose `eventJson` on compilation events through the NAPI bridge
- Record `turbopack-persistence` and `turbopack-compaction` trace spans in `.next/trace` with memory usage snapshots, in both dev and build workflows
## Details
Turbopack persistence and compaction operations were invisible in `.next/trace`. The only signal was a console log for operations exceeding 10s.
A new generic `TraceEvent` type replaces the need for per-operation event structs. It carries a name, start/end wall-clock timestamps, and a `Vec` of key-value attributes. On the JS side, `backgroundLogCompilationEvents` handles all `TraceEvent`s with a single code path — creating trace spans via `manualTraceChild` and recording memory usage snapshots. Adding new traced operations requires only a few lines of Rust and zero JS changes.
At @bgw's suggestion, I considered integrating as a `tracing_subscriber::Layer` to automatically forward spans to the compilation event system, but this ended up requiring quite a bit of code (a new subscriber, 'target' impl, span lifecycle management, new crate dependencies) for what is currently just 2 events, and we don't really anticipate adding more.
## Test plan
- [x] Integration test in `test/e2e/filesystem-cache` verifying `turbopack-persistence` spans exist with correct attributes
- [x] `pnpm test-dev-turbo test/e2e/filesystem-cache`
- [x] `pnpm test-start-turbo test/e2e/filesystem-cache`
Reverts vercel/next.js#90096
* always `track_mutation()` before the mutation
* only `track_mutation()` if we are actually going to mutate
The previous attempt was reverted because we would call `track_modification` too often which lead to a test failure in the filesystem-cache-test because the DB grew too much.
## Summary
- Pass `asset_suffix_path` as `Vc<Option<RcStr>>` instead of eagerly resolving it to `Option<RcStr>` in the chunking context option structs
- Add filesystem cache size growth assertions to e2e tests to detect unbounded cache growth regressions
## Why
Previously, `css_url_suffix` was eagerly awaited in `project.rs` before being passed into `ClientChunkingContextOptions`, `ServerChunkingContextOptions`, and `EdgeChunkingContextOptions`. This caused a new chunking context `Vc` to be created for every build, duplicating the entire build in cache and recompiling it.
By keeping it as a `Vc`, the chunking context identity is stable across builds, preventing unnecessary cache duplication.
## How
- Changed `css_url_suffix` field from `Option<RcStr>` to `Vc<Option<RcStr>>` in all three chunking context option structs
- Removed `.owned().await?.clone()` in `project.rs` (3 call sites), passing the `Vc` directly
- Added `.owned().await?` at the point of use in the 5 context builder functions
- Added cache size measurements to `filesystem-cache.test.ts`: normal changes limited to 10% growth, renames allow up to 50% due to dead cache entries
Enable the filesystem cache in canary builds in preparation for enabling it by default in the next release.
It can still be disabled by explicitly setting the option to `false`.
Flipping it for all users is deferred to #85975 so as not to interfere with potential patch releases.
Also fix the filesystem caching test that was accidentally disabled in #84632 due to the file rename.