Entangled expired lanes with SyncLane (#21083)

Makes the implementation simpler. Expiration is now a special case of
entanglement.

Also fixes an issue where expired lanes weren't batched with normal
sync updates. (See deleted TODO comment in test.)
This commit is contained in:
Andrew Clark 2021-03-25 11:24:49 -05:00 committed by GitHub
parent 03ede83d2e
commit 05ec0d7646
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 110 additions and 105 deletions

View File

@ -286,42 +286,34 @@ export function getNextLanes(root: FiberRoot, wipLanes: Lanes): Lanes {
let nextLanes = NoLanes;
let nextLanePriority = NoLanePriority;
const expiredLanes = root.expiredLanes;
const suspendedLanes = root.suspendedLanes;
const pingedLanes = root.pingedLanes;
// Check if any work has expired.
if (expiredLanes !== NoLanes) {
// TODO: Should entangle with SyncLane
nextLanes = expiredLanes;
nextLanePriority = return_highestLanePriority = SyncLanePriority;
} else {
// Do not work on any idle work until all the non-idle work has finished,
// even if the work is suspended.
const nonIdlePendingLanes = pendingLanes & NonIdleLanes;
if (nonIdlePendingLanes !== NoLanes) {
const nonIdleUnblockedLanes = nonIdlePendingLanes & ~suspendedLanes;
if (nonIdleUnblockedLanes !== NoLanes) {
nextLanes = getHighestPriorityLanes(nonIdleUnblockedLanes);
nextLanePriority = return_highestLanePriority;
} else {
const nonIdlePingedLanes = nonIdlePendingLanes & pingedLanes;
if (nonIdlePingedLanes !== NoLanes) {
nextLanes = getHighestPriorityLanes(nonIdlePingedLanes);
nextLanePriority = return_highestLanePriority;
}
}
// Do not work on any idle work until all the non-idle work has finished,
// even if the work is suspended.
const nonIdlePendingLanes = pendingLanes & NonIdleLanes;
if (nonIdlePendingLanes !== NoLanes) {
const nonIdleUnblockedLanes = nonIdlePendingLanes & ~suspendedLanes;
if (nonIdleUnblockedLanes !== NoLanes) {
nextLanes = getHighestPriorityLanes(nonIdleUnblockedLanes);
nextLanePriority = return_highestLanePriority;
} else {
// The only remaining work is Idle.
const unblockedLanes = pendingLanes & ~suspendedLanes;
if (unblockedLanes !== NoLanes) {
nextLanes = getHighestPriorityLanes(unblockedLanes);
const nonIdlePingedLanes = nonIdlePendingLanes & pingedLanes;
if (nonIdlePingedLanes !== NoLanes) {
nextLanes = getHighestPriorityLanes(nonIdlePingedLanes);
nextLanePriority = return_highestLanePriority;
}
}
} else {
// The only remaining work is Idle.
const unblockedLanes = pendingLanes & ~suspendedLanes;
if (unblockedLanes !== NoLanes) {
nextLanes = getHighestPriorityLanes(unblockedLanes);
nextLanePriority = return_highestLanePriority;
} else {
if (pingedLanes !== NoLanes) {
nextLanes = getHighestPriorityLanes(pingedLanes);
nextLanePriority = return_highestLanePriority;
} else {
if (pingedLanes !== NoLanes) {
nextLanes = getHighestPriorityLanes(pingedLanes);
nextLanePriority = return_highestLanePriority;
}
}
}
}
@ -463,6 +455,7 @@ export function markStarvedLanesAsExpired(
// expiration time. If so, we'll assume the update is being starved and mark
// it as expired to force it to finish.
let lanes = pendingLanes;
let expiredLanes = 0;
while (lanes > 0) {
const index = pickArbitraryLaneIndex(lanes);
const lane = 1 << index;
@ -481,11 +474,15 @@ export function markStarvedLanesAsExpired(
}
} else if (expirationTime <= currentTime) {
// This lane expired
root.expiredLanes |= lane;
expiredLanes |= lane;
}
lanes &= ~lane;
}
if (expiredLanes !== 0) {
markRootExpired(root, expiredLanes);
}
}
// This returns the highest priority pending lanes regardless of whether they
@ -668,7 +665,17 @@ export function markRootPinged(
}
export function markRootExpired(root: FiberRoot, expiredLanes: Lanes) {
root.expiredLanes |= expiredLanes & root.pendingLanes;
const entanglements = root.entanglements;
const SyncLaneIndex = 0;
entanglements[SyncLaneIndex] |= expiredLanes;
root.entangledLanes |= SyncLane;
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) {
@ -684,7 +691,6 @@ export function markRootFinished(root: FiberRoot, remainingLanes: Lanes) {
root.suspendedLanes = 0;
root.pingedLanes = 0;
root.expiredLanes &= remainingLanes;
root.mutableReadLanes &= remainingLanes;
root.entangledLanes &= remainingLanes;

View File

@ -286,42 +286,34 @@ export function getNextLanes(root: FiberRoot, wipLanes: Lanes): Lanes {
let nextLanes = NoLanes;
let nextLanePriority = NoLanePriority;
const expiredLanes = root.expiredLanes;
const suspendedLanes = root.suspendedLanes;
const pingedLanes = root.pingedLanes;
// Check if any work has expired.
if (expiredLanes !== NoLanes) {
// TODO: Should entangle with SyncLane
nextLanes = expiredLanes;
nextLanePriority = return_highestLanePriority = SyncLanePriority;
} else {
// Do not work on any idle work until all the non-idle work has finished,
// even if the work is suspended.
const nonIdlePendingLanes = pendingLanes & NonIdleLanes;
if (nonIdlePendingLanes !== NoLanes) {
const nonIdleUnblockedLanes = nonIdlePendingLanes & ~suspendedLanes;
if (nonIdleUnblockedLanes !== NoLanes) {
nextLanes = getHighestPriorityLanes(nonIdleUnblockedLanes);
nextLanePriority = return_highestLanePriority;
} else {
const nonIdlePingedLanes = nonIdlePendingLanes & pingedLanes;
if (nonIdlePingedLanes !== NoLanes) {
nextLanes = getHighestPriorityLanes(nonIdlePingedLanes);
nextLanePriority = return_highestLanePriority;
}
}
// Do not work on any idle work until all the non-idle work has finished,
// even if the work is suspended.
const nonIdlePendingLanes = pendingLanes & NonIdleLanes;
if (nonIdlePendingLanes !== NoLanes) {
const nonIdleUnblockedLanes = nonIdlePendingLanes & ~suspendedLanes;
if (nonIdleUnblockedLanes !== NoLanes) {
nextLanes = getHighestPriorityLanes(nonIdleUnblockedLanes);
nextLanePriority = return_highestLanePriority;
} else {
// The only remaining work is Idle.
const unblockedLanes = pendingLanes & ~suspendedLanes;
if (unblockedLanes !== NoLanes) {
nextLanes = getHighestPriorityLanes(unblockedLanes);
const nonIdlePingedLanes = nonIdlePendingLanes & pingedLanes;
if (nonIdlePingedLanes !== NoLanes) {
nextLanes = getHighestPriorityLanes(nonIdlePingedLanes);
nextLanePriority = return_highestLanePriority;
}
}
} else {
// The only remaining work is Idle.
const unblockedLanes = pendingLanes & ~suspendedLanes;
if (unblockedLanes !== NoLanes) {
nextLanes = getHighestPriorityLanes(unblockedLanes);
nextLanePriority = return_highestLanePriority;
} else {
if (pingedLanes !== NoLanes) {
nextLanes = getHighestPriorityLanes(pingedLanes);
nextLanePriority = return_highestLanePriority;
} else {
if (pingedLanes !== NoLanes) {
nextLanes = getHighestPriorityLanes(pingedLanes);
nextLanePriority = return_highestLanePriority;
}
}
}
}
@ -463,6 +455,7 @@ export function markStarvedLanesAsExpired(
// expiration time. If so, we'll assume the update is being starved and mark
// it as expired to force it to finish.
let lanes = pendingLanes;
let expiredLanes = 0;
while (lanes > 0) {
const index = pickArbitraryLaneIndex(lanes);
const lane = 1 << index;
@ -481,11 +474,15 @@ export function markStarvedLanesAsExpired(
}
} else if (expirationTime <= currentTime) {
// This lane expired
root.expiredLanes |= lane;
expiredLanes |= lane;
}
lanes &= ~lane;
}
if (expiredLanes !== 0) {
markRootExpired(root, expiredLanes);
}
}
// This returns the highest priority pending lanes regardless of whether they
@ -668,7 +665,17 @@ export function markRootPinged(
}
export function markRootExpired(root: FiberRoot, expiredLanes: Lanes) {
root.expiredLanes |= expiredLanes & root.pendingLanes;
const entanglements = root.entanglements;
const SyncLaneIndex = 0;
entanglements[SyncLaneIndex] |= expiredLanes;
root.entangledLanes |= SyncLane;
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) {
@ -684,7 +691,6 @@ export function markRootFinished(root: FiberRoot, remainingLanes: Lanes) {
root.suspendedLanes = 0;
root.pingedLanes = 0;
root.expiredLanes &= remainingLanes;
root.mutableReadLanes &= remainingLanes;
root.entangledLanes &= remainingLanes;

View File

@ -48,7 +48,6 @@ function FiberRootNode(containerInfo, tag, hydrate) {
this.pendingLanes = NoLanes;
this.suspendedLanes = NoLanes;
this.pingedLanes = NoLanes;
this.expiredLanes = NoLanes;
this.mutableReadLanes = NoLanes;
this.finishedLanes = NoLanes;

View File

@ -48,7 +48,6 @@ function FiberRootNode(containerInfo, tag, hydrate) {
this.pendingLanes = NoLanes;
this.suspendedLanes = NoLanes;
this.pingedLanes = NoLanes;
this.expiredLanes = NoLanes;
this.mutableReadLanes = NoLanes;
this.finishedLanes = NoLanes;

View File

@ -157,6 +157,7 @@ import {
markRootExpired,
markRootFinished,
lanePriorityToSchedulerPriority,
areLanesExpired,
} from './ReactFiberLane.new';
import {
DiscreteEventPriority,
@ -966,7 +967,7 @@ function performSyncWorkOnRoot(root) {
let exitStatus;
if (
root === workInProgressRoot &&
includesSomeLane(root.expiredLanes, workInProgressRootRenderLanes)
areLanesExpired(root, 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.
@ -1022,12 +1023,16 @@ function performSyncWorkOnRoot(root) {
return null;
}
// TODO: Do we still need this API? I think we can delete it. Was only used
// internally.
export function flushRoot(root: FiberRoot, lanes: Lanes) {
markRootExpired(root, lanes);
ensureRootIsScheduled(root, now());
if ((executionContext & (RenderContext | CommitContext)) === NoContext) {
resetRenderTimer();
flushSyncCallbackQueue();
if (lanes !== NoLanes) {
markRootExpired(root, lanes);
ensureRootIsScheduled(root, now());
if ((executionContext & (RenderContext | CommitContext)) === NoContext) {
resetRenderTimer();
flushSyncCallbackQueue();
}
}
}

View File

@ -157,6 +157,7 @@ import {
markRootExpired,
markRootFinished,
lanePriorityToSchedulerPriority,
areLanesExpired,
} from './ReactFiberLane.old';
import {
DiscreteEventPriority,
@ -966,7 +967,7 @@ function performSyncWorkOnRoot(root) {
let exitStatus;
if (
root === workInProgressRoot &&
includesSomeLane(root.expiredLanes, workInProgressRootRenderLanes)
areLanesExpired(root, 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.
@ -1022,12 +1023,16 @@ function performSyncWorkOnRoot(root) {
return null;
}
// TODO: Do we still need this API? I think we can delete it. Was only used
// internally.
export function flushRoot(root: FiberRoot, lanes: Lanes) {
markRootExpired(root, lanes);
ensureRootIsScheduled(root, now());
if ((executionContext & (RenderContext | CommitContext)) === NoContext) {
resetRenderTimer();
flushSyncCallbackQueue();
if (lanes !== NoLanes) {
markRootExpired(root, lanes);
ensureRootIsScheduled(root, now());
if ((executionContext & (RenderContext | CommitContext)) === NoContext) {
resetRenderTimer();
flushSyncCallbackQueue();
}
}
}

View File

@ -230,7 +230,6 @@ type BaseFiberRootProperties = {|
pendingLanes: Lanes,
suspendedLanes: Lanes,
pingedLanes: Lanes,
expiredLanes: Lanes,
mutableReadLanes: Lanes,
finishedLanes: Lanes,

View File

@ -682,28 +682,14 @@ describe('ReactExpiration', () => {
updateHighPri();
// Both normal pri updates should have expired.
if (gate(flags => flags.FIXME)) {
// The sync update and the expired normal pri updates render in a
// single batch.
expect(Scheduler).toHaveYielded([
'Sibling',
'High pri: 1',
'Normal pri: 2',
'Sibling',
]);
} else {
expect(Scheduler).toHaveYielded([
'Sibling',
'High pri: 0',
'Normal pri: 2',
'Sibling',
// TODO: This is the sync update. We should have rendered it in the same
// batch as the expired update.
'High pri: 1',
'Normal pri: 2',
'Sibling',
]);
}
// The sync update and the expired normal pri updates render in a
// single batch.
expect(Scheduler).toHaveYielded([
'Sibling',
'High pri: 1',
'Normal pri: 2',
'Sibling',
]);
});
});