-
Notifications
You must be signed in to change notification settings - Fork 51k
Fix unwinding context during selective hydration #25876
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 6 commits
ec94d67
12b14c4
e2f4ff3
b37a68a
7e6e61a
b7c8958
c27ac20
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -175,6 +175,7 @@ import { | |
| } from './ReactEventPriorities'; | ||
| import {requestCurrentTransition, NoTransition} from './ReactFiberTransition'; | ||
| import { | ||
| SelectiveHydrationException, | ||
| beginWork as originalBeginWork, | ||
| replayFunctionComponent, | ||
| } from './ReactFiberBeginWork'; | ||
|
|
@@ -316,13 +317,14 @@ let workInProgress: Fiber | null = null; | |
| // The lanes we're rendering | ||
| let workInProgressRootRenderLanes: Lanes = NoLanes; | ||
|
|
||
| opaque type SuspendedReason = 0 | 1 | 2 | 3 | 4 | 5; | ||
| opaque type SuspendedReason = 0 | 1 | 2 | 3 | 4 | 5 | 6; | ||
| const NotSuspended: SuspendedReason = 0; | ||
| const SuspendedOnError: SuspendedReason = 1; | ||
| const SuspendedOnData: SuspendedReason = 2; | ||
| const SuspendedOnImmediate: SuspendedReason = 3; | ||
| const SuspendedOnDeprecatedThrowPromise: SuspendedReason = 4; | ||
| const SuspendedAndReadyToUnwind: SuspendedReason = 5; | ||
| const SuspendedOnHydration: SuspendedReason = 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 | ||
|
|
@@ -1701,6 +1703,30 @@ export function getRenderLanes(): Lanes { | |
| return renderLanes; | ||
| } | ||
|
|
||
| function resetWorkInProgressStack() { | ||
| if (workInProgress == null) return; | ||
| let 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) { | ||
| const current = interruptedWork.alternate; | ||
| unwindInterruptedWork( | ||
| current, | ||
| interruptedWork, | ||
| workInProgressRootRenderLanes, | ||
| ); | ||
| interruptedWork = interruptedWork.return; | ||
| } | ||
| } | ||
|
|
||
| function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber { | ||
| root.finishedWork = null; | ||
| root.finishedLanes = NoLanes; | ||
|
|
@@ -1714,28 +1740,7 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber { | |
| cancelTimeout(timeoutHandle); | ||
| } | ||
|
|
||
| if (workInProgress !== null) { | ||
| let 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) { | ||
| const current = interruptedWork.alternate; | ||
| unwindInterruptedWork( | ||
| current, | ||
| interruptedWork, | ||
| workInProgressRootRenderLanes, | ||
| ); | ||
| interruptedWork = interruptedWork.return; | ||
| } | ||
| } | ||
| resetWorkInProgressStack(); | ||
| workInProgressRoot = root; | ||
| const rootWorkInProgress = createWorkInProgress(root.current, null); | ||
| workInProgress = rootWorkInProgress; | ||
|
|
@@ -1797,6 +1802,17 @@ function handleThrow(root, thrownValue): void { | |
| 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. | ||
| const isWakeable = | ||
|
|
@@ -2038,7 +2054,7 @@ function renderRootSync(root: FiberRoot, lanes: Lanes) { | |
| markRenderStarted(lanes); | ||
| } | ||
|
|
||
| do { | ||
| outer: do { | ||
| try { | ||
| if ( | ||
| workInProgressSuspendedReason !== NotSuspended && | ||
|
|
@@ -2054,11 +2070,23 @@ function renderRootSync(root: FiberRoot, lanes: Lanes) { | |
| // function and fork the behavior some other way. | ||
| const unitOfWork = workInProgress; | ||
| const 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. | ||
| workInProgress = null; | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A fix for this is added in the PR to add the SyncHydrationLane #25878, because it is only reproducible there.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it could technically be reproducible if the update expired. So how about we merge both of these changes in the same PR? That way they land as an atomic unit in www. In other words: don't merge this PR on its own. Let's merge the whole stack in #25878. |
||
| workInProgressRootExitStatus = RootDidNotComplete; | ||
| break outer; | ||
| } | ||
| default: { | ||
| // Continue with the normal work loop. | ||
| workInProgressSuspendedReason = NotSuspended; | ||
| workInProgressThrownValue = null; | ||
| unwindSuspendedUnitOfWork(unitOfWork, thrownValue); | ||
| break; | ||
| } | ||
| } | ||
| } | ||
| workLoopSync(); | ||
| break; | ||
|
|
@@ -2216,6 +2244,15 @@ function renderRootConcurrent(root: FiberRoot, lanes: Lanes) { | |
| unwindSuspendedUnitOfWork(unitOfWork, thrownValue); | ||
| 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; | ||
| workInProgress = null; | ||
| break outer; | ||
| } | ||
| default: { | ||
| throw new Error( | ||
| 'Unexpected SuspendedReason. This is a bug in React.', | ||
|
|
@@ -3741,6 +3778,7 @@ if (__DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback) { | |
| if ( | ||
| didSuspendOrErrorWhileHydratingDEV() || | ||
| originalError === SuspenseException || | ||
| originalError === SelectiveHydrationException || | ||
| (originalError !== null && | ||
| typeof originalError === 'object' && | ||
| typeof originalError.then === 'function') | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.