Skip to content

Commit c8335c5

Browse files
committed
Fix: Check RootDidNotComplete after store mutation
When re-rendering due to a concurrent store mutation, we must check for the RootDidNotComplete exit status again. This fixes the test introduced in the previous commit. As a follow-up, I'm going to look into to cleaning up the places where we check the exit status, so bugs like these are less likely. I think we might be able to combine most of it into a single switch statement.
1 parent b6665fe commit c8335c5

1 file changed

Lines changed: 39 additions & 61 deletions

File tree

packages/react-reconciler/src/ReactFiberWorkLoop.js

Lines changed: 39 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -892,58 +892,39 @@ export function performConcurrentWorkOnRoot(
892892
let exitStatus = shouldTimeSlice
893893
? renderRootConcurrent(root, lanes)
894894
: renderRootSync(root, lanes);
895-
if (exitStatus !== RootInProgress) {
896-
if (exitStatus === RootErrored) {
897-
// If something threw an error, try rendering one more time. We'll
898-
// render synchronously to block concurrent data mutations, and we'll
899-
// includes all pending updates are included. If it still fails after
900-
// the second attempt, we'll give up and commit the resulting tree.
901-
const originallyAttemptedLanes = lanes;
902-
const errorRetryLanes = getLanesToRetrySynchronouslyOnError(
903-
root,
904-
originallyAttemptedLanes,
905-
);
906-
if (errorRetryLanes !== NoLanes) {
907-
lanes = errorRetryLanes;
908-
exitStatus = recoverFromConcurrentError(
909-
root,
910-
originallyAttemptedLanes,
911-
errorRetryLanes,
912-
);
913-
}
914-
}
915-
if (exitStatus === RootFatalErrored) {
916-
const fatalError = workInProgressRootFatalError;
917-
prepareFreshStack(root, NoLanes);
918-
markRootSuspended(root, lanes);
919-
ensureRootIsScheduled(root);
920-
throw fatalError;
921-
}
922895

923-
if (exitStatus === RootDidNotComplete) {
924-
// The render unwound without completing the tree. This happens in special
925-
// cases where need to exit the current render without producing a
926-
// consistent tree or committing.
927-
markRootSuspended(root, lanes);
928-
} else {
929-
// The render completed.
930-
931-
// Check if this render may have yielded to a concurrent event, and if so,
932-
// confirm that any newly rendered stores are consistent.
933-
// TODO: It's possible that even a concurrent render may never have yielded
934-
// to the main thread, if it was fast enough, or if it expired. We could
935-
// skip the consistency check in that case, too.
936-
const renderWasConcurrent = !includesBlockingLane(root, lanes);
937-
const finishedWork: Fiber = (root.current.alternate: any);
938-
if (
939-
renderWasConcurrent &&
940-
!isRenderConsistentWithExternalStores(finishedWork)
941-
) {
942-
// A store was mutated in an interleaved event. Render again,
943-
// synchronously, to block further mutations.
944-
exitStatus = renderRootSync(root, lanes);
896+
if (exitStatus !== RootInProgress) {
897+
let renderWasConcurrent = shouldTimeSlice;
898+
do {
899+
if (exitStatus === RootDidNotComplete) {
900+
// The render unwound without completing the tree. This happens in special
901+
// cases where need to exit the current render without producing a
902+
// consistent tree or committing.
903+
markRootSuspended(root, lanes);
904+
} else {
905+
// The render completed.
906+
907+
// Check if this render may have yielded to a concurrent event, and if so,
908+
// confirm that any newly rendered stores are consistent.
909+
// TODO: It's possible that even a concurrent render may never have yielded
910+
// to the main thread, if it was fast enough, or if it expired. We could
911+
// skip the consistency check in that case, too.
912+
const finishedWork: Fiber = (root.current.alternate: any);
913+
if (
914+
renderWasConcurrent &&
915+
!isRenderConsistentWithExternalStores(finishedWork)
916+
) {
917+
// A store was mutated in an interleaved event. Render again,
918+
// synchronously, to block further mutations.
919+
exitStatus = renderRootSync(root, lanes);
920+
// We assume the tree is now consistent because we didn't yield to any
921+
// concurrent events.
922+
renderWasConcurrent = false;
923+
// Need to check the exit status again.
924+
continue;
925+
}
945926

946-
// We need to check again if something threw
927+
// Check if something threw
947928
if (exitStatus === RootErrored) {
948929
const originallyAttemptedLanes = lanes;
949930
const errorRetryLanes = getLanesToRetrySynchronouslyOnError(
@@ -957,8 +938,7 @@ export function performConcurrentWorkOnRoot(
957938
originallyAttemptedLanes,
958939
errorRetryLanes,
959940
);
960-
// We assume the tree is now consistent because we didn't yield to any
961-
// concurrent events.
941+
renderWasConcurrent = false;
962942
}
963943
}
964944
if (exitStatus === RootFatalErrored) {
@@ -969,16 +949,14 @@ export function performConcurrentWorkOnRoot(
969949
throw fatalError;
970950
}
971951

972-
// FIXME: Need to check for RootDidNotComplete again. The factoring here
973-
// isn't ideal.
952+
// We now have a consistent tree. The next step is either to commit it,
953+
// or, if something suspended, wait to commit it after a timeout.
954+
root.finishedWork = finishedWork;
955+
root.finishedLanes = lanes;
956+
finishConcurrentRender(root, exitStatus, finishedWork, lanes);
974957
}
975-
976-
// We now have a consistent tree. The next step is either to commit it,
977-
// or, if something suspended, wait to commit it after a timeout.
978-
root.finishedWork = finishedWork;
979-
root.finishedLanes = lanes;
980-
finishConcurrentRender(root, exitStatus, finishedWork, lanes);
981-
}
958+
break;
959+
} while (true);
982960
}
983961

984962
ensureRootIsScheduled(root);

0 commit comments

Comments
 (0)