Skip to content

Commit 3d20fb6

Browse files
committed
Hoist suspending logic into outermost work loop
This hoists the logic for checking whether the work loop is suspended into the outermost work loop. Depending on certain conditions, we may choose to exit the work loop, wait for a promise to resolve, wait for microtasks to fire, and so on. It needs to be in the outermost work loop so we can break out of it. No intentional behavior change in this commit.
1 parent bf1db70 commit 3d20fb6

File tree

2 files changed

+182
-168
lines changed

2 files changed

+182
-168
lines changed

packages/react-reconciler/src/ReactFiberWorkLoop.new.js

Lines changed: 91 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -1936,6 +1936,24 @@ function renderRootSync(root: FiberRoot, lanes: Lanes) {
19361936

19371937
do {
19381938
try {
1939+
if (workInProgressSuspendedReason !== NotSuspended) {
1940+
if (workInProgress !== null) {
1941+
// The current work-in-progress was already attempted. We need to unwind
1942+
// it before we continue the normal work loop.
1943+
const unitOfWork = workInProgress;
1944+
const thrownValue = workInProgressThrownValue;
1945+
workInProgressSuspendedReason = NotSuspended;
1946+
workInProgressThrownValue = null;
1947+
const wasPinged =
1948+
workInProgressSuspendedThenableState !== null &&
1949+
isThenableStateResolved(workInProgressSuspendedThenableState);
1950+
if (wasPinged) {
1951+
replaySuspendedUnitOfWork(unitOfWork, thrownValue);
1952+
} else {
1953+
unwindSuspendedUnitOfWork(unitOfWork, thrownValue);
1954+
}
1955+
}
1956+
}
19391957
workLoopSync();
19401958
break;
19411959
} catch (thrownValue) {
@@ -1980,18 +1998,6 @@ function renderRootSync(root: FiberRoot, lanes: Lanes) {
19801998
/** @noinline */
19811999
function workLoopSync() {
19822000
// Perform work without checking if we need to yield between fiber.
1983-
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;
1988-
workInProgressSuspendedReason = NotSuspended;
1989-
workInProgressThrownValue = null;
1990-
if (workInProgress !== null) {
1991-
resumeSuspendedUnitOfWork(workInProgress, thrownValue);
1992-
}
1993-
}
1994-
19952001
while (workInProgress !== null) {
19962002
performUnitOfWork(workInProgress);
19972003
}
@@ -2039,6 +2045,24 @@ function renderRootConcurrent(root: FiberRoot, lanes: Lanes) {
20392045

20402046
do {
20412047
try {
2048+
if (workInProgressSuspendedReason !== NotSuspended) {
2049+
if (workInProgress !== null) {
2050+
// The current work-in-progress was already attempted. We need to unwind
2051+
// it before we continue the normal work loop.
2052+
const unitOfWork = workInProgress;
2053+
const thrownValue = workInProgressThrownValue;
2054+
workInProgressSuspendedReason = NotSuspended;
2055+
workInProgressThrownValue = null;
2056+
const wasPinged =
2057+
workInProgressSuspendedThenableState !== null &&
2058+
isThenableStateResolved(workInProgressSuspendedThenableState);
2059+
if (wasPinged) {
2060+
replaySuspendedUnitOfWork(unitOfWork, thrownValue);
2061+
} else {
2062+
unwindSuspendedUnitOfWork(unitOfWork, thrownValue);
2063+
}
2064+
}
2065+
}
20422066
workLoopConcurrent();
20432067
break;
20442068
} catch (thrownValue) {
@@ -2091,18 +2115,6 @@ function renderRootConcurrent(root: FiberRoot, lanes: Lanes) {
20912115
/** @noinline */
20922116
function workLoopConcurrent() {
20932117
// Perform work until Scheduler asks us to yield
2094-
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;
2099-
workInProgressSuspendedReason = NotSuspended;
2100-
workInProgressThrownValue = null;
2101-
if (workInProgress !== null) {
2102-
resumeSuspendedUnitOfWork(workInProgress, thrownValue);
2103-
}
2104-
}
2105-
21062118
while (workInProgress !== null && !shouldYield()) {
21072119
// $FlowFixMe[incompatible-call] found when upgrading Flow
21082120
performUnitOfWork(workInProgress);
@@ -2137,69 +2149,15 @@ function performUnitOfWork(unitOfWork: Fiber): void {
21372149
ReactCurrentOwner.current = null;
21382150
}
21392151

2140-
function resumeSuspendedUnitOfWork(
2152+
function replaySuspendedUnitOfWork(
21412153
unitOfWork: Fiber,
21422154
thrownValue: mixed,
21432155
): void {
2144-
// This is a fork of performUnitOfWork specifcally for resuming a fiber that
2145-
// just suspended. In some cases, we may choose to retry the fiber immediately
2146-
// instead of unwinding the stack. It's a separate function to keep the
2147-
// additional logic out of the work loop's hot path.
2148-
2149-
const wasPinged =
2150-
workInProgressSuspendedThenableState !== null &&
2151-
isThenableStateResolved(workInProgressSuspendedThenableState);
2152-
2153-
if (!wasPinged) {
2154-
// The thenable wasn't pinged. Return to the normal work loop. This will
2155-
// unwind the stack, and potentially result in showing a fallback.
2156-
workInProgressSuspendedThenableState = null;
2157-
2158-
const returnFiber = unitOfWork.return;
2159-
if (returnFiber === null || workInProgressRoot === null) {
2160-
// Expected to be working on a non-root fiber. This is a fatal error
2161-
// because there's no ancestor that can handle it; the root is
2162-
// supposed to capture all errors that weren't caught by an error
2163-
// boundary.
2164-
workInProgressRootExitStatus = RootFatalErrored;
2165-
workInProgressRootFatalError = thrownValue;
2166-
// Set `workInProgress` to null. This represents advancing to the next
2167-
// sibling, or the parent if there are no siblings. But since the root
2168-
// has no siblings nor a parent, we set it to null. Usually this is
2169-
// handled by `completeUnitOfWork` or `unwindWork`, but since we're
2170-
// intentionally not calling those, we need set it here.
2171-
// TODO: Consider calling `unwindWork` to pop the contexts.
2172-
workInProgress = null;
2173-
return;
2174-
}
2175-
2176-
try {
2177-
// Find and mark the nearest Suspense or error boundary that can handle
2178-
// this "exception".
2179-
throwException(
2180-
workInProgressRoot,
2181-
returnFiber,
2182-
unitOfWork,
2183-
thrownValue,
2184-
workInProgressRootRenderLanes,
2185-
);
2186-
} catch (error) {
2187-
// We had trouble processing the error. An example of this happening is
2188-
// when accessing the `componentDidCatch` property of an error boundary
2189-
// throws an error. A weird edge case. There's a regression test for this.
2190-
// To prevent an infinite loop, bubble the error up to the next parent.
2191-
workInProgress = returnFiber;
2192-
throw error;
2193-
}
2194-
2195-
// Return to the normal work loop.
2196-
completeUnitOfWork(unitOfWork);
2197-
return;
2198-
}
2199-
2200-
// The work-in-progress was immediately pinged. Instead of unwinding the
2201-
// stack and potentially showing a fallback, unwind only the last stack frame,
2202-
// reset the fiber, and try rendering it again.
2156+
// This is a fork of performUnitOfWork specifcally for replaying a fiber that
2157+
// just suspended.
2158+
//
2159+
// Instead of unwinding the stack and potentially showing a fallback, unwind
2160+
// only the last stack frame, reset the fiber, and try rendering it again.
22032161
const current = unitOfWork.alternate;
22042162
unwindInterruptedWork(current, unitOfWork, workInProgressRootRenderLanes);
22052163
unitOfWork = workInProgress = resetWorkInProgress(unitOfWork, renderLanes);
@@ -2232,6 +2190,55 @@ function resumeSuspendedUnitOfWork(
22322190
ReactCurrentOwner.current = null;
22332191
}
22342192

2193+
function unwindSuspendedUnitOfWork(unitOfWork: Fiber, thrownValue: mixed) {
2194+
// This is a fork of performUnitOfWork specifcally for unwinding a fiber
2195+
// that threw an exception.
2196+
//
2197+
// Return to the normal work loop. This will unwind the stack, and potentially
2198+
// result in showing a fallback.
2199+
workInProgressSuspendedThenableState = null;
2200+
2201+
const returnFiber = unitOfWork.return;
2202+
if (returnFiber === null || workInProgressRoot === null) {
2203+
// Expected to be working on a non-root fiber. This is a fatal error
2204+
// because there's no ancestor that can handle it; the root is
2205+
// supposed to capture all errors that weren't caught by an error
2206+
// boundary.
2207+
workInProgressRootExitStatus = RootFatalErrored;
2208+
workInProgressRootFatalError = thrownValue;
2209+
// Set `workInProgress` to null. This represents advancing to the next
2210+
// sibling, or the parent if there are no siblings. But since the root
2211+
// has no siblings nor a parent, we set it to null. Usually this is
2212+
// handled by `completeUnitOfWork` or `unwindWork`, but since we're
2213+
// intentionally not calling those, we need set it here.
2214+
// TODO: Consider calling `unwindWork` to pop the contexts.
2215+
workInProgress = null;
2216+
return;
2217+
}
2218+
2219+
try {
2220+
// Find and mark the nearest Suspense or error boundary that can handle
2221+
// this "exception".
2222+
throwException(
2223+
workInProgressRoot,
2224+
returnFiber,
2225+
unitOfWork,
2226+
thrownValue,
2227+
workInProgressRootRenderLanes,
2228+
);
2229+
} catch (error) {
2230+
// We had trouble processing the error. An example of this happening is
2231+
// when accessing the `componentDidCatch` property of an error boundary
2232+
// throws an error. A weird edge case. There's a regression test for this.
2233+
// To prevent an infinite loop, bubble the error up to the next parent.
2234+
workInProgress = returnFiber;
2235+
throw error;
2236+
}
2237+
2238+
// Return to the normal work loop.
2239+
completeUnitOfWork(unitOfWork);
2240+
}
2241+
22352242
export function getSuspendedThenableState(): ThenableState | null {
22362243
return workInProgressSuspendedThenableState;
22372244
}

0 commit comments

Comments
 (0)