Skip to content

Commit e0668e4

Browse files
authored
Run fast immediates during prerender abort to fix flaky I/O stack traces (#89969)
Follow-up for #89834. In `prerenderAndAbortInSequentialTasks`, React's `finishHalt` (scheduled via `setImmediate` from `abort()`) could race with the component's pending `setTimeout` callback. When both ended up in the same timer phase, the component timer would fire after `abort()` but before `finishHalt`, linking the async graph and producing the more precise `await` location (21:9). When `finishHalt` won the race, it read an unlinked graph and fell back to the function declaration (20:16). Adding `DANGEROUSLY_runPendingImmediatesAfterCurrentTask()` to the abort task captures `finishHalt` as a fast immediate, ensuring it runs right after `abort()` before any other timers. This makes the result deterministic at the cost of always producing the less precise stack frame (function declaration instead of `await` expression). The resolve step is split into a separate task because it needs to wait for the abort's fast immediates to complete. When using `--debug-prerender`, we're also re-adding the hint to run `next dev` for even better stack traces, since dev mode can produce the precise `await` location by running the render to completion and resolving the I/O promises. [Flakiness metric](https://app.datadoghq.com/ci/test/runs?query=test_level%3Atest%20%40git.repository.id%3A%22github.com%2Fvercel%2Fnext.js%22%20%40test.type%3A%22nextjs%22%20%40test.status%3A%22fail%22%20%40test.suite%3A%22Cache%20Components%20Errors%22%20%40git.branch%3Acanary%20-%40ci.pipeline.name%3Atest-e2e-deploy-release&agg_m=count&agg_m_source=base&agg_t=count&currentTab=overview&eventStack=&fromUser=false&index=citest&start=1770404554855&end=1771009354855&paused=false)
1 parent 69d63ed commit e0668e4

File tree

4 files changed

+104
-11
lines changed

4 files changed

+104
-11
lines changed

packages/next/src/server/app-render/app-render-prerender-utils.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,15 @@ export function prerenderAndAbortInSequentialTasks<R>(
3333
})
3434
scheduleTimeout(() => {
3535
try {
36-
expectNoPendingImmediates()
36+
DANGEROUSLY_runPendingImmediatesAfterCurrentTask()
3737
abort()
38+
} catch (err) {
39+
reject(err)
40+
}
41+
})
42+
scheduleTimeout(() => {
43+
try {
44+
expectNoPendingImmediates()
3845
resolve(pendingResult)
3946
} catch (err) {
4047
reject(err)

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -984,6 +984,10 @@ export function logDisallowedDynamicError(
984984
console.error(`To get a more detailed stack trace and pinpoint the issue, try one of the following:
985985
- Start the app in development mode by running \`next dev\`, then open "${workStore.route}" in your browser to investigate the error.
986986
- Rerun the production build with \`next build --debug-prerender\` to generate better stack traces.`)
987+
} else if (!process.env.__NEXT_DEV_SERVER) {
988+
console.error(
989+
`To debug the issue, start the app in development mode by running \`next dev\`, then open "${workStore.route}" in your browser to investigate the error.`
990+
)
987991
}
988992
}
989993

0 commit comments

Comments
 (0)