next.js
3a4477cb - Stamp FileSystemCache revalidations and entries with the monotonic clock (#100357)

Commit
3 days ago
Stamp FileSystemCache revalidations and entries with the monotonic clock (#100357) ### What? `FileSystemCache` dates tag revalidations and cache entries with the wall clock, but since #94457 `areTagsExpired` compares them against the monotonic clock (`performance.timeOrigin + performance.now()`). When the wall clock runs ahead, a revalidation is dated in the future, so it's ignored and the old page keeps being served. ### Why? I ran into this with `next start`, when our E2E tests kept getting stale pages after `revalidateTag`. In a fresh 16.4.2 app with the wall clock 5ms ahead, 30 out of 30 reads right after a revalidation returned the old page. With this change, it's 0 of 30. The two clocks drift apart in ordinary setups, for example when NTP, a VM or WSL2 corrects the wall clock while the server runs. ### How? Use the monotonic clock everywhere that `FileSystemCache` dates something, as `incremental-cache/index.ts` already does: - `revalidateTag` and `set()` stamp with `performance.timeOrigin + performance.now()` instead of `Date.now()`. - `set()` also stores that timestamp on disk, as `routeCacheLastModified` in the `.meta` file and as `lastModified` in the fetch cache entry. Reads used to take the file's mtime(wall-clock time). Files without a stored time, from `next build` or an older Next.js version, still fall back to the mtime. They were written before the server started, so their mtime is earlier than any revalidation it records. This matters when entries are read from disk: with `cacheMaxMemorySize: 0`, or after an eviction from the memory cache. With only the in-memory stamps fixed, a page written just before a revalidation comes back with an mtime that's ahead, so it isn't even marked stale. Running `next start` with the memory cache off and the wall clock stepped 1s ahead, 20 of 40 revalidations left the old page in place that way. With both changes, it's 0 of 40. Notes: - `routeCacheLastModified` was only written when a build seed was promoted. Runtime writes set it now, so the "preserves seed age" test checks for it instead of its absence. - Fetch cache files are kept across deploys, and they gain a `lastModified` field. - `test/production/app-dir/file-system-cache-clock` offsets `performance.timeOrigin` at startup and checks the first request after `revalidateTag` for an ISR page, an ISR route handler and a dynamic page, with the default memory cache and `cacheMaxMemorySize: 0`. It also restarts on a fetch cache file without `lastModified`. It fails on canary, and with only the in-memory fix the `cacheMaxMemorySize: 0` cases fail. - Ran the new test with `test-start-webpack` and `test-start-turbo`, and four existing revalidation and ISR suites with `test-start-webpack`. Not run in dev.
Author
Parents
Loading