Skip to content

Commit

Permalink
Fix: Suspend while recovering from hydration error (#28800)
Browse files Browse the repository at this point in the history
Fixes a bug that happens when an error occurs during hydration, React
switches to client rendering, and then the client render suspends. It
works correctly if there's a Suspense boundary on the stack, but not if
it happens in the shell of the app.

Prior to this fix, the app would crash with an "Unknown root exit
status" error.

I left a TODO comment for how we might refactor this code to be less
confusing in the future.

DiffTrain build for commit 3f9e237.
  • Loading branch information
acdlite committed Apr 9, 2024
1 parent d0f9214 commit d2b1651
Show file tree
Hide file tree
Showing 10 changed files with 112 additions and 49 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
* @noflow
* @nolint
* @preventMunge
* @generated SignedSource<<fdd11cfd0a9f56383a8f5fff2c50fc9e>>
* @generated SignedSource<<881e78352310ebeea1f473a89441e0c9>>
*/

"use strict";
Expand Down Expand Up @@ -22773,20 +22773,31 @@ if (__DEV__) {
} // Check if something threw

if (exitStatus === RootErrored) {
var originallyAttemptedLanes = lanes;
var lanesThatJustErrored = lanes;
var errorRetryLanes = getLanesToRetrySynchronouslyOnError(
root,
originallyAttemptedLanes
lanesThatJustErrored
);

if (errorRetryLanes !== NoLanes) {
lanes = errorRetryLanes;
exitStatus = recoverFromConcurrentError(
root,
originallyAttemptedLanes,
lanesThatJustErrored,
errorRetryLanes
);
renderWasConcurrent = false;
renderWasConcurrent = false; // Need to check the exit status again.

if (exitStatus !== RootErrored) {
// The root did not error this time. Restart the exit algorithm
// from the beginning.
// TODO: Refactor the exit algorithm to be less confusing. Maybe
// more branches + recursion instead of a loop. I think the only
// thing that causes it to be a loop is the RootDidNotComplete
// check. If that's true, then we don't need a loop/recursion
// at all.
continue;
}
}
}

Expand Down Expand Up @@ -26590,7 +26601,7 @@ if (__DEV__) {
return root;
}

var ReactVersion = "19.0.0-canary-27752dcf";
var ReactVersion = "19.0.0-canary-89546c49";

/*
* The `'' + value` pattern (used in perf-sensitive code) throws for Symbol
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
* @noflow
* @nolint
* @preventMunge
* @generated SignedSource<<761db320e8f45126e2feb86ac8bca704>>
* @generated SignedSource<<5159750d4ca65c393d679d370a71d7cb>>
*/

"use strict";
Expand Down Expand Up @@ -7472,13 +7472,18 @@ function performConcurrentWorkOnRoot(root, didTimeout) {
root,
renderWasConcurrent
);
0 !== errorRetryLanes &&
if (
0 !== errorRetryLanes &&
((lanes = errorRetryLanes),
(exitStatus = recoverFromConcurrentError(
root,
renderWasConcurrent,
errorRetryLanes
)));
)),
(renderWasConcurrent = !1),
2 !== exitStatus)
)
continue;
}
if (1 === exitStatus) {
prepareFreshStack(root, 0);
Expand Down Expand Up @@ -9119,7 +9124,7 @@ var devToolsConfig$jscomp$inline_1019 = {
throw Error("TestRenderer does not support findFiberByHostInstance()");
},
bundleType: 0,
version: "19.0.0-canary-3d34fdc4",
version: "19.0.0-canary-df9de31a",
rendererPackageName: "react-test-renderer"
};
var internals$jscomp$inline_1238 = {
Expand Down Expand Up @@ -9150,7 +9155,7 @@ var internals$jscomp$inline_1238 = {
scheduleRoot: null,
setRefreshHandler: null,
getCurrentFiber: null,
reconcilerVersion: "19.0.0-canary-3d34fdc4"
reconcilerVersion: "19.0.0-canary-df9de31a"
};
if ("undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__) {
var hook$jscomp$inline_1239 = __REACT_DEVTOOLS_GLOBAL_HOOK__;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
* @noflow
* @nolint
* @preventMunge
* @generated SignedSource<<ec92ba0d6674d69f102b6b07bcc607e9>>
* @generated SignedSource<<21f520424802b9edb8fc0a8c96178e1c>>
*/

"use strict";
Expand Down Expand Up @@ -7966,13 +7966,18 @@ function performConcurrentWorkOnRoot(root, didTimeout) {
root,
renderWasConcurrent
);
0 !== errorRetryLanes &&
if (
0 !== errorRetryLanes &&
((lanes = errorRetryLanes),
(exitStatus = recoverFromConcurrentError(
root,
renderWasConcurrent,
errorRetryLanes
)));
)),
(renderWasConcurrent = !1),
2 !== exitStatus)
)
continue;
}
if (1 === exitStatus) {
prepareFreshStack(root, 0);
Expand Down Expand Up @@ -9757,7 +9762,7 @@ var devToolsConfig$jscomp$inline_1082 = {
throw Error("TestRenderer does not support findFiberByHostInstance()");
},
bundleType: 0,
version: "19.0.0-canary-09ccb82c",
version: "19.0.0-canary-12ee58d1",
rendererPackageName: "react-test-renderer"
};
(function (internals) {
Expand Down Expand Up @@ -9801,7 +9806,7 @@ var devToolsConfig$jscomp$inline_1082 = {
scheduleRoot: null,
setRefreshHandler: null,
getCurrentFiber: null,
reconcilerVersion: "19.0.0-canary-09ccb82c"
reconcilerVersion: "19.0.0-canary-12ee58d1"
});
exports._Scheduler = Scheduler;
exports.act = act;
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1 @@
64c8d2d45d49dbb2f59ea23e5e739eb79e124abc
3f9e237a2feb74f1fca23b76d9d2e9e1713e2ba1
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
* @noflow
* @nolint
* @preventMunge
* @generated SignedSource<<494d27c89fdf2fb47e01cb552bf35a07>>
* @generated SignedSource<<a4571cdfd524cd72b685d2a3adeafd2c>>
*/

"use strict";
Expand Down Expand Up @@ -26242,20 +26242,31 @@ to return true:wantsResponderID| |
} // Check if something threw

if (exitStatus === RootErrored) {
var originallyAttemptedLanes = lanes;
var lanesThatJustErrored = lanes;
var errorRetryLanes = getLanesToRetrySynchronouslyOnError(
root,
originallyAttemptedLanes
lanesThatJustErrored
);

if (errorRetryLanes !== NoLanes) {
lanes = errorRetryLanes;
exitStatus = recoverFromConcurrentError(
root,
originallyAttemptedLanes,
lanesThatJustErrored,
errorRetryLanes
);
renderWasConcurrent = false;
renderWasConcurrent = false; // Need to check the exit status again.

if (exitStatus !== RootErrored) {
// The root did not error this time. Restart the exit algorithm
// from the beginning.
// TODO: Refactor the exit algorithm to be less confusing. Maybe
// more branches + recursion instead of a loop. I think the only
// thing that causes it to be a loop is the RootDidNotComplete
// check. If that's true, then we don't need a loop/recursion
// at all.
continue;
}
}
}

Expand Down Expand Up @@ -30237,7 +30248,7 @@ to return true:wantsResponderID| |
return root;
}

var ReactVersion = "19.0.0-canary-d3b8f5e2";
var ReactVersion = "19.0.0-canary-704f6d1b";

/*
* The `'' + value` pattern (used in perf-sensitive code) throws for Symbol
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
* @noflow
* @nolint
* @preventMunge
* @generated SignedSource<<16efbcb0da2cbd078a40efb1a0552fe8>>
* @generated SignedSource<<2e33e857cb3eee98d5e57d1509811f8c>>
*/

"use strict";
Expand Down Expand Up @@ -9055,13 +9055,18 @@ function performConcurrentWorkOnRoot(root, didTimeout) {
root,
renderWasConcurrent
);
0 !== errorRetryLanes &&
if (
0 !== errorRetryLanes &&
((lanes = errorRetryLanes),
(exitStatus = recoverFromConcurrentError(
root,
renderWasConcurrent,
errorRetryLanes
)));
)),
(renderWasConcurrent = !1),
2 !== exitStatus)
)
continue;
}
if (1 === exitStatus) {
prepareFreshStack(root, 0);
Expand Down Expand Up @@ -10572,7 +10577,7 @@ var roots = new Map(),
devToolsConfig$jscomp$inline_1099 = {
findFiberByHostInstance: getInstanceFromNode,
bundleType: 0,
version: "19.0.0-canary-120922b5",
version: "19.0.0-canary-a19c8da4",
rendererPackageName: "react-native-renderer",
rendererConfig: {
getInspectorDataForInstance: getInspectorDataForInstance,
Expand Down Expand Up @@ -10615,7 +10620,7 @@ var internals$jscomp$inline_1366 = {
scheduleRoot: null,
setRefreshHandler: null,
getCurrentFiber: null,
reconcilerVersion: "19.0.0-canary-120922b5"
reconcilerVersion: "19.0.0-canary-a19c8da4"
};
if ("undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__) {
var hook$jscomp$inline_1367 = __REACT_DEVTOOLS_GLOBAL_HOOK__;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
* @noflow
* @nolint
* @preventMunge
* @generated SignedSource<<a3c25968a30f5f8cd268a3314ac130fe>>
* @generated SignedSource<<5fa81808919e6dc188f7876d0baea787>>
*/

"use strict";
Expand Down Expand Up @@ -9585,13 +9585,18 @@ function performConcurrentWorkOnRoot(root, didTimeout) {
root,
renderWasConcurrent
);
0 !== errorRetryLanes &&
if (
0 !== errorRetryLanes &&
((lanes = errorRetryLanes),
(exitStatus = recoverFromConcurrentError(
root,
renderWasConcurrent,
errorRetryLanes
)));
)),
(renderWasConcurrent = !1),
2 !== exitStatus)
)
continue;
}
if (1 === exitStatus) {
prepareFreshStack(root, 0);
Expand Down Expand Up @@ -11277,7 +11282,7 @@ var roots = new Map(),
devToolsConfig$jscomp$inline_1179 = {
findFiberByHostInstance: getInstanceFromNode,
bundleType: 0,
version: "19.0.0-canary-7ffb2ad4",
version: "19.0.0-canary-0d80c3d3",
rendererPackageName: "react-native-renderer",
rendererConfig: {
getInspectorDataForInstance: getInspectorDataForInstance,
Expand Down Expand Up @@ -11333,7 +11338,7 @@ var roots = new Map(),
scheduleRoot: null,
setRefreshHandler: null,
getCurrentFiber: null,
reconcilerVersion: "19.0.0-canary-7ffb2ad4"
reconcilerVersion: "19.0.0-canary-0d80c3d3"
});
exports.createPortal = function (children, containerTag) {
return createPortal$1(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
* @noflow
* @nolint
* @preventMunge
* @generated SignedSource<<24fee48abc3bddda74443b28d17bbc3e>>
* @generated SignedSource<<a64d4ff9138061f9c517e2327f49b803>>
*/

"use strict";
Expand Down Expand Up @@ -26682,20 +26682,31 @@ to return true:wantsResponderID| |
} // Check if something threw

if (exitStatus === RootErrored) {
var originallyAttemptedLanes = lanes;
var lanesThatJustErrored = lanes;
var errorRetryLanes = getLanesToRetrySynchronouslyOnError(
root,
originallyAttemptedLanes
lanesThatJustErrored
);

if (errorRetryLanes !== NoLanes) {
lanes = errorRetryLanes;
exitStatus = recoverFromConcurrentError(
root,
originallyAttemptedLanes,
lanesThatJustErrored,
errorRetryLanes
);
renderWasConcurrent = false;
renderWasConcurrent = false; // Need to check the exit status again.

if (exitStatus !== RootErrored) {
// The root did not error this time. Restart the exit algorithm
// from the beginning.
// TODO: Refactor the exit algorithm to be less confusing. Maybe
// more branches + recursion instead of a loop. I think the only
// thing that causes it to be a loop is the RootDidNotComplete
// check. If that's true, then we don't need a loop/recursion
// at all.
continue;
}
}
}

Expand Down Expand Up @@ -30677,7 +30688,7 @@ to return true:wantsResponderID| |
return root;
}

var ReactVersion = "19.0.0-canary-ae39878d";
var ReactVersion = "19.0.0-canary-f860c0a2";

/*
* The `'' + value` pattern (used in perf-sensitive code) throws for Symbol
Expand Down
Loading

0 comments on commit d2b1651

Please sign in to comment.