In work loop, add enum of reasons for suspending
This is a pure refactor, no change to behavior. When a component throws, the work loop can handle that in one of several ways — unwind immediately, wait for microtasks, and so on. I'm about to add another one, too. So I've changed the variable that tracks whether the work loop is suspended from a boolean (workInProgressIsSuspended) to an enum (workInProgressSuspendedReason).
Andrew Clark committed
Oct 24, 2022 at 10:13 UTC
d2c0ab10de957ba1758dad6405c824d251990ea4
2 files changed
+66
-40
packages/react-reconciler/src/ReactFiberWorkLoop.new.js
+33
-20
@@ -310,11 +310,18 @@ let workInProgress: Fiber | null = null;
310
// The lanes we're rendering
311
let workInProgressRootRenderLanes: Lanes = NoLanes;
312
313
+opaque type SuspendedReason = 0 | 1 | 2 | 3 | 4;
314
+const NotSuspended: SuspendedReason = 0;
315
+const SuspendedOnError: SuspendedReason = 1;
316
+// const SuspendedOnData: SuspendedReason = 2;
317
+const SuspendedOnImmediate: SuspendedReason = 3;
318
+const SuspendedAndReadyToUnwind: SuspendedReason = 4;
319
+
320
// When this is true, the work-in-progress fiber just suspended (or errored) and
321
// we've yet to unwind the stack. In some cases, we may yield to the main thread
322
// after this happens. If the fiber is pinged before we resume, we can retry
323
// immediately instead of unwinding the stack.
317
-let workInProgressIsSuspended: boolean = false;
324
+let workInProgressSuspendedReason: SuspendedReason = NotSuspended;
325
let workInProgressThrownValue: mixed = null;
326
let workInProgressSuspendedThenableState: ThenableState | null = null;
327
@@ -1676,9 +1683,10 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber {
1683
}
1684
1685
if (workInProgress !== null) {
1679
- let interruptedWork = workInProgressIsSuspended
1680
- ? workInProgress
1681
- : workInProgress.return;
1686
+ let interruptedWork =
1687
+ workInProgressSuspendedReason === NotSuspended
1688
+ ? workInProgress.return
1689
+ : workInProgress;
1690
while (interruptedWork !== null) {
1691
const current = interruptedWork.alternate;
1692
unwindInterruptedWork(
@@ -1693,7 +1701,7 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber {
1701
const rootWorkInProgress = createWorkInProgress(root.current, null);
1702
workInProgress = rootWorkInProgress;
1703
workInProgressRootRenderLanes = renderLanes = lanes;
1696
- workInProgressIsSuspended = false;
1704
+ workInProgressSuspendedReason = NotSuspended;
1705
workInProgressThrownValue = null;
1706
workInProgressSuspendedThenableState = null;
1707
workInProgressRootDidAttachPingListener = false;
@@ -1732,17 +1740,27 @@ function handleThrow(root, thrownValue): void {
1740
// deprecate the old API in favor of `use`.
1741
thrownValue = getSuspendedThenable();
1742
workInProgressSuspendedThenableState = getThenableStateAfterSuspending();
1743
+ workInProgressSuspendedReason = SuspendedOnImmediate;
1744
} else {
1745
// This is a regular error. If something earlier in the component already
1746
// suspended, we must clear the thenable state to unblock the work loop.
1747
workInProgressSuspendedThenableState = null;
1748
+
1749
+ const isWakeable =
1750
+ thrownValue !== null &&
1751
+ typeof thrownValue === 'object' &&
1752
+ // $FlowFixMe[method-unbinding]
1753
+ typeof thrownValue.then === 'function';
1754
+
1755
+ workInProgressSuspendedReason = isWakeable
1756
+ ? // A wakeable object was thrown by a legacy Suspense implementation.
1757
+ // This has slightly different behavior than suspending with `use`.
1758
+ SuspendedAndReadyToUnwind
1759
+ : // This is a regular error. If something earlier in the component already
1760
+ // suspended, we must clear the thenable state to unblock the work loop.
1761
+ SuspendedOnError;
1762
}
1763
1741
- // Setting this to `true` tells the work loop to unwind the stack instead
1742
- // of entering the begin phase. It's called "suspended" because it usually
1743
- // happens because of Suspense, but it also applies to errors. Think of it
1744
- // as suspending the execution of the work loop.
1745
- workInProgressIsSuspended = true;
1764
workInProgressThrownValue = thrownValue;
1765
1766
const erroredWork = workInProgress;
@@ -1762,12 +1780,7 @@ function handleThrow(root, thrownValue): void {
1780
1781
if (enableSchedulingProfiler) {
1782
markComponentRenderStopped();
1765
- if (
1766
- thrownValue !== null &&
1767
- typeof thrownValue === 'object' &&
1768
- // $FlowFixMe[method-unbinding]
1769
- typeof thrownValue.then === 'function'
1770
- ) {
1783
+ if (workInProgressSuspendedReason !== SuspendedOnError) {
1784
const wakeable: Wakeable = (thrownValue: any);
1785
markComponentSuspended(
1786
erroredWork,
@@ -1968,11 +1981,11 @@ function renderRootSync(root: FiberRoot, lanes: Lanes) {
1981
function workLoopSync() {
1982
// Perform work without checking if we need to yield between fiber.
1983
1971
- if (workInProgressIsSuspended) {
1984
+ if (workInProgressSuspendedReason !== NotSuspended) {
1985
// The current work-in-progress was already attempted. We need to unwind
1986
// it before we continue the normal work loop.
1987
const thrownValue = workInProgressThrownValue;
1975
- workInProgressIsSuspended = false;
1988
+ workInProgressSuspendedReason = NotSuspended;
1989
workInProgressThrownValue = null;
1990
if (workInProgress !== null) {
1991
resumeSuspendedUnitOfWork(workInProgress, thrownValue);
@@ -2079,11 +2092,11 @@ function renderRootConcurrent(root: FiberRoot, lanes: Lanes) {
2092
function workLoopConcurrent() {
2093
// Perform work until Scheduler asks us to yield
2094
2082
- if (workInProgressIsSuspended) {
2095
+ if (workInProgressSuspendedReason !== NotSuspended) {
2096
// The current work-in-progress was already attempted. We need to unwind
2097
// it before we continue the normal work loop.
2098
const thrownValue = workInProgressThrownValue;
2086
- workInProgressIsSuspended = false;
2099
+ workInProgressSuspendedReason = NotSuspended;
2100
workInProgressThrownValue = null;
2101
if (workInProgress !== null) {
2102
resumeSuspendedUnitOfWork(workInProgress, thrownValue);
packages/react-reconciler/src/ReactFiberWorkLoop.old.js
+33
-20
@@ -310,11 +310,18 @@ let workInProgress: Fiber | null = null;
310
// The lanes we're rendering
311
let workInProgressRootRenderLanes: Lanes = NoLanes;
312
313
+opaque type SuspendedReason = 0 | 1 | 2 | 3 | 4;
314
+const NotSuspended: SuspendedReason = 0;
315
+const SuspendedOnError: SuspendedReason = 1;
316
+// const SuspendedOnData: SuspendedReason = 2;
317
+const SuspendedOnImmediate: SuspendedReason = 3;
318
+const SuspendedAndReadyToUnwind: SuspendedReason = 4;
319
+
320
// When this is true, the work-in-progress fiber just suspended (or errored) and
321
// we've yet to unwind the stack. In some cases, we may yield to the main thread
322
// after this happens. If the fiber is pinged before we resume, we can retry
323
// immediately instead of unwinding the stack.
317
-let workInProgressIsSuspended: boolean = false;
324
+let workInProgressSuspendedReason: SuspendedReason = NotSuspended;
325
let workInProgressThrownValue: mixed = null;
326
let workInProgressSuspendedThenableState: ThenableState | null = null;
327
@@ -1676,9 +1683,10 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber {
1683
}
1684
1685
if (workInProgress !== null) {
1679
- let interruptedWork = workInProgressIsSuspended
1680
- ? workInProgress
1681
- : workInProgress.return;
1686
+ let interruptedWork =
1687
+ workInProgressSuspendedReason === NotSuspended
1688
+ ? workInProgress.return
1689
+ : workInProgress;
1690
while (interruptedWork !== null) {
1691
const current = interruptedWork.alternate;
1692
unwindInterruptedWork(
@@ -1693,7 +1701,7 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber {
1701
const rootWorkInProgress = createWorkInProgress(root.current, null);
1702
workInProgress = rootWorkInProgress;
1703
workInProgressRootRenderLanes = renderLanes = lanes;
1696
- workInProgressIsSuspended = false;
1704
+ workInProgressSuspendedReason = NotSuspended;
1705
workInProgressThrownValue = null;
1706
workInProgressSuspendedThenableState = null;
1707
workInProgressRootDidAttachPingListener = false;
@@ -1732,17 +1740,27 @@ function handleThrow(root, thrownValue): void {
1740
// deprecate the old API in favor of `use`.
1741
thrownValue = getSuspendedThenable();
1742
workInProgressSuspendedThenableState = getThenableStateAfterSuspending();
1743
+ workInProgressSuspendedReason = SuspendedOnImmediate;
1744
} else {
1745
// This is a regular error. If something earlier in the component already
1746
// suspended, we must clear the thenable state to unblock the work loop.
1747
workInProgressSuspendedThenableState = null;
1748
+
1749
+ const isWakeable =
1750
+ thrownValue !== null &&
1751
+ typeof thrownValue === 'object' &&
1752
+ // $FlowFixMe[method-unbinding]
1753
+ typeof thrownValue.then === 'function';
1754
+
1755
+ workInProgressSuspendedReason = isWakeable
1756
+ ? // A wakeable object was thrown by a legacy Suspense implementation.
1757
+ // This has slightly different behavior than suspending with `use`.
1758
+ SuspendedAndReadyToUnwind
1759
+ : // This is a regular error. If something earlier in the component already
1760
+ // suspended, we must clear the thenable state to unblock the work loop.
1761
+ SuspendedOnError;
1762
}
1763
1741
- // Setting this to `true` tells the work loop to unwind the stack instead
1742
- // of entering the begin phase. It's called "suspended" because it usually
1743
- // happens because of Suspense, but it also applies to errors. Think of it
1744
- // as suspending the execution of the work loop.
1745
- workInProgressIsSuspended = true;
1764
workInProgressThrownValue = thrownValue;
1765
1766
const erroredWork = workInProgress;
@@ -1762,12 +1780,7 @@ function handleThrow(root, thrownValue): void {
1780
1781
if (enableSchedulingProfiler) {
1782
markComponentRenderStopped();
1765
- if (
1766
- thrownValue !== null &&
1767
- typeof thrownValue === 'object' &&
1768
- // $FlowFixMe[method-unbinding]
1769
- typeof thrownValue.then === 'function'
1770
- ) {
1783
+ if (workInProgressSuspendedReason !== SuspendedOnError) {
1784
const wakeable: Wakeable = (thrownValue: any);
1785
markComponentSuspended(
1786
erroredWork,
@@ -1968,11 +1981,11 @@ function renderRootSync(root: FiberRoot, lanes: Lanes) {
1981
function workLoopSync() {
1982
// Perform work without checking if we need to yield between fiber.
1983
1971
- if (workInProgressIsSuspended) {
1984
+ if (workInProgressSuspendedReason !== NotSuspended) {
1985
// The current work-in-progress was already attempted. We need to unwind
1986
// it before we continue the normal work loop.
1987
const thrownValue = workInProgressThrownValue;
1975
- workInProgressIsSuspended = false;
1988
+ workInProgressSuspendedReason = NotSuspended;
1989
workInProgressThrownValue = null;
1990
if (workInProgress !== null) {
1991
resumeSuspendedUnitOfWork(workInProgress, thrownValue);
@@ -2079,11 +2092,11 @@ function renderRootConcurrent(root: FiberRoot, lanes: Lanes) {
2092
function workLoopConcurrent() {
2093
// Perform work until Scheduler asks us to yield
2094
2082
- if (workInProgressIsSuspended) {
2095
+ if (workInProgressSuspendedReason !== NotSuspended) {
2096
// The current work-in-progress was already attempted. We need to unwind
2097
// it before we continue the normal work loop.
2098
const thrownValue = workInProgressThrownValue;
2086
- workInProgressIsSuspended = false;
2099
+ workInProgressSuspendedReason = NotSuspended;
2100
workInProgressThrownValue = null;
2101
if (workInProgress !== null) {
2102
resumeSuspendedUnitOfWork(workInProgress, thrownValue);