@@ -2173,10 +2173,10 @@ function renderRootSync(root: FiberRoot, lanes: Lanes) {
2173
2173
break outer;
2174
2174
}
2175
2175
default : {
2176
- // Continue with the normal work loop.
2176
+ // Unwind then continue with the normal work loop.
2177
2177
workInProgressSuspendedReason = NotSuspended ;
2178
2178
workInProgressThrownValue = null ;
2179
- unwindSuspendedUnitOfWork ( unitOfWork , thrownValue ) ;
2179
+ throwAndUnwindWorkLoop ( unitOfWork , thrownValue ) ;
2180
2180
break ;
2181
2181
}
2182
2182
}
@@ -2285,7 +2285,7 @@ function renderRootConcurrent(root: FiberRoot, lanes: Lanes) {
2285
2285
// Unwind then continue with the normal work loop.
2286
2286
workInProgressSuspendedReason = NotSuspended ;
2287
2287
workInProgressThrownValue = null ;
2288
- unwindSuspendedUnitOfWork ( unitOfWork , thrownValue ) ;
2288
+ throwAndUnwindWorkLoop ( unitOfWork , thrownValue ) ;
2289
2289
break ;
2290
2290
}
2291
2291
case SuspendedOnData : {
@@ -2343,7 +2343,7 @@ function renderRootConcurrent(root: FiberRoot, lanes: Lanes) {
2343
2343
// Otherwise, unwind then continue with the normal work loop.
2344
2344
workInProgressSuspendedReason = NotSuspended ;
2345
2345
workInProgressThrownValue = null ;
2346
- unwindSuspendedUnitOfWork ( unitOfWork , thrownValue ) ;
2346
+ throwAndUnwindWorkLoop ( unitOfWork , thrownValue ) ;
2347
2347
}
2348
2348
break ;
2349
2349
}
@@ -2400,7 +2400,7 @@ function renderRootConcurrent(root: FiberRoot, lanes: Lanes) {
2400
2400
// Otherwise, unwind then continue with the normal work loop.
2401
2401
workInProgressSuspendedReason = NotSuspended ;
2402
2402
workInProgressThrownValue = null ;
2403
- unwindSuspendedUnitOfWork ( unitOfWork , thrownValue ) ;
2403
+ throwAndUnwindWorkLoop ( unitOfWork , thrownValue ) ;
2404
2404
break ;
2405
2405
}
2406
2406
case SuspendedOnDeprecatedThrowPromise : {
@@ -2410,7 +2410,7 @@ function renderRootConcurrent(root: FiberRoot, lanes: Lanes) {
2410
2410
// always unwind.
2411
2411
workInProgressSuspendedReason = NotSuspended ;
2412
2412
workInProgressThrownValue = null ;
2413
- unwindSuspendedUnitOfWork ( unitOfWork , thrownValue ) ;
2413
+ throwAndUnwindWorkLoop ( unitOfWork , thrownValue ) ;
2414
2414
break ;
2415
2415
}
2416
2416
case SuspendedOnHydration : {
@@ -2610,7 +2610,7 @@ function replaySuspendedUnitOfWork(unitOfWork: Fiber): void {
2610
2610
ReactCurrentOwner . current = null ;
2611
2611
}
2612
2612
2613
- function unwindSuspendedUnitOfWork ( unitOfWork : Fiber , thrownValue : mixed ) {
2613
+ function throwAndUnwindWorkLoop ( unitOfWork : Fiber , thrownValue : mixed ) {
2614
2614
// This is a fork of performUnitOfWork specifcally for unwinding a fiber
2615
2615
// that threw an exception.
2616
2616
//
@@ -2655,90 +2655,62 @@ function unwindSuspendedUnitOfWork(unitOfWork: Fiber, thrownValue: mixed) {
2655
2655
throw error ;
2656
2656
}
2657
2657
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
+ }
2660
2673
}
2661
2674
2662
2675
function completeUnitOfWork ( unitOfWork : Fiber ) : void {
2663
2676
// Attempt to complete the current unit of work, then move to the next
2664
2677
// sibling. If there are no more siblings, return to the parent fiber.
2665
2678
let completedWork : Fiber = unitOfWork ;
2666
2679
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
+
2667
2692
// The current, flushed, state of this fiber is the alternate. Ideally
2668
2693
// nothing should rely on this, but relying on it here means that we don't
2669
2694
// need an additional field on the work in progress.
2670
2695
const current = completedWork . alternate ;
2671
2696
const returnFiber = completedWork . return ;
2672
2697
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 ) ;
2695
2702
} 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 ( ) ;
2730
2709
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 ;
2742
2714
}
2743
2715
2744
2716
const siblingFiber = completedWork . sibling ;
@@ -2760,6 +2732,87 @@ function completeUnitOfWork(unitOfWork: Fiber): void {
2760
2732
}
2761
2733
}
2762
2734
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
+
2763
2816
function commitRoot (
2764
2817
root : FiberRoot ,
2765
2818
recoverableErrors : null | Array < CapturedValue < mixed >> ,
0 commit comments