Skip to content

Commit 9bda7b2

Browse files
authored
Suspended high pri work forces lower priority work to expire early (facebook#12965)
* onFatal, onComplete, onSuspend, onYield For every call to renderRoot, one of onFatal, onComplete, onSuspend, and onYield is called upon exiting. We use these in lieu of returning a tuple. I've also chosen not to inline them into renderRoot because these will eventually be lifted into the renderer. * Suspended high pri work forces lower priority work to expire early If an error is thrown, and there is lower priority pending work, we retry at the lower priority. The lower priority work should expire at the same time at which the high priority work would have expired. Effectively, this increases the priority of the low priority work. Simple example: If an error is thrown during a synchronous render, and there's an async update, the async update should flush synchronously in case it's able to fix the error. I've added a unit test for this scenario. User provided timeouts should have the same behavior, but I'll leave that for a future PR.
1 parent 2e75779 commit 9bda7b2

File tree

6 files changed

+179
-121
lines changed

6 files changed

+179
-121
lines changed

packages/react-reconciler/src/ReactFiberExpirationTime.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99

1010
import MAX_SIGNED_31_BIT_INT from './maxSigned31BitInt';
1111

12-
// TODO: Use an opaque type once ESLint et al support the syntax
1312
export type ExpirationTime = number;
1413

1514
export const NoWork = 0;

packages/react-reconciler/src/ReactFiberPendingPriority.js

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ export function markPendingPriorityLevel(
3535
}
3636
}
3737
}
38+
findNextPendingPriorityLevel(root);
3839
}
3940

4041
export function markCommittedPriorityLevels(
@@ -49,6 +50,7 @@ export function markCommittedPriorityLevels(
4950
root.earliestSuspendedTime = NoWork;
5051
root.latestSuspendedTime = NoWork;
5152
root.latestPingedTime = NoWork;
53+
findNextPendingPriorityLevel(root);
5254
return;
5355
}
5456

@@ -77,6 +79,7 @@ export function markCommittedPriorityLevels(
7779
// There's no suspended work. Treat the earliest remaining level as a
7880
// pending level.
7981
markPendingPriorityLevel(root, earliestRemainingTime);
82+
findNextPendingPriorityLevel(root);
8083
return;
8184
}
8285

@@ -91,18 +94,21 @@ export function markCommittedPriorityLevels(
9194
// There's no suspended work. Treat the earliest remaining level as a
9295
// pending level.
9396
markPendingPriorityLevel(root, earliestRemainingTime);
97+
findNextPendingPriorityLevel(root);
9498
return;
9599
}
96100

97101
if (earliestRemainingTime < earliestSuspendedTime) {
98102
// The earliest remaining time is earlier than all the suspended work.
99103
// Treat it as a pending update.
100104
markPendingPriorityLevel(root, earliestRemainingTime);
105+
findNextPendingPriorityLevel(root);
101106
return;
102107
}
103108

104109
// The earliest remaining time falls within the range of known suspended
105110
// levels. We should treat this as suspended work.
111+
findNextPendingPriorityLevel(root);
106112
}
107113

108114
export function markSuspendedPriorityLevel(
@@ -148,6 +154,7 @@ export function markSuspendedPriorityLevel(
148154
root.latestSuspendedTime = suspendedTime;
149155
}
150156
}
157+
findNextPendingPriorityLevel(root);
151158
}
152159

153160
export function markPingedPriorityLevel(
@@ -161,22 +168,29 @@ export function markPingedPriorityLevel(
161168
root.latestPingedTime = pingedTime;
162169
}
163170
}
171+
findNextPendingPriorityLevel(root);
164172
}
165173

166-
export function findNextPendingPriorityLevel(root: FiberRoot): ExpirationTime {
174+
function findNextPendingPriorityLevel(root) {
167175
const earliestSuspendedTime = root.earliestSuspendedTime;
168176
const earliestPendingTime = root.earliestPendingTime;
177+
let nextExpirationTimeToWorkOn;
178+
let expirationTime;
169179
if (earliestSuspendedTime === NoWork) {
170180
// Fast path. There's no suspended work.
171-
return earliestPendingTime;
172-
}
173-
174-
// First, check if there's known pending work.
175-
if (earliestPendingTime !== NoWork) {
176-
return earliestPendingTime;
181+
nextExpirationTimeToWorkOn = expirationTime = earliestPendingTime;
182+
} else if (earliestPendingTime !== NoWork) {
183+
// Check if there's known pending work.
184+
nextExpirationTimeToWorkOn = earliestPendingTime;
185+
expirationTime =
186+
earliestSuspendedTime < earliestPendingTime
187+
? earliestSuspendedTime
188+
: earliestPendingTime;
189+
} else {
190+
// Finally, if a suspended level was pinged, work on that. Otherwise there's
191+
// nothing to work on.
192+
nextExpirationTimeToWorkOn = expirationTime = root.latestPingedTime;
177193
}
178-
179-
// Finally, if a suspended level was pinged, work on that. Otherwise there's
180-
// nothing to work on.
181-
return root.latestPingedTime;
194+
root.nextExpirationTimeToWorkOn = nextExpirationTimeToWorkOn;
195+
root.expirationTime = expirationTime;
182196
}

packages/react-reconciler/src/ReactFiberRoot.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,8 @@ export type FiberRoot = {
5757
+hydrate: boolean,
5858
// Remaining expiration time on this root.
5959
// TODO: Lift this into the renderer
60-
remainingExpirationTime: ExpirationTime,
60+
nextExpirationTimeToWorkOn: ExpirationTime,
61+
expirationTime: ExpirationTime,
6162
// List of top-level batches. This list indicates whether a commit should be
6263
// deferred. Also contains completion callbacks.
6364
// TODO: Lift this into the renderer
@@ -90,7 +91,8 @@ export function createFiberRoot(
9091
context: null,
9192
pendingContext: null,
9293
hydrate,
93-
remainingExpirationTime: NoWork,
94+
nextExpirationTimeToWorkOn: NoWork,
95+
expirationTime: NoWork,
9496
firstBatch: null,
9597
nextScheduledRoot: null,
9698
};

0 commit comments

Comments
 (0)