Skip to content

Commit 56cb0b6

Browse files
committed
Improve reliability of owner stacks for async I/O errors
When running the following test isolated, and not as part of the full test suite, the owner stacks for async I/O errors were missing the top-most stack frame that's pointing at the `fetch` call. ``` pnpm test-dev test/e2e/app-dir/dynamic-io-errors/dynamic-io-errors.test.ts -t "should show a collapsed redbox with two errors" ``` The same could be reproduced with `pnpm next dev test/e2e/app-dir/dynamic-io-errors/fixtures/default` at http://localhost:3000/dynamic-root, unless http://localhost:3000/static was visited first. The likely reason for that is that React's async I/O tracking was recently optimized for performance reasons in facebook/react#33736 and facebook/react#33737. We can help React a bit with the tracking by explicitly awaiting our `makeHangingPromise` calls. With this fix, the case from above reliably works in isolation.
1 parent 90c71b4 commit 56cb0b6

File tree

3 files changed

+17
-15
lines changed

3 files changed

+17
-15
lines changed

packages/next/src/server/lib/patch-fetch.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -559,7 +559,7 @@ export function createPatchedFetcher(
559559
cacheSignal = null
560560
}
561561

562-
return makeHangingPromise<Response>(
562+
return await makeHangingPromise<Response>(
563563
workUnitStore.renderSignal,
564564
'fetch()'
565565
)
@@ -669,7 +669,7 @@ export function createPatchedFetcher(
669669
cacheSignal.endRead()
670670
cacheSignal = null
671671
}
672-
return makeHangingPromise<Response>(
672+
return await makeHangingPromise<Response>(
673673
workUnitStore.renderSignal,
674674
'fetch()'
675675
)
@@ -1010,7 +1010,7 @@ export function createPatchedFetcher(
10101010
cacheSignal.endRead()
10111011
cacheSignal = null
10121012
}
1013-
return makeHangingPromise<Response>(
1013+
return await makeHangingPromise<Response>(
10141014
workUnitStore.renderSignal,
10151015
'fetch()'
10161016
)
@@ -1044,7 +1044,7 @@ export function createPatchedFetcher(
10441044
switch (workUnitStore.type) {
10451045
case 'prerender':
10461046
case 'prerender-client':
1047-
return makeHangingPromise<Response>(
1047+
return await makeHangingPromise<Response>(
10481048
workUnitStore.renderSignal,
10491049
'fetch()'
10501050
)

packages/next/src/server/use-cache/use-cache-wrapper.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -821,7 +821,7 @@ export function cache(
821821
)
822822

823823
if (dynamicAccessAbortController.signal.aborted) {
824-
return makeHangingPromise(
824+
return await makeHangingPromise(
825825
workUnitStore.renderSignal,
826826
dynamicAccessAbortController.signal.reason.message
827827
)
@@ -886,7 +886,7 @@ export function cache(
886886
if (cacheSignal) {
887887
cacheSignal.endRead()
888888
}
889-
return makeHangingPromise(
889+
return await makeHangingPromise(
890890
workUnitStore.renderSignal,
891891
'dynamic "use cache"'
892892
)
@@ -935,7 +935,7 @@ export function cache(
935935
// transformed with an async function, before being passed into
936936
// the "use cache" function, which escapes the instrumentation.
937937
if (workUnitStore.allowEmptyStaticShell) {
938-
return makeHangingPromise(
938+
return await makeHangingPromise(
939939
workUnitStore.renderSignal,
940940
'dynamic "use cache"'
941941
)
@@ -1029,7 +1029,7 @@ export function cache(
10291029
if (cacheSignal) {
10301030
cacheSignal.endRead()
10311031
}
1032-
return makeHangingPromise(
1032+
return await makeHangingPromise(
10331033
workUnitStore.renderSignal,
10341034
'dynamic "use cache"'
10351035
)
@@ -1086,7 +1086,7 @@ export function cache(
10861086
)
10871087

10881088
if (result.type === 'prerender-dynamic') {
1089-
return result.hangingPromise
1089+
return await result.hangingPromise
10901090
}
10911091

10921092
const { stream: newStream, pendingCacheEntry } = result

test/e2e/app-dir/dynamic-io-errors/dynamic-io-errors.test.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -554,10 +554,11 @@ describe('Dynamic IO Errors', () => {
554554
"description": "Route "/dynamic-root": A component accessed data, headers, params, searchParams, or a short-lived cache without a Suspense boundary nor a "use cache" above it. See more info: https://nextjs.org/docs/messages/next-prerender-missing-suspense",
555555
"environmentLabel": "Server",
556556
"label": "Console Error",
557-
"source": "app/dynamic-root/page.tsx (45:56) @ FetchingComponent
558-
> 45 | {cached ? await fetchRandomCached(nonce) : await fetchRandom(nonce)}
559-
| ^",
557+
"source": "app/dynamic-root/page.tsx (59:26) @ fetchRandom
558+
> 59 | const response = await fetch(
559+
| ^",
560560
"stack": [
561+
"fetchRandom app/dynamic-root/page.tsx (59:26)",
561562
"FetchingComponent app/dynamic-root/page.tsx (45:56)",
562563
"Page app/dynamic-root/page.tsx (22:9)",
563564
"LogSafely <anonymous>",
@@ -567,10 +568,11 @@ describe('Dynamic IO Errors', () => {
567568
"description": "Route "/dynamic-root": A component accessed data, headers, params, searchParams, or a short-lived cache without a Suspense boundary nor a "use cache" above it. See more info: https://nextjs.org/docs/messages/next-prerender-missing-suspense",
568569
"environmentLabel": "Server",
569570
"label": "Console Error",
570-
"source": "app/dynamic-root/page.tsx (45:56) @ FetchingComponent
571-
> 45 | {cached ? await fetchRandomCached(nonce) : await fetchRandom(nonce)}
572-
| ^",
571+
"source": "app/dynamic-root/page.tsx (59:26) @ fetchRandom
572+
> 59 | const response = await fetch(
573+
| ^",
573574
"stack": [
575+
"fetchRandom app/dynamic-root/page.tsx (59:26)",
574576
"FetchingComponent app/dynamic-root/page.tsx (45:56)",
575577
"Page app/dynamic-root/page.tsx (27:7)",
576578
"LogSafely <anonymous>",

0 commit comments

Comments
 (0)