@samitouri / QOS-React / commits / 14084be286

Refactor algorithm for next Lanes to work on (#19302)

Some clean up to make the Lanes type easier to maintain. I removed the "start" and "end" range markers; they don't provide any information that isn't already encoded in the bitmask for each range, and there's no computation saved compared to the `pickArbitraryLane` function. The overall algorithm is largely the same but I did tweak some of the details. For example, if the lanes for a given priority are already being worked on, the previous algorithm would assign to the next available lane, including the dedicated hydration lanes that exist in between each priority. The updated algorithm skips over the hydration lanes and goes to the next priority level. In the rare instance when all the non-Idle update lanes are occupied, it will pick an abitrary default lane. This will have the effect of invalidating the current work-in-progress, and indicates a starvation scenario. Eventually, if there are too many interruptions, the expiration time mechanism will kick in and force the update to synchronously finish.

Andrew Clark committed Jul 9, 2020 at 18:53 UTC 14084be286d09df49d5410c751bc28f0180a54b2
2 files changed +103 -154
packages/react-reconciler/src/ReactFiberLane.js
+95 -153
@@ -78,51 +78,31 @@ export const NoLanes: Lanes = /* */ 0b0000000000000000000
78 export const NoLane: Lane = /* */ 0b0000000000000000000000000000000;
79
80 export const SyncLane: Lane = /* */ 0b0000000000000000000000000000001;
81 -const SyncUpdateRangeEnd = 1;
81 export const SyncBatchedLane: Lane = /* */ 0b0000000000000000000000000000010;
83 -const SyncBatchedUpdateRangeEnd = 2;
82
83 export const InputDiscreteHydrationLane: Lane = /* */ 0b0000000000000000000000000000100;
86 -const InputDiscreteLanes: Lanes = /* */ 0b0000000000000000000000000011100;
87 -const InputDiscreteUpdateRangeStart = 3;
88 -const InputDiscreteUpdateRangeEnd = 5;
84 +const InputDiscreteLanes: Lanes = /* */ 0b0000000000000000000000000011000;
85
86 const InputContinuousHydrationLane: Lane = /* */ 0b0000000000000000000000000100000;
91 -const InputContinuousLanes: Lanes = /* */ 0b0000000000000000000000011100000;
92 -const InputContinuousUpdateRangeStart = 6;
93 -const InputContinuousUpdateRangeEnd = 8;
87 +const InputContinuousLanes: Lanes = /* */ 0b0000000000000000000000011000000;
88
89 export const DefaultHydrationLane: Lane = /* */ 0b0000000000000000000000100000000;
96 -export const DefaultLanes: Lanes = /* */ 0b0000000000000000000111100000000;
97 -const DefaultUpdateRangeStart = 9;
98 -const DefaultUpdateRangeEnd = 12;
90 +export const DefaultLanes: Lanes = /* */ 0b0000000000000000000111000000000;
91
92 const TransitionShortHydrationLane: Lane = /* */ 0b0000000000000000001000000000000;
101 -const TransitionShortLanes: Lanes = /* */ 0b0000000000000011111000000000000;
102 -const TransitionShortUpdateRangeStart = 13;
103 -const TransitionShortUpdateRangeEnd = 17;
93 +const TransitionShortLanes: Lanes = /* */ 0b0000000000000011110000000000000;
94
95 const TransitionLongHydrationLane: Lane = /* */ 0b0000000000000100000000000000000;
106 -const TransitionLongLanes: Lanes = /* */ 0b0000000001111100000000000000000;
107 -const TransitionLongUpdateRangeStart = 18;
108 -const TransitionLongUpdateRangeEnd = 22;
109 -
110 -// Includes all updates. Except Idle updates, which have special semantics.
111 -const UpdateRangeEnd = TransitionLongUpdateRangeEnd;
96 +const TransitionLongLanes: Lanes = /* */ 0b0000000001111000000000000000000;
97
98 const RetryLanes: Lanes = /* */ 0b0000011110000000000000000000000;
114 -const RetryRangeStart = 22;
115 -const RetryRangeEnd = 26;
99
100 export const SelectiveHydrationLane: Lane = /* */ 0b0000100000000000000000000000000;
118 -const SelectiveHydrationRangeEnd = 27;
101
102 const NonIdleLanes = /* */ 0b0000111111111111111111111111111;
103
104 export const IdleHydrationLane: Lane = /* */ 0b0001000000000000000000000000000;
123 -const IdleLanes: Lanes = /* */ 0b0111000000000000000000000000000;
124 -const IdleUpdateRangeStart = 28;
125 -const IdleUpdateRangeEnd = 30;
105 +const IdleLanes: Lanes = /* */ 0b0110000000000000000000000000000;
106
107 export const OffscreenLane: Lane = /* */ 0b1000000000000000000000000000000;
108
@@ -141,105 +121,81 @@ export function setCurrentUpdateLanePriority(newLanePriority: LanePriority) {
121 // "Registers" used to "return" multiple values
122 // Used by getHighestPriorityLanes and getNextLanes:
123 let return_highestLanePriority: LanePriority = DefaultLanePriority;
144 -let return_updateRangeEnd: number = -1;
124
125 function getHighestPriorityLanes(lanes: Lanes | Lane): Lanes {
126 if ((SyncLane & lanes) !== NoLanes) {
127 return_highestLanePriority = SyncLanePriority;
149 - return_updateRangeEnd = SyncUpdateRangeEnd;
128 return SyncLane;
129 }
130 if ((SyncBatchedLane & lanes) !== NoLanes) {
131 return_highestLanePriority = SyncBatchedLanePriority;
154 - return_updateRangeEnd = SyncBatchedUpdateRangeEnd;
132 return SyncBatchedLane;
133 }
134 + if ((InputDiscreteHydrationLane & lanes) !== NoLanes) {
135 + return_highestLanePriority = InputDiscreteHydrationLanePriority;
136 + return InputDiscreteHydrationLane;
137 + }
138 const inputDiscreteLanes = InputDiscreteLanes & lanes;
139 if (inputDiscreteLanes !== NoLanes) {
159 - if (inputDiscreteLanes & InputDiscreteHydrationLane) {
160 - return_highestLanePriority = InputDiscreteHydrationLanePriority;
161 - return_updateRangeEnd = InputDiscreteUpdateRangeStart;
162 - return InputDiscreteHydrationLane;
163 - } else {
164 - return_highestLanePriority = InputDiscreteLanePriority;
165 - return_updateRangeEnd = InputDiscreteUpdateRangeEnd;
166 - return inputDiscreteLanes;
167 - }
140 + return_highestLanePriority = InputDiscreteLanePriority;
141 + return inputDiscreteLanes;
142 + }
143 + if ((lanes & InputContinuousHydrationLane) !== NoLanes) {
144 + return_highestLanePriority = InputContinuousHydrationLanePriority;
145 + return InputContinuousHydrationLane;
146 }
147 const inputContinuousLanes = InputContinuousLanes & lanes;
148 if (inputContinuousLanes !== NoLanes) {
171 - if (inputContinuousLanes & InputContinuousHydrationLane) {
172 - return_highestLanePriority = InputContinuousHydrationLanePriority;
173 - return_updateRangeEnd = InputContinuousUpdateRangeStart;
174 - return InputContinuousHydrationLane;
175 - } else {
176 - return_highestLanePriority = InputContinuousLanePriority;
177 - return_updateRangeEnd = InputContinuousUpdateRangeEnd;
178 - return inputContinuousLanes;
179 - }
149 + return_highestLanePriority = InputContinuousLanePriority;
150 + return inputContinuousLanes;
151 + }
152 + if ((lanes & DefaultHydrationLane) !== NoLanes) {
153 + return_highestLanePriority = DefaultHydrationLanePriority;
154 + return DefaultHydrationLane;
155 }
156 const defaultLanes = DefaultLanes & lanes;
157 if (defaultLanes !== NoLanes) {
183 - if (defaultLanes & DefaultHydrationLane) {
184 - return_highestLanePriority = DefaultHydrationLanePriority;
185 - return_updateRangeEnd = DefaultUpdateRangeStart;
186 - return DefaultHydrationLane;
187 - } else {
188 - return_highestLanePriority = DefaultLanePriority;
189 - return_updateRangeEnd = DefaultUpdateRangeEnd;
190 - return defaultLanes;
191 - }
158 + return_highestLanePriority = DefaultLanePriority;
159 + return defaultLanes;
160 + }
161 + if ((lanes & TransitionShortHydrationLane) !== NoLanes) {
162 + return_highestLanePriority = TransitionShortHydrationLanePriority;
163 + return TransitionShortHydrationLane;
164 }
165 const transitionShortLanes = TransitionShortLanes & lanes;
166 if (transitionShortLanes !== NoLanes) {
195 - if (transitionShortLanes & TransitionShortHydrationLane) {
196 - return_highestLanePriority = TransitionShortHydrationLanePriority;
197 - return_updateRangeEnd = TransitionShortUpdateRangeStart;
198 - return TransitionShortHydrationLane;
199 - } else {
200 - return_highestLanePriority = TransitionShortLanePriority;
201 - return_updateRangeEnd = TransitionShortUpdateRangeEnd;
202 - return transitionShortLanes;
203 - }
167 + return_highestLanePriority = TransitionShortLanePriority;
168 + return transitionShortLanes;
169 + }
170 + if ((lanes & TransitionLongHydrationLane) !== NoLanes) {
171 + return_highestLanePriority = TransitionLongHydrationLanePriority;
172 + return TransitionLongHydrationLane;
173 }
174 const transitionLongLanes = TransitionLongLanes & lanes;
175 if (transitionLongLanes !== NoLanes) {
207 - if (transitionLongLanes & TransitionLongHydrationLane) {
208 - return_highestLanePriority = TransitionLongHydrationLanePriority;
209 - return_updateRangeEnd = TransitionLongUpdateRangeStart;
210 - return TransitionLongHydrationLane;
211 - } else {
212 - return_highestLanePriority = TransitionLongLanePriority;
213 - return_updateRangeEnd = TransitionLongUpdateRangeEnd;
214 - return transitionLongLanes;
215 - }
176 + return_highestLanePriority = TransitionLongLanePriority;
177 + return transitionLongLanes;
178 }
179 const retryLanes = RetryLanes & lanes;
180 if (retryLanes !== NoLanes) {
181 return_highestLanePriority = RetryLanePriority;
220 - return_updateRangeEnd = RetryRangeEnd;
182 return retryLanes;
183 }
184 if (lanes & SelectiveHydrationLane) {
185 return_highestLanePriority = SelectiveHydrationLanePriority;
225 - return_updateRangeEnd = SelectiveHydrationRangeEnd;
186 return SelectiveHydrationLane;
187 }
188 + if ((lanes & IdleHydrationLane) !== NoLanes) {
189 + return_highestLanePriority = IdleHydrationLanePriority;
190 + return IdleHydrationLane;
191 + }
192 const idleLanes = IdleLanes & lanes;
193 if (idleLanes !== NoLanes) {
230 - if (idleLanes & IdleHydrationLane) {
231 - return_highestLanePriority = IdleHydrationLanePriority;
232 - return_updateRangeEnd = IdleUpdateRangeStart;
233 - return IdleHydrationLane;
234 - } else {
235 - return_highestLanePriority = IdleLanePriority;
236 - return_updateRangeEnd = IdleUpdateRangeEnd;
237 - return idleLanes;
238 - }
194 + return_highestLanePriority = IdleLanePriority;
195 + return idleLanes;
196 }
197 if ((OffscreenLane & lanes) !== NoLanes) {
198 return_highestLanePriority = OffscreenLanePriority;
242 - return_updateRangeEnd = TotalLanes;
199 return OffscreenLane;
200 }
201 if (__DEV__) {
@@ -247,7 +203,6 @@ function getHighestPriorityLanes(lanes: Lanes | Lane): Lanes {
203 }
204 // This shouldn't be reachable, but as a fallback, return the entire bitmask.
205 return_highestLanePriority = DefaultLanePriority;
250 - return_updateRangeEnd = DefaultUpdateRangeEnd;
206 return lanes;
207 }
208
@@ -316,7 +271,6 @@ export function getNextLanes(root: FiberRoot, wipLanes: Lanes): Lanes {
271
272 let nextLanes = NoLanes;
273 let nextLanePriority = NoLanePriority;
319 - let equalOrHigherPriorityLanes = NoLanes;
274
275 const expiredLanes = root.expiredLanes;
276 const suspendedLanes = root.suspendedLanes;
@@ -326,7 +280,6 @@ export function getNextLanes(root: FiberRoot, wipLanes: Lanes): Lanes {
280 if (expiredLanes !== NoLanes) {
281 nextLanes = expiredLanes;
282 nextLanePriority = return_highestLanePriority = SyncLanePriority;
329 - equalOrHigherPriorityLanes = (getLowestPriorityLane(nextLanes) << 1) - 1;
283 } else {
284 // Do not work on any idle work until all the non-idle work has finished,
285 // even if the work is suspended.
@@ -336,13 +289,11 @@ export function getNextLanes(root: FiberRoot, wipLanes: Lanes): Lanes {
289 if (nonIdleUnblockedLanes !== NoLanes) {
290 nextLanes = getHighestPriorityLanes(nonIdleUnblockedLanes);
291 nextLanePriority = return_highestLanePriority;
339 - equalOrHigherPriorityLanes = (1 << return_updateRangeEnd) - 1;
292 } else {
293 const nonIdlePingedLanes = nonIdlePendingLanes & pingedLanes;
294 if (nonIdlePingedLanes !== NoLanes) {
295 nextLanes = getHighestPriorityLanes(nonIdlePingedLanes);
296 nextLanePriority = return_highestLanePriority;
345 - equalOrHigherPriorityLanes = (1 << return_updateRangeEnd) - 1;
297 }
298 }
299 } else {
@@ -351,12 +302,10 @@ export function getNextLanes(root: FiberRoot, wipLanes: Lanes): Lanes {
302 if (unblockedLanes !== NoLanes) {
303 nextLanes = getHighestPriorityLanes(unblockedLanes);
304 nextLanePriority = return_highestLanePriority;
354 - equalOrHigherPriorityLanes = (1 << return_updateRangeEnd) - 1;
305 } else {
306 if (pingedLanes !== NoLanes) {
307 nextLanes = getHighestPriorityLanes(pingedLanes);
308 nextLanePriority = return_highestLanePriority;
359 - equalOrHigherPriorityLanes = (1 << return_updateRangeEnd) - 1;
309 }
310 }
311 }
@@ -370,7 +319,7 @@ export function getNextLanes(root: FiberRoot, wipLanes: Lanes): Lanes {
319
320 // If there are higher priority lanes, we'll include them even if they
321 // are suspended.
373 - nextLanes = pendingLanes & equalOrHigherPriorityLanes;
322 + nextLanes = pendingLanes & getEqualOrHigherPriorityLanes(nextLanes);
323
324 // If we're already in the middle of a render, switching lanes will interrupt
325 // it and we'll lose our progress. We should only do this if the new lanes are
@@ -520,45 +469,46 @@ export function findUpdateLane(
469 case SyncBatchedLanePriority:
470 return SyncBatchedLane;
471 case InputDiscreteLanePriority: {
523 - let lane = findLane(
524 - InputDiscreteUpdateRangeStart,
525 - UpdateRangeEnd,
526 - wipLanes,
527 - );
472 + const lane = pickArbitraryLane(InputDiscreteLanes & ~wipLanes);
473 if (lane === NoLane) {
529 - lane = InputDiscreteHydrationLane;
474 + // Shift to the next priority level
475 + return findUpdateLane(InputContinuousLanePriority, wipLanes);
476 }
477 return lane;
478 }
479 case InputContinuousLanePriority: {
534 - let lane = findLane(
535 - InputContinuousUpdateRangeStart,
536 - UpdateRangeEnd,
537 - wipLanes,
538 - );
480 + const lane = pickArbitraryLane(InputContinuousLanes & ~wipLanes);
481 if (lane === NoLane) {
540 - lane = InputContinuousHydrationLane;
482 + // Shift to the next priority level
483 + return findUpdateLane(DefaultLanePriority, wipLanes);
484 }
485 return lane;
486 }
487 case DefaultLanePriority: {
545 - let lane = findLane(DefaultUpdateRangeStart, UpdateRangeEnd, wipLanes);
488 + let lane = pickArbitraryLane(DefaultLanes & ~wipLanes);
489 if (lane === NoLane) {
547 - lane = DefaultHydrationLane;
490 + // If all the default lanes are already being worked on, look for a
491 + // lane in the transition range.
492 + lane = pickArbitraryLane(
493 + (TransitionShortLanes | TransitionLongLanes) & ~wipLanes,
494 + );
495 + if (lane === NoLane) {
496 + // All the transition lanes are taken, too. This should be very
497 + // rare, but as a last resort, pick a default lane. This will have
498 + // the effect of interrupting the current work-in-progress render.
499 + lane = pickArbitraryLane(DefaultLanes);
500 + }
501 }
502 return lane;
503 }
551 - case TransitionShortLanePriority:
504 + case TransitionShortLanePriority: // Should be handled by findTransitionLane instead
505 case TransitionLongLanePriority:
553 - // Should be handled by findTransitionLane instead
554 - break;
555 - case RetryLanePriority:
556 - // Should be handled by findRetryLane instead
506 + case RetryLanePriority: // Should be handled by findRetryLane instead
507 break;
508 case IdleLanePriority:
559 - let lane = findLane(IdleUpdateRangeStart, IdleUpdateRangeEnd, wipLanes);
509 + let lane = pickArbitraryLane(IdleLanes & ~wipLanes);
510 if (lane === NoLane) {
561 - lane = IdleHydrationLane;
511 + lane = pickArbitraryLane(IdleLanes);
512 }
513 return lane;
514 default:
@@ -580,37 +530,33 @@ export function findTransitionLane(
530 pendingLanes: Lanes,
531 ): Lane {
532 if (lanePriority === TransitionShortLanePriority) {
583 - let lane = findLane(
584 - TransitionShortUpdateRangeStart,
585 - TransitionShortUpdateRangeEnd,
586 - wipLanes | pendingLanes,
587 - );
533 + // First look for lanes that are completely unclaimed, i.e. have no
534 + // pending work.
535 + let lane = pickArbitraryLane(TransitionShortLanes & ~pendingLanes);
536 if (lane === NoLane) {
589 - lane = findLane(
590 - TransitionShortUpdateRangeStart,
591 - TransitionShortUpdateRangeEnd,
592 - wipLanes,
593 - );
537 + // If all lanes have pending work, look for a lane that isn't currently
538 + // being worked on.
539 + lane = pickArbitraryLane(TransitionShortLanes & ~wipLanes);
540 if (lane === NoLane) {
595 - lane = TransitionShortHydrationLane;
541 + // If everything is being worked on, pick any lane. This has the
542 + // effect of interrupting the current work-in-progress.
543 + lane = pickArbitraryLane(TransitionShortLanes);
544 }
545 }
546 return lane;
547 }
548 if (lanePriority === TransitionLongLanePriority) {
601 - let lane = findLane(
602 - TransitionLongUpdateRangeStart,
603 - TransitionLongUpdateRangeEnd,
604 - wipLanes | pendingLanes,
605 - );
549 + // First look for lanes that are completely unclaimed, i.e. have no
550 + // pending work.
551 + let lane = pickArbitraryLane(TransitionLongLanes & ~pendingLanes);
552 if (lane === NoLane) {
607 - lane = findLane(
608 - TransitionLongUpdateRangeStart,
609 - TransitionLongUpdateRangeEnd,
610 - wipLanes,
611 - );
553 + // If all lanes have pending work, look for a lane that isn't currently
554 + // being worked on.
555 + lane = pickArbitraryLane(TransitionLongLanes & ~wipLanes);
556 if (lane === NoLane) {
613 - lane = TransitionLongHydrationLane;
557 + // If everything is being worked on, pick any lane. This has the
558 + // effect of interrupting the current work-in-progress.
559 + lane = pickArbitraryLane(TransitionLongLanes);
560 }
561 }
562 return lane;
@@ -628,27 +574,15 @@ export function findRetryLane(wipLanes: Lanes): Lane {
574 // This is a fork of `findUpdateLane` designed specifically for Suspense
575 // "retries" — a special update that attempts to flip a Suspense boundary
576 // from its placeholder state to its primary/resolved state.
631 - let lane = findLane(RetryRangeStart, RetryRangeEnd, wipLanes);
577 + let lane = pickArbitraryLane(RetryLanes & ~wipLanes);
578 if (lane === NoLane) {
579 lane = pickArbitraryLane(RetryLanes);
580 }
581 return lane;
582 }
583
638 -function findLane(start, end, skipLanes) {
639 - // This finds the first bit between the `start` and `end` positions that isn't
640 - // in `skipLanes`.
641 - // TODO: This will always favor the rightmost bits. That's usually fine
642 - // because any bit that's pending will be part of `skipLanes`, so we'll do our
643 - // best to avoid accidental entanglement. However, lanes that are pending
644 - // inside an Offscreen tree aren't considered "pending" at the root level. So
645 - // they aren't included in `skipLanes`. So we should try not to favor any
646 - // particular part of the range, perhaps by incrementing an offset for each
647 - // distinct event. Must be the same within a single event, though.
648 - const bitsInRange = ((1 << (end - start)) - 1) << start;
649 - const possibleBits = bitsInRange & ~skipLanes;
650 - const leastSignificantBit = possibleBits & -possibleBits;
651 - return leastSignificantBit;
584 +function getHighestPriorityLane(lanes: Lanes) {
585 + return lanes & -lanes;
586 }
587
588 function getLowestPriorityLane(lanes: Lanes): Lane {
@@ -657,8 +591,16 @@ function getLowestPriorityLane(lanes: Lanes): Lane {
591 return index < 0 ? NoLanes : 1 << index;
592 }
593
594 +function getEqualOrHigherPriorityLanes(lanes: Lanes | Lane): Lanes {
595 + return (getLowestPriorityLane(lanes) << 1) - 1;
596 +}
597 +
598 export function pickArbitraryLane(lanes: Lanes): Lane {
661 - return getLowestPriorityLane(lanes);
599 + // This wrapper function gets inlined. Only exists so to communicate that it
600 + // doesn't matter which bit is selected; you can pick any bit without
601 + // affecting the algorithms where its used. Here I'm using
602 + // getHighestPriorityLane because it requires the fewest operations.
603 + return getHighestPriorityLane(lanes);
604 }
605
606 function pickArbitraryLaneIndex(lanes: Lane | Lanes) {
packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.js
+8 -1
@@ -2848,7 +2848,14 @@ describe('ReactSuspenseWithNoopRenderer', () => {
2848 foo.setState({suspend: false});
2849 });
2850
2851 - expect(Scheduler).toHaveYielded(['Foo']);
2851 + expect(Scheduler).toHaveYielded([
2852 + // First setState
2853 + 'Foo',
2854 + // Second setState. This update was scheduled while we were in the
2855 + // middle of rendering the previous update, so it was pushed to a separate
2856 + // batch to avoid invalidating the work-in-progress tree.
2857 + 'Foo',
2858 + ]);
2859 expect(root).toMatchRenderedOutput(<span prop="Foo" />);
2860 });
2861