@@ -2085,10 +2085,10 @@ function renderRootSync(root: FiberRoot, lanes: Lanes) {
20852085 break outer;
20862086 }
20872087 default : {
2088- // Continue with the normal work loop.
2088+ // Unwind then continue with the normal work loop.
20892089 workInProgressSuspendedReason = NotSuspended ;
20902090 workInProgressThrownValue = null ;
2091- unwindSuspendedUnitOfWork ( unitOfWork , thrownValue ) ;
2091+ throwAndUnwindWorkLoop ( unitOfWork , thrownValue ) ;
20922092 break ;
20932093 }
20942094 }
@@ -2197,7 +2197,7 @@ function renderRootConcurrent(root: FiberRoot, lanes: Lanes) {
21972197 // Unwind then continue with the normal work loop.
21982198 workInProgressSuspendedReason = NotSuspended ;
21992199 workInProgressThrownValue = null ;
2200- unwindSuspendedUnitOfWork ( unitOfWork , thrownValue ) ;
2200+ throwAndUnwindWorkLoop ( unitOfWork , thrownValue ) ;
22012201 break ;
22022202 }
22032203 case SuspendedOnData : {
@@ -2250,7 +2250,7 @@ function renderRootConcurrent(root: FiberRoot, lanes: Lanes) {
22502250 // Otherwise, unwind then continue with the normal work loop.
22512251 workInProgressSuspendedReason = NotSuspended ;
22522252 workInProgressThrownValue = null ;
2253- unwindSuspendedUnitOfWork ( unitOfWork , thrownValue ) ;
2253+ throwAndUnwindWorkLoop ( unitOfWork , thrownValue ) ;
22542254 }
22552255 break ;
22562256 }
@@ -2261,7 +2261,7 @@ function renderRootConcurrent(root: FiberRoot, lanes: Lanes) {
22612261 // always unwind.
22622262 workInProgressSuspendedReason = NotSuspended ;
22632263 workInProgressThrownValue = null ;
2264- unwindSuspendedUnitOfWork ( unitOfWork , thrownValue ) ;
2264+ throwAndUnwindWorkLoop ( unitOfWork , thrownValue ) ;
22652265 break ;
22662266 }
22672267 case SuspendedOnHydration : {
@@ -2461,7 +2461,7 @@ function replaySuspendedUnitOfWork(unitOfWork: Fiber): void {
24612461 ReactCurrentOwner . current = null ;
24622462}
24632463
2464- function unwindSuspendedUnitOfWork ( unitOfWork : Fiber , thrownValue : mixed ) {
2464+ function throwAndUnwindWorkLoop ( unitOfWork : Fiber , thrownValue : mixed ) {
24652465 // This is a fork of performUnitOfWork specifcally for unwinding a fiber
24662466 // that threw an exception.
24672467 //
@@ -2506,90 +2506,62 @@ function unwindSuspendedUnitOfWork(unitOfWork: Fiber, thrownValue: mixed) {
25062506 throw error ;
25072507 }
25082508
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+ }
25112524}
25122525
25132526function completeUnitOfWork ( unitOfWork : Fiber ) : void {
25142527 // Attempt to complete the current unit of work, then move to the next
25152528 // sibling. If there are no more siblings, return to the parent fiber.
25162529 let completedWork : Fiber = unitOfWork ;
25172530 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+
25182543 // The current, flushed, state of this fiber is the alternate. Ideally
25192544 // nothing should rely on this, but relying on it here means that we don't
25202545 // need an additional field on the work in progress.
25212546 const current = completedWork . alternate ;
25222547 const returnFiber = completedWork . return ;
25232548
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 ) ;
25462553 } 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 ( ) ;
25812560
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 ;
25932565 }
25942566
25952567 const siblingFiber = completedWork . sibling ;
@@ -2611,6 +2583,87 @@ function completeUnitOfWork(unitOfWork: Fiber): void {
26112583 }
26122584}
26132585
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+
26142667function commitRoot (
26152668 root : FiberRoot ,
26162669 recoverableErrors : null | Array < CapturedValue < mixed >> ,
0 commit comments