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 [3f9e237](3f9e237)
  • Loading branch information
acdlite committed Apr 9, 2024
1 parent a85aedc commit 442049b
Show file tree
Hide file tree
Showing 17 changed files with 209 additions and 81 deletions.
2 changes: 1 addition & 1 deletion compiled/facebook-www/REVISION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
64c8d2d45d49dbb2f59ea23e5e739eb79e124abc
3f9e237a2feb74f1fca23b76d9d2e9e1713e2ba1
21 changes: 16 additions & 5 deletions compiled/facebook-www/ReactART-dev.classic.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ if (__DEV__) {
return self;
}

var ReactVersion = "19.0.0-www-classic-8ce767ac";
var ReactVersion = "19.0.0-www-classic-877fd74a";

var LegacyRoot = 0;
var ConcurrentRoot = 1;
Expand Down Expand Up @@ -26099,20 +26099,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
21 changes: 16 additions & 5 deletions compiled/facebook-www/ReactART-dev.modern.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ if (__DEV__) {
return self;
}

var ReactVersion = "19.0.0-www-modern-070c93c4";
var ReactVersion = "19.0.0-www-modern-bf3441a6";

var LegacyRoot = 0;
var ConcurrentRoot = 1;
Expand Down Expand Up @@ -25306,20 +25306,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
13 changes: 9 additions & 4 deletions compiled/facebook-www/ReactART-prod.classic.js
Original file line number Diff line number Diff line change
Expand Up @@ -9091,13 +9091,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 @@ -10594,7 +10599,7 @@ var slice = Array.prototype.slice,
return null;
},
bundleType: 0,
version: "19.0.0-www-classic-e80018a0",
version: "19.0.0-www-classic-f1bbc587",
rendererPackageName: "react-art"
};
var internals$jscomp$inline_1322 = {
Expand Down Expand Up @@ -10625,7 +10630,7 @@ var internals$jscomp$inline_1322 = {
scheduleRoot: null,
setRefreshHandler: null,
getCurrentFiber: null,
reconcilerVersion: "19.0.0-www-classic-e80018a0"
reconcilerVersion: "19.0.0-www-classic-f1bbc587"
};
if ("undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__) {
var hook$jscomp$inline_1323 = __REACT_DEVTOOLS_GLOBAL_HOOK__;
Expand Down
13 changes: 9 additions & 4 deletions compiled/facebook-www/ReactART-prod.modern.js
Original file line number Diff line number Diff line change
Expand Up @@ -8619,13 +8619,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 @@ -10073,7 +10078,7 @@ var slice = Array.prototype.slice,
return null;
},
bundleType: 0,
version: "19.0.0-www-modern-2ada2d66",
version: "19.0.0-www-modern-4749dbb1",
rendererPackageName: "react-art"
};
var internals$jscomp$inline_1307 = {
Expand Down Expand Up @@ -10104,7 +10109,7 @@ var internals$jscomp$inline_1307 = {
scheduleRoot: null,
setRefreshHandler: null,
getCurrentFiber: null,
reconcilerVersion: "19.0.0-www-modern-2ada2d66"
reconcilerVersion: "19.0.0-www-modern-4749dbb1"
};
if ("undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__) {
var hook$jscomp$inline_1308 = __REACT_DEVTOOLS_GLOBAL_HOOK__;
Expand Down
21 changes: 16 additions & 5 deletions compiled/facebook-www/ReactDOM-dev.classic.js
Original file line number Diff line number Diff line change
Expand Up @@ -31812,20 +31812,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 @@ -36145,7 +36156,7 @@ if (__DEV__) {
return root;
}

var ReactVersion = "19.0.0-www-classic-4106d899";
var ReactVersion = "19.0.0-www-classic-87a9d756";

function createPortal$1(
children,
Expand Down
21 changes: 16 additions & 5 deletions compiled/facebook-www/ReactDOM-dev.modern.js
Original file line number Diff line number Diff line change
Expand Up @@ -39589,20 +39589,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 @@ -45641,7 +45652,7 @@ if (__DEV__) {
return root;
}

var ReactVersion = "19.0.0-www-modern-d80f9f9e";
var ReactVersion = "19.0.0-www-modern-1c3b0b72";

function createPortal$1(
children,
Expand Down
15 changes: 10 additions & 5 deletions compiled/facebook-www/ReactDOM-prod.classic.js
Original file line number Diff line number Diff line change
Expand Up @@ -10891,13 +10891,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 @@ -16991,7 +16996,7 @@ Internals.Events = [
var devToolsConfig$jscomp$inline_1727 = {
findFiberByHostInstance: getClosestInstanceFromNode,
bundleType: 0,
version: "19.0.0-www-classic-007b0a8e",
version: "19.0.0-www-classic-1ce9ab8c",
rendererPackageName: "react-dom"
};
var internals$jscomp$inline_2155 = {
Expand Down Expand Up @@ -17021,7 +17026,7 @@ var internals$jscomp$inline_2155 = {
scheduleRoot: null,
setRefreshHandler: null,
getCurrentFiber: null,
reconcilerVersion: "19.0.0-www-classic-007b0a8e"
reconcilerVersion: "19.0.0-www-classic-1ce9ab8c"
};
if ("undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__) {
var hook$jscomp$inline_2156 = __REACT_DEVTOOLS_GLOBAL_HOOK__;
Expand Down Expand Up @@ -17484,4 +17489,4 @@ exports.useFormState = function (action, initialState, permalink) {
exports.useFormStatus = function () {
return ReactSharedInternals.H.useHostTransitionStatus();
};
exports.version = "19.0.0-www-classic-007b0a8e";
exports.version = "19.0.0-www-classic-1ce9ab8c";
15 changes: 10 additions & 5 deletions compiled/facebook-www/ReactDOM-prod.modern.js
Original file line number Diff line number Diff line change
Expand Up @@ -13491,13 +13491,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 @@ -16353,7 +16358,7 @@ Internals.Events = [
var devToolsConfig$jscomp$inline_1720 = {
findFiberByHostInstance: getClosestInstanceFromNode,
bundleType: 0,
version: "19.0.0-www-modern-00807ede",
version: "19.0.0-www-modern-b5e235f2",
rendererPackageName: "react-dom"
};
var internals$jscomp$inline_2157 = {
Expand Down Expand Up @@ -16383,7 +16388,7 @@ var internals$jscomp$inline_2157 = {
scheduleRoot: null,
setRefreshHandler: null,
getCurrentFiber: null,
reconcilerVersion: "19.0.0-www-modern-00807ede"
reconcilerVersion: "19.0.0-www-modern-b5e235f2"
};
if ("undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__) {
var hook$jscomp$inline_2158 = __REACT_DEVTOOLS_GLOBAL_HOOK__;
Expand Down Expand Up @@ -16688,4 +16693,4 @@ exports.useFormState = function (action, initialState, permalink) {
exports.useFormStatus = function () {
return ReactSharedInternals.H.useHostTransitionStatus();
};
exports.version = "19.0.0-www-modern-00807ede";
exports.version = "19.0.0-www-modern-b5e235f2";
15 changes: 10 additions & 5 deletions compiled/facebook-www/ReactDOM-profiling.classic.js
Original file line number Diff line number Diff line change
Expand Up @@ -11484,13 +11484,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 @@ -17739,7 +17744,7 @@ Internals.Events = [
var devToolsConfig$jscomp$inline_1813 = {
findFiberByHostInstance: getClosestInstanceFromNode,
bundleType: 0,
version: "19.0.0-www-classic-5bbd766e",
version: "19.0.0-www-classic-6860f048",
rendererPackageName: "react-dom"
};
(function (internals) {
Expand Down Expand Up @@ -17783,7 +17788,7 @@ var devToolsConfig$jscomp$inline_1813 = {
scheduleRoot: null,
setRefreshHandler: null,
getCurrentFiber: null,
reconcilerVersion: "19.0.0-www-classic-5bbd766e"
reconcilerVersion: "19.0.0-www-classic-6860f048"
});
var ReactFiberErrorDialogWWW = require("ReactFiberErrorDialog");
if ("function" !== typeof ReactFiberErrorDialogWWW.showErrorDialog)
Expand Down Expand Up @@ -18233,7 +18238,7 @@ exports.useFormState = function (action, initialState, permalink) {
exports.useFormStatus = function () {
return ReactSharedInternals.H.useHostTransitionStatus();
};
exports.version = "19.0.0-www-classic-5bbd766e";
exports.version = "19.0.0-www-classic-6860f048";
"undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ &&
"function" ===
typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop &&
Expand Down
Loading

0 comments on commit 442049b

Please sign in to comment.