Skip to content

Commit 54371a9

Browse files
committed
Remove recursive calls to renderRoot.
There are a few leftover cases where `renderRoot` is called recursively. All of them are related to synchronously flushing work before its expiration time. We can remove these calls by tracking the last expired level on the root, similar to what we do for other types of pending work, like pings.
1 parent 628d185 commit 54371a9

File tree

2 files changed

+50
-18
lines changed

2 files changed

+50
-18
lines changed

packages/react-reconciler/src/ReactFiberRoot.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ type BaseFiberRootProperties = {|
8484
// The latest time at which a suspended component pinged the root to
8585
// render again
8686
lastPingedTime: ExpirationTime,
87+
lastExpiredTime: ExpirationTime,
8788
|};
8889

8990
// The following attributes are only used by interaction tracing builds.
@@ -132,6 +133,7 @@ function FiberRootNode(containerInfo, tag, hydrate) {
132133
this.lastSuspendedTime = NoWork;
133134
this.nextKnownPendingLevel = NoWork;
134135
this.lastPingedTime = NoWork;
136+
this.lastExpiredTime = NoWork;
135137

136138
if (enableSchedulerTracing) {
137139
this.interactionThreadID = unstable_getThreadID();
@@ -192,6 +194,10 @@ export function markRootSuspendedAtTime(
192194
if (expirationTime <= root.lastPingedTime) {
193195
root.lastPingedTime = NoWork;
194196
}
197+
198+
if (expirationTime <= root.lastExpiredTime) {
199+
root.lastExpiredTime = NoWork;
200+
}
195201
}
196202

197203
export function markRootUpdatedAtTime(
@@ -247,4 +253,19 @@ export function markRootFinishedAtTime(
247253
// Clear the pinged time
248254
root.lastPingedTime = NoWork;
249255
}
256+
257+
if (finishedExpirationTime <= root.lastExpiredTime) {
258+
// Clear the expired time
259+
root.lastExpiredTime = NoWork;
260+
}
261+
}
262+
263+
export function markRootExpiredAtTime(
264+
root: FiberRoot,
265+
expirationTime: ExpirationTime,
266+
): void {
267+
const lastExpiredTime = root.lastExpiredTime;
268+
if (lastExpiredTime === NoWork || lastExpiredTime > expirationTime) {
269+
root.lastExpiredTime = expirationTime;
270+
}
250271
}

packages/react-reconciler/src/ReactFiberWorkLoop.js

Lines changed: 29 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ import {
6868
markRootSuspendedAtTime,
6969
markRootFinishedAtTime,
7070
markRootUpdatedAtTime,
71+
markRootExpiredAtTime,
7172
} from './ReactFiberRoot';
7273
import {
7374
NoMode,
@@ -521,10 +522,14 @@ function getNextRootExpirationTimeToWorkOn(root: FiberRoot): ExpirationTime {
521522
// Determines the next expiration time that the root should render, taking
522523
// into account levels that may be suspended, or levels that may have
523524
// received a ping.
524-
//
525+
526+
const lastExpiredTime = root.lastExpiredTime;
527+
if (lastExpiredTime !== NoWork) {
528+
return lastExpiredTime;
529+
}
530+
525531
// "Pending" refers to any update that hasn't committed yet, including if it
526532
// suspended. The "suspended" range is therefore a subset.
527-
528533
const firstPendingTime = root.firstPendingTime;
529534
if (!isRootSuspendedAtTime(root, firstPendingTime)) {
530535
// The highest priority pending time is not suspended. Let's work on that.
@@ -547,6 +552,15 @@ function getNextRootExpirationTimeToWorkOn(root: FiberRoot): ExpirationTime {
547552
// the next level that the root has work on. This function is called on every
548553
// update, and right before exiting a task.
549554
function ensureRootIsScheduled(root: FiberRoot) {
555+
const lastExpiredTime = root.lastExpiredTime;
556+
if (lastExpiredTime !== NoWork) {
557+
// Special case: Expired work should flush synchronously.
558+
scheduleSyncCallback(
559+
performSyncWorkOnRoot.bind(null, root, lastExpiredTime),
560+
);
561+
return;
562+
}
563+
550564
const expirationTime = getNextRootExpirationTimeToWorkOn(root);
551565
const existingCallbackNode = root.callbackNode;
552566
if (expirationTime === NoWork) {
@@ -621,20 +635,16 @@ function performConcurrentWorkOnRoot(root, didTimeout) {
621635
// event time. The next update will compute a new event time.
622636
currentEventTime = NoWork;
623637

638+
if (didTimeout) {
639+
// An async update expired.
640+
const currentTime = requestCurrentTime();
641+
markRootExpiredAtTime(root, currentTime);
642+
}
643+
624644
// Determine the next expiration time to work on, using the fields stored
625645
// on the root.
626-
let expirationTime = getNextRootExpirationTimeToWorkOn(root);
646+
const expirationTime = getNextRootExpirationTimeToWorkOn(root);
627647
if (expirationTime !== NoWork) {
628-
if (didTimeout) {
629-
// An async update expired. There may be other expired updates on
630-
// this root.
631-
const currentTime = requestCurrentTime();
632-
if (currentTime < expirationTime) {
633-
// Render all the expired work in a single batch.
634-
expirationTime = currentTime;
635-
}
636-
}
637-
638648
const originalCallbackNode = root.callbackNode;
639649
try {
640650
renderRoot(root, expirationTime, didTimeout);
@@ -673,7 +683,9 @@ export function flushRoot(root: FiberRoot, expirationTime: ExpirationTime) {
673683
'means you attempted to commit from inside a lifecycle method.',
674684
);
675685
}
676-
performSyncWorkOnRoot(root, expirationTime);
686+
markRootExpiredAtTime(root, expirationTime);
687+
ensureRootIsScheduled(root);
688+
flushSyncCallbackQueue();
677689
}
678690

679691
export function flushDiscreteUpdates() {
@@ -741,9 +753,8 @@ function flushPendingDiscreteUpdates() {
741753
const roots = rootsWithPendingDiscreteUpdates;
742754
rootsWithPendingDiscreteUpdates = null;
743755
roots.forEach((expirationTime, root) => {
744-
scheduleSyncCallback(
745-
performSyncWorkOnRoot.bind(null, root, expirationTime),
746-
);
756+
markRootExpiredAtTime(root, expirationTime);
757+
ensureRootIsScheduled(root);
747758
});
748759
// Now flush the immediate queue.
749760
flushSyncCallbackQueue();
@@ -1032,7 +1043,7 @@ function renderRoot(
10321043
// synchronously, to see if the error goes away. If there are lower
10331044
// priority updates, let's include those, too, in case they fix the
10341045
// inconsistency. Render at Idle to include all updates.
1035-
performSyncWorkOnRoot(root, Idle);
1046+
markRootExpiredAtTime(root, Idle);
10361047
return;
10371048
}
10381049
// Commit the root in its errored state.

0 commit comments

Comments
 (0)