Skip to content

Commit 7cb9fd7

Browse files
authored
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
1 parent dc27b5a commit 7cb9fd7

9 files changed

+219
-40
lines changed

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

+3-3
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ const classComponentUpdater = {
214214
update.callback = callback;
215215
}
216216

217-
enqueueUpdate(fiber, update);
217+
enqueueUpdate(fiber, update, lane);
218218
const root = scheduleUpdateOnFiber(fiber, lane, eventTime);
219219
if (root !== null) {
220220
entangleTransitions(root, fiber, lane);
@@ -249,7 +249,7 @@ const classComponentUpdater = {
249249
update.callback = callback;
250250
}
251251

252-
enqueueUpdate(fiber, update);
252+
enqueueUpdate(fiber, update, lane);
253253
const root = scheduleUpdateOnFiber(fiber, lane, eventTime);
254254
if (root !== null) {
255255
entangleTransitions(root, fiber, lane);
@@ -283,7 +283,7 @@ const classComponentUpdater = {
283283
update.callback = callback;
284284
}
285285

286-
enqueueUpdate(fiber, update);
286+
enqueueUpdate(fiber, update, lane);
287287
const root = scheduleUpdateOnFiber(fiber, lane, eventTime);
288288
if (root !== null) {
289289
entangleTransitions(root, fiber, lane);

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

+58-14
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ import {
7777
warnIfNotCurrentlyActingUpdatesInDev,
7878
warnIfNotScopedWithMatchingAct,
7979
markSkippedUpdateLanes,
80+
isInterleavedUpdate,
8081
} from './ReactFiberWorkLoop.old';
8182

8283
import invariant from 'shared/invariant';
@@ -110,6 +111,7 @@ import {
110111
enqueueUpdate,
111112
entangleTransitions,
112113
} from './ReactUpdateQueue.old';
114+
import {pushInterleavedQueue} from './ReactFiberInterleavedUpdates.old';
113115

114116
const {ReactCurrentDispatcher, ReactCurrentBatchConfig} = ReactSharedInternals;
115117

@@ -122,8 +124,9 @@ type Update<S, A> = {|
122124
priority?: ReactPriorityLevel,
123125
|};
124126

125-
type UpdateQueue<S, A> = {|
127+
export type UpdateQueue<S, A> = {|
126128
pending: Update<S, A> | null,
129+
interleaved: Update<S, A> | null,
127130
lanes: Lanes,
128131
dispatch: (A => mixed) | null,
129132
lastRenderedReducer: ((S, A) => S) | null,
@@ -657,6 +660,7 @@ function mountReducer<S, I, A>(
657660
hook.memoizedState = hook.baseState = initialState;
658661
const queue = (hook.queue = {
659662
pending: null,
663+
interleaved: null,
660664
lanes: NoLanes,
661665
dispatch: null,
662666
lastRenderedReducer: reducer,
@@ -800,7 +804,22 @@ function updateReducer<S, I, A>(
800804
queue.lastRenderedState = newState;
801805
}
802806

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) {
804823
// `queue.lanes` is used for entangling transitions. We can set it back to
805824
// zero once the queue is empty.
806825
queue.lanes = NoLanes;
@@ -1132,6 +1151,7 @@ function useMutableSource<Source, Snapshot>(
11321151
// including any interleaving updates that occur.
11331152
const newQueue = {
11341153
pending: null,
1154+
interleaved: null,
11351155
lanes: NoLanes,
11361156
dispatch: null,
11371157
lastRenderedReducer: basicStateReducer,
@@ -1188,6 +1208,7 @@ function mountState<S>(
11881208
hook.memoizedState = hook.baseState = initialState;
11891209
const queue = (hook.queue = {
11901210
pending: null,
1211+
interleaved: null,
11911212
lanes: NoLanes,
11921213
dispatch: null,
11931214
lastRenderedReducer: basicStateReducer,
@@ -1869,7 +1890,7 @@ function refreshCache<T>(fiber: Fiber, seedKey: ?() => T, seedValue: T) {
18691890
cache: seededCache,
18701891
};
18711892
refreshUpdate.payload = payload;
1872-
enqueueUpdate(provider, refreshUpdate);
1893+
enqueueUpdate(provider, refreshUpdate, lane);
18731894
return;
18741895
}
18751896
}
@@ -1904,17 +1925,6 @@ function dispatchAction<S, A>(
19041925
next: (null: any),
19051926
};
19061927

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-
19181928
const alternate = fiber.alternate;
19191929
if (
19201930
fiber === currentlyRenderingFiber ||
@@ -1924,7 +1934,41 @@ function dispatchAction<S, A>(
19241934
// queue -> linked list of updates. After this render pass, we'll restart
19251935
// and apply the stashed updates on top of the work-in-progress hook.
19261936
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;
19271946
} 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+
19281972
if (
19291973
fiber.lanes === NoLanes &&
19301974
(alternate === null || alternate.lanes === NoLanes)
Original file line numberDiff line numberDiff line change
@@ -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
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import type {ReactContext} from 'shared/ReactTypes';
1111
import type {Fiber, ContextDependency} from './ReactInternalTypes';
1212
import type {StackCursor} from './ReactFiberStack.old';
1313
import type {Lanes} from './ReactFiberLane.old';
14+
import type {SharedQueue} from './ReactUpdateQueue.old';
1415

1516
import {isPrimaryRenderer} from './ReactFiberHostConfig';
1617
import {createCursor, push, pop} from './ReactFiberStack.old';
@@ -31,7 +32,7 @@ import {
3132

3233
import invariant from 'shared/invariant';
3334
import is from 'shared/objectIs';
34-
import {createUpdate, enqueueUpdate, ForceUpdate} from './ReactUpdateQueue.old';
35+
import {createUpdate, ForceUpdate} from './ReactUpdateQueue.old';
3536
import {markWorkInProgressReceivedUpdate} from './ReactFiberBeginWork.old';
3637
import {enableSuspenseServerRenderer} from 'shared/ReactFeatureFlags';
3738

@@ -211,16 +212,30 @@ export function propagateContextChange<T>(
211212

212213
if (fiber.tag === ClassComponent) {
213214
// 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);
218217
update.tag = ForceUpdate;
219218
// TODO: Because we don't have a work-in-progress, this will add the
220219
// update to the current fiber, too, which means it will persist even if
221220
// this render is thrown away. Since it's a race condition, not sure it's
222221
// 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+
}
224239
}
225240
fiber.lanes = mergeLanes(fiber.lanes, renderLanes);
226241
const alternate = fiber.alternate;

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -318,7 +318,7 @@ export function updateContainer(
318318
update.callback = callback;
319319
}
320320

321-
enqueueUpdate(current, update);
321+
enqueueUpdate(current, update, lane);
322322
const root = scheduleUpdateOnFiber(current, lane, eventTime);
323323
if (root !== null) {
324324
entangleTransitions(root, current, lane);

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,7 @@ function throwException(
293293
// prevent a bail out.
294294
const update = createUpdate(NoTimestamp, SyncLane);
295295
update.tag = ForceUpdate;
296-
enqueueUpdate(sourceFiber, update);
296+
enqueueUpdate(sourceFiber, update, SyncLane);
297297
}
298298
}
299299

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

+23-3
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ import {
3434
disableSchedulerTimeoutInWorkLoop,
3535
enableDoubleInvokingEffects,
3636
skipUnmountedBoundaries,
37-
enableDiscreteEventMicroTasks,
3837
} from 'shared/ReactFeatureFlags';
3938
import ReactSharedInternals from 'shared/ReactSharedInternals';
4039
import invariant from 'shared/invariant';
@@ -207,6 +206,7 @@ import {
207206
pop as popFromStack,
208207
createCursor,
209208
} from './ReactFiberStack.old';
209+
import {enqueueInterleavedUpdates} from './ReactFiberInterleavedUpdates.old';
210210

211211
import {
212212
markNestedUpdateScheduled,
@@ -217,6 +217,7 @@ import {
217217
syncNestedUpdateFlag,
218218
} from './ReactProfilerTimer.old';
219219

220+
import {enableDiscreteEventMicroTasks} from 'shared/ReactFeatureFlags';
220221
// DEV stuff
221222
import getComponentName from 'shared/getComponentName';
222223
import ReactStrictModeWarnings from './ReactStrictModeWarnings.old';
@@ -537,6 +538,7 @@ export function scheduleUpdateOnFiber(
537538
}
538539
}
539540

541+
// TODO: Consolidate with `isInterleavedUpdate` check
540542
if (root === workInProgressRoot) {
541543
// Received an update to a tree that's in the middle of rendering. Mark
542544
// that there was an interleaved update work on this root. Unless the
@@ -674,6 +676,22 @@ function markUpdateLaneFromFiberToRoot(
674676
}
675677
}
676678

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+
677695
// Use this function to schedule a task for a root. There's only one task per
678696
// root; if a task was already scheduled, we'll check to make sure the priority
679697
// 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) {
13761394
workInProgressRootUpdatedLanes = NoLanes;
13771395
workInProgressRootPingedLanes = NoLanes;
13781396

1397+
enqueueInterleavedUpdates();
1398+
13791399
if (enableSchedulerTracing) {
13801400
spawnedWorkDuringRender = null;
13811401
}
@@ -2307,7 +2327,7 @@ function captureCommitPhaseErrorOnRoot(
23072327
) {
23082328
const errorInfo = createCapturedValue(error, sourceFiber);
23092329
const update = createRootErrorUpdate(rootFiber, errorInfo, (SyncLane: Lane));
2310-
enqueueUpdate(rootFiber, update);
2330+
enqueueUpdate(rootFiber, update, (SyncLane: Lane));
23112331
const eventTime = requestEventTime();
23122332
const root = markUpdateLaneFromFiberToRoot(rootFiber, (SyncLane: Lane));
23132333
if (root !== null) {
@@ -2354,7 +2374,7 @@ export function captureCommitPhaseError(
23542374
errorInfo,
23552375
(SyncLane: Lane),
23562376
);
2357-
enqueueUpdate(fiber, update);
2377+
enqueueUpdate(fiber, update, (SyncLane: Lane));
23582378
const eventTime = requestEventTime();
23592379
const root = markUpdateLaneFromFiberToRoot(fiber, (SyncLane: Lane));
23602380
if (root !== null) {

0 commit comments

Comments
 (0)