Skip to content

Commit 3ff2c7c

Browse files
authored
Invalid actualDuration+treeBaseDuration for hidden+suspended trees (#14065)
* Fixed `treeBaseDuration` by propagating its value from the suspended tree to the Fragment React temporarily wraps around it when showing the fallback UI. * Fixed `actualDuration` by recording elapsed profiler time in the event of an error. * Fixed `actualDuration` in concurrent mode by propagating the time spent rendering the suspending component to its parent. Also updated ReactSuspensePlaceholder-test.internal to cover these new cases.
1 parent 5afa1c4 commit 3ff2c7c

File tree

3 files changed

+297
-24
lines changed

3 files changed

+297
-24
lines changed

packages/react-reconciler/src/ReactFiberBeginWork.js

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,12 @@ import {
6666
} from './ReactChildFiber';
6767
import {processUpdateQueue} from './ReactUpdateQueue';
6868
import {NoWork, Never} from './ReactFiberExpirationTime';
69-
import {ConcurrentMode, StrictMode, NoContext} from './ReactTypeOfMode';
69+
import {
70+
ConcurrentMode,
71+
NoContext,
72+
ProfileMode,
73+
StrictMode,
74+
} from './ReactTypeOfMode';
7075
import {
7176
shouldSetTextContent,
7277
shouldDeprioritizeSubtree,
@@ -743,7 +748,7 @@ function mountLazyComponent(
743748
) {
744749
if (_current !== null) {
745750
// An lazy component only mounts if it suspended inside a non-
746-
// concurrent tree, in an inconsistent state. We want to tree it like
751+
// concurrent tree, in an inconsistent state. We want to treat it like
747752
// a new mount, even though an empty version of it already committed.
748753
// Disconnect the alternate pointers.
749754
_current.alternate = null;
@@ -829,7 +834,7 @@ function mountIncompleteClassComponent(
829834
) {
830835
if (_current !== null) {
831836
// An incomplete component only mounts if it suspended inside a non-
832-
// concurrent tree, in an inconsistent state. We want to tree it like
837+
// concurrent tree, in an inconsistent state. We want to treat it like
833838
// a new mount, even though an empty version of it already committed.
834839
// Disconnect the alternate pointers.
835840
_current.alternate = null;
@@ -886,7 +891,7 @@ function mountIndeterminateComponent(
886891
) {
887892
if (_current !== null) {
888893
// An indeterminate component only mounts if it suspended inside a non-
889-
// concurrent tree, in an inconsistent state. We want to tree it like
894+
// concurrent tree, in an inconsistent state. We want to treat it like
890895
// a new mount, even though an empty version of it already committed.
891896
// Disconnect the alternate pointers.
892897
_current.alternate = null;
@@ -1188,6 +1193,19 @@ function updateSuspenseComponent(
11881193
}
11891194
}
11901195

1196+
// Because primaryChildFragment is a new fiber that we're inserting as the
1197+
// parent of a new tree, we need to set its treeBaseDuration.
1198+
if (enableProfilerTimer && workInProgress.mode & ProfileMode) {
1199+
// treeBaseDuration is the sum of all the child tree base durations.
1200+
let treeBaseDuration = 0;
1201+
let hiddenChild = primaryChildFragment.child;
1202+
while (hiddenChild !== null) {
1203+
treeBaseDuration += hiddenChild.treeBaseDuration;
1204+
hiddenChild = hiddenChild.sibling;
1205+
}
1206+
primaryChildFragment.treeBaseDuration = treeBaseDuration;
1207+
}
1208+
11911209
// Clone the fallback child fragment, too. These we'll continue
11921210
// working on.
11931211
const fallbackChildFragment = (primaryChildFragment.sibling = createWorkInProgress(
@@ -1239,6 +1257,7 @@ function updateSuspenseComponent(
12391257
NoWork,
12401258
null,
12411259
);
1260+
12421261
primaryChildFragment.effectTag |= Placement;
12431262
primaryChildFragment.child = currentPrimaryChild;
12441263
currentPrimaryChild.return = primaryChildFragment;
@@ -1254,6 +1273,19 @@ function updateSuspenseComponent(
12541273
primaryChildFragment.child = progressedPrimaryChild;
12551274
}
12561275

1276+
// Because primaryChildFragment is a new fiber that we're inserting as the
1277+
// parent of a new tree, we need to set its treeBaseDuration.
1278+
if (enableProfilerTimer && workInProgress.mode & ProfileMode) {
1279+
// treeBaseDuration is the sum of all the child tree base durations.
1280+
let treeBaseDuration = 0;
1281+
let hiddenChild = primaryChildFragment.child;
1282+
while (hiddenChild !== null) {
1283+
treeBaseDuration += hiddenChild.treeBaseDuration;
1284+
hiddenChild = hiddenChild.sibling;
1285+
}
1286+
primaryChildFragment.treeBaseDuration = treeBaseDuration;
1287+
}
1288+
12571289
// Create a fragment from the fallback children, too.
12581290
const fallbackChildFragment = (primaryChildFragment.sibling = createFiberFromFragment(
12591291
nextFallbackChildren,

packages/react-reconciler/src/ReactFiberScheduler.js

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1049,9 +1049,18 @@ function completeUnitOfWork(workInProgress: Fiber): Fiber | null {
10491049
return null;
10501050
}
10511051
} else {
1052-
if (workInProgress.mode & ProfileMode) {
1052+
if (enableProfilerTimer && workInProgress.mode & ProfileMode) {
10531053
// Record the render duration for the fiber that errored.
10541054
stopProfilerTimerIfRunningAndRecordDelta(workInProgress, false);
1055+
1056+
// Include the time spent working on failed children before continuing.
1057+
let actualDuration = workInProgress.actualDuration;
1058+
let child = workInProgress.child;
1059+
while (child !== null) {
1060+
actualDuration += child.actualDuration;
1061+
child = child.sibling;
1062+
}
1063+
workInProgress.actualDuration = actualDuration;
10551064
}
10561065

10571066
// This fiber did not complete because something threw. Pop values off
@@ -1076,19 +1085,6 @@ function completeUnitOfWork(workInProgress: Fiber): Fiber | null {
10761085
ReactFiberInstrumentation.debugTool.onCompleteWork(workInProgress);
10771086
}
10781087

1079-
if (enableProfilerTimer) {
1080-
// Include the time spent working on failed children before continuing.
1081-
if (next.mode & ProfileMode) {
1082-
let actualDuration = next.actualDuration;
1083-
let child = next.child;
1084-
while (child !== null) {
1085-
actualDuration += child.actualDuration;
1086-
child = child.sibling;
1087-
}
1088-
next.actualDuration = actualDuration;
1089-
}
1090-
}
1091-
10921088
// If completing this work spawned new work, do that next. We'll come
10931089
// back here again.
10941090
// Since we're restarting, remove anything that is not a host effect
@@ -1314,6 +1310,12 @@ function renderRoot(root: FiberRoot, isYieldy: boolean): void {
13141310
didFatal = true;
13151311
onUncaughtError(thrownValue);
13161312
} else {
1313+
if (enableProfilerTimer && nextUnitOfWork.mode & ProfileMode) {
1314+
// Record the time spent rendering before an error was thrown.
1315+
// This avoids inaccurate Profiler durations in the case of a suspended render.
1316+
stopProfilerTimerIfRunningAndRecordDelta(nextUnitOfWork, true);
1317+
}
1318+
13171319
if (__DEV__) {
13181320
// Reset global debug state
13191321
// We assume this is defined in DEV

0 commit comments

Comments
 (0)