mirror of
https://github.com/zebrajr/react.git
synced 2025-12-06 00:20:04 +01:00
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:
parent
03ede83d2e
commit
05ec0d7646
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -230,7 +230,6 @@ type BaseFiberRootProperties = {|
|
|||
pendingLanes: Lanes,
|
||||
suspendedLanes: Lanes,
|
||||
pingedLanes: Lanes,
|
||||
expiredLanes: Lanes,
|
||||
mutableReadLanes: Lanes,
|
||||
|
||||
finishedLanes: Lanes,
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user