Skip to content

Commit 5507039

Browse files
committed
Fix unwinding context during selective hydration (#25876)
This PR includes the previously reverted #25695 and #25754, and the fix for the regression test added in #25867. Tested internally with a previous failed test, and it's passing now. Co-authored-by: Andrew Clark <[email protected]> DiffTrain build for [7efa9e5](7efa9e5) [View git log for this commit](https://github.com/facebook/react/commits/7efa9e59707b341f10fab79724e0fca373187925)
1 parent 6b30524 commit 5507039

28 files changed

+1138
-506
lines changed

compiled/facebook-www/REVISION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
84a0a171ea0ecd25e287bd3d3dd30e932beb4677
1+
7efa9e59707b341f10fab79724e0fca373187925
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
84a0a171ea0ecd25e287bd3d3dd30e932beb4677
1+
7efa9e59707b341f10fab79724e0fca373187925

compiled/facebook-www/React-dev.classic.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ if (
2727
}
2828
"use strict";
2929

30-
var ReactVersion = "18.3.0-www-classic-84a0a171e-20221214";
30+
var ReactVersion = "18.3.0-www-classic-7efa9e597-20221215";
3131

3232
// ATTENTION
3333
// When adding new symbols to this file,

compiled/facebook-www/React-dev.modern.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ if (
2727
}
2828
"use strict";
2929

30-
var ReactVersion = "18.3.0-www-modern-84a0a171e-20221214";
30+
var ReactVersion = "18.3.0-www-modern-7efa9e597-20221215";
3131

3232
// ATTENTION
3333
// When adding new symbols to this file,

compiled/facebook-www/React-prod.classic.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -643,4 +643,4 @@ exports.useSyncExternalStore = function(
643643
);
644644
};
645645
exports.useTransition = useTransition;
646-
exports.version = "18.3.0-www-classic-84a0a171e-20221214";
646+
exports.version = "18.3.0-www-classic-7efa9e597-20221215";

compiled/facebook-www/React-prod.modern.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -635,4 +635,4 @@ exports.useSyncExternalStore = function(
635635
);
636636
};
637637
exports.useTransition = useTransition;
638-
exports.version = "18.3.0-www-modern-84a0a171e-20221214";
638+
exports.version = "18.3.0-www-modern-7efa9e597-20221215";

compiled/facebook-www/React-profiling.classic.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -654,7 +654,7 @@ exports.useSyncExternalStore = function(
654654
);
655655
};
656656
exports.useTransition = useTransition;
657-
exports.version = "18.3.0-www-classic-84a0a171e-20221214";
657+
exports.version = "18.3.0-www-classic-7efa9e597-20221215";
658658

659659
/* global __REACT_DEVTOOLS_GLOBAL_HOOK__ */
660660
if (

compiled/facebook-www/React-profiling.modern.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -646,7 +646,7 @@ exports.useSyncExternalStore = function(
646646
);
647647
};
648648
exports.useTransition = useTransition;
649-
exports.version = "18.3.0-www-modern-84a0a171e-20221214";
649+
exports.version = "18.3.0-www-modern-7efa9e597-20221215";
650650

651651
/* global __REACT_DEVTOOLS_GLOBAL_HOOK__ */
652652
if (

compiled/facebook-www/ReactART-dev.classic.js

Lines changed: 95 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ function _assertThisInitialized(self) {
6969
return self;
7070
}
7171

72-
var ReactVersion = "18.3.0-www-classic-84a0a171e-20221214";
72+
var ReactVersion = "18.3.0-www-classic-7efa9e597-20221215";
7373

7474
var LegacyRoot = 0;
7575
var ConcurrentRoot = 1;
@@ -12716,7 +12716,14 @@ function getMarkerInstances() {
1271612716
return null;
1271712717
}
1271812718

12719-
var ReactCurrentOwner$1 = ReactSharedInternals.ReactCurrentOwner;
12719+
var ReactCurrentOwner$1 = ReactSharedInternals.ReactCurrentOwner; // A special exception that's used to unwind the stack when an update flows
12720+
// into a dehydrated boundary.
12721+
12722+
var SelectiveHydrationException = new Error(
12723+
"This is not a real error. It's an implementation detail of React's " +
12724+
"selective hydration feature. If this leaks into userspace, it's a bug in " +
12725+
"React. Please file an issue."
12726+
);
1272012727
var didReceiveUpdate = false;
1272112728
var didWarnAboutBadClass;
1272212729
var didWarnAboutModulePatternComponent;
@@ -14911,18 +14918,29 @@ function updateDehydratedSuspenseComponent(
1491114918
current,
1491214919
attemptHydrationAtLane,
1491314920
eventTime
14914-
);
14915-
}
14916-
} // If we have scheduled higher pri work above, this will just abort the render
14917-
// since we now have higher priority work. We'll try to infinitely suspend until
14918-
// we yield. TODO: We could probably just force yielding earlier instead.
14921+
); // Throw a special object that signals to the work loop that it should
14922+
// interrupt the current render.
14923+
//
14924+
// Because we're inside a React-only execution stack, we don't
14925+
// strictly need to throw here — we could instead modify some internal
14926+
// work loop state. But using an exception means we don't need to
14927+
// check for this case on every iteration of the work loop. So doing
14928+
// it this way moves the check out of the fast path.
1491914929

14920-
renderDidSuspendDelayIfPossible(); // If we rendered synchronously, we won't yield so have to render something.
14921-
// This will cause us to delete any existing content.
14930+
throw SelectiveHydrationException;
14931+
}
14932+
} // If we did not selectively hydrate, we'll continue rendering without
14933+
// hydrating. Mark this tree as suspended to prevent it from committing
14934+
// outside a transition.
14935+
//
14936+
// This path should only happen if the hydration lane already suspended.
14937+
// Currently, it also happens during sync updates because there is no
14938+
// hydration lane for sync updates.
1492214939
// TODO: We should ideally have a sync hydration lane that we can apply to do
1492314940
// a pass where we hydrate this subtree in place using the previous Context and then
1492414941
// reapply the update afterwards.
1492514942

14943+
renderDidSuspendDelayIfPossible();
1492614944
return retrySuspenseComponentWithoutHydrating(
1492714945
current,
1492814946
workInProgress,
@@ -22920,7 +22938,8 @@ var SuspendedOnError = 1;
2292022938
var SuspendedOnData = 2;
2292122939
var SuspendedOnImmediate = 3;
2292222940
var SuspendedOnDeprecatedThrowPromise = 4;
22923-
var SuspendedAndReadyToUnwind = 5; // When this is true, the work-in-progress fiber just suspended (or errored) and
22941+
var SuspendedAndReadyToUnwind = 5;
22942+
var SuspendedOnHydration = 6; // When this is true, the work-in-progress fiber just suspended (or errored) and
2292422943
// we've yet to unwind the stack. In some cases, we may yield to the main thread
2292522944
// after this happens. If the fiber is pinged before we resume, we can retry
2292622945
// immediately instead of unwinding the stack.
@@ -24114,6 +24133,30 @@ function getRenderLanes() {
2411424133
return renderLanes$1;
2411524134
}
2411624135

24136+
function resetWorkInProgressStack() {
24137+
if (workInProgress === null) return;
24138+
var interruptedWork;
24139+
24140+
if (workInProgressSuspendedReason === NotSuspended) {
24141+
// Normal case. Work-in-progress hasn't started yet. Unwind all
24142+
// its parents.
24143+
interruptedWork = workInProgress.return;
24144+
} else {
24145+
// Work-in-progress is in suspended state. Reset the work loop and unwind
24146+
// both the suspended fiber and all its parents.
24147+
resetSuspendedWorkLoopOnUnwind();
24148+
interruptedWork = workInProgress;
24149+
}
24150+
24151+
while (interruptedWork !== null) {
24152+
var current = interruptedWork.alternate;
24153+
unwindInterruptedWork(current, interruptedWork);
24154+
interruptedWork = interruptedWork.return;
24155+
}
24156+
24157+
workInProgress = null;
24158+
}
24159+
2411724160
function prepareFreshStack(root, lanes) {
2411824161
root.finishedWork = null;
2411924162
root.finishedLanes = NoLanes;
@@ -24127,27 +24170,7 @@ function prepareFreshStack(root, lanes) {
2412724170
cancelTimeout(timeoutHandle);
2412824171
}
2412924172

24130-
if (workInProgress !== null) {
24131-
var interruptedWork;
24132-
24133-
if (workInProgressSuspendedReason === NotSuspended) {
24134-
// Normal case. Work-in-progress hasn't started yet. Unwind all
24135-
// its parents.
24136-
interruptedWork = workInProgress.return;
24137-
} else {
24138-
// Work-in-progress is in suspended state. Reset the work loop and unwind
24139-
// both the suspended fiber and all its parents.
24140-
resetSuspendedWorkLoopOnUnwind();
24141-
interruptedWork = workInProgress;
24142-
}
24143-
24144-
while (interruptedWork !== null) {
24145-
var current = interruptedWork.alternate;
24146-
unwindInterruptedWork(current, interruptedWork);
24147-
interruptedWork = interruptedWork.return;
24148-
}
24149-
}
24150-
24173+
resetWorkInProgressStack();
2415124174
workInProgressRoot = root;
2415224175
var rootWorkInProgress = createWorkInProgress(root.current, null);
2415324176
workInProgress = rootWorkInProgress;
@@ -24206,6 +24229,17 @@ function handleThrow(root, thrownValue) {
2420624229
workInProgressSuspendedReason = shouldAttemptToSuspendUntilDataResolves()
2420724230
? SuspendedOnData
2420824231
: SuspendedOnImmediate;
24232+
} else if (thrownValue === SelectiveHydrationException) {
24233+
// An update flowed into a dehydrated boundary. Before we can apply the
24234+
// update, we need to finish hydrating. Interrupt the work-in-progress
24235+
// render so we can restart at the hydration lane.
24236+
//
24237+
// The ideal implementation would be able to switch contexts without
24238+
// unwinding the current stack.
24239+
//
24240+
// We could name this something more general but as of now it's the only
24241+
// case where we think this should happen.
24242+
workInProgressSuspendedReason = SuspendedOnHydration;
2420924243
} else {
2421024244
// This is a regular error.
2421124245
var isWakeable =
@@ -24431,7 +24465,7 @@ function renderRootSync(root, lanes) {
2443124465
markRenderStarted(lanes);
2443224466
}
2443324467

24434-
do {
24468+
outer: do {
2443524469
try {
2443624470
if (
2443724471
workInProgressSuspendedReason !== NotSuspended &&
@@ -24447,9 +24481,25 @@ function renderRootSync(root, lanes) {
2444724481
// function and fork the behavior some other way.
2444824482
var unitOfWork = workInProgress;
2444924483
var thrownValue = workInProgressThrownValue;
24450-
workInProgressSuspendedReason = NotSuspended;
24451-
workInProgressThrownValue = null;
24452-
unwindSuspendedUnitOfWork(unitOfWork, thrownValue); // Continue with the normal work loop.
24484+
24485+
switch (workInProgressSuspendedReason) {
24486+
case SuspendedOnHydration: {
24487+
// Selective hydration. An update flowed into a dehydrated tree.
24488+
// Interrupt the current render so the work loop can switch to the
24489+
// hydration lane.
24490+
resetWorkInProgressStack();
24491+
workInProgressRootExitStatus = RootDidNotComplete;
24492+
break outer;
24493+
}
24494+
24495+
default: {
24496+
// Continue with the normal work loop.
24497+
workInProgressSuspendedReason = NotSuspended;
24498+
workInProgressThrownValue = null;
24499+
unwindSuspendedUnitOfWork(unitOfWork, thrownValue);
24500+
break;
24501+
}
24502+
}
2445324503
}
2445424504

2445524505
workLoopSync();
@@ -24614,6 +24664,15 @@ function renderRootConcurrent(root, lanes) {
2461424664
break;
2461524665
}
2461624666

24667+
case SuspendedOnHydration: {
24668+
// Selective hydration. An update flowed into a dehydrated tree.
24669+
// Interrupt the current render so the work loop can switch to the
24670+
// hydration lane.
24671+
resetWorkInProgressStack();
24672+
workInProgressRootExitStatus = RootDidNotComplete;
24673+
break outer;
24674+
}
24675+
2461724676
default: {
2461824677
throw new Error(
2461924678
"Unexpected SuspendedReason. This is a bug in React."
@@ -25983,6 +26042,7 @@ if (replayFailedUnitOfWorkWithInvokeGuardedCallback) {
2598326042
if (
2598426043
didSuspendOrErrorWhileHydratingDEV() ||
2598526044
originalError === SuspenseException ||
26045+
originalError === SelectiveHydrationException ||
2598626046
(originalError !== null &&
2598726047
typeof originalError === "object" &&
2598826048
typeof originalError.then === "function")

0 commit comments

Comments
 (0)