Skip to content

Commit

Permalink
In work loop, add enum of reasons for suspending
Browse files Browse the repository at this point in the history
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).
  • Loading branch information
acdlite committed Oct 29, 2022
1 parent 5450dd4 commit d2c0ab1
Show file tree
Hide file tree
Showing 2 changed files with 66 additions and 40 deletions.
53 changes: 33 additions & 20 deletions packages/react-reconciler/src/ReactFiberWorkLoop.new.js
Original file line number Diff line number Diff line change
Expand Up @@ -310,11 +310,18 @@ let workInProgress: Fiber | null = null;
// The lanes we're rendering
let workInProgressRootRenderLanes: Lanes = NoLanes;

opaque type SuspendedReason = 0 | 1 | 2 | 3 | 4;
const NotSuspended: SuspendedReason = 0;
const SuspendedOnError: SuspendedReason = 1;
// const SuspendedOnData: SuspendedReason = 2;
const SuspendedOnImmediate: SuspendedReason = 3;
const SuspendedAndReadyToUnwind: SuspendedReason = 4;

// When this is true, the work-in-progress fiber just suspended (or errored) and
// we've yet to unwind the stack. In some cases, we may yield to the main thread
// after this happens. If the fiber is pinged before we resume, we can retry
// immediately instead of unwinding the stack.
let workInProgressIsSuspended: boolean = false;
let workInProgressSuspendedReason: SuspendedReason = NotSuspended;
let workInProgressThrownValue: mixed = null;
let workInProgressSuspendedThenableState: ThenableState | null = null;

Expand Down Expand Up @@ -1676,9 +1683,10 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber {
}

if (workInProgress !== null) {
let interruptedWork = workInProgressIsSuspended
? workInProgress
: workInProgress.return;
let interruptedWork =
workInProgressSuspendedReason === NotSuspended
? workInProgress.return
: workInProgress;
while (interruptedWork !== null) {
const current = interruptedWork.alternate;
unwindInterruptedWork(
Expand All @@ -1693,7 +1701,7 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber {
const rootWorkInProgress = createWorkInProgress(root.current, null);
workInProgress = rootWorkInProgress;
workInProgressRootRenderLanes = renderLanes = lanes;
workInProgressIsSuspended = false;
workInProgressSuspendedReason = NotSuspended;
workInProgressThrownValue = null;
workInProgressSuspendedThenableState = null;
workInProgressRootDidAttachPingListener = false;
Expand Down Expand Up @@ -1732,17 +1740,27 @@ function handleThrow(root, thrownValue): void {
// deprecate the old API in favor of `use`.
thrownValue = getSuspendedThenable();
workInProgressSuspendedThenableState = getThenableStateAfterSuspending();
workInProgressSuspendedReason = SuspendedOnImmediate;
} else {
// This is a regular error. If something earlier in the component already
// suspended, we must clear the thenable state to unblock the work loop.
workInProgressSuspendedThenableState = null;

const isWakeable =
thrownValue !== null &&
typeof thrownValue === 'object' &&
// $FlowFixMe[method-unbinding]
typeof thrownValue.then === 'function';

workInProgressSuspendedReason = isWakeable
? // A wakeable object was thrown by a legacy Suspense implementation.
// This has slightly different behavior than suspending with `use`.
SuspendedAndReadyToUnwind
: // This is a regular error. If something earlier in the component already
// suspended, we must clear the thenable state to unblock the work loop.
SuspendedOnError;
}

// Setting this to `true` tells the work loop to unwind the stack instead
// of entering the begin phase. It's called "suspended" because it usually
// happens because of Suspense, but it also applies to errors. Think of it
// as suspending the execution of the work loop.
workInProgressIsSuspended = true;
workInProgressThrownValue = thrownValue;

const erroredWork = workInProgress;
Expand All @@ -1762,12 +1780,7 @@ function handleThrow(root, thrownValue): void {

if (enableSchedulingProfiler) {
markComponentRenderStopped();
if (
thrownValue !== null &&
typeof thrownValue === 'object' &&
// $FlowFixMe[method-unbinding]
typeof thrownValue.then === 'function'
) {
if (workInProgressSuspendedReason !== SuspendedOnError) {
const wakeable: Wakeable = (thrownValue: any);
markComponentSuspended(
erroredWork,
Expand Down Expand Up @@ -1968,11 +1981,11 @@ function renderRootSync(root: FiberRoot, lanes: Lanes) {
function workLoopSync() {
// Perform work without checking if we need to yield between fiber.

if (workInProgressIsSuspended) {
if (workInProgressSuspendedReason !== NotSuspended) {
// The current work-in-progress was already attempted. We need to unwind
// it before we continue the normal work loop.
const thrownValue = workInProgressThrownValue;
workInProgressIsSuspended = false;
workInProgressSuspendedReason = NotSuspended;
workInProgressThrownValue = null;
if (workInProgress !== null) {
resumeSuspendedUnitOfWork(workInProgress, thrownValue);
Expand Down Expand Up @@ -2079,11 +2092,11 @@ function renderRootConcurrent(root: FiberRoot, lanes: Lanes) {
function workLoopConcurrent() {
// Perform work until Scheduler asks us to yield

if (workInProgressIsSuspended) {
if (workInProgressSuspendedReason !== NotSuspended) {
// The current work-in-progress was already attempted. We need to unwind
// it before we continue the normal work loop.
const thrownValue = workInProgressThrownValue;
workInProgressIsSuspended = false;
workInProgressSuspendedReason = NotSuspended;
workInProgressThrownValue = null;
if (workInProgress !== null) {
resumeSuspendedUnitOfWork(workInProgress, thrownValue);
Expand Down
53 changes: 33 additions & 20 deletions packages/react-reconciler/src/ReactFiberWorkLoop.old.js
Original file line number Diff line number Diff line change
Expand Up @@ -310,11 +310,18 @@ let workInProgress: Fiber | null = null;
// The lanes we're rendering
let workInProgressRootRenderLanes: Lanes = NoLanes;

opaque type SuspendedReason = 0 | 1 | 2 | 3 | 4;
const NotSuspended: SuspendedReason = 0;
const SuspendedOnError: SuspendedReason = 1;
// const SuspendedOnData: SuspendedReason = 2;
const SuspendedOnImmediate: SuspendedReason = 3;
const SuspendedAndReadyToUnwind: SuspendedReason = 4;

// When this is true, the work-in-progress fiber just suspended (or errored) and
// we've yet to unwind the stack. In some cases, we may yield to the main thread
// after this happens. If the fiber is pinged before we resume, we can retry
// immediately instead of unwinding the stack.
let workInProgressIsSuspended: boolean = false;
let workInProgressSuspendedReason: SuspendedReason = NotSuspended;
let workInProgressThrownValue: mixed = null;
let workInProgressSuspendedThenableState: ThenableState | null = null;

Expand Down Expand Up @@ -1676,9 +1683,10 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber {
}

if (workInProgress !== null) {
let interruptedWork = workInProgressIsSuspended
? workInProgress
: workInProgress.return;
let interruptedWork =
workInProgressSuspendedReason === NotSuspended
? workInProgress.return
: workInProgress;
while (interruptedWork !== null) {
const current = interruptedWork.alternate;
unwindInterruptedWork(
Expand All @@ -1693,7 +1701,7 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber {
const rootWorkInProgress = createWorkInProgress(root.current, null);
workInProgress = rootWorkInProgress;
workInProgressRootRenderLanes = renderLanes = lanes;
workInProgressIsSuspended = false;
workInProgressSuspendedReason = NotSuspended;
workInProgressThrownValue = null;
workInProgressSuspendedThenableState = null;
workInProgressRootDidAttachPingListener = false;
Expand Down Expand Up @@ -1732,17 +1740,27 @@ function handleThrow(root, thrownValue): void {
// deprecate the old API in favor of `use`.
thrownValue = getSuspendedThenable();
workInProgressSuspendedThenableState = getThenableStateAfterSuspending();
workInProgressSuspendedReason = SuspendedOnImmediate;
} else {
// This is a regular error. If something earlier in the component already
// suspended, we must clear the thenable state to unblock the work loop.
workInProgressSuspendedThenableState = null;

const isWakeable =
thrownValue !== null &&
typeof thrownValue === 'object' &&
// $FlowFixMe[method-unbinding]
typeof thrownValue.then === 'function';

workInProgressSuspendedReason = isWakeable
? // A wakeable object was thrown by a legacy Suspense implementation.
// This has slightly different behavior than suspending with `use`.
SuspendedAndReadyToUnwind
: // This is a regular error. If something earlier in the component already
// suspended, we must clear the thenable state to unblock the work loop.
SuspendedOnError;
}

// Setting this to `true` tells the work loop to unwind the stack instead
// of entering the begin phase. It's called "suspended" because it usually
// happens because of Suspense, but it also applies to errors. Think of it
// as suspending the execution of the work loop.
workInProgressIsSuspended = true;
workInProgressThrownValue = thrownValue;

const erroredWork = workInProgress;
Expand All @@ -1762,12 +1780,7 @@ function handleThrow(root, thrownValue): void {

if (enableSchedulingProfiler) {
markComponentRenderStopped();
if (
thrownValue !== null &&
typeof thrownValue === 'object' &&
// $FlowFixMe[method-unbinding]
typeof thrownValue.then === 'function'
) {
if (workInProgressSuspendedReason !== SuspendedOnError) {
const wakeable: Wakeable = (thrownValue: any);
markComponentSuspended(
erroredWork,
Expand Down Expand Up @@ -1968,11 +1981,11 @@ function renderRootSync(root: FiberRoot, lanes: Lanes) {
function workLoopSync() {
// Perform work without checking if we need to yield between fiber.

if (workInProgressIsSuspended) {
if (workInProgressSuspendedReason !== NotSuspended) {
// The current work-in-progress was already attempted. We need to unwind
// it before we continue the normal work loop.
const thrownValue = workInProgressThrownValue;
workInProgressIsSuspended = false;
workInProgressSuspendedReason = NotSuspended;
workInProgressThrownValue = null;
if (workInProgress !== null) {
resumeSuspendedUnitOfWork(workInProgress, thrownValue);
Expand Down Expand Up @@ -2079,11 +2092,11 @@ function renderRootConcurrent(root: FiberRoot, lanes: Lanes) {
function workLoopConcurrent() {
// Perform work until Scheduler asks us to yield

if (workInProgressIsSuspended) {
if (workInProgressSuspendedReason !== NotSuspended) {
// The current work-in-progress was already attempted. We need to unwind
// it before we continue the normal work loop.
const thrownValue = workInProgressThrownValue;
workInProgressIsSuspended = false;
workInProgressSuspendedReason = NotSuspended;
workInProgressThrownValue = null;
if (workInProgress !== null) {
resumeSuspendedUnitOfWork(workInProgress, thrownValue);
Expand Down

0 comments on commit d2c0ab1

Please sign in to comment.