Detect `'use cache'` module-scope deadlocks early in dev (#93500)
When a `'use cache'` fill stalls in dev today, the user has to wait the
full `useCacheTimeout` — 54 seconds by default — before any error
surfaces, and the resulting `UseCacheTimeoutError` is too generic to
point at the cause. A common cause is module-scoped state that ends up
joining a promise from the outer render scope — for example a top-level
`Map<string, Promise>` used to dedupe fetches, where the cache body and
the outer scope both await the same promise. That promise hangs because
Next.js intentionally converts uncached fetches into hanging promises
during prerendering (and in dev while filling caches in the static or
runtime stage), so the cache function ends up waiting forever on an
outer-scope fetch that will never resolve. Most users reload long before
the timeout appears, so they never see any signal that something
specific is wrong.
This change adds a dev-only probe that surfaces this class of deadlock
earlier. Once a cache fill has been idle for ten seconds, the dev server
re-runs the same cache function in a worker thread with a fresh module
scope. If it completes there, the hang in the main process is
attributable to outer-scope state, and we abort the fill with
`UseCacheDeadlockError` whose message points the user at the dedupe
pattern and how to fix it. If the probe also hangs or fails for any
other reason — decode failure, missing module, the body throwing — we
fall back to the regular cache-fill timeout, so the probe is a positive
signal only and never false-positives a deadlock.
The probe is gated on `__NEXT_DEV_SERVER` and tree-shakes out of the
production runtime entirely. The worker pool is lazy, with no process
forked until a probe actually fires, reused across probes for the same
dev session, and torn down on HMR or worker crash. A snapshot of the
outer request store is forwarded to the worker so that cache functions —
including private caches that read `cookies()`, `headers()`, or
`draftMode()` — see the same values they would in a real invocation.