Skip to content

Commit

Permalink
Fix sloppy factoring in performSyncWorkOnRoot (#21246)
Browse files Browse the repository at this point in the history
* Warn if `finishedLanes` is empty in commit phase

See #21233 for context.

* Fix sloppy factoring when assigning finishedLanes

`finishedLanes` is assigned in `performSyncWorkOnRoot` and
`performSyncWorkOnRoot`. It's meant to represent whichever lanes we
used to render, but because of some sloppy factoring, it can sometimes
equal `NoLanes`.

The fixes are:
- Always check if the lanes are not `NoLanes` before entering the work
loop. There was a branch where this wasn't always true.
- In `performSyncWorkOnRoot`, don't assume the next lanes are sync; the
priority may have changed, or they may have been flushed by a
previous task.
- Don't re-assign the `lanes` variable (the one that gets assigned to
`finishedLanes` until right before we enter the work loop, so that it
is always corresponds to the newest complete root.
  • Loading branch information
acdlite committed Apr 20, 2021
1 parent 7812003 commit bd7f4a0
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 50 deletions.
6 changes: 0 additions & 6 deletions packages/react-reconciler/src/ReactFiberLane.new.js
Expand Up @@ -614,12 +614,6 @@ export function markRootExpired(root: FiberRoot, expiredLanes: Lanes) {
root.pendingLanes |= SyncLane;
}

export function areLanesExpired(root: FiberRoot, lanes: Lanes) {
const SyncLaneIndex = 0;
const entanglements = root.entanglements;
return (entanglements[SyncLaneIndex] & lanes) !== NoLanes;
}

export function markRootMutableRead(root: FiberRoot, updateLane: Lane) {
root.mutableReadLanes |= updateLane & root.pendingLanes;
}
Expand Down
6 changes: 0 additions & 6 deletions packages/react-reconciler/src/ReactFiberLane.old.js
Expand Up @@ -614,12 +614,6 @@ export function markRootExpired(root: FiberRoot, expiredLanes: Lanes) {
root.pendingLanes |= SyncLane;
}

export function areLanesExpired(root: FiberRoot, lanes: Lanes) {
const SyncLaneIndex = 0;
const entanglements = root.entanglements;
return (entanglements[SyncLaneIndex] & lanes) !== NoLanes;
}

export function markRootMutableRead(root: FiberRoot, updateLane: Lane) {
root.mutableReadLanes |= updateLane & root.pendingLanes;
}
Expand Down
60 changes: 41 additions & 19 deletions packages/react-reconciler/src/ReactFiberWorkLoop.new.js
Expand Up @@ -163,7 +163,6 @@ import {
markRootPinged,
markRootExpired,
markRootFinished,
areLanesExpired,
getHighestPriorityLane,
addFiberToLanesMap,
movePendingFibersToMemoized,
Expand Down Expand Up @@ -840,9 +839,10 @@ function performConcurrentWorkOnRoot(root, didTimeout) {
// synchronously to block concurrent data mutations, and we'll includes
// all pending updates are included. If it still fails after the second
// attempt, we'll give up and commit the resulting tree.
lanes = getLanesToRetrySynchronouslyOnError(root);
if (lanes !== NoLanes) {
exitStatus = renderRootSync(root, lanes);
const errorRetryLanes = getLanesToRetrySynchronouslyOnError(root);
if (errorRetryLanes !== NoLanes) {
lanes = errorRetryLanes;
exitStatus = renderRootSync(root, errorRetryLanes);
}
}

Expand Down Expand Up @@ -1007,21 +1007,29 @@ function performSyncWorkOnRoot(root) {

flushPassiveEffects();

let lanes;
let exitStatus;
if (
root === workInProgressRoot &&
areLanesExpired(root, workInProgressRootRenderLanes)
let lanes = getNextLanes(root, NoLanes);
if (includesSomeLane(lanes, SyncLane)) {
if (
root === workInProgressRoot &&
includesSomeLane(lanes, workInProgressRootRenderLanes)
) {
// There's a partial tree, and at least one of its lanes has expired. Finish
// rendering it before rendering the rest of the expired work.
lanes = workInProgressRootRenderLanes;
}
} else if (
!(
enableSyncDefaultUpdates &&
(includesSomeLane(lanes, DefaultLane) ||
includesSomeLane(lanes, DefaultHydrationLane))
)
) {
// There's a partial tree, and at least one of its lanes has expired. Finish
// rendering it before rendering the rest of the expired work.
lanes = workInProgressRootRenderLanes;
exitStatus = renderRootSync(root, lanes);
} else {
lanes = getNextLanes(root, NoLanes);
exitStatus = renderRootSync(root, lanes);
// There's no remaining sync work left.
ensureRootIsScheduled(root, now());
return null;
}

let exitStatus = renderRootSync(root, lanes);
if (root.tag !== LegacyRoot && exitStatus === RootErrored) {
executionContext |= RetryAfterError;

Expand All @@ -1039,8 +1047,9 @@ function performSyncWorkOnRoot(root) {
// synchronously to block concurrent data mutations, and we'll includes
// all pending updates are included. If it still fails after the second
// attempt, we'll give up and commit the resulting tree.
lanes = getLanesToRetrySynchronouslyOnError(root);
if (lanes !== NoLanes) {
const errorRetryLanes = getLanesToRetrySynchronouslyOnError(root);
if (errorRetryLanes !== NoLanes) {
lanes = errorRetryLanes;
exitStatus = renderRootSync(root, lanes);
}
}
Expand All @@ -1058,7 +1067,11 @@ function performSyncWorkOnRoot(root) {
const finishedWork: Fiber = (root.current.alternate: any);
root.finishedWork = finishedWork;
root.finishedLanes = lanes;
if (enableSyncDefaultUpdates && !includesSomeLane(lanes, SyncLane)) {
if (
enableSyncDefaultUpdates &&
(includesSomeLane(lanes, DefaultLane) ||
includesSomeLane(lanes, DefaultHydrationLane))
) {
finishConcurrentRender(root, exitStatus, lanes);
} else {
commitRoot(root);
Expand Down Expand Up @@ -1826,6 +1839,15 @@ function commitRootImpl(root, renderPriorityLevel) {
}

return null;
} else {
if (__DEV__) {
if (lanes === NoLanes) {
console.error(
'root.finishedLanes should not be empty during a commit. This is a ' +
'bug in React.',
);
}
}
}
root.finishedWork = null;
root.finishedLanes = NoLanes;
Expand Down
60 changes: 41 additions & 19 deletions packages/react-reconciler/src/ReactFiberWorkLoop.old.js
Expand Up @@ -163,7 +163,6 @@ import {
markRootPinged,
markRootExpired,
markRootFinished,
areLanesExpired,
getHighestPriorityLane,
addFiberToLanesMap,
movePendingFibersToMemoized,
Expand Down Expand Up @@ -840,9 +839,10 @@ function performConcurrentWorkOnRoot(root, didTimeout) {
// synchronously to block concurrent data mutations, and we'll includes
// all pending updates are included. If it still fails after the second
// attempt, we'll give up and commit the resulting tree.
lanes = getLanesToRetrySynchronouslyOnError(root);
if (lanes !== NoLanes) {
exitStatus = renderRootSync(root, lanes);
const errorRetryLanes = getLanesToRetrySynchronouslyOnError(root);
if (errorRetryLanes !== NoLanes) {
lanes = errorRetryLanes;
exitStatus = renderRootSync(root, errorRetryLanes);
}
}

Expand Down Expand Up @@ -1007,21 +1007,29 @@ function performSyncWorkOnRoot(root) {

flushPassiveEffects();

let lanes;
let exitStatus;
if (
root === workInProgressRoot &&
areLanesExpired(root, workInProgressRootRenderLanes)
let lanes = getNextLanes(root, NoLanes);
if (includesSomeLane(lanes, SyncLane)) {
if (
root === workInProgressRoot &&
includesSomeLane(lanes, workInProgressRootRenderLanes)
) {
// There's a partial tree, and at least one of its lanes has expired. Finish
// rendering it before rendering the rest of the expired work.
lanes = workInProgressRootRenderLanes;
}
} else if (
!(
enableSyncDefaultUpdates &&
(includesSomeLane(lanes, DefaultLane) ||
includesSomeLane(lanes, DefaultHydrationLane))
)
) {
// There's a partial tree, and at least one of its lanes has expired. Finish
// rendering it before rendering the rest of the expired work.
lanes = workInProgressRootRenderLanes;
exitStatus = renderRootSync(root, lanes);
} else {
lanes = getNextLanes(root, NoLanes);
exitStatus = renderRootSync(root, lanes);
// There's no remaining sync work left.
ensureRootIsScheduled(root, now());
return null;
}

let exitStatus = renderRootSync(root, lanes);
if (root.tag !== LegacyRoot && exitStatus === RootErrored) {
executionContext |= RetryAfterError;

Expand All @@ -1039,8 +1047,9 @@ function performSyncWorkOnRoot(root) {
// synchronously to block concurrent data mutations, and we'll includes
// all pending updates are included. If it still fails after the second
// attempt, we'll give up and commit the resulting tree.
lanes = getLanesToRetrySynchronouslyOnError(root);
if (lanes !== NoLanes) {
const errorRetryLanes = getLanesToRetrySynchronouslyOnError(root);
if (errorRetryLanes !== NoLanes) {
lanes = errorRetryLanes;
exitStatus = renderRootSync(root, lanes);
}
}
Expand All @@ -1058,7 +1067,11 @@ function performSyncWorkOnRoot(root) {
const finishedWork: Fiber = (root.current.alternate: any);
root.finishedWork = finishedWork;
root.finishedLanes = lanes;
if (enableSyncDefaultUpdates && !includesSomeLane(lanes, SyncLane)) {
if (
enableSyncDefaultUpdates &&
(includesSomeLane(lanes, DefaultLane) ||
includesSomeLane(lanes, DefaultHydrationLane))
) {
finishConcurrentRender(root, exitStatus, lanes);
} else {
commitRoot(root);
Expand Down Expand Up @@ -1826,6 +1839,15 @@ function commitRootImpl(root, renderPriorityLevel) {
}

return null;
} else {
if (__DEV__) {
if (lanes === NoLanes) {
console.error(
'root.finishedLanes should not be empty during a commit. This is a ' +
'bug in React.',
);
}
}
}
root.finishedWork = null;
root.finishedLanes = NoLanes;
Expand Down

0 comments on commit bd7f4a0

Please sign in to comment.