@@ -2173,10 +2173,10 @@ function renderRootSync(root: FiberRoot, lanes: Lanes) {
21732173 break outer;
21742174 }
21752175 default : {
2176- // Continue with the normal work loop.
2176+ // Unwind then continue with the normal work loop.
21772177 workInProgressSuspendedReason = NotSuspended ;
21782178 workInProgressThrownValue = null ;
2179- unwindSuspendedUnitOfWork ( unitOfWork , thrownValue ) ;
2179+ throwAndUnwindWorkLoop ( unitOfWork , thrownValue ) ;
21802180 break ;
21812181 }
21822182 }
@@ -2285,7 +2285,7 @@ function renderRootConcurrent(root: FiberRoot, lanes: Lanes) {
22852285 // Unwind then continue with the normal work loop.
22862286 workInProgressSuspendedReason = NotSuspended ;
22872287 workInProgressThrownValue = null ;
2288- unwindSuspendedUnitOfWork ( unitOfWork , thrownValue ) ;
2288+ throwAndUnwindWorkLoop ( unitOfWork , thrownValue ) ;
22892289 break ;
22902290 }
22912291 case SuspendedOnData : {
@@ -2343,7 +2343,7 @@ function renderRootConcurrent(root: FiberRoot, lanes: Lanes) {
23432343 // Otherwise, unwind then continue with the normal work loop.
23442344 workInProgressSuspendedReason = NotSuspended ;
23452345 workInProgressThrownValue = null ;
2346- unwindSuspendedUnitOfWork ( unitOfWork , thrownValue ) ;
2346+ throwAndUnwindWorkLoop ( unitOfWork , thrownValue ) ;
23472347 }
23482348 break ;
23492349 }
@@ -2400,7 +2400,7 @@ function renderRootConcurrent(root: FiberRoot, lanes: Lanes) {
24002400 // Otherwise, unwind then continue with the normal work loop.
24012401 workInProgressSuspendedReason = NotSuspended ;
24022402 workInProgressThrownValue = null ;
2403- unwindSuspendedUnitOfWork ( unitOfWork , thrownValue ) ;
2403+ throwAndUnwindWorkLoop ( unitOfWork , thrownValue ) ;
24042404 break ;
24052405 }
24062406 case SuspendedOnDeprecatedThrowPromise : {
@@ -2410,7 +2410,7 @@ function renderRootConcurrent(root: FiberRoot, lanes: Lanes) {
24102410 // always unwind.
24112411 workInProgressSuspendedReason = NotSuspended ;
24122412 workInProgressThrownValue = null ;
2413- unwindSuspendedUnitOfWork ( unitOfWork , thrownValue ) ;
2413+ throwAndUnwindWorkLoop ( unitOfWork , thrownValue ) ;
24142414 break ;
24152415 }
24162416 case SuspendedOnHydration : {
@@ -2610,7 +2610,7 @@ function replaySuspendedUnitOfWork(unitOfWork: Fiber): void {
26102610 ReactCurrentOwner . current = null ;
26112611}
26122612
2613- function unwindSuspendedUnitOfWork ( unitOfWork : Fiber , thrownValue : mixed ) {
2613+ function throwAndUnwindWorkLoop ( unitOfWork : Fiber , thrownValue : mixed ) {
26142614 // This is a fork of performUnitOfWork specifcally for unwinding a fiber
26152615 // that threw an exception.
26162616 //
@@ -2655,90 +2655,62 @@ function unwindSuspendedUnitOfWork(unitOfWork: Fiber, thrownValue: mixed) {
26552655 throw error ;
26562656 }
26572657
2658- // Return to the normal work loop.
2659- completeUnitOfWork ( unitOfWork ) ;
2658+ if ( unitOfWork . flags & Incomplete ) {
2659+ // Unwind the stack until we reach the nearest boundary.
2660+ unwindUnitOfWork ( unitOfWork ) ;
2661+ } else {
2662+ // Although the fiber suspended, we're intentionally going to commit it in
2663+ // an inconsistent state. We can do this safely in cases where we know the
2664+ // inconsistent tree will be hidden.
2665+ //
2666+ // This currently only applies to Legacy Suspense implementation, but we may
2667+ // port a version of this to concurrent roots, too, when performing a
2668+ // synchronous render. Because that will allow us to mutate the tree as we
2669+ // go instead of buffering mutations until the end. Though it's unclear if
2670+ // this particular path is how that would be implemented.
2671+ completeUnitOfWork ( unitOfWork ) ;
2672+ }
26602673}
26612674
26622675function completeUnitOfWork ( unitOfWork : Fiber ) : void {
26632676 // Attempt to complete the current unit of work, then move to the next
26642677 // sibling. If there are no more siblings, return to the parent fiber.
26652678 let completedWork : Fiber = unitOfWork ;
26662679 do {
2680+ if ( ( completedWork . flags & Incomplete ) !== NoFlags ) {
2681+ // This fiber did not complete, because one of its children did not
2682+ // complete. Switch to unwinding the stack instead of completing it.
2683+ //
2684+ // The reason "unwind" and "complete" is interleaved is because when
2685+ // something suspends, we continue rendering the siblings even though
2686+ // they will be replaced by a fallback.
2687+ // TODO: Disable sibling prerendering, then remove this branch.
2688+ unwindUnitOfWork ( completedWork ) ;
2689+ return ;
2690+ }
2691+
26672692 // The current, flushed, state of this fiber is the alternate. Ideally
26682693 // nothing should rely on this, but relying on it here means that we don't
26692694 // need an additional field on the work in progress.
26702695 const current = completedWork . alternate ;
26712696 const returnFiber = completedWork . return ;
26722697
2673- // Check if the work completed or if something threw.
2674- if ( ( completedWork . flags & Incomplete ) === NoFlags ) {
2675- setCurrentDebugFiberInDEV ( completedWork ) ;
2676- let next ;
2677- if (
2678- ! enableProfilerTimer ||
2679- ( completedWork . mode & ProfileMode ) === NoMode
2680- ) {
2681- next = completeWork ( current , completedWork , renderLanes ) ;
2682- } else {
2683- startProfilerTimer ( completedWork ) ;
2684- next = completeWork ( current , completedWork , renderLanes ) ;
2685- // Update render duration assuming we didn't error.
2686- stopProfilerTimerIfRunningAndRecordDelta ( completedWork , false ) ;
2687- }
2688- resetCurrentDebugFiberInDEV ( ) ;
2689-
2690- if ( next !== null ) {
2691- // Completing this fiber spawned new work. Work on that next.
2692- workInProgress = next ;
2693- return ;
2694- }
2698+ setCurrentDebugFiberInDEV ( completedWork ) ;
2699+ let next ;
2700+ if ( ! enableProfilerTimer || ( completedWork . mode & ProfileMode ) === NoMode ) {
2701+ next = completeWork ( current , completedWork , renderLanes ) ;
26952702 } else {
2696- // This fiber did not complete because something threw. Pop values off
2697- // the stack without entering the complete phase. If this is a boundary,
2698- // capture values if possible.
2699- const next = unwindWork ( current , completedWork , renderLanes ) ;
2700-
2701- // Because this fiber did not complete, don't reset its lanes.
2702-
2703- if ( next !== null ) {
2704- // If completing this work spawned new work, do that next. We'll come
2705- // back here again.
2706- // Since we're restarting, remove anything that is not a host effect
2707- // from the effect tag.
2708- next . flags &= HostEffectMask ;
2709- workInProgress = next ;
2710- return ;
2711- }
2712-
2713- if (
2714- enableProfilerTimer &&
2715- ( completedWork . mode & ProfileMode ) !== NoMode
2716- ) {
2717- // Record the render duration for the fiber that errored.
2718- stopProfilerTimerIfRunningAndRecordDelta ( completedWork , false ) ;
2719-
2720- // Include the time spent working on failed children before continuing.
2721- let actualDuration = completedWork . actualDuration ;
2722- let child = completedWork . child ;
2723- while ( child !== null ) {
2724- // $FlowFixMe[unsafe-addition] addition with possible null/undefined value
2725- actualDuration += child . actualDuration ;
2726- child = child . sibling ;
2727- }
2728- completedWork . actualDuration = actualDuration ;
2729- }
2703+ startProfilerTimer ( completedWork ) ;
2704+ next = completeWork ( current , completedWork , renderLanes ) ;
2705+ // Update render duration assuming we didn't error.
2706+ stopProfilerTimerIfRunningAndRecordDelta ( completedWork , false ) ;
2707+ }
2708+ resetCurrentDebugFiberInDEV ( ) ;
27302709
2731- if ( returnFiber !== null ) {
2732- // Mark the parent fiber as incomplete and clear its subtree flags.
2733- returnFiber . flags |= Incomplete ;
2734- returnFiber . subtreeFlags = NoFlags ;
2735- returnFiber . deletions = null ;
2736- } else {
2737- // We've unwound all the way to the root.
2738- workInProgressRootExitStatus = RootDidNotComplete ;
2739- workInProgress = null ;
2740- return ;
2741- }
2710+ if ( next !== null ) {
2711+ // Completing this fiber spawned new work. Work on that next.
2712+ workInProgress = next ;
2713+ return ;
27422714 }
27432715
27442716 const siblingFiber = completedWork . sibling ;
@@ -2760,6 +2732,87 @@ function completeUnitOfWork(unitOfWork: Fiber): void {
27602732 }
27612733}
27622734
2735+ function unwindUnitOfWork ( unitOfWork : Fiber ) : void {
2736+ let incompleteWork : Fiber = unitOfWork ;
2737+ do {
2738+ // The current, flushed, state of this fiber is the alternate. Ideally
2739+ // nothing should rely on this, but relying on it here means that we don't
2740+ // need an additional field on the work in progress.
2741+ const current = incompleteWork . alternate ;
2742+
2743+ // This fiber did not complete because something threw. Pop values off
2744+ // the stack without entering the complete phase. If this is a boundary,
2745+ // capture values if possible.
2746+ const next = unwindWork ( current , incompleteWork , renderLanes ) ;
2747+
2748+ // Because this fiber did not complete, don't reset its lanes.
2749+
2750+ if ( next !== null ) {
2751+ // Found a boundary that can handle this exception. Re-renter the
2752+ // begin phase. This branch will return us to the normal work loop.
2753+ //
2754+ // Since we're restarting, remove anything that is not a host effect
2755+ // from the effect tag.
2756+ next . flags &= HostEffectMask ;
2757+ workInProgress = next ;
2758+ return ;
2759+ }
2760+
2761+ // Keep unwinding until we reach either a boundary or the root.
2762+
2763+ if ( enableProfilerTimer && ( incompleteWork . mode & ProfileMode ) !== NoMode ) {
2764+ // Record the render duration for the fiber that errored.
2765+ stopProfilerTimerIfRunningAndRecordDelta ( incompleteWork , false ) ;
2766+
2767+ // Include the time spent working on failed children before continuing.
2768+ let actualDuration = incompleteWork . actualDuration ;
2769+ let child = incompleteWork . child ;
2770+ while ( child !== null ) {
2771+ // $FlowFixMe[unsafe-addition] addition with possible null/undefined value
2772+ actualDuration += child . actualDuration ;
2773+ child = child . sibling ;
2774+ }
2775+ incompleteWork . actualDuration = actualDuration ;
2776+ }
2777+
2778+ // TODO: Once we stop prerendering siblings, instead of resetting the parent
2779+ // of the node being unwound, we should be able to reset node itself as we
2780+ // unwind the stack. Saves an additional null check.
2781+ const returnFiber = incompleteWork . return ;
2782+ if ( returnFiber !== null ) {
2783+ // Mark the parent fiber as incomplete and clear its subtree flags.
2784+ // TODO: Once we stop prerendering siblings, we may be able to get rid of
2785+ // the Incomplete flag because unwinding to the nearest boundary will
2786+ // happen synchronously.
2787+ returnFiber . flags |= Incomplete ;
2788+ returnFiber . subtreeFlags = NoFlags ;
2789+ returnFiber . deletions = null ;
2790+ }
2791+
2792+ // If there are siblings, work on them now even though they're going to be
2793+ // replaced by a fallback. We're "prerendering" them. Historically our
2794+ // rationale for this behavior has been to initiate any lazy data requests
2795+ // in the siblings, and also to warm up the CPU cache.
2796+ // TODO: Don't prerender siblings. With `use`, we suspend the work loop
2797+ // until the data has resolved, anyway.
2798+ const siblingFiber = incompleteWork . sibling ;
2799+ if ( siblingFiber !== null ) {
2800+ // This branch will return us to the normal work loop.
2801+ workInProgress = siblingFiber ;
2802+ return ;
2803+ }
2804+ // Otherwise, return to the parent
2805+ // $FlowFixMe[incompatible-type] we bail out when we get a null
2806+ incompleteWork = returnFiber ;
2807+ // Update the next thing we're working on in case something throws.
2808+ workInProgress = incompleteWork ;
2809+ } while ( incompleteWork !== null ) ;
2810+
2811+ // We've unwound all the way to the root.
2812+ workInProgressRootExitStatus = RootDidNotComplete ;
2813+ workInProgress = null ;
2814+ }
2815+
27632816function commitRoot (
27642817 root : FiberRoot ,
27652818 recoverableErrors : null | Array < CapturedValue < mixed >> ,
0 commit comments