Skip to content

Commit 62ae188

Browse files
committed
Don't shift interleaved updates to separate lane
Now that interleaved updates are added to a special queue, we no longer need to shift them into their own lane. We can add to a lane that's already in the middle of rendering without risk of tearing. See facebook#20615 for more background. I've only changed this in the new fork, and only behind the enableTransitionEntanglements flag. Most of this commit involves updating tests. The "shift-to-a-new" lane trick was intentionally used in a handful of tests where two or more updates need to be scheduled in different lanes. Most of these tests were written before `startTransition` existed, and all of them were written before transitions were assigned arbitrary lanes. So I ported these tests to use `startTransition` instead, which is the idiomatic way to mark an update as parallel. I didn't change the old fork at all. Writing these tests in such a way that they also pass in the old fork actually revealed a few flaws in the current implementation regarding interrupting a suspended refresh transition early, which is a good reminder that we should be writing our tests using idiomatic patterns as much as we possibly can.
1 parent 261cf29 commit 62ae188

11 files changed

+465
-334
lines changed

packages/react-reconciler/src/ReactFiberLane.new.js

Lines changed: 79 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,10 @@ export type Lane = number;
3636
export type LaneMap<T> = Array<T>;
3737

3838
import invariant from 'shared/invariant';
39-
import {enableCache} from 'shared/ReactFeatureFlags';
39+
import {
40+
enableCache,
41+
enableTransitionEntanglement,
42+
} from 'shared/ReactFeatureFlags';
4043

4144
import {
4245
ImmediatePriority as ImmediateSchedulerPriority,
@@ -498,56 +501,88 @@ export function findUpdateLane(
498501
lanePriority: LanePriority,
499502
wipLanes: Lanes,
500503
): Lane {
501-
switch (lanePriority) {
502-
case NoLanePriority:
503-
break;
504-
case SyncLanePriority:
505-
return SyncLane;
506-
case SyncBatchedLanePriority:
507-
return SyncBatchedLane;
508-
case InputDiscreteLanePriority: {
509-
const lane = pickArbitraryLane(InputDiscreteLanes & ~wipLanes);
510-
if (lane === NoLane) {
511-
// Shift to the next priority level
512-
return findUpdateLane(InputContinuousLanePriority, wipLanes);
504+
if (enableTransitionEntanglement) {
505+
// Ignore wipLanes. Always assign to the same bit per priority.
506+
switch (lanePriority) {
507+
case NoLanePriority:
508+
break;
509+
case SyncLanePriority:
510+
return SyncLane;
511+
case SyncBatchedLanePriority:
512+
return SyncBatchedLane;
513+
case InputDiscreteLanePriority: {
514+
return pickArbitraryLane(InputDiscreteLanes);
513515
}
514-
return lane;
515-
}
516-
case InputContinuousLanePriority: {
517-
const lane = pickArbitraryLane(InputContinuousLanes & ~wipLanes);
518-
if (lane === NoLane) {
519-
// Shift to the next priority level
520-
return findUpdateLane(DefaultLanePriority, wipLanes);
516+
case InputContinuousLanePriority: {
517+
return pickArbitraryLane(InputContinuousLanes);
518+
}
519+
case DefaultLanePriority: {
520+
return pickArbitraryLane(DefaultLanes);
521521
}
522-
return lane;
522+
case TransitionPriority: // Should be handled by findTransitionLane instead
523+
case RetryLanePriority: // Should be handled by findRetryLane instead
524+
break;
525+
case IdleLanePriority:
526+
return pickArbitraryLane(IdleLanes);
527+
default:
528+
// The remaining priorities are not valid for updates
529+
break;
523530
}
524-
case DefaultLanePriority: {
525-
let lane = pickArbitraryLane(DefaultLanes & ~wipLanes);
526-
if (lane === NoLane) {
527-
// If all the default lanes are already being worked on, look for a
528-
// lane in the transition range.
529-
lane = pickArbitraryLane(TransitionLanes & ~wipLanes);
531+
} else {
532+
// Old behavior that uses wipLanes to shift interleaved updates into a
533+
// separate lane. This is no longer needed because we put interleaved
534+
// updates on a special queue.
535+
switch (lanePriority) {
536+
case NoLanePriority:
537+
break;
538+
case SyncLanePriority:
539+
return SyncLane;
540+
case SyncBatchedLanePriority:
541+
return SyncBatchedLane;
542+
case InputDiscreteLanePriority: {
543+
const lane = pickArbitraryLane(InputDiscreteLanes & ~wipLanes);
530544
if (lane === NoLane) {
531-
// All the transition lanes are taken, too. This should be very
532-
// rare, but as a last resort, pick a default lane. This will have
533-
// the effect of interrupting the current work-in-progress render.
534-
lane = pickArbitraryLane(DefaultLanes);
545+
// Shift to the next priority level
546+
return findUpdateLane(InputContinuousLanePriority, wipLanes);
535547
}
548+
return lane;
536549
}
537-
return lane;
538-
}
539-
case TransitionPriority: // Should be handled by findTransitionLane instead
540-
case RetryLanePriority: // Should be handled by findRetryLane instead
541-
break;
542-
case IdleLanePriority:
543-
let lane = pickArbitraryLane(IdleLanes & ~wipLanes);
544-
if (lane === NoLane) {
545-
lane = pickArbitraryLane(IdleLanes);
550+
case InputContinuousLanePriority: {
551+
const lane = pickArbitraryLane(InputContinuousLanes & ~wipLanes);
552+
if (lane === NoLane) {
553+
// Shift to the next priority level
554+
return findUpdateLane(DefaultLanePriority, wipLanes);
555+
}
556+
return lane;
546557
}
547-
return lane;
548-
default:
549-
// The remaining priorities are not valid for updates
550-
break;
558+
case DefaultLanePriority: {
559+
let lane = pickArbitraryLane(DefaultLanes & ~wipLanes);
560+
if (lane === NoLane) {
561+
// If all the default lanes are already being worked on, look for a
562+
// lane in the transition range.
563+
lane = pickArbitraryLane(TransitionLanes & ~wipLanes);
564+
if (lane === NoLane) {
565+
// All the transition lanes are taken, too. This should be very
566+
// rare, but as a last resort, pick a default lane. This will have
567+
// the effect of interrupting the current work-in-progress render.
568+
lane = pickArbitraryLane(DefaultLanes);
569+
}
570+
}
571+
return lane;
572+
}
573+
case TransitionPriority: // Should be handled by findTransitionLane instead
574+
case RetryLanePriority: // Should be handled by findRetryLane instead
575+
break;
576+
case IdleLanePriority:
577+
let lane = pickArbitraryLane(IdleLanes & ~wipLanes);
578+
if (lane === NoLane) {
579+
lane = pickArbitraryLane(IdleLanes);
580+
}
581+
return lane;
582+
default:
583+
// The remaining priorities are not valid for updates
584+
break;
585+
}
551586
}
552587
invariant(
553588
false,

packages/react-reconciler/src/ReactFiberLane.old.js

Lines changed: 79 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,10 @@ export type Lane = number;
3636
export type LaneMap<T> = Array<T>;
3737

3838
import invariant from 'shared/invariant';
39-
import {enableCache} from 'shared/ReactFeatureFlags';
39+
import {
40+
enableCache,
41+
enableTransitionEntanglement,
42+
} from 'shared/ReactFeatureFlags';
4043

4144
import {
4245
ImmediatePriority as ImmediateSchedulerPriority,
@@ -498,56 +501,88 @@ export function findUpdateLane(
498501
lanePriority: LanePriority,
499502
wipLanes: Lanes,
500503
): Lane {
501-
switch (lanePriority) {
502-
case NoLanePriority:
503-
break;
504-
case SyncLanePriority:
505-
return SyncLane;
506-
case SyncBatchedLanePriority:
507-
return SyncBatchedLane;
508-
case InputDiscreteLanePriority: {
509-
const lane = pickArbitraryLane(InputDiscreteLanes & ~wipLanes);
510-
if (lane === NoLane) {
511-
// Shift to the next priority level
512-
return findUpdateLane(InputContinuousLanePriority, wipLanes);
504+
if (enableTransitionEntanglement) {
505+
// Ignore wipLanes. Always assign to the same bit per priority.
506+
switch (lanePriority) {
507+
case NoLanePriority:
508+
break;
509+
case SyncLanePriority:
510+
return SyncLane;
511+
case SyncBatchedLanePriority:
512+
return SyncBatchedLane;
513+
case InputDiscreteLanePriority: {
514+
return pickArbitraryLane(InputDiscreteLanes);
513515
}
514-
return lane;
515-
}
516-
case InputContinuousLanePriority: {
517-
const lane = pickArbitraryLane(InputContinuousLanes & ~wipLanes);
518-
if (lane === NoLane) {
519-
// Shift to the next priority level
520-
return findUpdateLane(DefaultLanePriority, wipLanes);
516+
case InputContinuousLanePriority: {
517+
return pickArbitraryLane(InputContinuousLanes);
518+
}
519+
case DefaultLanePriority: {
520+
return pickArbitraryLane(DefaultLanes);
521521
}
522-
return lane;
522+
case TransitionPriority: // Should be handled by findTransitionLane instead
523+
case RetryLanePriority: // Should be handled by findRetryLane instead
524+
break;
525+
case IdleLanePriority:
526+
return pickArbitraryLane(IdleLanes);
527+
default:
528+
// The remaining priorities are not valid for updates
529+
break;
523530
}
524-
case DefaultLanePriority: {
525-
let lane = pickArbitraryLane(DefaultLanes & ~wipLanes);
526-
if (lane === NoLane) {
527-
// If all the default lanes are already being worked on, look for a
528-
// lane in the transition range.
529-
lane = pickArbitraryLane(TransitionLanes & ~wipLanes);
531+
} else {
532+
// Old behavior that uses wipLanes to shift interleaved updates into a
533+
// separate lane. This is no longer needed because we put interleaved
534+
// updates on a special queue.
535+
switch (lanePriority) {
536+
case NoLanePriority:
537+
break;
538+
case SyncLanePriority:
539+
return SyncLane;
540+
case SyncBatchedLanePriority:
541+
return SyncBatchedLane;
542+
case InputDiscreteLanePriority: {
543+
const lane = pickArbitraryLane(InputDiscreteLanes & ~wipLanes);
530544
if (lane === NoLane) {
531-
// All the transition lanes are taken, too. This should be very
532-
// rare, but as a last resort, pick a default lane. This will have
533-
// the effect of interrupting the current work-in-progress render.
534-
lane = pickArbitraryLane(DefaultLanes);
545+
// Shift to the next priority level
546+
return findUpdateLane(InputContinuousLanePriority, wipLanes);
535547
}
548+
return lane;
536549
}
537-
return lane;
538-
}
539-
case TransitionPriority: // Should be handled by findTransitionLane instead
540-
case RetryLanePriority: // Should be handled by findRetryLane instead
541-
break;
542-
case IdleLanePriority:
543-
let lane = pickArbitraryLane(IdleLanes & ~wipLanes);
544-
if (lane === NoLane) {
545-
lane = pickArbitraryLane(IdleLanes);
550+
case InputContinuousLanePriority: {
551+
const lane = pickArbitraryLane(InputContinuousLanes & ~wipLanes);
552+
if (lane === NoLane) {
553+
// Shift to the next priority level
554+
return findUpdateLane(DefaultLanePriority, wipLanes);
555+
}
556+
return lane;
546557
}
547-
return lane;
548-
default:
549-
// The remaining priorities are not valid for updates
550-
break;
558+
case DefaultLanePriority: {
559+
let lane = pickArbitraryLane(DefaultLanes & ~wipLanes);
560+
if (lane === NoLane) {
561+
// If all the default lanes are already being worked on, look for a
562+
// lane in the transition range.
563+
lane = pickArbitraryLane(TransitionLanes & ~wipLanes);
564+
if (lane === NoLane) {
565+
// All the transition lanes are taken, too. This should be very
566+
// rare, but as a last resort, pick a default lane. This will have
567+
// the effect of interrupting the current work-in-progress render.
568+
lane = pickArbitraryLane(DefaultLanes);
569+
}
570+
}
571+
return lane;
572+
}
573+
case TransitionPriority: // Should be handled by findTransitionLane instead
574+
case RetryLanePriority: // Should be handled by findRetryLane instead
575+
break;
576+
case IdleLanePriority:
577+
let lane = pickArbitraryLane(IdleLanes & ~wipLanes);
578+
if (lane === NoLane) {
579+
lane = pickArbitraryLane(IdleLanes);
580+
}
581+
return lane;
582+
default:
583+
// The remaining priorities are not valid for updates
584+
break;
585+
}
551586
}
552587
invariant(
553588
false,

packages/react-reconciler/src/ReactFiberWorkLoop.new.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import {
3434
disableSchedulerTimeoutInWorkLoop,
3535
enableDoubleInvokingEffects,
3636
skipUnmountedBoundaries,
37+
enableTransitionEntanglement,
3738
} from 'shared/ReactFeatureFlags';
3839
import ReactSharedInternals from 'shared/ReactSharedInternals';
3940
import invariant from 'shared/invariant';
@@ -832,6 +833,7 @@ function performConcurrentWorkOnRoot(root, didTimeout) {
832833
let exitStatus = renderRootConcurrent(root, lanes);
833834

834835
if (
836+
!enableTransitionEntanglement &&
835837
includesSomeLane(
836838
workInProgressRootIncludedLanes,
837839
workInProgressRootUpdatedLanes,
@@ -1037,6 +1039,7 @@ function performSyncWorkOnRoot(root) {
10371039
lanes = workInProgressRootRenderLanes;
10381040
exitStatus = renderRootSync(root, lanes);
10391041
if (
1042+
!enableTransitionEntanglement &&
10401043
includesSomeLane(
10411044
workInProgressRootIncludedLanes,
10421045
workInProgressRootUpdatedLanes,

packages/react-reconciler/src/ReactFiberWorkLoop.old.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import {
3434
disableSchedulerTimeoutInWorkLoop,
3535
enableDoubleInvokingEffects,
3636
skipUnmountedBoundaries,
37+
enableTransitionEntanglement,
3738
} from 'shared/ReactFeatureFlags';
3839
import ReactSharedInternals from 'shared/ReactSharedInternals';
3940
import invariant from 'shared/invariant';
@@ -832,6 +833,7 @@ function performConcurrentWorkOnRoot(root, didTimeout) {
832833
let exitStatus = renderRootConcurrent(root, lanes);
833834

834835
if (
836+
!enableTransitionEntanglement &&
835837
includesSomeLane(
836838
workInProgressRootIncludedLanes,
837839
workInProgressRootUpdatedLanes,
@@ -1037,6 +1039,7 @@ function performSyncWorkOnRoot(root) {
10371039
lanes = workInProgressRootRenderLanes;
10381040
exitStatus = renderRootSync(root, lanes);
10391041
if (
1042+
!enableTransitionEntanglement &&
10401043
includesSomeLane(
10411044
workInProgressRootIncludedLanes,
10421045
workInProgressRootUpdatedLanes,

packages/react-reconciler/src/__tests__/DebugTracing-test.internal.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,9 @@ describe('DebugTracing', () => {
327327
]);
328328
});
329329

330+
// This test is coupled to lane implementation details, so I'm disabling it in
331+
// the new fork until it stabilizes so we don't have to repeatedly update it.
332+
// @gate !enableTransitionEntanglement
330333
// @gate experimental && build === 'development' && enableDebugTracing
331334
it('should log cascading passive updates', () => {
332335
function Example() {

0 commit comments

Comments
 (0)