Skip to content

Commit

Permalink
Throttle retries even if everything has loaded (#26611)
Browse files Browse the repository at this point in the history
If a Suspense fallback is shown, and the data finishes loading really
quickly after that, we throttle the content from appearing for 500ms to
reduce thrash.

This already works for successive fallback states (like if one fallback
is nested inside another) but it wasn't being applied to the final step
in the sequence: if there were no more unresolved Suspense boundaries in
the tree, the content would appear immediately.

This fixes the throttling behavior so that it applies to all renders
that are the result of suspended data being loaded. (Our internal jargon
term for this is a "retry".)

DiffTrain build for commit 8256781.
  • Loading branch information
acdlite committed Apr 13, 2023
1 parent 66c7f48 commit c650bf1
Show file tree
Hide file tree
Showing 13 changed files with 469 additions and 685 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20260,109 +20260,89 @@ function queueRecoverableErrors(errors) {
}

function finishConcurrentRender(root, exitStatus, finishedWork, lanes) {
// TODO: The fact that most of these branches are identical suggests that some
// of the exit statuses are not best modeled as exit statuses and should be
// tracked orthogonally.
switch (exitStatus) {
case RootInProgress:
case RootFatalErrored: {
throw new Error("Root did not complete. This is a bug in React.");
}

case RootErrored: {
// We should have already attempted to retry this tree. If we reached
// this point, it errored again. Commit it.
commitRootWhenReady(
root,
finishedWork,
workInProgressRootRecoverableErrors,
workInProgressTransitions,
lanes
);
break;
}

case RootSuspended: {
markRootSuspended(root, lanes); // We have an acceptable loading state. We need to figure out if we
// should immediately commit it or wait a bit.

if (
includesOnlyRetries(lanes) && // do not delay if we're inside an act() scope
!shouldForceFlushFallbacksInDEV()
) {
// This render only included retries, no updates. Throttle committing
// retries so that we don't show too many loading states too quickly.
var msUntilTimeout =
globalMostRecentFallbackTime + FALLBACK_THROTTLE_MS - now$1(); // Don't bother with a very short suspense time.

if (msUntilTimeout > 10) {
var nextLanes = getNextLanes(root, NoLanes);

if (nextLanes !== NoLanes) {
// There's additional work on this root.
break;
} // The render is suspended, it hasn't timed out, and there's no
// lower priority work to do. Instead of committing the fallback
// immediately, wait for more data to arrive.

root.timeoutHandle = scheduleTimeout(
commitRootWhenReady.bind(
null,
root,
finishedWork,
workInProgressRootRecoverableErrors,
workInProgressTransitions,
lanes
),
msUntilTimeout
);
break;
}
} // The work expired. Commit immediately.

commitRootWhenReady(
root,
finishedWork,
workInProgressRootRecoverableErrors,
workInProgressTransitions,
lanes
);
break;
}

case RootSuspendedWithDelay: {
markRootSuspended(root, lanes);

if (includesOnlyTransitions(lanes)) {
// This is a transition, so we should exit without committing a
// placeholder and without scheduling a timeout. Delay indefinitely
// until we receive more data.
break;
markRootSuspended(root, lanes);
return;
} // Commit the placeholder.

commitRootWhenReady(
root,
finishedWork,
workInProgressRootRecoverableErrors,
workInProgressTransitions,
lanes
);
break;
}

case RootErrored:
case RootSuspended:
case RootCompleted: {
// The work completed.
commitRootWhenReady(
root,
finishedWork,
workInProgressRootRecoverableErrors,
workInProgressTransitions,
lanes
);
break;
}

default: {
throw new Error("Unknown root exit status.");
}
}

if (shouldForceFlushFallbacksInDEV()) {
// We're inside an `act` scope. Commit immediately.
commitRoot(
root,
workInProgressRootRecoverableErrors,
workInProgressTransitions
);
} else {
if (includesOnlyRetries(lanes)) {
// This render only included retries, no updates. Throttle committing
// retries so that we don't show too many loading states too quickly.
var msUntilTimeout =
globalMostRecentFallbackTime + FALLBACK_THROTTLE_MS - now$1(); // Don't bother with a very short suspense time.

if (msUntilTimeout > 10) {
markRootSuspended(root, lanes);
var nextLanes = getNextLanes(root, NoLanes);

if (nextLanes !== NoLanes) {
// There's additional work we can do on this root. We might as well
// attempt to work on that while we're suspended.
return;
} // The render is suspended, it hasn't timed out, and there's no
// lower priority work to do. Instead of committing the fallback
// immediately, wait for more data to arrive.
// TODO: Combine retry throttling with Suspensey commits. Right now they
// run one after the other.

root.timeoutHandle = scheduleTimeout(
commitRootWhenReady.bind(
null,
root,
finishedWork,
workInProgressRootRecoverableErrors,
workInProgressTransitions,
lanes
),
msUntilTimeout
);
return;
}
}

commitRootWhenReady(
root,
finishedWork,
workInProgressRootRecoverableErrors,
workInProgressTransitions,
lanes
);
}
}

function commitRootWhenReady(
Expand All @@ -20372,6 +20352,8 @@ function commitRootWhenReady(
transitions,
lanes
) {
// TODO: Combine retry throttling with Suspensey commits. Right now they run
// one after the other.
if (includesOnlyNonUrgentLanes(lanes)) {
// the suspensey resources. The renderer is responsible for accumulating
// all the load events. This all happens in a single synchronous
Expand All @@ -20391,22 +20373,14 @@ function commitRootWhenReady(
// us that it's ready. This will be canceled if we start work on the
// root again.
root.cancelPendingCommit = schedulePendingCommit(
commitRoot.bind(
null,
root,
workInProgressRootRecoverableErrors,
workInProgressTransitions
)
commitRoot.bind(null, root, recoverableErrors, transitions)
);
markRootSuspended(root, lanes);
return;
}
} // Otherwise, commit immediately.

commitRoot(
root,
workInProgressRootRecoverableErrors,
workInProgressTransitions
);
commitRoot(root, recoverableErrors, transitions);
}

function isRenderConsistentWithExternalStores(finishedWork) {
Expand Down Expand Up @@ -23752,7 +23726,7 @@ function createFiberRoot(
return root;
}

var ReactVersion = "18.3.0-next-72c890e31-20230412";
var ReactVersion = "18.3.0-next-8256781fd-20230412";

// Might add PROFILE later.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6498,70 +6498,51 @@ function performConcurrentWorkOnRoot(root, didTimeout) {
}
root.finishedWork = originallyAttemptedLanes;
root.finishedLanes = lanes;
switch (didTimeout) {
case 0:
case 1:
throw Error("Root did not complete. This is a bug in React.");
case 2:
commitRootWhenReady(
root,
originallyAttemptedLanes,
workInProgressRootRecoverableErrors,
workInProgressTransitions,
lanes
);
break;
case 3:
markRootSuspended(root, lanes);
if (
(lanes & 125829120) === lanes &&
((didTimeout = globalMostRecentFallbackTime + 500 - now()),
10 < didTimeout)
) {
if (0 !== getNextLanes(root, 0)) break;
root.timeoutHandle = scheduleTimeout(
commitRootWhenReady.bind(
null,
root,
originallyAttemptedLanes,
workInProgressRootRecoverableErrors,
workInProgressTransitions,
lanes
),
didTimeout
);
a: {
switch (didTimeout) {
case 0:
case 1:
throw Error("Root did not complete. This is a bug in React.");
case 4:
if ((lanes & 8388480) === lanes) {
markRootSuspended(root, lanes);
break a;
}
break;
}
commitRootWhenReady(
root,
originallyAttemptedLanes,
workInProgressRootRecoverableErrors,
workInProgressTransitions,
lanes
);
break;
case 4:
case 2:
case 3:
case 5:
break;
default:
throw Error("Unknown root exit status.");
}
if (
(lanes & 125829120) === lanes &&
((didTimeout = globalMostRecentFallbackTime + 500 - now()),
10 < didTimeout)
) {
markRootSuspended(root, lanes);
if ((lanes & 8388480) === lanes) break;
commitRootWhenReady(
root,
originallyAttemptedLanes,
workInProgressRootRecoverableErrors,
workInProgressTransitions,
lanes
);
break;
case 5:
commitRootWhenReady(
root,
originallyAttemptedLanes,
workInProgressRootRecoverableErrors,
workInProgressTransitions,
lanes
if (0 !== getNextLanes(root, 0)) break a;
root.timeoutHandle = scheduleTimeout(
commitRootWhenReady.bind(
null,
root,
originallyAttemptedLanes,
workInProgressRootRecoverableErrors,
workInProgressTransitions,
lanes
),
didTimeout
);
break;
default:
throw Error("Unknown root exit status.");
break a;
}
commitRootWhenReady(
root,
originallyAttemptedLanes,
workInProgressRootRecoverableErrors,
workInProgressTransitions,
lanes
);
}
}
}
Expand Down Expand Up @@ -6612,11 +6593,7 @@ function commitRootWhenReady(
lanes
) {
0 === (lanes & 42) && accumulateSuspenseyCommitOnFiber(finishedWork);
commitRoot(
root,
workInProgressRootRecoverableErrors,
workInProgressTransitions
);
commitRoot(root, recoverableErrors, transitions);
}
function isRenderConsistentWithExternalStores(finishedWork) {
for (var node = finishedWork; ; ) {
Expand Down Expand Up @@ -8610,7 +8587,7 @@ var devToolsConfig$jscomp$inline_1021 = {
throw Error("TestRenderer does not support findFiberByHostInstance()");
},
bundleType: 0,
version: "18.3.0-next-72c890e31-20230412",
version: "18.3.0-next-8256781fd-20230412",
rendererPackageName: "react-test-renderer"
};
var internals$jscomp$inline_1204 = {
Expand Down Expand Up @@ -8641,7 +8618,7 @@ var internals$jscomp$inline_1204 = {
scheduleRoot: null,
setRefreshHandler: null,
getCurrentFiber: null,
reconcilerVersion: "18.3.0-next-72c890e31-20230412"
reconcilerVersion: "18.3.0-next-8256781fd-20230412"
};
if ("undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__) {
var hook$jscomp$inline_1205 = __REACT_DEVTOOLS_GLOBAL_HOOK__;
Expand Down
Loading

0 comments on commit c650bf1

Please sign in to comment.