Block prefetch task until sufficient response is received#96017
Conversation
Failing test suitesCommit: c53c7fa | About building and testing Next.js
Expand output● Cache Components Errors - Client Hook Abort Reasons › Build With --debug-prerender › focused client hook abort reasons › normal aborting › should capture useSearchParams after the data slot ● Cache Components Errors - Client Hook Abort Reasons › Build With --debug-prerender › focused client hook abort reasons › normal aborting › should capture usePathname before the data slot ● Cache Components Errors - Client Hook Abort Reasons › Build With --debug-prerender › focused client hook abort reasons › normal aborting › should capture useParams after the data slot ● Cache Components Errors - Client Hook Abort Reasons › Build With --debug-prerender › focused client hook abort reasons › normal aborting › should capture useSelectedLayoutSegments before the data slot ● Cache Components Errors - Client Hook Abort Reasons › Build With --debug-prerender › focused client hook abort reasons › normal aborting › should capture useSelectedLayoutSegment after the data slot ● Cache Components Errors - Client Hook Abort Reasons › Build With --debug-prerender › focused client hook abort reasons › sync IO aborting › should capture useParams before sync IO and data ... truncated ... |
Stats from current PRWarning No stats were collected for Webpack because its stats job did not complete (it failed, was cancelled, or timed out). The results below only cover the bundlers that finished. 🔴 1 regression
📊 All Metrics📖 Metrics GlossaryDev Server Metrics:
Build Metrics:
Change Thresholds:
⚡ Dev Server
⚡ Production Builds
📦 Production Builds (Webpack) (Legacy)📦 Production Builds (Webpack)
📦 Bundle SizesBundle Sizes⚡ TurbopackClient Main Bundles
Server Middleware
Build DetailsBuild Manifests
🔄 Shared (bundler-independent)Runtimes
📝 Changed Files (18 files)Files with changes:
View diffsapp-page-exp..ntime.dev.jsfailed to diffapp-page-exp..time.prod.jsfailed to diffapp-page-tur..ntime.dev.jsfailed to diffapp-page-tur..time.prod.jsfailed to diffapp-page-tur..ntime.dev.jsfailed to diffapp-page-tur..time.prod.jsfailed to diffapp-page.runtime.dev.jsfailed to diffapp-page.runtime.prod.jsfailed to diffapp-route-ex..ntime.dev.jsDiff too large to display app-route-ex..time.prod.jsDiff too large to display app-route-tu..ntime.dev.jsDiff too large to display app-route-tu..time.prod.jsDiff too large to display app-route-tu..ntime.dev.jsDiff too large to display app-route-tu..time.prod.jsDiff too large to display app-route.runtime.dev.jsDiff too large to display app-route.ru..time.prod.jsDiff too large to display pages-api.ru..time.prod.jsDiff too large to display pages.runtime.prod.jsDiff too large to display 📎 Tarball URLCommit: c53c7fa |
This is a refactor of the prefetching algorithm to block the prefetch task from completing until the response is received. Currently, a prefetch task is fire-and-forget: once a prefetch's requests are spawned, the scheduler marks the originating task as complete regardless of what the server ends up returning. However, certain planned optimizations require us to verify the response from the server to determine whether additional work is necessary. For example, we may attempt to do a static prefetch instead of a runtime prefetch if we optimistically assume that the static prefetch does not vary on runtime data. We need to verify that assumption is correct before marking the task as complete. Now, whenever a pass over the task spawns a segment request, or encounters an entry whose response hasn't arrived yet, the task registers itself on the entry and exits as Blocked instead of Done. When the entry resolves, the blocked task is pinged and the pass re-runs against the received data. Only a pass that observes every response it cares about advances the task to the next phase or completes it. Rejected entries are handled per segment: a rejection counts as an observed response, so the pass skips that segment and keeps prefetching the rest. The task never registers on a rejected entry, since nothing ever pings one. Retries are governed by the existing per-entry logic. Also fixes a cache bug this exposed: when a revalidation response is keyed at a more generic vary path than the stale partial entry that prompted it (because the segment turned out not to vary on some param), the stale entry shadowed the result on every lookup — the upgraded data was unreachable and the revalidation was wasted. With blocking, that dead end became an infinite loop: each fulfillment re-pinged the blocked task, which re-read the same stale entry and spawned another revalidation. Upserting (or re-keying) a segment now deletes settled entries at more specific vary paths that the incoming entry supersedes. The router-act test helper's "at least one request initiated" watchdog is adjusted to account for the new blocking behavior: countable requests can now be legitimately gated on an in-flight App Shell response (which the watchdog intentionally doesn't count). On expiry it now proceeds to response processing if the act scope intercepted a shell request, and keeps waiting while shell requests are in flight elsewhere on the page, instead of timing out.
5935469 to
81202cf
Compare
f1ad2b9 to
c53c7fa
Compare
This is a refactor of the prefetching algorithm to block the prefetch task from completing until the response is received.
Currently, a prefetch task is fire-and-forget: once a prefetch's requests are spawned, the scheduler marks the originating task as complete regardless of what the server ends up returning. However, certain planned optimizations require us to verify the response from the server to determine whether additional work is necessary. For example, we may attempt to do a static prefetch instead of a runtime prefetch if we optimistically assume that the static prefetch does not vary on runtime data. We need to verify that assumption is correct before marking the task as complete.
Now, whenever a pass over the task spawns a segment request, or encounters an entry whose response hasn't arrived yet, the task registers itself on the entry and exits as Blocked instead of Done. When the entry resolves, the blocked task is pinged and the pass re-runs against the received data. Only a pass that observes every response it cares about advances the task to the next phase or completes it.
Rejected entries are handled per segment: a rejection counts as an observed response, so the pass skips that segment and keeps prefetching the rest. The task never registers on a rejected entry, since nothing ever pings one. Retries are governed by the existing per-entry logic.
Also fixes a cache bug this exposed: when a revalidation response is keyed at a more generic vary path than the stale partial entry that prompted it (because the segment turned out not to vary on some param), the stale entry shadowed the result on every lookup — the upgraded data was unreachable and the revalidation was wasted. With blocking, that dead end became an infinite loop: each fulfillment re-pinged the blocked task, which re-read the same stale entry and spawned another revalidation. Upserting (or re-keying) a segment now deletes settled entries at more specific vary paths that the incoming entry supersedes.
The router-act test helper's "at least one request initiated" watchdog is adjusted to account for the new blocking behavior: countable requests can now be legitimately gated on an in-flight App Shell response (which the watchdog intentionally doesn't count). On expiry it now proceeds to response processing if the act scope intercepted a shell request, and keeps waiting while shell requests are in flight elsewhere on the page, instead of timing out.