Skip to content

Commit

Permalink
Track entangled lanes separately from update lane (#27505)
Browse files Browse the repository at this point in the history
A small refactor to how the lane entanglement mechanism works. We can
now distinguish between the lane that "spawned" a render task (i.e. a
new update) versus the lanes that it's entangled with. Both the update
lane and the entangled lanes will be included while rendering, but by
keeping them separate, we don't lose the original priority.

In practical terms, this means we can now entangle a low priority update
with a higher priority lane while rendering at the lower priority.

To do this, lanes that are entangled at the root are now tracked using
the same variable that we use to track the "base lanes" when revealing a
previously hidden tree — conceptually, they are the same thing. I also
renamed this variable (from subtreeLanes to entangledRenderLanes) to
better reflect how it's used.

My primary motivation is related to useDeferredValue, which I'll address
in a later PR.

DiffTrain build for commit 309c8ad.
  • Loading branch information
acdlite committed Oct 15, 2023
1 parent 785a98f commit e94c09b
Show file tree
Hide file tree
Showing 13 changed files with 671 additions and 578 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
* @noflow
* @nolint
* @preventMunge
* @generated SignedSource<<714756724eaa1bcdef36d80f02d51c1a>>
* @generated SignedSource<<5b025a45475b905abc64b47d27761956>>
*/

'use strict';
Expand Down Expand Up @@ -1053,6 +1053,7 @@ var SyncHydrationLane =
var SyncLane =
/* */
2;
var SyncLaneIndex = 1;
var InputContinuousHydrationLane =
/* */
4;
Expand Down Expand Up @@ -1297,13 +1298,18 @@ function getNextLanes(root, wipLanes) {
}
}

return nextLanes;
}
function getEntangledLanes(root, renderLanes) {
var entangledLanes = renderLanes;

if ((root.current.mode & ConcurrentUpdatesByDefaultMode) !== NoMode);
else if ((nextLanes & InputContinuousLane) !== NoLanes) {
else if ((entangledLanes & InputContinuousLane) !== NoLanes) {
// When updates are sync by default, we entangle continuous priority updates
// and default updates, so they render in the same batch. The only reason
// they use separate lanes is because continuous updates should interrupt
// transitions, but default updates should not.
nextLanes |= pendingLanes & DefaultLane;
entangledLanes |= entangledLanes & DefaultLane;
} // Check for entangled lanes and add them to the batch.
//
// A lane is said to be entangled with another when it's not allowed to render
Expand All @@ -1327,21 +1333,21 @@ function getNextLanes(root, wipLanes) {
// we should ensure that there is no partial work at the
// time we apply the entanglement.

var entangledLanes = root.entangledLanes;
var allEntangledLanes = root.entangledLanes;

if (entangledLanes !== NoLanes) {
if (allEntangledLanes !== NoLanes) {
var entanglements = root.entanglements;
var lanes = nextLanes & entangledLanes;
var lanes = entangledLanes & allEntangledLanes;

while (lanes > 0) {
var index = pickArbitraryLaneIndex(lanes);
var lane = 1 << index;
nextLanes |= entanglements[index];
entangledLanes |= entanglements[index];
lanes &= ~lane;
}
}

return nextLanes;
return entangledLanes;
}

function computeExpirationTime(lane, currentTime) {
Expand Down Expand Up @@ -1419,6 +1425,7 @@ function markStarvedLanesAsExpired(root, currentTime) {
var expirationTimes = root.expirationTimes; // Iterate through the pending lanes and check if we've reached their
// expiration time. If so, we'll assume the update is being starved and mark
// it as expired to force it to finish.
// TODO: We should be able to replace this with upgradePendingLanesToSync
//
// We exclude retry lanes because those must always be time sliced, in order
// to unwrap uncached promises.
Expand Down Expand Up @@ -1688,6 +1695,15 @@ function markRootEntangled(root, entangledLanes) {
lanes &= ~lane;
}
}
function upgradePendingLaneToSync(root, lane) {
// Since we're upgrading the priority of the given lane, there is now pending
// sync work.
root.pendingLanes |= SyncLane; // Entangle the sync lane with the lane we're upgrading. This means SyncLane
// will not be allowed to finish without also finishing the given lane.

root.entangledLanes |= SyncLane;
root.entanglements[SyncLaneIndex] |= lane;
}
function markHiddenUpdate(root, update, lane) {
var index = laneToIndex(lane);
var hiddenUpdates = root.hiddenUpdates;
Expand Down Expand Up @@ -5813,22 +5829,24 @@ function resetChildFibers(workInProgress, lanes) {
// InvisibleParentContext that is currently managed by SuspenseContext.

var currentTreeHiddenStackCursor = createCursor(null);
var prevRenderLanesStackCursor = createCursor(NoLanes);
var prevEntangledRenderLanesCursor = createCursor(NoLanes);
function pushHiddenContext(fiber, context) {
var prevRenderLanes = getRenderLanes();
push(prevRenderLanesStackCursor, prevRenderLanes, fiber);
var prevEntangledRenderLanes = getEntangledRenderLanes();
push(prevEntangledRenderLanesCursor, prevEntangledRenderLanes, fiber);
push(currentTreeHiddenStackCursor, context, fiber); // When rendering a subtree that's currently hidden, we must include all
// lanes that would have rendered if the hidden subtree hadn't been deferred.
// That is, in order to reveal content from hidden -> visible, we must commit
// all the updates that we skipped when we originally hid the tree.

setRenderLanes(mergeLanes(prevRenderLanes, context.baseLanes));
setEntangledRenderLanes(
mergeLanes(prevEntangledRenderLanes, context.baseLanes)
);
}
function reuseHiddenContextOnStack(fiber) {
// This subtree is not currently hidden, so we don't need to add any lanes
// to the render lanes. But we still need to push something to avoid a
// context mismatch. Reuse the existing context on the stack.
push(prevRenderLanesStackCursor, getRenderLanes(), fiber);
push(prevEntangledRenderLanesCursor, getEntangledRenderLanes(), fiber);
push(
currentTreeHiddenStackCursor,
currentTreeHiddenStackCursor.current,
Expand All @@ -5837,9 +5855,9 @@ function reuseHiddenContextOnStack(fiber) {
}
function popHiddenContext(fiber) {
// Restore the previous render lanes from the stack
setRenderLanes(prevRenderLanesStackCursor.current);
setEntangledRenderLanes(prevEntangledRenderLanesCursor.current);
pop(currentTreeHiddenStackCursor, fiber);
pop(prevRenderLanesStackCursor, fiber);
pop(prevEntangledRenderLanesCursor, fiber);
}
function isCurrentTreeHidden() {
return currentTreeHiddenStackCursor.current !== null;
Expand Down Expand Up @@ -6215,7 +6233,10 @@ function processRootScheduleInMicrotask() {
currentEventTransitionLane !== NoLane &&
shouldAttemptEagerTransition()
) {
markRootEntangled(root, mergeLanes(currentEventTransitionLane, SyncLane));
// A transition was scheduled during an event, but we're going to try to
// render it synchronously anyway. We do this during a popstate event to
// preserve the scroll position of the previous page.
upgradePendingLaneToSync(root, currentEventTransitionLane);
}

var nextLanes = scheduleTaskForRootDuringMicrotask(root, currentTime);
Expand Down Expand Up @@ -6624,7 +6645,7 @@ var didWarnAboutAsyncClientComponent;
// optimizations later.
// These are set right before calling the component.

var renderLanes$1 = NoLanes; // The work-in-progress fiber. I've named it differently to distinguish it from
var renderLanes = NoLanes; // The work-in-progress fiber. I've named it differently to distinguish it from
// the work-in-progress hook.

var currentlyRenderingFiber$1 = null; // Hooks are stored as a linked list on the fiber's memoizedState field. The
Expand Down Expand Up @@ -6867,7 +6888,7 @@ function renderWithHooks(
secondArg,
nextRenderLanes
) {
renderLanes$1 = nextRenderLanes;
renderLanes = nextRenderLanes;
currentlyRenderingFiber$1 = workInProgress;

{
Expand Down Expand Up @@ -6968,7 +6989,7 @@ function finishRenderingHooks(current, workInProgress, Component) {
// hookTypesDev could catch more cases (e.g. context) but only in DEV bundles.

var didRenderTooFewHooks = currentHook !== null && currentHook.next !== null;
renderLanes$1 = NoLanes;
renderLanes = NoLanes;
currentlyRenderingFiber$1 = null;
currentHook = null;
workInProgressHook = null;
Expand Down Expand Up @@ -7202,7 +7223,7 @@ function resetHooksOnUnwind(workInProgress) {
didScheduleRenderPhaseUpdate = false;
}

renderLanes$1 = NoLanes;
renderLanes = NoLanes;
currentlyRenderingFiber$1 = null;
currentHook = null;
workInProgressHook = null;
Expand Down Expand Up @@ -7468,7 +7489,7 @@ function updateReducerImpl(hook, current, reducer) {

var shouldSkipUpdate = isHiddenUpdate
? !isSubsetOfLanes(getWorkInProgressRootRenderLanes(), updateLane)
: !isSubsetOfLanes(renderLanes$1, updateLane);
: !isSubsetOfLanes(renderLanes, updateLane);

if (shouldSkipUpdate) {
// Priority is insufficient. Skip this update. If this is the first
Expand Down Expand Up @@ -7525,7 +7546,7 @@ function updateReducerImpl(hook, current, reducer) {
// sufficient, don't apply the update. Otherwise, apply the update,
// but leave it in the queue so it can be either reverted or
// rebased in a subsequent render.
if (isSubsetOfLanes(renderLanes$1, revertLane)) {
if (isSubsetOfLanes(renderLanes, revertLane)) {
// The transition that this optimistic update is associated with
// has finished. Pretend the update doesn't exist by skipping
// over it.
Expand Down Expand Up @@ -7698,7 +7719,9 @@ function mountSyncExternalStore(subscribe, getSnapshot, getServerSnapshot) {
);
}

if (!includesBlockingLane(root, renderLanes$1)) {
var rootRenderLanes = getWorkInProgressRootRenderLanes();

if (!includesBlockingLane(root, rootRenderLanes)) {
pushStoreConsistencyCheck(fiber, getSnapshot, nextSnapshot);
}
} // Read the current snapshot from the store on every render. This breaks the
Expand Down Expand Up @@ -7795,7 +7818,7 @@ function updateSyncExternalStore(subscribe, getSnapshot, getServerSnapshot) {
);
}

if (!includesBlockingLane(root, renderLanes$1)) {
if (!includesBlockingLane(root, renderLanes)) {
pushStoreConsistencyCheck(fiber, getSnapshot, nextSnapshot);
}
}
Expand Down Expand Up @@ -8527,7 +8550,7 @@ function mountDeferredValueImpl(hook, value, initialValue) {
function updateDeferredValueImpl(hook, prevValue, value, initialValue) {
// TODO: We should also check if this component is going from
// hidden -> visible. If so, it should use the initialValue arg.
var shouldDeferValue = !includesOnlyNonUrgentLanes(renderLanes$1);
var shouldDeferValue = !includesOnlyNonUrgentLanes(renderLanes);

if (shouldDeferValue) {
// This is an urgent update. If the value has changed, keep using the
Expand Down Expand Up @@ -20811,9 +20834,9 @@ var workInProgressRootDidAttachPingListener = false; // A contextual version of
// HiddenContext module.
//
// Most things in the work loop should deal with workInProgressRootRenderLanes.
// Most things in begin/complete phases should deal with renderLanes.
// Most things in begin/complete phases should deal with entangledRenderLanes.

var renderLanes = NoLanes; // Whether to root completed, errored, suspended, etc.
var entangledRenderLanes = NoLanes; // Whether to root completed, errored, suspended, etc.

var workInProgressRootExitStatus = RootInProgress; // A fatal error, if one is thrown

Expand Down Expand Up @@ -21596,11 +21619,11 @@ function flushSync(fn) {
// place that ever modifies it. Which module it lives in doesn't matter for
// performance because this function will get inlined regardless

function setRenderLanes(subtreeRenderLanes) {
renderLanes = subtreeRenderLanes;
function setEntangledRenderLanes(newEntangledRenderLanes) {
entangledRenderLanes = newEntangledRenderLanes;
}
function getRenderLanes() {
return renderLanes;
function getEntangledRenderLanes() {
return entangledRenderLanes;
}

function resetWorkInProgressStack() {
Expand Down Expand Up @@ -21651,7 +21674,7 @@ function prepareFreshStack(root, lanes) {
workInProgressRoot = root;
var rootWorkInProgress = createWorkInProgress(root.current, null);
workInProgress = rootWorkInProgress;
workInProgressRootRenderLanes = renderLanes = lanes;
workInProgressRootRenderLanes = lanes;
workInProgressSuspendedReason = NotSuspended;
workInProgressThrownValue = null;
workInProgressRootDidAttachPingListener = false;
Expand All @@ -21661,7 +21684,15 @@ function prepareFreshStack(root, lanes) {
workInProgressRootInterleavedUpdatedLanes = NoLanes;
workInProgressRootPingedLanes = NoLanes;
workInProgressRootConcurrentErrors = null;
workInProgressRootRecoverableErrors = null;
workInProgressRootRecoverableErrors = null; // Get the lanes that are entangled with whatever we're about to render. We
// track these separately so we can distinguish the priority of the render
// task from the priority of the lanes it is entangled with. For example, a
// transition may not be allowed to finish unless it includes the Sync lane,
// which is currently suspended. We should be able to render the Transition
// and Sync lane in the same batch, but at Transition priority, because the
// Sync lane already suspended.

entangledRenderLanes = getEntangledLanes(root, lanes);
finishQueueingConcurrentUpdates();

{
Expand Down Expand Up @@ -22251,10 +22282,10 @@ function performUnitOfWork(unitOfWork) {

if ((unitOfWork.mode & ProfileMode) !== NoMode) {
startProfilerTimer(unitOfWork);
next = beginWork(current, unitOfWork, renderLanes);
next = beginWork(current, unitOfWork, entangledRenderLanes);
stopProfilerTimerIfRunningAndRecordDelta(unitOfWork, true);
} else {
next = beginWork(current, unitOfWork, renderLanes);
next = beginWork(current, unitOfWork, entangledRenderLanes);
}

resetCurrentFiber();
Expand Down Expand Up @@ -22367,9 +22398,9 @@ function replaySuspendedUnitOfWork(unitOfWork) {
unwindInterruptedWork(current, unitOfWork);
unitOfWork = workInProgress = resetWorkInProgress(
unitOfWork,
renderLanes
entangledRenderLanes
);
next = beginWork(current, unitOfWork, renderLanes);
next = beginWork(current, unitOfWork, entangledRenderLanes);
break;
}
}
Expand Down Expand Up @@ -22479,10 +22510,10 @@ function completeUnitOfWork(unitOfWork) {
var next = void 0;

if ((completedWork.mode & ProfileMode) === NoMode) {
next = completeWork(current, completedWork, renderLanes);
next = completeWork(current, completedWork, entangledRenderLanes);
} else {
startProfilerTimer(completedWork);
next = completeWork(current, completedWork, renderLanes); // Update render duration assuming we didn't error.
next = completeWork(current, completedWork, entangledRenderLanes); // Update render duration assuming we didn't error.

stopProfilerTimerIfRunningAndRecordDelta(completedWork, false);
}
Expand Down Expand Up @@ -24770,7 +24801,7 @@ function createFiberRoot(
return root;
}

var ReactVersion = "18.3.0-canary-09fbee89d-20231013";
var ReactVersion = "18.3.0-canary-309c8ad96-20231015";

// Might add PROFILE later.

Expand Down
Loading

0 comments on commit e94c09b

Please sign in to comment.