@samitouri / QOS-React / commits / d2c0ab10de

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);