Skip to content

Commit

Permalink
Land interleaved updates change in main fork (#20710)
Browse files Browse the repository at this point in the history
* Land #20615 in main fork

Includes change to interleaved updates.

```
yarn replace-fork
```

* Check deferRenderPhaseUpdateToNextBatch in test
  • Loading branch information
acdlite authored Feb 2, 2021
1 parent dc27b5a commit 7cb9fd7
Show file tree
Hide file tree
Showing 9 changed files with 219 additions and 40 deletions.
6 changes: 3 additions & 3 deletions packages/react-reconciler/src/ReactFiberClassComponent.old.js
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ const classComponentUpdater = {
update.callback = callback;
}

enqueueUpdate(fiber, update);
enqueueUpdate(fiber, update, lane);
const root = scheduleUpdateOnFiber(fiber, lane, eventTime);
if (root !== null) {
entangleTransitions(root, fiber, lane);
Expand Down Expand Up @@ -249,7 +249,7 @@ const classComponentUpdater = {
update.callback = callback;
}

enqueueUpdate(fiber, update);
enqueueUpdate(fiber, update, lane);
const root = scheduleUpdateOnFiber(fiber, lane, eventTime);
if (root !== null) {
entangleTransitions(root, fiber, lane);
Expand Down Expand Up @@ -283,7 +283,7 @@ const classComponentUpdater = {
update.callback = callback;
}

enqueueUpdate(fiber, update);
enqueueUpdate(fiber, update, lane);
const root = scheduleUpdateOnFiber(fiber, lane, eventTime);
if (root !== null) {
entangleTransitions(root, fiber, lane);
Expand Down
72 changes: 58 additions & 14 deletions packages/react-reconciler/src/ReactFiberHooks.old.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ import {
warnIfNotCurrentlyActingUpdatesInDev,
warnIfNotScopedWithMatchingAct,
markSkippedUpdateLanes,
isInterleavedUpdate,
} from './ReactFiberWorkLoop.old';

import invariant from 'shared/invariant';
Expand Down Expand Up @@ -110,6 +111,7 @@ import {
enqueueUpdate,
entangleTransitions,
} from './ReactUpdateQueue.old';
import {pushInterleavedQueue} from './ReactFiberInterleavedUpdates.old';

const {ReactCurrentDispatcher, ReactCurrentBatchConfig} = ReactSharedInternals;

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

type UpdateQueue<S, A> = {|
export type UpdateQueue<S, A> = {|
pending: Update<S, A> | null,
interleaved: Update<S, A> | null,
lanes: Lanes,
dispatch: (A => mixed) | null,
lastRenderedReducer: ((S, A) => S) | null,
Expand Down Expand Up @@ -657,6 +660,7 @@ function mountReducer<S, I, A>(
hook.memoizedState = hook.baseState = initialState;
const queue = (hook.queue = {
pending: null,
interleaved: null,
lanes: NoLanes,
dispatch: null,
lastRenderedReducer: reducer,
Expand Down Expand Up @@ -800,7 +804,22 @@ function updateReducer<S, I, A>(
queue.lastRenderedState = newState;
}

if (baseQueue === null) {
// Interleaved updates are stored on a separate queue. We aren't going to
// process them during this render, but we do need to track which lanes
// are remaining.
const lastInterleaved = queue.interleaved;
if (lastInterleaved !== null) {
let interleaved = lastInterleaved;
do {
const interleavedLane = interleaved.lane;
currentlyRenderingFiber.lanes = mergeLanes(
currentlyRenderingFiber.lanes,
interleavedLane,
);
markSkippedUpdateLanes(interleavedLane);
interleaved = ((interleaved: any).next: Update<S, A>);
} while (interleaved !== lastInterleaved);
} else if (baseQueue === null) {
// `queue.lanes` is used for entangling transitions. We can set it back to
// zero once the queue is empty.
queue.lanes = NoLanes;
Expand Down Expand Up @@ -1132,6 +1151,7 @@ function useMutableSource<Source, Snapshot>(
// including any interleaving updates that occur.
const newQueue = {
pending: null,
interleaved: null,
lanes: NoLanes,
dispatch: null,
lastRenderedReducer: basicStateReducer,
Expand Down Expand Up @@ -1188,6 +1208,7 @@ function mountState<S>(
hook.memoizedState = hook.baseState = initialState;
const queue = (hook.queue = {
pending: null,
interleaved: null,
lanes: NoLanes,
dispatch: null,
lastRenderedReducer: basicStateReducer,
Expand Down Expand Up @@ -1869,7 +1890,7 @@ function refreshCache<T>(fiber: Fiber, seedKey: ?() => T, seedValue: T) {
cache: seededCache,
};
refreshUpdate.payload = payload;
enqueueUpdate(provider, refreshUpdate);
enqueueUpdate(provider, refreshUpdate, lane);
return;
}
}
Expand Down Expand Up @@ -1904,17 +1925,6 @@ function dispatchAction<S, A>(
next: (null: any),
};

// Append the update to the end of the list.
const pending = queue.pending;
if (pending === null) {
// This is the first update. Create a circular list.
update.next = update;
} else {
update.next = pending.next;
pending.next = update;
}
queue.pending = update;

const alternate = fiber.alternate;
if (
fiber === currentlyRenderingFiber ||
Expand All @@ -1924,7 +1934,41 @@ function dispatchAction<S, A>(
// queue -> linked list of updates. After this render pass, we'll restart
// and apply the stashed updates on top of the work-in-progress hook.
didScheduleRenderPhaseUpdateDuringThisPass = didScheduleRenderPhaseUpdate = true;
const pending = queue.pending;
if (pending === null) {
// This is the first update. Create a circular list.
update.next = update;
} else {
update.next = pending.next;
pending.next = update;
}
queue.pending = update;
} else {
if (isInterleavedUpdate(fiber, lane)) {
const interleaved = queue.interleaved;
if (interleaved === null) {
// This is the first update. Create a circular list.
update.next = update;
// At the end of the current render, this queue's interleaved updates will
// be transfered to the pending queue.
pushInterleavedQueue(queue);
} else {
update.next = interleaved.next;
interleaved.next = update;
}
queue.interleaved = update;
} else {
const pending = queue.pending;
if (pending === null) {
// This is the first update. Create a circular list.
update.next = update;
} else {
update.next = pending.next;
pending.next = update;
}
queue.pending = update;
}

if (
fiber.lanes === NoLanes &&
(alternate === null || alternate.lanes === NoLanes)
Expand Down
55 changes: 55 additions & 0 deletions packages/react-reconciler/src/ReactFiberInterleavedUpdates.old.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/

import type {UpdateQueue as HookQueue} from './ReactFiberHooks.old';
import type {SharedQueue as ClassQueue} from './ReactUpdateQueue.old';

// An array of all update queues that received updates during the current
// render. When this render exits, either because it finishes or because it is
// interrupted, the interleaved updates will be transfered onto the main part
// of the queue.
let interleavedQueues: Array<
HookQueue<any, any> | ClassQueue<any>,
> | null = null;

export function pushInterleavedQueue(
queue: HookQueue<any, any> | ClassQueue<any>,
) {
if (interleavedQueues === null) {
interleavedQueues = [queue];
} else {
interleavedQueues.push(queue);
}
}

export function enqueueInterleavedUpdates() {
// Transfer the interleaved updates onto the main queue. Each queue has a
// `pending` field and an `interleaved` field. When they are not null, they
// point to the last node in a circular linked list. We need to append the
// interleaved list to the end of the pending list by joining them into a
// single, circular list.
if (interleavedQueues !== null) {
for (let i = 0; i < interleavedQueues.length; i++) {
const queue = interleavedQueues[i];
const lastInterleavedUpdate = queue.interleaved;
if (lastInterleavedUpdate !== null) {
queue.interleaved = null;
const firstInterleavedUpdate = lastInterleavedUpdate.next;
const lastPendingUpdate = queue.pending;
if (lastPendingUpdate !== null) {
const firstPendingUpdate = lastPendingUpdate.next;
lastPendingUpdate.next = (firstInterleavedUpdate: any);
lastInterleavedUpdate.next = (firstPendingUpdate: any);
}
queue.pending = (lastInterleavedUpdate: any);
}
}
interleavedQueues = null;
}
}
27 changes: 21 additions & 6 deletions packages/react-reconciler/src/ReactFiberNewContext.old.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import type {ReactContext} from 'shared/ReactTypes';
import type {Fiber, ContextDependency} from './ReactInternalTypes';
import type {StackCursor} from './ReactFiberStack.old';
import type {Lanes} from './ReactFiberLane.old';
import type {SharedQueue} from './ReactUpdateQueue.old';

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

import invariant from 'shared/invariant';
import is from 'shared/objectIs';
import {createUpdate, enqueueUpdate, ForceUpdate} from './ReactUpdateQueue.old';
import {createUpdate, ForceUpdate} from './ReactUpdateQueue.old';
import {markWorkInProgressReceivedUpdate} from './ReactFiberBeginWork.old';
import {enableSuspenseServerRenderer} from 'shared/ReactFeatureFlags';

Expand Down Expand Up @@ -211,16 +212,30 @@ export function propagateContextChange<T>(

if (fiber.tag === ClassComponent) {
// Schedule a force update on the work-in-progress.
const update = createUpdate(
NoTimestamp,
pickArbitraryLane(renderLanes),
);
const lane = pickArbitraryLane(renderLanes);
const update = createUpdate(NoTimestamp, lane);
update.tag = ForceUpdate;
// TODO: Because we don't have a work-in-progress, this will add the
// update to the current fiber, too, which means it will persist even if
// this render is thrown away. Since it's a race condition, not sure it's
// worth fixing.
enqueueUpdate(fiber, update);

// Inlined `enqueueUpdate` to remove interleaved update check
const updateQueue = fiber.updateQueue;
if (updateQueue === null) {
// Only occurs if the fiber has been unmounted.
} else {
const sharedQueue: SharedQueue<any> = (updateQueue: any).shared;
const pending = sharedQueue.pending;
if (pending === null) {
// This is the first update. Create a circular list.
update.next = update;
} else {
update.next = pending.next;
pending.next = update;
}
sharedQueue.pending = update;
}
}
fiber.lanes = mergeLanes(fiber.lanes, renderLanes);
const alternate = fiber.alternate;
Expand Down
2 changes: 1 addition & 1 deletion packages/react-reconciler/src/ReactFiberReconciler.old.js
Original file line number Diff line number Diff line change
Expand Up @@ -318,7 +318,7 @@ export function updateContainer(
update.callback = callback;
}

enqueueUpdate(current, update);
enqueueUpdate(current, update, lane);
const root = scheduleUpdateOnFiber(current, lane, eventTime);
if (root !== null) {
entangleTransitions(root, current, lane);
Expand Down
2 changes: 1 addition & 1 deletion packages/react-reconciler/src/ReactFiberThrow.old.js
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ function throwException(
// prevent a bail out.
const update = createUpdate(NoTimestamp, SyncLane);
update.tag = ForceUpdate;
enqueueUpdate(sourceFiber, update);
enqueueUpdate(sourceFiber, update, SyncLane);
}
}

Expand Down
26 changes: 23 additions & 3 deletions packages/react-reconciler/src/ReactFiberWorkLoop.old.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ import {
disableSchedulerTimeoutInWorkLoop,
enableDoubleInvokingEffects,
skipUnmountedBoundaries,
enableDiscreteEventMicroTasks,
} from 'shared/ReactFeatureFlags';
import ReactSharedInternals from 'shared/ReactSharedInternals';
import invariant from 'shared/invariant';
Expand Down Expand Up @@ -207,6 +206,7 @@ import {
pop as popFromStack,
createCursor,
} from './ReactFiberStack.old';
import {enqueueInterleavedUpdates} from './ReactFiberInterleavedUpdates.old';

import {
markNestedUpdateScheduled,
Expand All @@ -217,6 +217,7 @@ import {
syncNestedUpdateFlag,
} from './ReactProfilerTimer.old';

import {enableDiscreteEventMicroTasks} from 'shared/ReactFeatureFlags';
// DEV stuff
import getComponentName from 'shared/getComponentName';
import ReactStrictModeWarnings from './ReactStrictModeWarnings.old';
Expand Down Expand Up @@ -537,6 +538,7 @@ export function scheduleUpdateOnFiber(
}
}

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

export function isInterleavedUpdate(fiber: Fiber, lane: Lane) {
return (
// TODO: Optimize slightly by comparing to root that fiber belongs to.
// Requires some refactoring. Not a big deal though since it's rare for
// concurrent apps to have more than a single root.
workInProgressRoot !== null &&
(fiber.mode & BlockingMode) !== NoMode &&
// If this is a render phase update (i.e. UNSAFE_componentWillReceiveProps),
// then don't treat this as an interleaved update. This pattern is
// accompanied by a warning but we haven't fully deprecated it yet. We can
// remove once the deferRenderPhaseUpdateToNextBatch flag is enabled.
(deferRenderPhaseUpdateToNextBatch ||
(executionContext & RenderContext) === NoContext)
);
}

// Use this function to schedule a task for a root. There's only one task per
// root; if a task was already scheduled, we'll check to make sure the priority
// of the existing task is the same as the priority of the next level that the
Expand Down Expand Up @@ -1376,6 +1394,8 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes) {
workInProgressRootUpdatedLanes = NoLanes;
workInProgressRootPingedLanes = NoLanes;

enqueueInterleavedUpdates();

if (enableSchedulerTracing) {
spawnedWorkDuringRender = null;
}
Expand Down Expand Up @@ -2307,7 +2327,7 @@ function captureCommitPhaseErrorOnRoot(
) {
const errorInfo = createCapturedValue(error, sourceFiber);
const update = createRootErrorUpdate(rootFiber, errorInfo, (SyncLane: Lane));
enqueueUpdate(rootFiber, update);
enqueueUpdate(rootFiber, update, (SyncLane: Lane));
const eventTime = requestEventTime();
const root = markUpdateLaneFromFiberToRoot(rootFiber, (SyncLane: Lane));
if (root !== null) {
Expand Down Expand Up @@ -2354,7 +2374,7 @@ export function captureCommitPhaseError(
errorInfo,
(SyncLane: Lane),
);
enqueueUpdate(fiber, update);
enqueueUpdate(fiber, update, (SyncLane: Lane));
const eventTime = requestEventTime();
const root = markUpdateLaneFromFiberToRoot(fiber, (SyncLane: Lane));
if (root !== null) {
Expand Down
Loading

0 comments on commit 7cb9fd7

Please sign in to comment.