Skip to content

Commit df76b85

Browse files
unstubbablegnoff
authored andcommitted
Use runtime stage end time to ignore I/O debug info from dynamic stage
This ensures that any I/O awaited during the dynamic stage (i.e. after other uncached I/O resolved) does not contaminate the owner stacks used for error reporting.
1 parent 5418883 commit df76b85

File tree

6 files changed

+53
-7
lines changed

6 files changed

+53
-7
lines changed

packages/next/src/client/app-index.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,6 @@ if (clientResumeFetch) {
214214
callServer,
215215
findSourceMapURL,
216216
debugChannel,
217-
// @ts-expect-error This is not yet part of the React types
218217
startTime: 0,
219218
}
220219
)

packages/next/src/server/app-render/app-render.tsx

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -828,6 +828,7 @@ async function generateDynamicFlightRenderResultWithStagesInDev(
828828
dynamicChunks,
829829
staticInterruptReason,
830830
runtimeInterruptReason,
831+
runtimeStageEndTime,
831832
debugChannel: returnedDebugChannel,
832833
requestStore: finalRequestStore,
833834
} = await renderWithRestartOnCacheMissInDev(
@@ -854,6 +855,7 @@ async function generateDynamicFlightRenderResultWithStagesInDev(
854855
dynamicChunks,
855856
staticInterruptReason,
856857
runtimeInterruptReason,
858+
runtimeStageEndTime,
857859
ctx,
858860
clientReferenceManifest,
859861
finalRequestStore,
@@ -1547,6 +1549,7 @@ function assertClientReferenceManifest(
15471549
function App<T>({
15481550
reactServerStream,
15491551
reactDebugStream,
1552+
debugEndTime,
15501553
preinitScripts,
15511554
clientReferenceManifest,
15521555
ServerInsertedHTMLProvider,
@@ -1556,6 +1559,7 @@ function App<T>({
15561559
/* eslint-disable @next/internal/no-ambiguous-jsx -- React Client */
15571560
reactServerStream: Readable | BinaryStreamOf<T>
15581561
reactDebugStream: Readable | ReadableStream<Uint8Array> | undefined
1562+
debugEndTime: number | undefined
15591563
preinitScripts: () => void
15601564
clientReferenceManifest: NonNullable<RenderOpts['clientReferenceManifest']>
15611565
ServerInsertedHTMLProvider: ComponentType<{
@@ -1569,6 +1573,7 @@ function App<T>({
15691573
getFlightStream<InitialRSCPayload>(
15701574
reactServerStream,
15711575
reactDebugStream,
1576+
debugEndTime,
15721577
clientReferenceManifest,
15731578
nonce
15741579
)
@@ -1614,15 +1619,13 @@ function App<T>({
16141619
// consistent for now.
16151620
function ErrorApp<T>({
16161621
reactServerStream,
1617-
reactDebugStream,
16181622
preinitScripts,
16191623
clientReferenceManifest,
16201624
ServerInsertedHTMLProvider,
16211625
nonce,
16221626
images,
16231627
}: {
16241628
reactServerStream: BinaryStreamOf<T>
1625-
reactDebugStream: ReadableStream<Uint8Array> | undefined
16261629
preinitScripts: () => void
16271630
clientReferenceManifest: NonNullable<RenderOpts['clientReferenceManifest']>
16281631
ServerInsertedHTMLProvider: ComponentType<{
@@ -1636,7 +1639,8 @@ function ErrorApp<T>({
16361639
const response = ReactClient.use(
16371640
getFlightStream<InitialRSCPayload>(
16381641
reactServerStream,
1639-
reactDebugStream,
1642+
undefined,
1643+
undefined,
16401644
clientReferenceManifest,
16411645
nonce
16421646
)
@@ -2578,6 +2582,7 @@ async function renderToStream(
25782582
dynamicChunks,
25792583
staticInterruptReason,
25802584
runtimeInterruptReason,
2585+
runtimeStageEndTime,
25812586
debugChannel: returnedDebugChannel,
25822587
requestStore: finalRequestStore,
25832588
} = await renderWithRestartOnCacheMissInDev(
@@ -2604,6 +2609,7 @@ async function renderToStream(
26042609
dynamicChunks,
26052610
staticInterruptReason,
26062611
runtimeInterruptReason,
2612+
runtimeStageEndTime,
26072613
ctx,
26082614
clientReferenceManifest,
26092615
finalRequestStore,
@@ -2724,6 +2730,7 @@ async function renderToStream(
27242730
<App
27252731
reactServerStream={reactServerResult.tee()}
27262732
reactDebugStream={reactDebugStream}
2733+
debugEndTime={undefined}
27272734
preinitScripts={preinitScripts}
27282735
clientReferenceManifest={clientReferenceManifest}
27292736
ServerInsertedHTMLProvider={ServerInsertedHTMLProvider}
@@ -2770,6 +2777,7 @@ async function renderToStream(
27702777
<App
27712778
reactServerStream={reactServerResult.tee()}
27722779
reactDebugStream={reactDebugStream}
2780+
debugEndTime={undefined}
27732781
preinitScripts={preinitScripts}
27742782
clientReferenceManifest={clientReferenceManifest}
27752783
ServerInsertedHTMLProvider={ServerInsertedHTMLProvider}
@@ -2930,7 +2938,6 @@ async function renderToStream(
29302938
element: (
29312939
<ErrorApp
29322940
reactServerStream={errorServerStream}
2933-
reactDebugStream={undefined}
29342941
ServerInsertedHTMLProvider={ServerInsertedHTMLProvider}
29352942
preinitScripts={errorPreinitScripts}
29362943
clientReferenceManifest={clientReferenceManifest}
@@ -3195,6 +3202,7 @@ async function renderWithRestartOnCacheMissInDev(
31953202
staticInterruptReason: initialStageController.getStaticInterruptReason(),
31963203
runtimeInterruptReason:
31973204
initialStageController.getRuntimeInterruptReason(),
3205+
runtimeStageEndTime: initialStageController.getRuntimeStageEndTime(),
31983206
debugChannel,
31993207
requestStore,
32003208
}
@@ -3311,6 +3319,7 @@ async function renderWithRestartOnCacheMissInDev(
33113319
dynamicChunks,
33123320
staticInterruptReason: finalStageController.getStaticInterruptReason(),
33133321
runtimeInterruptReason: finalStageController.getRuntimeInterruptReason(),
3322+
runtimeStageEndTime: initialStageController.getRuntimeStageEndTime(),
33143323
debugChannel,
33153324
requestStore,
33163325
}
@@ -3469,6 +3478,7 @@ async function spawnStaticShellValidationInDev(
34693478
dynamicServerChunks: Array<Uint8Array>,
34703479
staticInterruptReason: Error | null,
34713480
runtimeInterruptReason: Error | null,
3481+
runtimeStageEndTime: number,
34723482
ctx: AppRenderContext,
34733483
clientReferenceManifest: NonNullable<RenderOpts['clientReferenceManifest']>,
34743484
requestStore: RequestStore,
@@ -3477,7 +3487,7 @@ async function spawnStaticShellValidationInDev(
34773487
): Promise<void> {
34783488
// TODO replace this with a delay on the entire dev render once the result is propagated
34793489
// via the websocket and not the main render itself
3480-
await new Promise((r) => setTimeout(r, 300))
3490+
await new Promise((r) => setTimeout(r, 2000))
34813491
const {
34823492
componentMod: ComponentMod,
34833493
getDynamicParamFromSegment,
@@ -3547,10 +3557,17 @@ async function spawnStaticShellValidationInDev(
35473557
debugChannelClient.on('data', (c) => debugChunks.push(c))
35483558
}
35493559

3560+
// For both runtime and static validation we use the same end time which is
3561+
// when the runtime stage ended. This ensures that any I/O that is awaited
3562+
// (start of the await) in the dynamic stage won't be considered by React when
3563+
// constructing the owner stacks that we use for the validation errors.
3564+
const debugEndTime = runtimeStageEndTime
3565+
35503566
const runtimeResult = await validateStagedShell(
35513567
runtimeServerChunks,
35523568
dynamicServerChunks,
35533569
debugChunks,
3570+
debugEndTime,
35543571
rootParams,
35553572
fallbackRouteParams,
35563573
allowEmptyStaticShell,
@@ -3573,6 +3590,7 @@ async function spawnStaticShellValidationInDev(
35733590
staticServerChunks,
35743591
dynamicServerChunks,
35753592
debugChunks,
3593+
debugEndTime,
35763594
rootParams,
35773595
fallbackRouteParams,
35783596
allowEmptyStaticShell,
@@ -3647,6 +3665,7 @@ async function warmupModuleCacheForRuntimeValidationInDev(
36473665
<App
36483666
reactServerStream={runtimeServerStream}
36493667
reactDebugStream={undefined}
3668+
debugEndTime={undefined}
36503669
preinitScripts={preinitScripts}
36513670
clientReferenceManifest={clientReferenceManifest}
36523671
ServerInsertedHTMLProvider={ServerInsertedHTMLProvider}
@@ -3723,6 +3742,7 @@ async function validateStagedShell(
37233742
stageChunks: Array<Uint8Array>,
37243743
allServerChunks: Array<Uint8Array>,
37253744
debugChunks: null | Array<Uint8Array>,
3745+
debugEndTime: number | undefined,
37263746
rootParams: Params,
37273747
fallbackRouteParams: OpaqueFallbackRouteParams | null,
37283748
allowEmptyStaticShell: boolean,
@@ -3796,6 +3816,7 @@ async function validateStagedShell(
37963816
<App
37973817
reactServerStream={serverStream}
37983818
reactDebugStream={debugChannelClient}
3819+
debugEndTime={debugEndTime}
37993820
preinitScripts={preinitScripts}
38003821
clientReferenceManifest={clientReferenceManifest}
38013822
ServerInsertedHTMLProvider={ServerInsertedHTMLProvider}
@@ -4295,6 +4316,7 @@ async function prerenderToStream(
42954316
<App
42964317
reactServerStream={initialServerResult.asUnclosingStream()}
42974318
reactDebugStream={undefined}
4319+
debugEndTime={undefined}
42984320
preinitScripts={preinitScripts}
42994321
clientReferenceManifest={clientReferenceManifest}
43004322
ServerInsertedHTMLProvider={ServerInsertedHTMLProvider}
@@ -4528,6 +4550,7 @@ async function prerenderToStream(
45284550
<App
45294551
reactServerStream={reactServerResult.asUnclosingStream()}
45304552
reactDebugStream={undefined}
4553+
debugEndTime={undefined}
45314554
preinitScripts={preinitScripts}
45324555
clientReferenceManifest={clientReferenceManifest}
45334556
ServerInsertedHTMLProvider={ServerInsertedHTMLProvider}
@@ -4687,6 +4710,7 @@ async function prerenderToStream(
46874710
<App
46884711
reactServerStream={foreverStream}
46894712
reactDebugStream={undefined}
4713+
debugEndTime={undefined}
46904714
preinitScripts={() => {}}
46914715
clientReferenceManifest={clientReferenceManifest}
46924716
ServerInsertedHTMLProvider={ServerInsertedHTMLProvider}
@@ -4844,6 +4868,7 @@ async function prerenderToStream(
48444868
<App
48454869
reactServerStream={reactServerResult.asUnclosingStream()}
48464870
reactDebugStream={undefined}
4871+
debugEndTime={undefined}
48474872
preinitScripts={preinitScripts}
48484873
clientReferenceManifest={clientReferenceManifest}
48494874
ServerInsertedHTMLProvider={ServerInsertedHTMLProvider}
@@ -4987,6 +5012,7 @@ async function prerenderToStream(
49875012
<App
49885013
reactServerStream={foreverStream}
49895014
reactDebugStream={undefined}
5015+
debugEndTime={undefined}
49905016
preinitScripts={() => {}}
49915017
clientReferenceManifest={clientReferenceManifest}
49925018
ServerInsertedHTMLProvider={ServerInsertedHTMLProvider}
@@ -5073,6 +5099,7 @@ async function prerenderToStream(
50735099
<App
50745100
reactServerStream={reactServerResult.asUnclosingStream()}
50755101
reactDebugStream={undefined}
5102+
debugEndTime={undefined}
50765103
preinitScripts={preinitScripts}
50775104
clientReferenceManifest={clientReferenceManifest}
50785105
ServerInsertedHTMLProvider={ServerInsertedHTMLProvider}
@@ -5248,7 +5275,6 @@ async function prerenderToStream(
52485275
// eslint-disable-next-line @next/internal/no-ambiguous-jsx
52495276
<ErrorApp
52505277
reactServerStream={errorServerStream}
5251-
reactDebugStream={undefined}
52525278
ServerInsertedHTMLProvider={ServerInsertedHTMLProvider}
52535279
preinitScripts={errorPreinitScripts}
52545280
clientReferenceManifest={clientReferenceManifest}

packages/next/src/server/app-render/staged-rendering.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export class StagedRenderingController {
1515
naturalStage: RenderStage = RenderStage.Static
1616
staticInterruptReason: Error | null = null
1717
runtimeInterruptReason: Error | null = null
18+
runtimeStageEndTime: number = Infinity
1819
/** Whether sync IO should interrupt the render */
1920
enableSyncInterrupt = true
2021

@@ -129,6 +130,10 @@ export class StagedRenderingController {
129130
return this.runtimeInterruptReason
130131
}
131132

133+
getRuntimeStageEndTime() {
134+
return this.runtimeStageEndTime
135+
}
136+
132137
abandonRender() {
133138
if (!this.mayAbandon) {
134139
throw new InvariantError(
@@ -188,6 +193,7 @@ export class StagedRenderingController {
188193
this.runtimeStagePromise.resolve()
189194
}
190195
if (currentStage < RenderStage.Dynamic && stage >= RenderStage.Dynamic) {
196+
this.runtimeStageEndTime = performance.now() + performance.timeOrigin
191197
const dynamicListeners = this.dynamicStageListeners
192198
for (let i = 0; i < dynamicListeners.length; i++) {
193199
dynamicListeners[i]()

packages/next/src/server/app-render/use-flight-response.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ const findSourceMapURL =
3333
export function getFlightStream<T>(
3434
flightStream: Readable | BinaryStreamOf<T>,
3535
debugStream: Readable | ReadableStream<Uint8Array> | undefined,
36+
debugEndTime: number | undefined,
3637
clientReferenceManifest: DeepReadonly<ClientReferenceManifest>,
3738
nonce: string | undefined
3839
): Promise<T> {
@@ -65,6 +66,7 @@ export function getFlightStream<T>(
6566
},
6667
nonce,
6768
debugChannel: debugStream ? { readable: debugStream } : undefined,
69+
endTime: debugEndTime,
6870
})
6971
} else {
7072
// The types of flightStream and debugStream should match.
@@ -90,6 +92,7 @@ export function getFlightStream<T>(
9092
findSourceMapURL,
9193
nonce,
9294
debugChannel: debugStream,
95+
endTime: debugEndTime,
9396
}
9497
)
9598
}

packages/next/types/$$compiled.internal.d.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,8 @@ declare module 'react-server-dom-webpack/client.browser' {
115115
replayConsoleLogs?: boolean
116116
temporaryReferences?: TemporaryReferenceSet
117117
debugChannel?: { readable?: ReadableStream; writable?: WritableStream }
118+
startTime?: number
119+
endTime?: number
118120
}
119121

120122
export function createFromFetch<T>(
@@ -318,6 +320,8 @@ declare module 'react-server-dom-webpack/client.edge' {
318320
replayConsoleLogs?: boolean
319321
environmentName?: string
320322
debugChannel?: { readable?: ReadableStream }
323+
startTime?: number
324+
endTime?: number
321325
}
322326

323327
export type EncodeFormActionCallback = <A>(

test/e2e/app-dir/cache-components-errors/fixtures/default/app/dynamic-root/page.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Suspense } from 'react'
22

33
import { IndirectionOne, IndirectionTwo } from './indirection'
4+
import { cookies } from 'next/headers'
45

56
export default async function Page() {
67
return (
@@ -56,8 +57,15 @@ const fetchRandomCached = async (entropy: string) => {
5657
}
5758

5859
const fetchRandom = async (entropy: string) => {
60+
// Hide uncached I/O behind a runtime API call, to ensure we still get the
61+
// correct owner stack for the error.
62+
await cookies()
5963
const response = await fetch(
6064
'https://next-data-api-endpoint.vercel.app/api/random?b=' + entropy
6165
)
66+
// The error should point at the fetch above, and not at the following fetch.
67+
await fetch(
68+
'https://next-data-api-endpoint.vercel.app/api/random?b=' + entropy + 'x'
69+
)
6270
return response.text()
6371
}

0 commit comments

Comments
 (0)