Skip to content

Commit 40c1118

Browse files
committed
Track entangled lanes separately from update lane (#27505)
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 [309c8ad](309c8ad)
1 parent bd95626 commit 40c1118

23 files changed

Lines changed: 3088 additions & 2694 deletions

‎compiled/facebook-www/REVISION‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
09fbee89d62bc1c4a00e64474346cb9bc87682cb
1+
309c8ad9688c491e5b17beb07ab01d65594914ce

‎compiled/facebook-www/React-dev.classic.js‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ if (
2727
}
2828
"use strict";
2929

30-
var ReactVersion = "18.3.0-www-classic-d874e0af";
30+
var ReactVersion = "18.3.0-www-classic-fe25c3f7";
3131

3232
// ATTENTION
3333
// When adding new symbols to this file,

‎compiled/facebook-www/React-prod.classic.js‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -587,4 +587,4 @@ exports.useSyncExternalStore = function (
587587
exports.useTransition = function () {
588588
return ReactCurrentDispatcher.current.useTransition();
589589
};
590-
exports.version = "18.3.0-www-classic-96adf723";
590+
exports.version = "18.3.0-www-classic-c9d845f9";

‎compiled/facebook-www/React-prod.modern.js‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -579,4 +579,4 @@ exports.useSyncExternalStore = function (
579579
exports.useTransition = function () {
580580
return ReactCurrentDispatcher.current.useTransition();
581581
};
582-
exports.version = "18.3.0-www-modern-10dc99c2";
582+
exports.version = "18.3.0-www-modern-5f20e279";

‎compiled/facebook-www/React-profiling.modern.js‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -590,7 +590,7 @@ exports.useSyncExternalStore = function (
590590
exports.useTransition = function () {
591591
return ReactCurrentDispatcher.current.useTransition();
592592
};
593-
exports.version = "18.3.0-www-modern-932ea55a";
593+
exports.version = "18.3.0-www-modern-a6764733";
594594

595595
/* global __REACT_DEVTOOLS_GLOBAL_HOOK__ */
596596
if (

‎compiled/facebook-www/ReactART-dev.classic.js‎

Lines changed: 70 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ function _assertThisInitialized(self) {
6969
return self;
7070
}
7171

72-
var ReactVersion = "18.3.0-www-classic-9e42c512";
72+
var ReactVersion = "18.3.0-www-classic-69d5173c";
7373

7474
var LegacyRoot = 0;
7575
var ConcurrentRoot = 1;
@@ -1570,6 +1570,7 @@ var SyncHydrationLane =
15701570
var SyncLane =
15711571
/* */
15721572
2;
1573+
var SyncLaneIndex = 1;
15731574
var InputContinuousHydrationLane =
15741575
/* */
15751576
4;
@@ -1881,13 +1882,18 @@ function getNextLanes(root, wipLanes) {
18811882
}
18821883
}
18831884

1885+
return nextLanes;
1886+
}
1887+
function getEntangledLanes(root, renderLanes) {
1888+
var entangledLanes = renderLanes;
1889+
18841890
if ((root.current.mode & ConcurrentUpdatesByDefaultMode) !== NoMode);
1885-
else if ((nextLanes & InputContinuousLane) !== NoLanes) {
1891+
else if ((entangledLanes & InputContinuousLane) !== NoLanes) {
18861892
// When updates are sync by default, we entangle continuous priority updates
18871893
// and default updates, so they render in the same batch. The only reason
18881894
// they use separate lanes is because continuous updates should interrupt
18891895
// transitions, but default updates should not.
1890-
nextLanes |= pendingLanes & DefaultLane;
1896+
entangledLanes |= entangledLanes & DefaultLane;
18911897
} // Check for entangled lanes and add them to the batch.
18921898
//
18931899
// A lane is said to be entangled with another when it's not allowed to render
@@ -1911,21 +1917,21 @@ function getNextLanes(root, wipLanes) {
19111917
// we should ensure that there is no partial work at the
19121918
// time we apply the entanglement.
19131919

1914-
var entangledLanes = root.entangledLanes;
1920+
var allEntangledLanes = root.entangledLanes;
19151921

1916-
if (entangledLanes !== NoLanes) {
1922+
if (allEntangledLanes !== NoLanes) {
19171923
var entanglements = root.entanglements;
1918-
var lanes = nextLanes & entangledLanes;
1924+
var lanes = entangledLanes & allEntangledLanes;
19191925

19201926
while (lanes > 0) {
19211927
var index = pickArbitraryLaneIndex(lanes);
19221928
var lane = 1 << index;
1923-
nextLanes |= entanglements[index];
1929+
entangledLanes |= entanglements[index];
19241930
lanes &= ~lane;
19251931
}
19261932
}
19271933

1928-
return nextLanes;
1934+
return entangledLanes;
19291935
}
19301936

19311937
function computeExpirationTime(lane, currentTime) {
@@ -2003,6 +2009,7 @@ function markStarvedLanesAsExpired(root, currentTime) {
20032009
var expirationTimes = root.expirationTimes; // Iterate through the pending lanes and check if we've reached their
20042010
// expiration time. If so, we'll assume the update is being starved and mark
20052011
// it as expired to force it to finish.
2012+
// TODO: We should be able to replace this with upgradePendingLanesToSync
20062013
//
20072014
// We exclude retry lanes because those must always be time sliced, in order
20082015
// to unwrap uncached promises.
@@ -2272,6 +2279,15 @@ function markRootEntangled(root, entangledLanes) {
22722279
lanes &= ~lane;
22732280
}
22742281
}
2282+
function upgradePendingLaneToSync(root, lane) {
2283+
// Since we're upgrading the priority of the given lane, there is now pending
2284+
// sync work.
2285+
root.pendingLanes |= SyncLane; // Entangle the sync lane with the lane we're upgrading. This means SyncLane
2286+
// will not be allowed to finish without also finishing the given lane.
2287+
2288+
root.entangledLanes |= SyncLane;
2289+
root.entanglements[SyncLaneIndex] |= lane;
2290+
}
22752291
function markHiddenUpdate(root, update, lane) {
22762292
var index = laneToIndex(lane);
22772293
var hiddenUpdates = root.hiddenUpdates;
@@ -7050,22 +7066,24 @@ function resetChildFibers(workInProgress, lanes) {
70507066
// InvisibleParentContext that is currently managed by SuspenseContext.
70517067

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

7062-
setRenderLanes(mergeLanes(prevRenderLanes, context.baseLanes));
7078+
setEntangledRenderLanes(
7079+
mergeLanes(prevEntangledRenderLanes, context.baseLanes)
7080+
);
70637081
}
70647082
function reuseHiddenContextOnStack(fiber) {
70657083
// This subtree is not currently hidden, so we don't need to add any lanes
70667084
// to the render lanes. But we still need to push something to avoid a
70677085
// context mismatch. Reuse the existing context on the stack.
7068-
push(prevRenderLanesStackCursor, getRenderLanes(), fiber);
7086+
push(prevEntangledRenderLanesCursor, getEntangledRenderLanes(), fiber);
70697087
push(
70707088
currentTreeHiddenStackCursor,
70717089
currentTreeHiddenStackCursor.current,
@@ -7074,9 +7092,9 @@ function reuseHiddenContextOnStack(fiber) {
70747092
}
70757093
function popHiddenContext(fiber) {
70767094
// Restore the previous render lanes from the stack
7077-
setRenderLanes(prevRenderLanesStackCursor.current);
7095+
setEntangledRenderLanes(prevEntangledRenderLanesCursor.current);
70787096
pop(currentTreeHiddenStackCursor, fiber);
7079-
pop(prevRenderLanesStackCursor, fiber);
7097+
pop(prevEntangledRenderLanesCursor, fiber);
70807098
}
70817099
function isCurrentTreeHidden() {
70827100
return currentTreeHiddenStackCursor.current !== null;
@@ -7485,7 +7503,10 @@ function processRootScheduleInMicrotask() {
74857503
currentEventTransitionLane !== NoLane &&
74867504
shouldAttemptEagerTransition()
74877505
) {
7488-
markRootEntangled(root, mergeLanes(currentEventTransitionLane, SyncLane));
7506+
// A transition was scheduled during an event, but we're going to try to
7507+
// render it synchronously anyway. We do this during a popstate event to
7508+
// preserve the scroll position of the previous page.
7509+
upgradePendingLaneToSync(root, currentEventTransitionLane);
74897510
}
74907511

74917512
var nextLanes = scheduleTaskForRootDuringMicrotask(root, currentTime);
@@ -7894,7 +7915,7 @@ var didWarnAboutAsyncClientComponent;
78947915
// optimizations later.
78957916
// These are set right before calling the component.
78967917

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

79007921
var currentlyRenderingFiber$1 = null; // Hooks are stored as a linked list on the fiber's memoizedState field. The
@@ -8137,7 +8158,7 @@ function renderWithHooks(
81378158
secondArg,
81388159
nextRenderLanes
81398160
) {
8140-
renderLanes$1 = nextRenderLanes;
8161+
renderLanes = nextRenderLanes;
81418162
currentlyRenderingFiber$1 = workInProgress;
81428163

81438164
{
@@ -8255,7 +8276,7 @@ function finishRenderingHooks(current, workInProgress, Component) {
82558276
// hookTypesDev could catch more cases (e.g. context) but only in DEV bundles.
82568277

82578278
var didRenderTooFewHooks = currentHook !== null && currentHook.next !== null;
8258-
renderLanes$1 = NoLanes;
8279+
renderLanes = NoLanes;
82598280
currentlyRenderingFiber$1 = null;
82608281
currentHook = null;
82618282
workInProgressHook = null;
@@ -8482,7 +8503,7 @@ function resetHooksOnUnwind(workInProgress) {
84828503
didScheduleRenderPhaseUpdate = false;
84838504
}
84848505

8485-
renderLanes$1 = NoLanes;
8506+
renderLanes = NoLanes;
84868507
currentlyRenderingFiber$1 = null;
84878508
currentHook = null;
84888509
workInProgressHook = null;
@@ -8816,7 +8837,7 @@ function updateReducerImpl(hook, current, reducer) {
88168837

88178838
var shouldSkipUpdate = isHiddenUpdate
88188839
? !isSubsetOfLanes(getWorkInProgressRootRenderLanes(), updateLane)
8819-
: !isSubsetOfLanes(renderLanes$1, updateLane);
8840+
: !isSubsetOfLanes(renderLanes, updateLane);
88208841

88218842
if (shouldSkipUpdate) {
88228843
// Priority is insufficient. Skip this update. If this is the first
@@ -8873,7 +8894,7 @@ function updateReducerImpl(hook, current, reducer) {
88738894
// sufficient, don't apply the update. Otherwise, apply the update,
88748895
// but leave it in the queue so it can be either reverted or
88758896
// rebased in a subsequent render.
8876-
if (isSubsetOfLanes(renderLanes$1, revertLane)) {
8897+
if (isSubsetOfLanes(renderLanes, revertLane)) {
88778898
// The transition that this optimistic update is associated with
88788899
// has finished. Pretend the update doesn't exist by skipping
88798900
// over it.
@@ -9046,7 +9067,9 @@ function mountSyncExternalStore(subscribe, getSnapshot, getServerSnapshot) {
90469067
);
90479068
}
90489069

9049-
if (!includesBlockingLane(root, renderLanes$1)) {
9070+
var rootRenderLanes = getWorkInProgressRootRenderLanes();
9071+
9072+
if (!includesBlockingLane(root, rootRenderLanes)) {
90509073
pushStoreConsistencyCheck(fiber, getSnapshot, nextSnapshot);
90519074
}
90529075
} // Read the current snapshot from the store on every render. This breaks the
@@ -9143,7 +9166,7 @@ function updateSyncExternalStore(subscribe, getSnapshot, getServerSnapshot) {
91439166
);
91449167
}
91459168

9146-
if (!includesBlockingLane(root, renderLanes$1)) {
9169+
if (!includesBlockingLane(root, renderLanes)) {
91479170
pushStoreConsistencyCheck(fiber, getSnapshot, nextSnapshot);
91489171
}
91499172
}
@@ -9789,7 +9812,7 @@ function mountDeferredValueImpl(hook, value, initialValue) {
97899812
function updateDeferredValueImpl(hook, prevValue, value, initialValue) {
97909813
// TODO: We should also check if this component is going from
97919814
// hidden -> visible. If so, it should use the initialValue arg.
9792-
var shouldDeferValue = !includesOnlyNonUrgentLanes(renderLanes$1);
9815+
var shouldDeferValue = !includesOnlyNonUrgentLanes(renderLanes);
97939816

97949817
if (shouldDeferValue) {
97959818
// This is an urgent update. If the value has changed, keep using the
@@ -23839,9 +23862,9 @@ var workInProgressRootDidAttachPingListener = false; // A contextual version of
2383923862
// HiddenContext module.
2384023863
//
2384123864
// Most things in the work loop should deal with workInProgressRootRenderLanes.
23842-
// Most things in begin/complete phases should deal with renderLanes.
23865+
// Most things in begin/complete phases should deal with entangledRenderLanes.
2384323866

23844-
var renderLanes = NoLanes; // Whether to root completed, errored, suspended, etc.
23867+
var entangledRenderLanes = NoLanes; // Whether to root completed, errored, suspended, etc.
2384523868

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

@@ -24809,11 +24832,11 @@ function isInvalidExecutionContextForEventFunction() {
2480924832
// place that ever modifies it. Which module it lives in doesn't matter for
2481024833
// performance because this function will get inlined regardless
2481124834

24812-
function setRenderLanes(subtreeRenderLanes) {
24813-
renderLanes = subtreeRenderLanes;
24835+
function setEntangledRenderLanes(newEntangledRenderLanes) {
24836+
entangledRenderLanes = newEntangledRenderLanes;
2481424837
}
24815-
function getRenderLanes() {
24816-
return renderLanes;
24838+
function getEntangledRenderLanes() {
24839+
return entangledRenderLanes;
2481724840
}
2481824841

2481924842
function resetWorkInProgressStack() {
@@ -24864,7 +24887,7 @@ function prepareFreshStack(root, lanes) {
2486424887
workInProgressRoot = root;
2486524888
var rootWorkInProgress = createWorkInProgress(root.current, null);
2486624889
workInProgress = rootWorkInProgress;
24867-
workInProgressRootRenderLanes = renderLanes = lanes;
24890+
workInProgressRootRenderLanes = lanes;
2486824891
workInProgressSuspendedReason = NotSuspended;
2486924892
workInProgressThrownValue = null;
2487024893
workInProgressRootDidAttachPingListener = false;
@@ -24874,7 +24897,15 @@ function prepareFreshStack(root, lanes) {
2487424897
workInProgressRootInterleavedUpdatedLanes = NoLanes;
2487524898
workInProgressRootPingedLanes = NoLanes;
2487624899
workInProgressRootConcurrentErrors = null;
24877-
workInProgressRootRecoverableErrors = null;
24900+
workInProgressRootRecoverableErrors = null; // Get the lanes that are entangled with whatever we're about to render. We
24901+
// track these separately so we can distinguish the priority of the render
24902+
// task from the priority of the lanes it is entangled with. For example, a
24903+
// transition may not be allowed to finish unless it includes the Sync lane,
24904+
// which is currently suspended. We should be able to render the Transition
24905+
// and Sync lane in the same batch, but at Transition priority, because the
24906+
// Sync lane already suspended.
24907+
24908+
entangledRenderLanes = getEntangledLanes(root, lanes);
2487824909
finishQueueingConcurrentUpdates();
2487924910

2488024911
{
@@ -25570,10 +25601,10 @@ function performUnitOfWork(unitOfWork) {
2557025601

2557125602
if ((unitOfWork.mode & ProfileMode) !== NoMode) {
2557225603
startProfilerTimer(unitOfWork);
25573-
next = beginWork(current, unitOfWork, renderLanes);
25604+
next = beginWork(current, unitOfWork, entangledRenderLanes);
2557425605
stopProfilerTimerIfRunningAndRecordDelta(unitOfWork, true);
2557525606
} else {
25576-
next = beginWork(current, unitOfWork, renderLanes);
25607+
next = beginWork(current, unitOfWork, entangledRenderLanes);
2557725608
}
2557825609

2557925610
resetCurrentFiber();
@@ -25686,9 +25717,9 @@ function replaySuspendedUnitOfWork(unitOfWork) {
2568625717
unwindInterruptedWork(current, unitOfWork);
2568725718
unitOfWork = workInProgress = resetWorkInProgress(
2568825719
unitOfWork,
25689-
renderLanes
25720+
entangledRenderLanes
2569025721
);
25691-
next = beginWork(current, unitOfWork, renderLanes);
25722+
next = beginWork(current, unitOfWork, entangledRenderLanes);
2569225723
break;
2569325724
}
2569425725
}
@@ -25798,10 +25829,10 @@ function completeUnitOfWork(unitOfWork) {
2579825829
var next = void 0;
2579925830

2580025831
if ((completedWork.mode & ProfileMode) === NoMode) {
25801-
next = completeWork(current, completedWork, renderLanes);
25832+
next = completeWork(current, completedWork, entangledRenderLanes);
2580225833
} else {
2580325834
startProfilerTimer(completedWork);
25804-
next = completeWork(current, completedWork, renderLanes); // Update render duration assuming we didn't error.
25835+
next = completeWork(current, completedWork, entangledRenderLanes); // Update render duration assuming we didn't error.
2580525836

2580625837
stopProfilerTimerIfRunningAndRecordDelta(completedWork, false);
2580725838
}

0 commit comments

Comments
 (0)