86
import {
87
createWorkInProgress,
88
assignFiberPropertiesInDEV,
89
+ resetWorkInProgress,
90
} from './ReactFiber.new';
91
import {isRootDehydrated} from './ReactFiberShellHydration';
92
import {didSuspendOrErrorWhileHydratingDEV} from './ReactFiberHydrationContext.new';
246
isConcurrentActEnvironment,
247
} from './ReactFiberAct.new';
248
import {processTransitionCallbacks} from './ReactFiberTracingMarkerComponent.new';
249
+import {
250
+ resetWakeableState,
251
+ trackSuspendedWakeable,
252
+ suspendedWakeableWasPinged,
253
+ attemptToPingSuspendedWakeable,
254
+} from './ReactFiberWakeable.new';
255
256
const ceil = Math.ceil;
257
287
// The lanes we're rendering
288
let workInProgressRootRenderLanes: Lanes = NoLanes;
289
290
+// When this is true, the work-in-progress fiber just suspended (or errored) and
291
+// we've yet to unwind the stack. In some cases, we may yield to the main thread
292
+// after this happens. If the fiber is pinged before we resume, we can retry
293
+// immediately instead of unwinding the stack.
294
+let workInProgressIsSuspended: boolean = false;
295
+
296
// A contextual version of workInProgressRootRenderLanes. It is a superset of
297
// the lanes that we started working on at the root. When we enter a subtree
298
// that is currently hidden, we add the lanes that would have committed if
1556
);
1557
interruptedWork = interruptedWork.return;
1558
}
1559
+ resetWakeableState();
1560
}
1561
workInProgressRoot = root;
1562
const rootWorkInProgress = createWorkInProgress(root.current, null);
1563
workInProgress = rootWorkInProgress;
1564
workInProgressRootRenderLanes = renderLanes = lanes;
1565
+ workInProgressIsSuspended = false;
1566
workInProgressRootExitStatus = RootInProgress;
1567
workInProgressRootFatalError = null;
1568
workInProgressRootSkippedLanes = NoLanes;
1581
return rootWorkInProgress;
1582
}
1583
1569
-function handleError(root, thrownValue): void {
1584
+function handleError(root, thrownValue): Wakeable | null {
1585
do {
1586
let erroredWork = workInProgress;
1587
try {
1607
// intentionally not calling those, we need set it here.
1608
// TODO: Consider calling `unwindWork` to pop the contexts.
1609
workInProgress = null;
1595
- return;
1610
+ return null;
1611
}
1612
1613
if (enableProfilerTimer && erroredWork.mode & ProfileMode) {
1640
}
1641
}
1642
1628
- throwException(
1643
+ const maybeWakeable = throwException(
1644
root,
1645
erroredWork.return,
1646
erroredWork,
1647
thrownValue,
1648
workInProgressRootRenderLanes,
1649
);
1635
- completeUnitOfWork(erroredWork);
1650
+ // Setting this to `true` tells the work loop to unwind the stack instead
1651
+ // of entering the begin phase. It's called "suspended" because it usually
1652
+ // happens because of Suspense, but it also applies to errors. Think of it
1653
+ // as suspending the execution of the work loop.
1654
+ workInProgressIsSuspended = true;
1655
+
1656
+ // Return to the normal work loop.
1657
+ return maybeWakeable;
1658
} catch (yetAnotherThrownValue) {
1659
// Something in the return path also threw.
1660
thrownValue = yetAnotherThrownValue;
1668
}
1669
continue;
1670
}
1649
- // Return to the normal work loop.
1650
- return;
1671
} while (true);
1672
}
1673
1830
// The work loop is an extremely hot path. Tell Closure not to inline it.
1831
/** @noinline */
1832
function workLoopSync() {
1813
- // Already timed out, so perform work without checking if we need to yield.
1833
+ // Perform work without checking if we need to yield between fiber.
1834
+
1835
+ if (workInProgressIsSuspended && workInProgress !== null) {
1836
+ // The current work-in-progress was already attempted. We need to unwind
1837
+ // it before we continue the normal work loop.
1838
+ resumeSuspendedUnitOfWork(workInProgress);
1839
+ }
1840
+
1841
while (workInProgress !== null) {
1842
performUnitOfWork(workInProgress);
1843
}
1887
workLoopConcurrent();
1888
break;
1889
} catch (thrownValue) {
1863
- handleError(root, thrownValue);
1890
+ const maybeWakeable = handleError(root, thrownValue);
1891
+ if (maybeWakeable !== null) {
1892
+ // If this fiber just suspended, it's possible the data is already
1893
+ // cached. Yield to the the main thread to give it a chance to ping. If
1894
+ // it does, we can retry immediately without unwinding the stack.
1895
+ trackSuspendedWakeable(maybeWakeable);
1896
+ break;
1897
+ }
1898
}
1899
} while (true);
1900
resetContextDependencies();
1933
/** @noinline */
1934
function workLoopConcurrent() {
1935
// Perform work until Scheduler asks us to yield
1936
+
1937
+ if (workInProgressIsSuspended && workInProgress !== null) {
1938
+ // The current work-in-progress was already attempted. We need to unwind
1939
+ // it before we continue the normal work loop.
1940
+ resumeSuspendedUnitOfWork(workInProgress);
1941
+ }
1942
+
1943
while (workInProgress !== null && !shouldYield()) {
1944
performUnitOfWork(workInProgress);
1945
}
1973
ReactCurrentOwner.current = null;
1974
}
1975
1976
+function resumeSuspendedUnitOfWork(unitOfWork: Fiber): void {
1977
+ // This is a fork of performUnitOfWork specifcally for resuming a fiber that
1978
+ // just suspended. In some cases, we may choose to retry the fiber immediately
1979
+ // instead of unwinding the stack. It's a separate function to keep the
1980
+ // additional logic out of the work loop's hot path.
1981
+
1982
+ if (!suspendedWakeableWasPinged()) {
1983
+ // The wakeable wasn't pinged. Return to the normal work loop. This will
1984
+ // unwind the stack, and potentially result in showing a fallback.
1985
+ workInProgressIsSuspended = false;
1986
+ resetWakeableState();
1987
+ completeUnitOfWork(unitOfWork);
1988
+ return;
1989
+ }
1990
+
1991
+ // The work-in-progress was immediately pinged. Instead of unwinding the
1992
+ // stack and potentially showing a fallback, reset the fiber and try rendering
1993
+ // it again.
1994
+ unitOfWork = workInProgress = resetWorkInProgress(unitOfWork, renderLanes);
1995
+
1996
+ const current = unitOfWork.alternate;
1997
+ setCurrentDebugFiberInDEV(unitOfWork);
1998
+
1999
+ let next;
2000
+ if (enableProfilerTimer && (unitOfWork.mode & ProfileMode) !== NoMode) {
2001
+ startProfilerTimer(unitOfWork);
2002
+ next = beginWork(current, unitOfWork, renderLanes);
2003
+ stopProfilerTimerIfRunningAndRecordDelta(unitOfWork, true);
2004
+ } else {
2005
+ next = beginWork(current, unitOfWork, renderLanes);
2006
+ }
2007
+
2008
+ // The begin phase finished successfully without suspending. Reset the state
2009
+ // used to track the fiber while it was suspended. Then return to the normal
2010
+ // work loop.
2011
+ workInProgressIsSuspended = false;
2012
+ resetWakeableState();
2013
+
2014
+ resetCurrentDebugFiberInDEV();
2015
+ unitOfWork.memoizedProps = unitOfWork.pendingProps;
2016
+ if (next === null) {
2017
+ // If this doesn't spawn new work, complete the current work.
2018
+ completeUnitOfWork(unitOfWork);
2019
+ } else {
2020
+ workInProgress = next;
2021
+ }
2022
+
2023
+ ReactCurrentOwner.current = null;
2024
+}
2025
+
2026
function completeUnitOfWork(unitOfWork: Fiber): void {
2027
// Attempt to complete the current unit of work, then move to the next
2028
// sibling. If there are no more siblings, return to the parent fiber.
2834
// Received a ping at the same priority level at which we're currently
2835
// rendering. We might want to restart this render. This should mirror
2836
// the logic of whether or not a root suspends once it completes.
2746
-
2747
- // TODO: If we're rendering sync either due to Sync, Batched or expired,
2748
- // we should probably never restart.
2749
-
2750
- // If we're suspended with delay, or if it's a retry, we'll always suspend
2751
- // so we can always restart.
2752
- if (
2753
- workInProgressRootExitStatus === RootSuspendedWithDelay ||
2754
- (workInProgressRootExitStatus === RootSuspended &&
2755
- includesOnlyRetries(workInProgressRootRenderLanes) &&
2756
- now() - globalMostRecentFallbackTime < FALLBACK_THROTTLE_MS)
2757
- ) {
2758
- // Restart from the root.
2759
- prepareFreshStack(root, NoLanes);
2837
+ const didPingSuspendedWakeable = attemptToPingSuspendedWakeable(wakeable);
2838
+ if (didPingSuspendedWakeable) {
2839
+ // Successfully pinged the in-progress fiber. Don't unwind the stack.
2840
} else {
2761
- // Even though we can't restart right now, we might get an
2762
- // opportunity later. So we mark this render as having a ping.
2763
- workInProgressRootPingedLanes = mergeLanes(
2764
- workInProgressRootPingedLanes,
2765
- pingedLanes,
2766
- );
2841
+ // TODO: If we're rendering sync either due to Sync, Batched or expired,
2842
+ // we should probably never restart.
2843
+
2844
+ // If we're suspended with delay, or if it's a retry, we'll always suspend
2845
+ // so we can always restart.
2846
+ if (
2847
+ workInProgressRootExitStatus === RootSuspendedWithDelay ||
2848
+ (workInProgressRootExitStatus === RootSuspended &&
2849
+ includesOnlyRetries(workInProgressRootRenderLanes) &&
2850
+ now() - globalMostRecentFallbackTime < FALLBACK_THROTTLE_MS)
2851
+ ) {
2852
+ // Restart from the root.
2853
+ prepareFreshStack(root, NoLanes);
2854
+ } else {
2855
+ // Even though we can't restart right now, we might get an
2856
+ // opportunity later. So we mark this render as having a ping.
2857
+ workInProgressRootPingedLanes = mergeLanes(
2858
+ workInProgressRootPingedLanes,
2859
+ pingedLanes,
2860
+ );
2861
+ }
2862
}
2863
}
2864