Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Land interleaved updates change in main fork #20710

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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