Skip to content

Commit 02e326b

Browse files
committed
fast immediates: initial implementation
1 parent c964caa commit 02e326b

22 files changed

Lines changed: 2702 additions & 13 deletions

File tree

packages/next/errors.json

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -951,5 +951,18 @@
951951
"950": "The manifests singleton was not initialized.",
952952
"951": "The client reference manifest for route \"%s\" does not exist.",
953953
"952": "Cannot access \"%s\" without a work store.",
954-
"953": "This is a proxied client reference manifest. The property \"%s\" is not handled."
954+
"953": "This is a proxied client reference manifest. The property \"%s\" is not handled.",
955+
"954": "Cannot start capturing immediates again without finishing the previous task (state: %s)",
956+
"955": "An unexpected error occurred while executing immediates",
957+
"956": "performWork can only be called while waiting (state: %s)",
958+
"957": "Cannot stop capturing immediates before execution is finished (state: %s)",
959+
"958": "Expected all captured immediates to have been executed (state: %s)",
960+
"959": "scheduleWorkAfterTicksAndMicrotasks can only be called while waiting (state: %s)",
961+
"960": "DANGEROUSLY_runPendingImmediatesAfterCurrentTask cannot be called in the edge runtime",
962+
"961": "The \"callback\" argument must be of type function. Received %s",
963+
"962": "expectNoPendingImmediates cannot be called in the edge runtime",
964+
"963": "Fast setImmediate is not available in the edge runtime.",
965+
"964": "An unexpected error occurred while starting to capture immediates",
966+
"965": "Expected setImmediate to reject invalid arguments",
967+
"966": "Expected process.nextTick to reject invalid arguments"
955968
}

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

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import { InvariantError } from '../../shared/lib/invariant-error'
22
import { createAtomicTimerGroup } from './app-render-scheduling'
3+
import {
4+
DANGEROUSLY_runPendingImmediatesAfterCurrentTask,
5+
expectNoPendingImmediates,
6+
} from '../node-environment-extensions/fast-set-immediate.external'
37

48
/**
59
* This is a utility function to make scheduling sequential tasks that run back to back easier.
@@ -20,16 +24,21 @@ export function prerenderAndAbortInSequentialTasks<R>(
2024
let pendingResult: Promise<R>
2125
scheduleTimeout(() => {
2226
try {
27+
DANGEROUSLY_runPendingImmediatesAfterCurrentTask()
2328
pendingResult = prerender()
2429
pendingResult.catch(() => {})
2530
} catch (err) {
2631
reject(err)
2732
}
2833
})
29-
3034
scheduleTimeout(() => {
31-
abort()
32-
resolve(pendingResult)
35+
try {
36+
expectNoPendingImmediates()
37+
abort()
38+
resolve(pendingResult)
39+
} catch (err) {
40+
reject(err)
41+
}
3342
})
3443
})
3544
}
@@ -55,20 +64,29 @@ export function prerenderAndAbortInSequentialTasksWithStages<R>(
5564
let pendingResult: Promise<R>
5665
scheduleTimeout(() => {
5766
try {
67+
DANGEROUSLY_runPendingImmediatesAfterCurrentTask()
5868
pendingResult = prerender()
5969
pendingResult.catch(() => {})
6070
} catch (err) {
6171
reject(err)
6272
}
6373
})
64-
6574
scheduleTimeout(() => {
66-
advanceStage()
75+
try {
76+
DANGEROUSLY_runPendingImmediatesAfterCurrentTask()
77+
advanceStage()
78+
} catch (err) {
79+
reject(err)
80+
}
6781
})
68-
6982
scheduleTimeout(() => {
70-
abort()
71-
resolve(pendingResult)
83+
try {
84+
expectNoPendingImmediates()
85+
abort()
86+
resolve(pendingResult)
87+
} catch (err) {
88+
reject(err)
89+
}
7290
})
7391
})
7492
}

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

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import { InvariantError } from '../../shared/lib/invariant-error'
22
import { createAtomicTimerGroup } from './app-render-scheduling'
3+
import {
4+
DANGEROUSLY_runPendingImmediatesAfterCurrentTask,
5+
expectNoPendingImmediates,
6+
} from '../node-environment-extensions/fast-set-immediate.external'
37

48
/**
59
* This is a utility function to make scheduling sequential tasks that run back to back easier.
@@ -20,15 +24,21 @@ export function scheduleInSequentialTasks<R>(
2024
let pendingResult: R | Promise<R>
2125
scheduleTimeout(() => {
2226
try {
27+
DANGEROUSLY_runPendingImmediatesAfterCurrentTask()
2328
pendingResult = render()
2429
} catch (err) {
2530
reject(err)
2631
}
2732
})
2833

2934
scheduleTimeout(() => {
30-
followup()
31-
resolve(pendingResult)
35+
try {
36+
expectNoPendingImmediates()
37+
followup()
38+
resolve(pendingResult)
39+
} catch (err) {
40+
reject(err)
41+
}
3242
})
3343
})
3444
}
@@ -55,6 +65,7 @@ export function pipelineInSequentialTasks<A, B, C>(
5565
let oneResult: A
5666
scheduleTimeout(() => {
5767
try {
68+
DANGEROUSLY_runPendingImmediatesAfterCurrentTask()
5869
oneResult = one()
5970
} catch (err) {
6071
clearTimeout(twoId)
@@ -69,6 +80,7 @@ export function pipelineInSequentialTasks<A, B, C>(
6980
// if `one` threw, then this timeout would've been cleared,
7081
// so if we got here, we're guaranteed to have a value.
7182
try {
83+
DANGEROUSLY_runPendingImmediatesAfterCurrentTask()
7284
twoResult = two(oneResult!)
7385
} catch (err) {
7486
clearTimeout(threeId)
@@ -82,6 +94,7 @@ export function pipelineInSequentialTasks<A, B, C>(
8294
// if `two` threw, then this timeout would've been cleared,
8395
// so if we got here, we're guaranteed to have a value.
8496
try {
97+
expectNoPendingImmediates()
8598
threeResult = three(twoResult!)
8699
} catch (err) {
87100
clearTimeout(fourId)

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { InvariantError } from '../../shared/lib/invariant-error'
2+
import { unpatchedSetImmediate } from '../node-environment-extensions/fast-set-immediate.external'
23

34
/*
45
==========================
@@ -112,12 +113,15 @@ export function createAtomicTimerGroup(delayMs = 0) {
112113
let didFirstTimerRun = false
113114

114115
// As a sanity check, we schedule an immediate from the first timeout
115-
// to check if the execution was interrupted.
116+
// to check if the execution was interrupted (i.e. if it ran between the timeouts).
117+
// Note that we're deliberately bypassing the "fast setImmediate" patch here --
118+
// otherwise, this check would always fail, because the immediate
119+
// would always run before the second timeout.
116120
let didImmediateRun = false
117121
function runFirstCallback(callback: () => void) {
118122
didFirstTimerRun = true
119123
if (shouldAttemptPatching) {
120-
setImmediate(() => {
124+
unpatchedSetImmediate(() => {
121125
didImmediateRun = true
122126
})
123127
}

0 commit comments

Comments
 (0)