Skip to content

Commit b5eba8f

Browse files
authored
[Cache Components] Schedule work on timeouts (#84344)
Previously we used the immediate queue to schedule the consecutive tasks that would prerender and abort and page prerender. This works fine but since React does not consider immediates as IO for async work tracking it means we can't use it for scheduling more advanced cases like static->runtime->dynamic where the IO that unblocks in runtime phase can be picked up. While React could change to include immediates as IO the argument here is that it isn't really IO since it is immediate work while timeouts generally have to schedule something to fire later with timeout zero being a sort of special case where later === now. So instead we are going to move to scheduling our consecutive tasks using timeout as well that way we can align the Next.js and React heuristic for identifying IO and make certain kinds of debugging easier to implement.
1 parent 52a26cd commit b5eba8f

File tree

5 files changed

+112
-122
lines changed

5 files changed

+112
-122
lines changed

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

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { InvariantError } from '../../shared/lib/invariant-error'
22

33
/**
44
* This is a utility function to make scheduling sequential tasks that run back to back easier.
5-
* We schedule on the same queue (setImmediate) at the same time to ensure no other events can sneak in between.
5+
* We schedule on the same queue (setTimeout) at the same time to ensure no other events can sneak in between.
66
*/
77
export function prerenderAndAbortInSequentialTasks<R>(
88
prerender: () => Promise<R>,
@@ -15,18 +15,18 @@ export function prerenderAndAbortInSequentialTasks<R>(
1515
} else {
1616
return new Promise((resolve, reject) => {
1717
let pendingResult: Promise<R>
18-
setImmediate(() => {
18+
setTimeout(() => {
1919
try {
2020
pendingResult = prerender()
2121
pendingResult.catch(() => {})
2222
} catch (err) {
2323
reject(err)
2424
}
25-
})
26-
setImmediate(() => {
25+
}, 0)
26+
setTimeout(() => {
2727
abort()
2828
resolve(pendingResult)
29-
})
29+
}, 0)
3030
})
3131
}
3232
}
@@ -47,21 +47,21 @@ export function prerenderAndAbortInSequentialTasksWithStages<R>(
4747
} else {
4848
return new Promise((resolve, reject) => {
4949
let pendingResult: Promise<R>
50-
setImmediate(() => {
50+
setTimeout(() => {
5151
try {
5252
pendingResult = prerender()
5353
pendingResult.catch(() => {})
5454
} catch (err) {
5555
reject(err)
5656
}
57-
})
58-
setImmediate(() => {
57+
}, 0)
58+
setTimeout(() => {
5959
advanceStage()
60-
})
61-
setImmediate(() => {
60+
}, 0)
61+
setTimeout(() => {
6262
abort()
6363
resolve(pendingResult)
64-
})
64+
}, 0)
6565
})
6666
}
6767
}

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { InvariantError } from '../../shared/lib/invariant-error'
22

33
/**
44
* This is a utility function to make scheduling sequential tasks that run back to back easier.
5-
* We schedule on the same queue (setImmediate) at the same time to ensure no other events can sneak in between.
5+
* We schedule on the same queue (setTimeout) at the same time to ensure no other events can sneak in between.
66
*/
77
export function scheduleInSequentialTasks<R>(
88
render: () => R | Promise<R>,
@@ -15,17 +15,17 @@ export function scheduleInSequentialTasks<R>(
1515
} else {
1616
return new Promise((resolve, reject) => {
1717
let pendingResult: R | Promise<R>
18-
setImmediate(() => {
18+
setTimeout(() => {
1919
try {
2020
pendingResult = render()
2121
} catch (err) {
2222
reject(err)
2323
}
24-
})
25-
setImmediate(() => {
24+
}, 0)
25+
setTimeout(() => {
2626
followup()
2727
resolve(pendingResult)
28-
})
28+
}, 0)
2929
})
3030
}
3131
}

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

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ import {
2727
type ServerComponentsHmrCache,
2828
type SetIncrementalFetchCacheContext,
2929
} from '../response-cache'
30-
import { waitAtLeastOneReactRenderTask } from '../../lib/scheduler'
3130
import { cloneResponse } from './clone-response'
3231
import type { IncrementalCache } from './incremental-cache'
3332

@@ -911,7 +910,7 @@ export function createPatchedFetcher(
911910
// make sure we still exclude them from prerenders if
912911
// cacheComponents is on so we introduce an artificial task boundary
913912
// here.
914-
await waitAtLeastOneReactRenderTask()
913+
await getTimeoutBoundary()
915914
break
916915
case 'prerender-ppr':
917916
case 'prerender-legacy':
@@ -1191,3 +1190,16 @@ export function patchFetch(options: PatchableModule) {
11911190
// Set the global fetch to the patched fetch.
11921191
globalThis.fetch = createPatchedFetcher(original, options)
11931192
}
1193+
1194+
let currentTimeoutBoundary: null | Promise<void> = null
1195+
function getTimeoutBoundary() {
1196+
if (!currentTimeoutBoundary) {
1197+
currentTimeoutBoundary = new Promise((r) => {
1198+
setTimeout(() => {
1199+
currentTimeoutBoundary = null
1200+
r()
1201+
}, 0)
1202+
})
1203+
}
1204+
return currentTimeoutBoundary
1205+
}

0 commit comments

Comments
 (0)