Skip to content

Hold cacheSignal across unstable_cache foreground revalidation#93617

Merged
unstubbable merged 1 commit into
canaryfrom
hl/fix-unstable-cache-revalidation
May 7, 2026
Merged

Hold cacheSignal across unstable_cache foreground revalidation#93617
unstubbable merged 1 commit into
canaryfrom
hl/fix-unstable-cache-revalidation

Conversation

@unstubbable

@unstubbable unstubbable commented May 7, 2026

Copy link
Copy Markdown
Contributor

When unstable_cache finds a stale entry during the prospective phase of a prerender, it dispatches a foreground revalidation and returns the in-flight promise. The wrapper's cacheSignal.beginRead / endRead pair was scoped only to the synchronous lookup.endRead in the outer finally fires the moment the function returns the promise, before the recompute and cacheNewResult have completed. The prospective prerender's cacheSignal then resolves cacheReady while the recompute is still in flight, ending the prospective phase early. Any 'use cache' invocation rendered after the unstable_cache await in the same component never gets reached, so its RDC entry is never populated, and the final prerender phase throws "Unexpected cache miss after cache warming phase during prerendering."

Returning await pendingRevalidates[invocationKey] instead of returning the promise directly keeps the wrapper function suspended until the recompute and cacheNewResult actually complete. The outer finally then runs endRead at the right time, the prospective phase keeps waiting, and downstream 'use cache' invocations are reached and populate the RDC as expected.

Adds a regression test under the use-cache suite that exercises an unstable_cache await followed by a downstream 'use cache' component. Without the fix, the test fails consistently under __NEXT_CACHE_COMPONENTS=true with the cache-miss-after-warming error.

When `unstable_cache` finds a stale entry during the prospective phase
of a prerender, it dispatches a foreground revalidation and returns the
in-flight promise. The wrapper's `cacheSignal.beginRead` / `endRead`
pair was scoped only to the synchronous lookup — `endRead` in the outer
`finally` fires the moment the function returns the promise, before the
recompute and `cacheNewResult` have run. The prospective prerender's
`cacheSignal` then resolves `cacheReady` while the recompute is still in
flight, ending the prospective phase early. Any `'use cache'` invocation
rendered after the `unstable_cache` await in the same component never
gets reached, so its RDC entry is never populated, and the final
prerender phase throws "Unexpected cache miss after cache warming phase
during prerendering."

Returning `await pendingRevalidates[invocationKey]` instead of returning
the promise directly keeps the wrapper function suspended until the
recompute and `cacheNewResult` actually complete. The outer `finally`
then runs `endRead` at the right time, the prospective phase keeps
waiting, and downstream `'use cache'` invocations are reached and
populate the RDC as expected.

Adds a regression test under the `use-cache` suite that exercises an
`unstable_cache` await followed by a downstream `'use cache'` component;
without the fix, the test fails consistently under
`__NEXT_CACHE_COMPONENTS=true` with the cache-miss-after-warming error.

Copy link
Copy Markdown
Contributor Author

@github-actions

github-actions Bot commented May 7, 2026

Copy link
Copy Markdown
Contributor

Tests Passed

Commit: f6ed95d

@unstubbable unstubbable marked this pull request as ready for review May 7, 2026 21:41
@unstubbable unstubbable requested a review from gnoff May 7, 2026 21:43

unstubbable commented May 7, 2026

Copy link
Copy Markdown
Contributor Author

Merge activity

  • May 7, 10:42 PM UTC: A user started a stack merge that includes this pull request via Graphite.
  • May 7, 10:42 PM UTC: @unstubbable merged this pull request with Graphite.

@unstubbable unstubbable merged commit 1669ede into canary May 7, 2026
185 of 186 checks passed
@unstubbable unstubbable deleted the hl/fix-unstable-cache-revalidation branch May 7, 2026 22:42
unstubbable added a commit that referenced this pull request May 18, 2026
…93617)

When `unstable_cache` finds a stale entry during the prospective phase of a prerender, it dispatches a foreground revalidation and returns the in-flight promise. The wrapper's `cacheSignal.beginRead` / `endRead` pair was scoped only to the synchronous lookup.`endRead` in the outer `finally` fires the moment the function returns the promise, before the recompute and `cacheNewResult` have completed. The prospective prerender's `cacheSignal` then resolves `cacheReady` while the recompute is still in flight, ending the prospective phase early. Any `'use cache'` invocation rendered after the `unstable_cache` await in the same component never gets reached, so its RDC entry is never populated, and the final prerender phase throws "Unexpected cache miss after cache warming phase during prerendering."

Returning `await pendingRevalidates[invocationKey]` instead of returning the promise directly keeps the wrapper function suspended until the recompute and `cacheNewResult` actually complete. The outer `finally` then runs `endRead` at the right time, the prospective phase keeps waiting, and downstream `'use cache'` invocations are reached and populate the RDC as expected.

Adds a regression test under the `use-cache` suite that exercises an `unstable_cache` await followed by a downstream `'use cache'` component. Without the fix, the test fails consistently under `__NEXT_CACHE_COMPONENTS=true` with the cache-miss-after-warming error.
unstubbable added a commit that referenced this pull request May 18, 2026
…93617)

When `unstable_cache` finds a stale entry during the prospective phase of a prerender, it dispatches a foreground revalidation and returns the in-flight promise. The wrapper's `cacheSignal.beginRead` / `endRead` pair was scoped only to the synchronous lookup.`endRead` in the outer `finally` fires the moment the function returns the promise, before the recompute and `cacheNewResult` have completed. The prospective prerender's `cacheSignal` then resolves `cacheReady` while the recompute is still in flight, ending the prospective phase early. Any `'use cache'` invocation rendered after the `unstable_cache` await in the same component never gets reached, so its RDC entry is never populated, and the final prerender phase throws "Unexpected cache miss after cache warming phase during prerendering."

Returning `await pendingRevalidates[invocationKey]` instead of returning the promise directly keeps the wrapper function suspended until the recompute and `cacheNewResult` actually complete. The outer `finally` then runs `endRead` at the right time, the prospective phase keeps waiting, and downstream `'use cache'` invocations are reached and populate the RDC as expected.

Adds a regression test under the `use-cache` suite that exercises an `unstable_cache` await followed by a downstream `'use cache'` component. Without the fix, the test fails consistently under `__NEXT_CACHE_COMPONENTS=true` with the cache-miss-after-warming error.
unstubbable added a commit that referenced this pull request May 19, 2026
…93918)

Backports:

- #93617
- #93601

---------

Co-authored-by: Swarnava Sengupta <swarnava.sengupta@vercel.com>
Co-authored-by: Or Nakash <ornakash@gmail.com>
@github-actions github-actions Bot locked as resolved and limited conversation to collaborators May 22, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants