Skip to content

Commit e94c09b

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 commit 309c8ad.
1 parent 785a98f commit e94c09b

File tree

13 files changed

+671
-578
lines changed

13 files changed

+671
-578
lines changed

compiled-rn/facebook-fbsource/xplat/js/RKJSModules/vendor/react-test-renderer/cjs/ReactTestRenderer-dev.js

Lines changed: 71 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* @noflow
88
* @nolint
99
* @preventMunge
10-
* @generated SignedSource<<714756724eaa1bcdef36d80f02d51c1a>>
10+
* @generated SignedSource<<5b025a45475b905abc64b47d27761956>>
1111
*/
1212

1313
'use strict';
@@ -1053,6 +1053,7 @@ var SyncHydrationLane =
10531053
var SyncLane =
10541054
/* */
10551055
2;
1056+
var SyncLaneIndex = 1;
10561057
var InputContinuousHydrationLane =
10571058
/* */
10581059
4;
@@ -1297,13 +1298,18 @@ function getNextLanes(root, wipLanes) {
12971298
}
12981299
}
12991300

1301+
return nextLanes;
1302+
}
1303+
function getEntangledLanes(root, renderLanes) {
1304+
var entangledLanes = renderLanes;
1305+
13001306
if ((root.current.mode & ConcurrentUpdatesByDefaultMode) !== NoMode);
1301-
else if ((nextLanes & InputContinuousLane) !== NoLanes) {
1307+
else if ((entangledLanes & InputContinuousLane) !== NoLanes) {
13021308
// When updates are sync by default, we entangle continuous priority updates
13031309
// and default updates, so they render in the same batch. The only reason
13041310
// they use separate lanes is because continuous updates should interrupt
13051311
// transitions, but default updates should not.
1306-
nextLanes |= pendingLanes & DefaultLane;
1312+
entangledLanes |= entangledLanes & DefaultLane;
13071313
} // Check for entangled lanes and add them to the batch.
13081314
//
13091315
// A lane is said to be entangled with another when it's not allowed to render
@@ -1327,21 +1333,21 @@ function getNextLanes(root, wipLanes) {
13271333
// we should ensure that there is no partial work at the
13281334
// time we apply the entanglement.
13291335

1330-
var entangledLanes = root.entangledLanes;
1336+
var allEntangledLanes = root.entangledLanes;
13311337

1332-
if (entangledLanes !== NoLanes) {
1338+
if (allEntangledLanes !== NoLanes) {
13331339
var entanglements = root.entanglements;
1334-
var lanes = nextLanes & entangledLanes;
1340+
var lanes = entangledLanes & allEntangledLanes;
13351341

13361342
while (lanes > 0) {
13371343
var index = pickArbitraryLaneIndex(lanes);
13381344
var lane = 1 << index;
1339-
nextLanes |= entanglements[index];
1345+
entangledLanes |= entanglements[index];
13401346
lanes &= ~lane;
13411347
}
13421348
}
13431349

1344-
return nextLanes;
1350+
return entangledLanes;
13451351
}
13461352

13471353
function computeExpirationTime(lane, currentTime) {
@@ -1419,6 +1425,7 @@ function markStarvedLanesAsExpired(root, currentTime) {
14191425
var expirationTimes = root.expirationTimes; // Iterate through the pending lanes and check if we've reached their
14201426
// expiration time. If so, we'll assume the update is being starved and mark
14211427
// it as expired to force it to finish.
1428+
// TODO: We should be able to replace this with upgradePendingLanesToSync
14221429
//
14231430
// We exclude retry lanes because those must always be time sliced, in order
14241431
// to unwrap uncached promises.
@@ -1688,6 +1695,15 @@ function markRootEntangled(root, entangledLanes) {
16881695
lanes &= ~lane;
16891696
}
16901697
}
1698+
function upgradePendingLaneToSync(root, lane) {
1699+
// Since we're upgrading the priority of the given lane, there is now pending
1700+
// sync work.
1701+
root.pendingLanes |= SyncLane; // Entangle the sync lane with the lane we're upgrading. This means SyncLane
1702+
// will not be allowed to finish without also finishing the given lane.
1703+
1704+
root.entangledLanes |= SyncLane;
1705+
root.entanglements[SyncLaneIndex] |= lane;
1706+
}
16911707
function markHiddenUpdate(root, update, lane) {
16921708
var index = laneToIndex(lane);
16931709
var hiddenUpdates = root.hiddenUpdates;
@@ -5813,22 +5829,24 @@ function resetChildFibers(workInProgress, lanes) {
58135829
// InvisibleParentContext that is currently managed by SuspenseContext.
58145830

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

5825-
setRenderLanes(mergeLanes(prevRenderLanes, context.baseLanes));
5841+
setEntangledRenderLanes(
5842+
mergeLanes(prevEntangledRenderLanes, context.baseLanes)
5843+
);
58265844
}
58275845
function reuseHiddenContextOnStack(fiber) {
58285846
// This subtree is not currently hidden, so we don't need to add any lanes
58295847
// to the render lanes. But we still need to push something to avoid a
58305848
// context mismatch. Reuse the existing context on the stack.
5831-
push(prevRenderLanesStackCursor, getRenderLanes(), fiber);
5849+
push(prevEntangledRenderLanesCursor, getEntangledRenderLanes(), fiber);
58325850
push(
58335851
currentTreeHiddenStackCursor,
58345852
currentTreeHiddenStackCursor.current,
@@ -5837,9 +5855,9 @@ function reuseHiddenContextOnStack(fiber) {
58375855
}
58385856
function popHiddenContext(fiber) {
58395857
// Restore the previous render lanes from the stack
5840-
setRenderLanes(prevRenderLanesStackCursor.current);
5858+
setEntangledRenderLanes(prevEntangledRenderLanesCursor.current);
58415859
pop(currentTreeHiddenStackCursor, fiber);
5842-
pop(prevRenderLanesStackCursor, fiber);
5860+
pop(prevEntangledRenderLanesCursor, fiber);
58435861
}
58445862
function isCurrentTreeHidden() {
58455863
return currentTreeHiddenStackCursor.current !== null;
@@ -6215,7 +6233,10 @@ function processRootScheduleInMicrotask() {
62156233
currentEventTransitionLane !== NoLane &&
62166234
shouldAttemptEagerTransition()
62176235
) {
6218-
markRootEntangled(root, mergeLanes(currentEventTransitionLane, SyncLane));
6236+
// A transition was scheduled during an event, but we're going to try to
6237+
// render it synchronously anyway. We do this during a popstate event to
6238+
// preserve the scroll position of the previous page.
6239+
upgradePendingLaneToSync(root, currentEventTransitionLane);
62196240
}
62206241

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

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

66306651
var currentlyRenderingFiber$1 = null; // Hooks are stored as a linked list on the fiber's memoizedState field. The
@@ -6867,7 +6888,7 @@ function renderWithHooks(
68676888
secondArg,
68686889
nextRenderLanes
68696890
) {
6870-
renderLanes$1 = nextRenderLanes;
6891+
renderLanes = nextRenderLanes;
68716892
currentlyRenderingFiber$1 = workInProgress;
68726893

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

69706991
var didRenderTooFewHooks = currentHook !== null && currentHook.next !== null;
6971-
renderLanes$1 = NoLanes;
6992+
renderLanes = NoLanes;
69726993
currentlyRenderingFiber$1 = null;
69736994
currentHook = null;
69746995
workInProgressHook = null;
@@ -7202,7 +7223,7 @@ function resetHooksOnUnwind(workInProgress) {
72027223
didScheduleRenderPhaseUpdate = false;
72037224
}
72047225

7205-
renderLanes$1 = NoLanes;
7226+
renderLanes = NoLanes;
72067227
currentlyRenderingFiber$1 = null;
72077228
currentHook = null;
72087229
workInProgressHook = null;
@@ -7468,7 +7489,7 @@ function updateReducerImpl(hook, current, reducer) {
74687489

74697490
var shouldSkipUpdate = isHiddenUpdate
74707491
? !isSubsetOfLanes(getWorkInProgressRootRenderLanes(), updateLane)
7471-
: !isSubsetOfLanes(renderLanes$1, updateLane);
7492+
: !isSubsetOfLanes(renderLanes, updateLane);
74727493

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

7701-
if (!includesBlockingLane(root, renderLanes$1)) {
7722+
var rootRenderLanes = getWorkInProgressRootRenderLanes();
7723+
7724+
if (!includesBlockingLane(root, rootRenderLanes)) {
77027725
pushStoreConsistencyCheck(fiber, getSnapshot, nextSnapshot);
77037726
}
77047727
} // Read the current snapshot from the store on every render. This breaks the
@@ -7795,7 +7818,7 @@ function updateSyncExternalStore(subscribe, getSnapshot, getServerSnapshot) {
77957818
);
77967819
}
77977820

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

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

20816-
var renderLanes = NoLanes; // Whether to root completed, errored, suspended, etc.
20839+
var entangledRenderLanes = NoLanes; // Whether to root completed, errored, suspended, etc.
2081720840

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

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

21599-
function setRenderLanes(subtreeRenderLanes) {
21600-
renderLanes = subtreeRenderLanes;
21622+
function setEntangledRenderLanes(newEntangledRenderLanes) {
21623+
entangledRenderLanes = newEntangledRenderLanes;
2160121624
}
21602-
function getRenderLanes() {
21603-
return renderLanes;
21625+
function getEntangledRenderLanes() {
21626+
return entangledRenderLanes;
2160421627
}
2160521628

2160621629
function resetWorkInProgressStack() {
@@ -21651,7 +21674,7 @@ function prepareFreshStack(root, lanes) {
2165121674
workInProgressRoot = root;
2165221675
var rootWorkInProgress = createWorkInProgress(root.current, null);
2165321676
workInProgress = rootWorkInProgress;
21654-
workInProgressRootRenderLanes = renderLanes = lanes;
21677+
workInProgressRootRenderLanes = lanes;
2165521678
workInProgressSuspendedReason = NotSuspended;
2165621679
workInProgressThrownValue = null;
2165721680
workInProgressRootDidAttachPingListener = false;
@@ -21661,7 +21684,15 @@ function prepareFreshStack(root, lanes) {
2166121684
workInProgressRootInterleavedUpdatedLanes = NoLanes;
2166221685
workInProgressRootPingedLanes = NoLanes;
2166321686
workInProgressRootConcurrentErrors = null;
21664-
workInProgressRootRecoverableErrors = null;
21687+
workInProgressRootRecoverableErrors = null; // Get the lanes that are entangled with whatever we're about to render. We
21688+
// track these separately so we can distinguish the priority of the render
21689+
// task from the priority of the lanes it is entangled with. For example, a
21690+
// transition may not be allowed to finish unless it includes the Sync lane,
21691+
// which is currently suspended. We should be able to render the Transition
21692+
// and Sync lane in the same batch, but at Transition priority, because the
21693+
// Sync lane already suspended.
21694+
21695+
entangledRenderLanes = getEntangledLanes(root, lanes);
2166521696
finishQueueingConcurrentUpdates();
2166621697

2166721698
{
@@ -22251,10 +22282,10 @@ function performUnitOfWork(unitOfWork) {
2225122282

2225222283
if ((unitOfWork.mode & ProfileMode) !== NoMode) {
2225322284
startProfilerTimer(unitOfWork);
22254-
next = beginWork(current, unitOfWork, renderLanes);
22285+
next = beginWork(current, unitOfWork, entangledRenderLanes);
2225522286
stopProfilerTimerIfRunningAndRecordDelta(unitOfWork, true);
2225622287
} else {
22257-
next = beginWork(current, unitOfWork, renderLanes);
22288+
next = beginWork(current, unitOfWork, entangledRenderLanes);
2225822289
}
2225922290

2226022291
resetCurrentFiber();
@@ -22367,9 +22398,9 @@ function replaySuspendedUnitOfWork(unitOfWork) {
2236722398
unwindInterruptedWork(current, unitOfWork);
2236822399
unitOfWork = workInProgress = resetWorkInProgress(
2236922400
unitOfWork,
22370-
renderLanes
22401+
entangledRenderLanes
2237122402
);
22372-
next = beginWork(current, unitOfWork, renderLanes);
22403+
next = beginWork(current, unitOfWork, entangledRenderLanes);
2237322404
break;
2237422405
}
2237522406
}
@@ -22479,10 +22510,10 @@ function completeUnitOfWork(unitOfWork) {
2247922510
var next = void 0;
2248022511

2248122512
if ((completedWork.mode & ProfileMode) === NoMode) {
22482-
next = completeWork(current, completedWork, renderLanes);
22513+
next = completeWork(current, completedWork, entangledRenderLanes);
2248322514
} else {
2248422515
startProfilerTimer(completedWork);
22485-
next = completeWork(current, completedWork, renderLanes); // Update render duration assuming we didn't error.
22516+
next = completeWork(current, completedWork, entangledRenderLanes); // Update render duration assuming we didn't error.
2248622517

2248722518
stopProfilerTimerIfRunningAndRecordDelta(completedWork, false);
2248822519
}
@@ -24770,7 +24801,7 @@ function createFiberRoot(
2477024801
return root;
2477124802
}
2477224803

24773-
var ReactVersion = "18.3.0-canary-09fbee89d-20231013";
24804+
var ReactVersion = "18.3.0-canary-309c8ad96-20231015";
2477424805

2477524806
// Might add PROFILE later.
2477624807

0 commit comments

Comments
 (0)