@@ -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,59 @@ 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
2536+ unwindUnitOfWork ( completedWork ) ;
2537+ return ;
2538+ }
2539+
25182540 // The current, flushed, state of this fiber is the alternate. Ideally
25192541 // nothing should rely on this, but relying on it here means that we don't
25202542 // need an additional field on the work in progress.
25212543 const current = completedWork . alternate ;
25222544 const returnFiber = completedWork . return ;
25232545
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 ) ;
25462550 } 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 ( ) ;
25812557
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 ;
25932562 }
25942563
25952564 const siblingFiber = completedWork . sibling ;
@@ -2611,6 +2580,87 @@ function completeUnitOfWork(unitOfWork: Fiber): void {
26112580 }
26122581}
26132582
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+
26142664function commitRoot (
26152665 root : FiberRoot ,
26162666 recoverableErrors : null | Array < CapturedValue < mixed >> ,
0 commit comments