@samitouri / QOS-React-2 / commits / 7cb9fd7ef8

Land interleaved updates change in main fork (#20710)

* Land #20615 in main fork Includes change to interleaved updates. ``` yarn replace-fork ``` * Check deferRenderPhaseUpdateToNextBatch in test

Andrew Clark committed Feb 1, 2021 at 18:05 UTC 7cb9fd7ef822436aef13c8cbf648af1e21a5309a
9 files changed +219 -40
packages/react-reconciler/src/ReactFiberClassComponent.old.js
+3 -3
@@ -214,7 +214,7 @@ const classComponentUpdater = {
214 update.callback = callback;
215 }
216
217 - enqueueUpdate(fiber, update);
217 + enqueueUpdate(fiber, update, lane);
218 const root = scheduleUpdateOnFiber(fiber, lane, eventTime);
219 if (root !== null) {
220 entangleTransitions(root, fiber, lane);
@@ -249,7 +249,7 @@ const classComponentUpdater = {
249 update.callback = callback;
250 }
251
252 - enqueueUpdate(fiber, update);
252 + enqueueUpdate(fiber, update, lane);
253 const root = scheduleUpdateOnFiber(fiber, lane, eventTime);
254 if (root !== null) {
255 entangleTransitions(root, fiber, lane);
@@ -283,7 +283,7 @@ const classComponentUpdater = {
283 update.callback = callback;
284 }
285
286 - enqueueUpdate(fiber, update);
286 + enqueueUpdate(fiber, update, lane);
287 const root = scheduleUpdateOnFiber(fiber, lane, eventTime);
288 if (root !== null) {
289 entangleTransitions(root, fiber, lane);
packages/react-reconciler/src/ReactFiberHooks.old.js
+58 -14
@@ -77,6 +77,7 @@ import {
77 warnIfNotCurrentlyActingUpdatesInDev,
78 warnIfNotScopedWithMatchingAct,
79 markSkippedUpdateLanes,
80 + isInterleavedUpdate,
81 } from './ReactFiberWorkLoop.old';
82
83 import invariant from 'shared/invariant';
@@ -110,6 +111,7 @@ import {
111 enqueueUpdate,
112 entangleTransitions,
113 } from './ReactUpdateQueue.old';
114 +import {pushInterleavedQueue} from './ReactFiberInterleavedUpdates.old';
115
116 const {ReactCurrentDispatcher, ReactCurrentBatchConfig} = ReactSharedInternals;
117
@@ -122,8 +124,9 @@ type Update<S, A> = {|
124 priority?: ReactPriorityLevel,
125 |};
126
125 -type UpdateQueue<S, A> = {|
127 +export type UpdateQueue<S, A> = {|
128 pending: Update<S, A> | null,
129 + interleaved: Update<S, A> | null,
130 lanes: Lanes,
131 dispatch: (A => mixed) | null,
132 lastRenderedReducer: ((S, A) => S) | null,
@@ -657,6 +660,7 @@ function mountReducer<S, I, A>(
660 hook.memoizedState = hook.baseState = initialState;
661 const queue = (hook.queue = {
662 pending: null,
663 + interleaved: null,
664 lanes: NoLanes,
665 dispatch: null,
666 lastRenderedReducer: reducer,
@@ -800,7 +804,22 @@ function updateReducer<S, I, A>(
804 queue.lastRenderedState = newState;
805 }
806
803 - if (baseQueue === null) {
807 + // Interleaved updates are stored on a separate queue. We aren't going to
808 + // process them during this render, but we do need to track which lanes
809 + // are remaining.
810 + const lastInterleaved = queue.interleaved;
811 + if (lastInterleaved !== null) {
812 + let interleaved = lastInterleaved;
813 + do {
814 + const interleavedLane = interleaved.lane;
815 + currentlyRenderingFiber.lanes = mergeLanes(
816 + currentlyRenderingFiber.lanes,
817 + interleavedLane,
818 + );
819 + markSkippedUpdateLanes(interleavedLane);
820 + interleaved = ((interleaved: any).next: Update<S, A>);
821 + } while (interleaved !== lastInterleaved);
822 + } else if (baseQueue === null) {
823 // `queue.lanes` is used for entangling transitions. We can set it back to
824 // zero once the queue is empty.
825 queue.lanes = NoLanes;
@@ -1132,6 +1151,7 @@ function useMutableSource<Source, Snapshot>(
1151 // including any interleaving updates that occur.
1152 const newQueue = {
1153 pending: null,
1154 + interleaved: null,
1155 lanes: NoLanes,
1156 dispatch: null,
1157 lastRenderedReducer: basicStateReducer,
@@ -1188,6 +1208,7 @@ function mountState<S>(
1208 hook.memoizedState = hook.baseState = initialState;
1209 const queue = (hook.queue = {
1210 pending: null,
1211 + interleaved: null,
1212 lanes: NoLanes,
1213 dispatch: null,
1214 lastRenderedReducer: basicStateReducer,
@@ -1869,7 +1890,7 @@ function refreshCache<T>(fiber: Fiber, seedKey: ?() => T, seedValue: T) {
1890 cache: seededCache,
1891 };
1892 refreshUpdate.payload = payload;
1872 - enqueueUpdate(provider, refreshUpdate);
1893 + enqueueUpdate(provider, refreshUpdate, lane);
1894 return;
1895 }
1896 }
@@ -1904,17 +1925,6 @@ function dispatchAction<S, A>(
1925 next: (null: any),
1926 };
1927
1907 - // Append the update to the end of the list.
1908 - const pending = queue.pending;
1909 - if (pending === null) {
1910 - // This is the first update. Create a circular list.
1911 - update.next = update;
1912 - } else {
1913 - update.next = pending.next;
1914 - pending.next = update;
1915 - }
1916 - queue.pending = update;
1917 -
1928 const alternate = fiber.alternate;
1929 if (
1930 fiber === currentlyRenderingFiber ||
@@ -1924,7 +1934,41 @@ function dispatchAction<S, A>(
1934 // queue -> linked list of updates. After this render pass, we'll restart
1935 // and apply the stashed updates on top of the work-in-progress hook.
1936 didScheduleRenderPhaseUpdateDuringThisPass = didScheduleRenderPhaseUpdate = true;
1937 + const pending = queue.pending;
1938 + if (pending === null) {
1939 + // This is the first update. Create a circular list.
1940 + update.next = update;
1941 + } else {
1942 + update.next = pending.next;
1943 + pending.next = update;
1944 + }
1945 + queue.pending = update;
1946 } else {
1947 + if (isInterleavedUpdate(fiber, lane)) {
1948 + const interleaved = queue.interleaved;
1949 + if (interleaved === null) {
1950 + // This is the first update. Create a circular list.
1951 + update.next = update;
1952 + // At the end of the current render, this queue's interleaved updates will
1953 + // be transfered to the pending queue.
1954 + pushInterleavedQueue(queue);
1955 + } else {
1956 + update.next = interleaved.next;
1957 + interleaved.next = update;
1958 + }
1959 + queue.interleaved = update;
1960 + } else {
1961 + const pending = queue.pending;
1962 + if (pending === null) {
1963 + // This is the first update. Create a circular list.
1964 + update.next = update;
1965 + } else {
1966 + update.next = pending.next;
1967 + pending.next = update;
1968 + }
1969 + queue.pending = update;
1970 + }
1971 +
1972 if (
1973 fiber.lanes === NoLanes &&
1974 (alternate === null || alternate.lanes === NoLanes)
packages/react-reconciler/src/ReactFiberInterleavedUpdates.old.js new
+55
@@ -0,0 +1,55 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +
10 +import type {UpdateQueue as HookQueue} from './ReactFiberHooks.old';
11 +import type {SharedQueue as ClassQueue} from './ReactUpdateQueue.old';
12 +
13 +// An array of all update queues that received updates during the current
14 +// render. When this render exits, either because it finishes or because it is
15 +// interrupted, the interleaved updates will be transfered onto the main part
16 +// of the queue.
17 +let interleavedQueues: Array<
18 + HookQueue<any, any> | ClassQueue<any>,
19 +> | null = null;
20 +
21 +export function pushInterleavedQueue(
22 + queue: HookQueue<any, any> | ClassQueue<any>,
23 +) {
24 + if (interleavedQueues === null) {
25 + interleavedQueues = [queue];
26 + } else {
27 + interleavedQueues.push(queue);
28 + }
29 +}
30 +
31 +export function enqueueInterleavedUpdates() {
32 + // Transfer the interleaved updates onto the main queue. Each queue has a
33 + // `pending` field and an `interleaved` field. When they are not null, they
34 + // point to the last node in a circular linked list. We need to append the
35 + // interleaved list to the end of the pending list by joining them into a
36 + // single, circular list.
37 + if (interleavedQueues !== null) {
38 + for (let i = 0; i < interleavedQueues.length; i++) {
39 + const queue = interleavedQueues[i];
40 + const lastInterleavedUpdate = queue.interleaved;
41 + if (lastInterleavedUpdate !== null) {
42 + queue.interleaved = null;
43 + const firstInterleavedUpdate = lastInterleavedUpdate.next;
44 + const lastPendingUpdate = queue.pending;
45 + if (lastPendingUpdate !== null) {
46 + const firstPendingUpdate = lastPendingUpdate.next;
47 + lastPendingUpdate.next = (firstInterleavedUpdate: any);
48 + lastInterleavedUpdate.next = (firstPendingUpdate: any);
49 + }
50 + queue.pending = (lastInterleavedUpdate: any);
51 + }
52 + }
53 + interleavedQueues = null;
54 + }
55 +}
packages/react-reconciler/src/ReactFiberNewContext.old.js
+21 -6
@@ -11,6 +11,7 @@ import type {ReactContext} from 'shared/ReactTypes';
11 import type {Fiber, ContextDependency} from './ReactInternalTypes';
12 import type {StackCursor} from './ReactFiberStack.old';
13 import type {Lanes} from './ReactFiberLane.old';
14 +import type {SharedQueue} from './ReactUpdateQueue.old';
15
16 import {isPrimaryRenderer} from './ReactFiberHostConfig';
17 import {createCursor, push, pop} from './ReactFiberStack.old';
@@ -31,7 +32,7 @@ import {
32
33 import invariant from 'shared/invariant';
34 import is from 'shared/objectIs';
34 -import {createUpdate, enqueueUpdate, ForceUpdate} from './ReactUpdateQueue.old';
35 +import {createUpdate, ForceUpdate} from './ReactUpdateQueue.old';
36 import {markWorkInProgressReceivedUpdate} from './ReactFiberBeginWork.old';
37 import {enableSuspenseServerRenderer} from 'shared/ReactFeatureFlags';
38
@@ -211,16 +212,30 @@ export function propagateContextChange<T>(
212
213 if (fiber.tag === ClassComponent) {
214 // Schedule a force update on the work-in-progress.
214 - const update = createUpdate(
215 - NoTimestamp,
216 - pickArbitraryLane(renderLanes),
217 - );
215 + const lane = pickArbitraryLane(renderLanes);
216 + const update = createUpdate(NoTimestamp, lane);
217 update.tag = ForceUpdate;
218 // TODO: Because we don't have a work-in-progress, this will add the
219 // update to the current fiber, too, which means it will persist even if
220 // this render is thrown away. Since it's a race condition, not sure it's
221 // worth fixing.
223 - enqueueUpdate(fiber, update);
222 +
223 + // Inlined `enqueueUpdate` to remove interleaved update check
224 + const updateQueue = fiber.updateQueue;
225 + if (updateQueue === null) {
226 + // Only occurs if the fiber has been unmounted.
227 + } else {
228 + const sharedQueue: SharedQueue<any> = (updateQueue: any).shared;
229 + const pending = sharedQueue.pending;
230 + if (pending === null) {
231 + // This is the first update. Create a circular list.
232 + update.next = update;
233 + } else {
234 + update.next = pending.next;
235 + pending.next = update;
236 + }
237 + sharedQueue.pending = update;
238 + }
239 }
240 fiber.lanes = mergeLanes(fiber.lanes, renderLanes);
241 const alternate = fiber.alternate;
packages/react-reconciler/src/ReactFiberReconciler.old.js
+1 -1
@@ -318,7 +318,7 @@ export function updateContainer(
318 update.callback = callback;
319 }
320
321 - enqueueUpdate(current, update);
321 + enqueueUpdate(current, update, lane);
322 const root = scheduleUpdateOnFiber(current, lane, eventTime);
323 if (root !== null) {
324 entangleTransitions(root, current, lane);
packages/react-reconciler/src/ReactFiberThrow.old.js
+1 -1
@@ -293,7 +293,7 @@ function throwException(
293 // prevent a bail out.
294 const update = createUpdate(NoTimestamp, SyncLane);
295 update.tag = ForceUpdate;
296 - enqueueUpdate(sourceFiber, update);
296 + enqueueUpdate(sourceFiber, update, SyncLane);
297 }
298 }
299
packages/react-reconciler/src/ReactFiberWorkLoop.old.js
+23 -3
@@ -34,7 +34,6 @@ import {
34 disableSchedulerTimeoutInWorkLoop,
35 enableDoubleInvokingEffects,
36 skipUnmountedBoundaries,
37 - enableDiscreteEventMicroTasks,
37 } from 'shared/ReactFeatureFlags';
38 import ReactSharedInternals from 'shared/ReactSharedInternals';
39 import invariant from 'shared/invariant';
@@ -207,6 +206,7 @@ import {
206 pop as popFromStack,
207 createCursor,
208 } from './ReactFiberStack.old';
209 +import {enqueueInterleavedUpdates} from './ReactFiberInterleavedUpdates.old';
210
211 import {
212 markNestedUpdateScheduled,
@@ -217,6 +217,7 @@ import {
217 syncNestedUpdateFlag,
218 } from './ReactProfilerTimer.old';
219
220 +import {enableDiscreteEventMicroTasks} from 'shared/ReactFeatureFlags';
221 // DEV stuff
222 import getComponentName from 'shared/getComponentName';
223 import ReactStrictModeWarnings from './ReactStrictModeWarnings.old';
@@ -537,6 +538,7 @@ export function scheduleUpdateOnFiber(
538 }
539 }
540
541 + // TODO: Consolidate with `isInterleavedUpdate` check
542 if (root === workInProgressRoot) {
543 // Received an update to a tree that's in the middle of rendering. Mark
544 // that there was an interleaved update work on this root. Unless the
@@ -674,6 +676,22 @@ function markUpdateLaneFromFiberToRoot(
676 }
677 }
678
679 +export function isInterleavedUpdate(fiber: Fiber, lane: Lane) {
680 + return (
681 + // TODO: Optimize slightly by comparing to root that fiber belongs to.
682 + // Requires some refactoring. Not a big deal though since it's rare for
683 + // concurrent apps to have more than a single root.
684 + workInProgressRoot !== null &&
685 + (fiber.mode & BlockingMode) !== NoMode &&
686 + // If this is a render phase update (i.e. UNSAFE_componentWillReceiveProps),
687 + // then don't treat this as an interleaved update. This pattern is
688 + // accompanied by a warning but we haven't fully deprecated it yet. We can
689 + // remove once the deferRenderPhaseUpdateToNextBatch flag is enabled.
690 + (deferRenderPhaseUpdateToNextBatch ||
691 + (executionContext & RenderContext) === NoContext)
692 + );
693 +}
694 +
695 // Use this function to schedule a task for a root. There's only one task per
696 // root; if a task was already scheduled, we'll check to make sure the priority
697 // of the existing task is the same as the priority of the next level that the
@@ -1376,6 +1394,8 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes) {
1394 workInProgressRootUpdatedLanes = NoLanes;
1395 workInProgressRootPingedLanes = NoLanes;
1396
1397 + enqueueInterleavedUpdates();
1398 +
1399 if (enableSchedulerTracing) {
1400 spawnedWorkDuringRender = null;
1401 }
@@ -2307,7 +2327,7 @@ function captureCommitPhaseErrorOnRoot(
2327 ) {
2328 const errorInfo = createCapturedValue(error, sourceFiber);
2329 const update = createRootErrorUpdate(rootFiber, errorInfo, (SyncLane: Lane));
2310 - enqueueUpdate(rootFiber, update);
2330 + enqueueUpdate(rootFiber, update, (SyncLane: Lane));
2331 const eventTime = requestEventTime();
2332 const root = markUpdateLaneFromFiberToRoot(rootFiber, (SyncLane: Lane));
2333 if (root !== null) {
@@ -2354,7 +2374,7 @@ export function captureCommitPhaseError(
2374 errorInfo,
2375 (SyncLane: Lane),
2376 );
2357 - enqueueUpdate(fiber, update);
2377 + enqueueUpdate(fiber, update, (SyncLane: Lane));
2378 const eventTime = requestEventTime();
2379 const root = markUpdateLaneFromFiberToRoot(fiber, (SyncLane: Lane));
2380 if (root !== null) {
packages/react-reconciler/src/ReactUpdateQueue.old.js
+47 -11
@@ -105,7 +105,11 @@ import {Callback, ShouldCapture, DidCapture} from './ReactFiberFlags';
105 import {debugRenderPhaseSideEffectsForStrictMode} from 'shared/ReactFeatureFlags';
106
107 import {StrictMode} from './ReactTypeOfMode';
108 -import {markSkippedUpdateLanes} from './ReactFiberWorkLoop.old';
108 +import {
109 + markSkippedUpdateLanes,
110 + isInterleavedUpdate,
111 +} from './ReactFiberWorkLoop.old';
112 +import {pushInterleavedQueue} from './ReactFiberInterleavedUpdates.old';
113
114 import invariant from 'shared/invariant';
115
@@ -124,8 +128,9 @@ export type Update<State> = {|
128 next: Update<State> | null,
129 |};
130
127 -type SharedQueue<State> = {|
131 +export type SharedQueue<State> = {|
132 pending: Update<State> | null,
133 + interleaved: Update<State> | null,
134 lanes: Lanes,
135 |};
136
@@ -165,6 +170,7 @@ export function initializeUpdateQueue<State>(fiber: Fiber): void {
170 lastBaseUpdate: null,
171 shared: {
172 pending: null,
173 + interleaved: null,
174 lanes: NoLanes,
175 },
176 effects: null,
@@ -205,7 +211,11 @@ export function createUpdate(eventTime: number, lane: Lane): Update<*> {
211 return update;
212 }
213
208 -export function enqueueUpdate<State>(fiber: Fiber, update: Update<State>) {
214 +export function enqueueUpdate<State>(
215 + fiber: Fiber,
216 + update: Update<State>,
217 + lane: Lane,
218 +) {
219 const updateQueue = fiber.updateQueue;
220 if (updateQueue === null) {
221 // Only occurs if the fiber has been unmounted.
@@ -213,15 +223,31 @@ export function enqueueUpdate<State>(fiber: Fiber, update: Update<State>) {
223 }
224
225 const sharedQueue: SharedQueue<State> = (updateQueue: any).shared;
216 - const pending = sharedQueue.pending;
217 - if (pending === null) {
218 - // This is the first update. Create a circular list.
219 - update.next = update;
226 +
227 + if (isInterleavedUpdate(fiber, lane)) {
228 + const interleaved = sharedQueue.interleaved;
229 + if (interleaved === null) {
230 + // This is the first update. Create a circular list.
231 + update.next = update;
232 + // At the end of the current render, this queue's interleaved updates will
233 + // be transfered to the pending queue.
234 + pushInterleavedQueue(sharedQueue);
235 + } else {
236 + update.next = interleaved.next;
237 + interleaved.next = update;
238 + }
239 + sharedQueue.interleaved = update;
240 } else {
221 - update.next = pending.next;
222 - pending.next = update;
241 + const pending = sharedQueue.pending;
242 + if (pending === null) {
243 + // This is the first update. Create a circular list.
244 + update.next = update;
245 + } else {
246 + update.next = pending.next;
247 + pending.next = update;
248 + }
249 + sharedQueue.pending = update;
250 }
224 - sharedQueue.pending = update;
251
252 if (__DEV__) {
253 if (
@@ -591,7 +617,17 @@ export function processUpdateQueue<State>(
617 queue.firstBaseUpdate = newFirstBaseUpdate;
618 queue.lastBaseUpdate = newLastBaseUpdate;
619
594 - if (firstBaseUpdate === null) {
620 + // Interleaved updates are stored on a separate queue. We aren't going to
621 + // process them during this render, but we do need to track which lanes
622 + // are remaining.
623 + const lastInterleaved = queue.shared.interleaved;
624 + if (lastInterleaved !== null) {
625 + let interleaved = lastInterleaved;
626 + do {
627 + newLanes = mergeLanes(newLanes, interleaved.lane);
628 + interleaved = ((interleaved: any).next: Update<State>);
629 + } while (interleaved !== lastInterleaved);
630 + } else if (firstBaseUpdate === null) {
631 // `queue.lanes` is used for entangling transitions. We can set it back to
632 // zero once the queue is empty.
633 queue.shared.lanes = NoLanes;
packages/react-reconciler/src/__tests__/ReactIncrementalUpdates-test.js
+10 -1
@@ -320,7 +320,16 @@ describe('ReactIncrementalUpdates', () => {
320 });
321
322 expect(instance.state).toEqual({a: 'a', b: 'b'});
323 - expect(Scheduler).toHaveYielded(['componentWillReceiveProps', 'render']);
323 +
324 + if (gate(flags => flags.deferRenderPhaseUpdateToNextBatch)) {
325 + expect(Scheduler).toHaveYielded([
326 + 'componentWillReceiveProps',
327 + 'render',
328 + 'render',
329 + ]);
330 + } else {
331 + expect(Scheduler).toHaveYielded(['componentWillReceiveProps', 'render']);
332 + }
333 });
334
335 it('updates triggered from inside a class setState updater', () => {