@@ -2085,10 +2085,10 @@ function renderRootSync(root: FiberRoot, lanes: Lanes) {
2085
2085
break outer;
2086
2086
}
2087
2087
default : {
2088
- // Continue with the normal work loop.
2088
+ // Unwind then continue with the normal work loop.
2089
2089
workInProgressSuspendedReason = NotSuspended ;
2090
2090
workInProgressThrownValue = null ;
2091
- unwindSuspendedUnitOfWork ( unitOfWork , thrownValue ) ;
2091
+ throwAndUnwindWorkLoop ( unitOfWork , thrownValue ) ;
2092
2092
break ;
2093
2093
}
2094
2094
}
@@ -2197,7 +2197,7 @@ function renderRootConcurrent(root: FiberRoot, lanes: Lanes) {
2197
2197
// Unwind then continue with the normal work loop.
2198
2198
workInProgressSuspendedReason = NotSuspended ;
2199
2199
workInProgressThrownValue = null ;
2200
- unwindSuspendedUnitOfWork ( unitOfWork , thrownValue ) ;
2200
+ throwAndUnwindWorkLoop ( unitOfWork , thrownValue ) ;
2201
2201
break ;
2202
2202
}
2203
2203
case SuspendedOnData : {
@@ -2250,7 +2250,7 @@ function renderRootConcurrent(root: FiberRoot, lanes: Lanes) {
2250
2250
// Otherwise, unwind then continue with the normal work loop.
2251
2251
workInProgressSuspendedReason = NotSuspended ;
2252
2252
workInProgressThrownValue = null ;
2253
- unwindSuspendedUnitOfWork ( unitOfWork , thrownValue ) ;
2253
+ throwAndUnwindWorkLoop ( unitOfWork , thrownValue ) ;
2254
2254
}
2255
2255
break ;
2256
2256
}
@@ -2261,7 +2261,7 @@ function renderRootConcurrent(root: FiberRoot, lanes: Lanes) {
2261
2261
// always unwind.
2262
2262
workInProgressSuspendedReason = NotSuspended ;
2263
2263
workInProgressThrownValue = null ;
2264
- unwindSuspendedUnitOfWork ( unitOfWork , thrownValue ) ;
2264
+ throwAndUnwindWorkLoop ( unitOfWork , thrownValue ) ;
2265
2265
break ;
2266
2266
}
2267
2267
case SuspendedOnHydration : {
@@ -2461,7 +2461,7 @@ function replaySuspendedUnitOfWork(unitOfWork: Fiber): void {
2461
2461
ReactCurrentOwner . current = null ;
2462
2462
}
2463
2463
2464
- function unwindSuspendedUnitOfWork ( unitOfWork : Fiber , thrownValue : mixed ) {
2464
+ function throwAndUnwindWorkLoop ( unitOfWork : Fiber , thrownValue : mixed ) {
2465
2465
// This is a fork of performUnitOfWork specifcally for unwinding a fiber
2466
2466
// that threw an exception.
2467
2467
//
@@ -2506,90 +2506,62 @@ function unwindSuspendedUnitOfWork(unitOfWork: Fiber, thrownValue: mixed) {
2506
2506
throw error ;
2507
2507
}
2508
2508
2509
- // Return to the normal work loop.
2510
- completeUnitOfWork ( unitOfWork ) ;
2509
+ if ( unitOfWork . flags & Incomplete ) {
2510
+ // Unwind the stack until we reach the nearest boundary.
2511
+ unwindUnitOfWork ( unitOfWork ) ;
2512
+ } else {
2513
+ // Although the fiber suspended, we're intentionally going to commit it in
2514
+ // an inconsistent state. We can do this safely in cases where we know the
2515
+ // inconsistent tree will be hidden.
2516
+ //
2517
+ // This currently only applies to Legacy Suspense implementation, but we may
2518
+ // port a version of this to concurrent roots, too, when performing a
2519
+ // synchronous render. Because that will allow us to mutate the tree as we
2520
+ // go instead of buffering mutations until the end. Though it's unclear if
2521
+ // this particular path is how that would be implemented.
2522
+ completeUnitOfWork ( unitOfWork ) ;
2523
+ }
2511
2524
}
2512
2525
2513
2526
function completeUnitOfWork ( unitOfWork : Fiber ) : void {
2514
2527
// Attempt to complete the current unit of work, then move to the next
2515
2528
// sibling. If there are no more siblings, return to the parent fiber.
2516
2529
let completedWork : Fiber = unitOfWork ;
2517
2530
do {
2531
+ if ( ( completedWork . flags & Incomplete ) !== NoFlags ) {
2532
+ // This fiber did not complete, because one of its children did not
2533
+ // complete. Switch to unwinding the stack instead of completing it.
2534
+ //
2535
+ // The reason "unwind" and "complete" is interleaved is because when
2536
+ // something suspends, we continue rendering the siblings even though
2537
+ // they will be replaced by a fallback.
2538
+ // TODO: Disable sibling prerendering, then remove this branch.
2539
+ unwindUnitOfWork ( completedWork ) ;
2540
+ return ;
2541
+ }
2542
+
2518
2543
// The current, flushed, state of this fiber is the alternate. Ideally
2519
2544
// nothing should rely on this, but relying on it here means that we don't
2520
2545
// need an additional field on the work in progress.
2521
2546
const current = completedWork . alternate ;
2522
2547
const returnFiber = completedWork . return ;
2523
2548
2524
- // Check if the work completed or if something threw.
2525
- if ( ( completedWork . flags & Incomplete ) === NoFlags ) {
2526
- setCurrentDebugFiberInDEV ( completedWork ) ;
2527
- let next ;
2528
- if (
2529
- ! enableProfilerTimer ||
2530
- ( completedWork . mode & ProfileMode ) === NoMode
2531
- ) {
2532
- next = completeWork ( current , completedWork , renderLanes ) ;
2533
- } else {
2534
- startProfilerTimer ( completedWork ) ;
2535
- next = completeWork ( current , completedWork , renderLanes ) ;
2536
- // Update render duration assuming we didn't error.
2537
- stopProfilerTimerIfRunningAndRecordDelta ( completedWork , false ) ;
2538
- }
2539
- resetCurrentDebugFiberInDEV ( ) ;
2540
-
2541
- if ( next !== null ) {
2542
- // Completing this fiber spawned new work. Work on that next.
2543
- workInProgress = next ;
2544
- return ;
2545
- }
2549
+ setCurrentDebugFiberInDEV ( completedWork ) ;
2550
+ let next ;
2551
+ if ( ! enableProfilerTimer || ( completedWork . mode & ProfileMode ) === NoMode ) {
2552
+ next = completeWork ( current , completedWork , renderLanes ) ;
2546
2553
} else {
2547
- // This fiber did not complete because something threw. Pop values off
2548
- // the stack without entering the complete phase. If this is a boundary,
2549
- // capture values if possible.
2550
- const next = unwindWork ( current , completedWork , renderLanes ) ;
2551
-
2552
- // Because this fiber did not complete, don't reset its lanes.
2553
-
2554
- if ( next !== null ) {
2555
- // If completing this work spawned new work, do that next. We'll come
2556
- // back here again.
2557
- // Since we're restarting, remove anything that is not a host effect
2558
- // from the effect tag.
2559
- next . flags &= HostEffectMask ;
2560
- workInProgress = next ;
2561
- return ;
2562
- }
2563
-
2564
- if (
2565
- enableProfilerTimer &&
2566
- ( completedWork . mode & ProfileMode ) !== NoMode
2567
- ) {
2568
- // Record the render duration for the fiber that errored.
2569
- stopProfilerTimerIfRunningAndRecordDelta ( completedWork , false ) ;
2570
-
2571
- // Include the time spent working on failed children before continuing.
2572
- let actualDuration = completedWork . actualDuration ;
2573
- let child = completedWork . child ;
2574
- while ( child !== null ) {
2575
- // $FlowFixMe[unsafe-addition] addition with possible null/undefined value
2576
- actualDuration += child . actualDuration ;
2577
- child = child . sibling ;
2578
- }
2579
- completedWork . actualDuration = actualDuration ;
2580
- }
2554
+ startProfilerTimer ( completedWork ) ;
2555
+ next = completeWork ( current , completedWork , renderLanes ) ;
2556
+ // Update render duration assuming we didn't error.
2557
+ stopProfilerTimerIfRunningAndRecordDelta ( completedWork , false ) ;
2558
+ }
2559
+ resetCurrentDebugFiberInDEV ( ) ;
2581
2560
2582
- if ( returnFiber !== null ) {
2583
- // Mark the parent fiber as incomplete and clear its subtree flags.
2584
- returnFiber . flags |= Incomplete ;
2585
- returnFiber . subtreeFlags = NoFlags ;
2586
- returnFiber . deletions = null ;
2587
- } else {
2588
- // We've unwound all the way to the root.
2589
- workInProgressRootExitStatus = RootDidNotComplete ;
2590
- workInProgress = null ;
2591
- return ;
2592
- }
2561
+ if ( next !== null ) {
2562
+ // Completing this fiber spawned new work. Work on that next.
2563
+ workInProgress = next ;
2564
+ return ;
2593
2565
}
2594
2566
2595
2567
const siblingFiber = completedWork . sibling ;
@@ -2611,6 +2583,87 @@ function completeUnitOfWork(unitOfWork: Fiber): void {
2611
2583
}
2612
2584
}
2613
2585
2586
+ function unwindUnitOfWork ( unitOfWork : Fiber ) : void {
2587
+ let incompleteWork : Fiber = unitOfWork ;
2588
+ do {
2589
+ // The current, flushed, state of this fiber is the alternate. Ideally
2590
+ // nothing should rely on this, but relying on it here means that we don't
2591
+ // need an additional field on the work in progress.
2592
+ const current = incompleteWork . alternate ;
2593
+
2594
+ // This fiber did not complete because something threw. Pop values off
2595
+ // the stack without entering the complete phase. If this is a boundary,
2596
+ // capture values if possible.
2597
+ const next = unwindWork ( current , incompleteWork , renderLanes ) ;
2598
+
2599
+ // Because this fiber did not complete, don't reset its lanes.
2600
+
2601
+ if ( next !== null ) {
2602
+ // Found a boundary that can handle this exception. Re-renter the
2603
+ // begin phase. This branch will return us to the normal work loop.
2604
+ //
2605
+ // Since we're restarting, remove anything that is not a host effect
2606
+ // from the effect tag.
2607
+ next . flags &= HostEffectMask ;
2608
+ workInProgress = next ;
2609
+ return ;
2610
+ }
2611
+
2612
+ // Keep unwinding until we reach either a boundary or the root.
2613
+
2614
+ if ( enableProfilerTimer && ( incompleteWork . mode & ProfileMode ) !== NoMode ) {
2615
+ // Record the render duration for the fiber that errored.
2616
+ stopProfilerTimerIfRunningAndRecordDelta ( incompleteWork , false ) ;
2617
+
2618
+ // Include the time spent working on failed children before continuing.
2619
+ let actualDuration = incompleteWork . actualDuration ;
2620
+ let child = incompleteWork . child ;
2621
+ while ( child !== null ) {
2622
+ // $FlowFixMe[unsafe-addition] addition with possible null/undefined value
2623
+ actualDuration += child . actualDuration ;
2624
+ child = child . sibling ;
2625
+ }
2626
+ incompleteWork . actualDuration = actualDuration ;
2627
+ }
2628
+
2629
+ // TODO: Once we stop prerendering siblings, instead of resetting the parent
2630
+ // of the node being unwound, we should be able to reset node itself as we
2631
+ // unwind the stack. Saves an additional null check.
2632
+ const returnFiber = incompleteWork . return ;
2633
+ if ( returnFiber !== null ) {
2634
+ // Mark the parent fiber as incomplete and clear its subtree flags.
2635
+ // TODO: Once we stop prerendering siblings, we may be able to get rid of
2636
+ // the Incomplete flag because unwinding to the nearest boundary will
2637
+ // happen synchronously.
2638
+ returnFiber . flags |= Incomplete ;
2639
+ returnFiber . subtreeFlags = NoFlags ;
2640
+ returnFiber . deletions = null ;
2641
+ }
2642
+
2643
+ // If there are siblings, work on them now even though they're going to be
2644
+ // replaced by a fallback. We're "prerendering" them. Historically our
2645
+ // rationale for this behavior has been to initiate any lazy data requests
2646
+ // in the siblings, and also to warm up the CPU cache.
2647
+ // TODO: Don't prerender siblings. With `use`, we suspend the work loop
2648
+ // until the data has resolved, anyway.
2649
+ const siblingFiber = incompleteWork . sibling ;
2650
+ if ( siblingFiber !== null ) {
2651
+ // This branch will return us to the normal work loop.
2652
+ workInProgress = siblingFiber ;
2653
+ return ;
2654
+ }
2655
+ // Otherwise, return to the parent
2656
+ // $FlowFixMe[incompatible-type] we bail out when we get a null
2657
+ incompleteWork = returnFiber ;
2658
+ // Update the next thing we're working on in case something throws.
2659
+ workInProgress = incompleteWork ;
2660
+ } while ( incompleteWork !== null ) ;
2661
+
2662
+ // We've unwound all the way to the root.
2663
+ workInProgressRootExitStatus = RootDidNotComplete ;
2664
+ workInProgress = null ;
2665
+ }
2666
+
2614
2667
function commitRoot (
2615
2668
root : FiberRoot ,
2616
2669
recoverableErrors : null | Array < CapturedValue < mixed >> ,
0 commit comments