Skip to content

Commit

Permalink
Fix unwinding context during selective hydration (#25876)
Browse files Browse the repository at this point in the history
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 <git@andrewclark.io>

DiffTrain build for [7efa9e5](7efa9e5)
[View git log for this commit](https://github.com/facebook/react/commits/7efa9e59707b341f10fab79724e0fca373187925)
  • Loading branch information
tyao1 committed Dec 15, 2022
1 parent 6b30524 commit 5507039
Show file tree
Hide file tree
Showing 28 changed files with 1,138 additions and 506 deletions.
2 changes: 1 addition & 1 deletion compiled/facebook-www/REVISION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
84a0a171ea0ecd25e287bd3d3dd30e932beb4677
7efa9e59707b341f10fab79724e0fca373187925
2 changes: 1 addition & 1 deletion compiled/facebook-www/REVISION_TRANSFORMS
Original file line number Diff line number Diff line change
@@ -1 +1 @@
84a0a171ea0ecd25e287bd3d3dd30e932beb4677
7efa9e59707b341f10fab79724e0fca373187925
2 changes: 1 addition & 1 deletion compiled/facebook-www/React-dev.classic.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ if (
}
"use strict";

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

// ATTENTION
// When adding new symbols to this file,
Expand Down
2 changes: 1 addition & 1 deletion compiled/facebook-www/React-dev.modern.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ if (
}
"use strict";

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

// ATTENTION
// When adding new symbols to this file,
Expand Down
2 changes: 1 addition & 1 deletion compiled/facebook-www/React-prod.classic.js
Original file line number Diff line number Diff line change
Expand Up @@ -643,4 +643,4 @@ exports.useSyncExternalStore = function(
);
};
exports.useTransition = useTransition;
exports.version = "18.3.0-www-classic-84a0a171e-20221214";
exports.version = "18.3.0-www-classic-7efa9e597-20221215";
2 changes: 1 addition & 1 deletion compiled/facebook-www/React-prod.modern.js
Original file line number Diff line number Diff line change
Expand Up @@ -635,4 +635,4 @@ exports.useSyncExternalStore = function(
);
};
exports.useTransition = useTransition;
exports.version = "18.3.0-www-modern-84a0a171e-20221214";
exports.version = "18.3.0-www-modern-7efa9e597-20221215";
2 changes: 1 addition & 1 deletion compiled/facebook-www/React-profiling.classic.js
Original file line number Diff line number Diff line change
Expand Up @@ -654,7 +654,7 @@ exports.useSyncExternalStore = function(
);
};
exports.useTransition = useTransition;
exports.version = "18.3.0-www-classic-84a0a171e-20221214";
exports.version = "18.3.0-www-classic-7efa9e597-20221215";

/* global __REACT_DEVTOOLS_GLOBAL_HOOK__ */
if (
Expand Down
2 changes: 1 addition & 1 deletion compiled/facebook-www/React-profiling.modern.js
Original file line number Diff line number Diff line change
Expand Up @@ -646,7 +646,7 @@ exports.useSyncExternalStore = function(
);
};
exports.useTransition = useTransition;
exports.version = "18.3.0-www-modern-84a0a171e-20221214";
exports.version = "18.3.0-www-modern-7efa9e597-20221215";

/* global __REACT_DEVTOOLS_GLOBAL_HOOK__ */
if (
Expand Down
130 changes: 95 additions & 35 deletions compiled/facebook-www/ReactART-dev.classic.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ function _assertThisInitialized(self) {
return self;
}

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

var LegacyRoot = 0;
var ConcurrentRoot = 1;
Expand Down Expand Up @@ -12716,7 +12716,14 @@ function getMarkerInstances() {
return null;
}

var ReactCurrentOwner$1 = ReactSharedInternals.ReactCurrentOwner;
var ReactCurrentOwner$1 = ReactSharedInternals.ReactCurrentOwner; // A special exception that's used to unwind the stack when an update flows
// into a dehydrated boundary.

var SelectiveHydrationException = new Error(
"This is not a real error. It's an implementation detail of React's " +
"selective hydration feature. If this leaks into userspace, it's a bug in " +
"React. Please file an issue."
);
var didReceiveUpdate = false;
var didWarnAboutBadClass;
var didWarnAboutModulePatternComponent;
Expand Down Expand Up @@ -14911,18 +14918,29 @@ function updateDehydratedSuspenseComponent(
current,
attemptHydrationAtLane,
eventTime
);
}
} // If we have scheduled higher pri work above, this will just abort the render
// since we now have higher priority work. We'll try to infinitely suspend until
// we yield. TODO: We could probably just force yielding earlier instead.
); // Throw a special object that signals to the work loop that it should
// interrupt the current render.
//
// Because we're inside a React-only execution stack, we don't
// strictly need to throw here — we could instead modify some internal
// work loop state. But using an exception means we don't need to
// check for this case on every iteration of the work loop. So doing
// it this way moves the check out of the fast path.

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

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

function resetWorkInProgressStack() {
if (workInProgress === null) return;
var interruptedWork;

if (workInProgressSuspendedReason === NotSuspended) {
// Normal case. Work-in-progress hasn't started yet. Unwind all
// its parents.
interruptedWork = workInProgress.return;
} else {
// Work-in-progress is in suspended state. Reset the work loop and unwind
// both the suspended fiber and all its parents.
resetSuspendedWorkLoopOnUnwind();
interruptedWork = workInProgress;
}

while (interruptedWork !== null) {
var current = interruptedWork.alternate;
unwindInterruptedWork(current, interruptedWork);
interruptedWork = interruptedWork.return;
}

workInProgress = null;
}

function prepareFreshStack(root, lanes) {
root.finishedWork = null;
root.finishedLanes = NoLanes;
Expand All @@ -24127,27 +24170,7 @@ function prepareFreshStack(root, lanes) {
cancelTimeout(timeoutHandle);
}

if (workInProgress !== null) {
var interruptedWork;

if (workInProgressSuspendedReason === NotSuspended) {
// Normal case. Work-in-progress hasn't started yet. Unwind all
// its parents.
interruptedWork = workInProgress.return;
} else {
// Work-in-progress is in suspended state. Reset the work loop and unwind
// both the suspended fiber and all its parents.
resetSuspendedWorkLoopOnUnwind();
interruptedWork = workInProgress;
}

while (interruptedWork !== null) {
var current = interruptedWork.alternate;
unwindInterruptedWork(current, interruptedWork);
interruptedWork = interruptedWork.return;
}
}

resetWorkInProgressStack();
workInProgressRoot = root;
var rootWorkInProgress = createWorkInProgress(root.current, null);
workInProgress = rootWorkInProgress;
Expand Down Expand Up @@ -24206,6 +24229,17 @@ function handleThrow(root, thrownValue) {
workInProgressSuspendedReason = shouldAttemptToSuspendUntilDataResolves()
? SuspendedOnData
: SuspendedOnImmediate;
} else if (thrownValue === SelectiveHydrationException) {
// An update flowed into a dehydrated boundary. Before we can apply the
// update, we need to finish hydrating. Interrupt the work-in-progress
// render so we can restart at the hydration lane.
//
// The ideal implementation would be able to switch contexts without
// unwinding the current stack.
//
// We could name this something more general but as of now it's the only
// case where we think this should happen.
workInProgressSuspendedReason = SuspendedOnHydration;
} else {
// This is a regular error.
var isWakeable =
Expand Down Expand Up @@ -24431,7 +24465,7 @@ function renderRootSync(root, lanes) {
markRenderStarted(lanes);
}

do {
outer: do {
try {
if (
workInProgressSuspendedReason !== NotSuspended &&
Expand All @@ -24447,9 +24481,25 @@ function renderRootSync(root, lanes) {
// function and fork the behavior some other way.
var unitOfWork = workInProgress;
var thrownValue = workInProgressThrownValue;
workInProgressSuspendedReason = NotSuspended;
workInProgressThrownValue = null;
unwindSuspendedUnitOfWork(unitOfWork, thrownValue); // Continue with the normal work loop.

switch (workInProgressSuspendedReason) {
case SuspendedOnHydration: {
// Selective hydration. An update flowed into a dehydrated tree.
// Interrupt the current render so the work loop can switch to the
// hydration lane.
resetWorkInProgressStack();
workInProgressRootExitStatus = RootDidNotComplete;
break outer;
}

default: {
// Continue with the normal work loop.
workInProgressSuspendedReason = NotSuspended;
workInProgressThrownValue = null;
unwindSuspendedUnitOfWork(unitOfWork, thrownValue);
break;
}
}
}

workLoopSync();
Expand Down Expand Up @@ -24614,6 +24664,15 @@ function renderRootConcurrent(root, lanes) {
break;
}

case SuspendedOnHydration: {
// Selective hydration. An update flowed into a dehydrated tree.
// Interrupt the current render so the work loop can switch to the
// hydration lane.
resetWorkInProgressStack();
workInProgressRootExitStatus = RootDidNotComplete;
break outer;
}

default: {
throw new Error(
"Unexpected SuspendedReason. This is a bug in React."
Expand Down Expand Up @@ -25983,6 +26042,7 @@ if (replayFailedUnitOfWorkWithInvokeGuardedCallback) {
if (
didSuspendOrErrorWhileHydratingDEV() ||
originalError === SuspenseException ||
originalError === SelectiveHydrationException ||
(originalError !== null &&
typeof originalError === "object" &&
typeof originalError.then === "function")
Expand Down
Loading

0 comments on commit 5507039

Please sign in to comment.