@@ -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,59 @@ 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
2536
+ unwindUnitOfWork ( completedWork ) ;
2537
+ return ;
2538
+ }
2539
+
2518
2540
// The current, flushed, state of this fiber is the alternate. Ideally
2519
2541
// nothing should rely on this, but relying on it here means that we don't
2520
2542
// need an additional field on the work in progress.
2521
2543
const current = completedWork . alternate ;
2522
2544
const returnFiber = completedWork . return ;
2523
2545
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
- }
2546
+ setCurrentDebugFiberInDEV ( completedWork ) ;
2547
+ let next ;
2548
+ if ( ! enableProfilerTimer || ( completedWork . mode & ProfileMode ) === NoMode ) {
2549
+ next = completeWork ( current , completedWork , renderLanes ) ;
2546
2550
} 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
- }
2551
+ startProfilerTimer ( completedWork ) ;
2552
+ next = completeWork ( current , completedWork , renderLanes ) ;
2553
+ // Update render duration assuming we didn't error.
2554
+ stopProfilerTimerIfRunningAndRecordDelta ( completedWork , false ) ;
2555
+ }
2556
+ resetCurrentDebugFiberInDEV ( ) ;
2581
2557
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
- }
2558
+ if ( next !== null ) {
2559
+ // Completing this fiber spawned new work. Work on that next.
2560
+ workInProgress = next ;
2561
+ return ;
2593
2562
}
2594
2563
2595
2564
const siblingFiber = completedWork . sibling ;
@@ -2611,6 +2580,87 @@ function completeUnitOfWork(unitOfWork: Fiber): void {
2611
2580
}
2612
2581
}
2613
2582
2583
+ function unwindUnitOfWork ( unitOfWork : Fiber ) : void {
2584
+ let incompleteWork : Fiber = unitOfWork ;
2585
+ do {
2586
+ // The current, flushed, state of this fiber is the alternate. Ideally
2587
+ // nothing should rely on this, but relying on it here means that we don't
2588
+ // need an additional field on the work in progress.
2589
+ const current = incompleteWork . alternate ;
2590
+
2591
+ // This fiber did not complete because something threw. Pop values off
2592
+ // the stack without entering the complete phase. If this is a boundary,
2593
+ // capture values if possible.
2594
+ const next = unwindWork ( current , incompleteWork , renderLanes ) ;
2595
+
2596
+ // Because this fiber did not complete, don't reset its lanes.
2597
+
2598
+ if ( next !== null ) {
2599
+ // Found a boundary that can handle this exception. Re-renter the
2600
+ // begin phase. This branch will return us to the normal work loop.
2601
+ //
2602
+ // Since we're restarting, remove anything that is not a host effect
2603
+ // from the effect tag.
2604
+ next . flags &= HostEffectMask ;
2605
+ workInProgress = next ;
2606
+ return ;
2607
+ }
2608
+
2609
+ // Keep unwinding until we reach either a boundary or the root.
2610
+
2611
+ if ( enableProfilerTimer && ( incompleteWork . mode & ProfileMode ) !== NoMode ) {
2612
+ // Record the render duration for the fiber that errored.
2613
+ stopProfilerTimerIfRunningAndRecordDelta ( incompleteWork , false ) ;
2614
+
2615
+ // Include the time spent working on failed children before continuing.
2616
+ let actualDuration = incompleteWork . actualDuration ;
2617
+ let child = incompleteWork . child ;
2618
+ while ( child !== null ) {
2619
+ // $FlowFixMe[unsafe-addition] addition with possible null/undefined value
2620
+ actualDuration += child . actualDuration ;
2621
+ child = child . sibling ;
2622
+ }
2623
+ incompleteWork . actualDuration = actualDuration ;
2624
+ }
2625
+
2626
+ // TODO: Once we stop prerendering siblings, instead of resetting the parent
2627
+ // of the node being unwound, we should be able to reset node itself as we
2628
+ // unwind the stack. Saves an additional null check.
2629
+ const returnFiber = incompleteWork . return ;
2630
+ if ( returnFiber !== null ) {
2631
+ // Mark the parent fiber as incomplete and clear its subtree flags.
2632
+ // TODO: Once we stop prerendering siblings, we may be able to get rid of
2633
+ // the Incomplete flag because unwinding to the nearest boundary will
2634
+ // happen synchronously.
2635
+ returnFiber . flags |= Incomplete ;
2636
+ returnFiber . subtreeFlags = NoFlags ;
2637
+ returnFiber . deletions = null ;
2638
+ }
2639
+
2640
+ // If there are siblings, work on them now even though they're going to be
2641
+ // replaced by a fallback. We're "prerendering" them. Historically our
2642
+ // rationale for this behavior has been to initiate any lazy data requests
2643
+ // in the siblings, and also to warm up the CPU cache.
2644
+ // TODO: Don't prerender siblings. With `use`, we suspend the work loop
2645
+ // until the data has resolved, anyway.
2646
+ const siblingFiber = incompleteWork . sibling ;
2647
+ if ( siblingFiber !== null ) {
2648
+ // This branch will return us to the normal work loop.
2649
+ workInProgress = siblingFiber ;
2650
+ return ;
2651
+ }
2652
+ // Otherwise, return to the parent
2653
+ // $FlowFixMe[incompatible-type] we bail out when we get a null
2654
+ incompleteWork = returnFiber ;
2655
+ // Update the next thing we're working on in case something throws.
2656
+ workInProgress = incompleteWork ;
2657
+ } while ( incompleteWork !== null ) ;
2658
+
2659
+ // We've unwound all the way to the root.
2660
+ workInProgressRootExitStatus = RootDidNotComplete ;
2661
+ workInProgress = null ;
2662
+ }
2663
+
2614
2664
function commitRoot (
2615
2665
root : FiberRoot ,
2616
2666
recoverableErrors : null | Array < CapturedValue < mixed >> ,
0 commit comments