Decouple update priority tracking from Scheduler package (#19121)

* Initial currentLanePriority implementation

* Minor updates from review

* Fix typos and enable flag

* Fix feature flags and lint

* Fix simple event tests by switching to withSuspenseConfig

* Don't lower the priority of setPending in startTransition below InputContinuous

* Move currentUpdateLanePriority in commit root into the first effect block

* Refactor requestUpdateLane to log for priority mismatches

Also verifies that the update lane priority matches the scheduler lane priority before using it

* Fix four tests by adding ReactDOM.unstable_runWithPriority

* Fix partial hydration when using update lane priority

* Fix partial hydration when using update lane priority

* Rename feature flag and only log for now

* Move unstable_runWithPriority to ReactFiberReconciler

* Add unstable_runWithPriority to ReactNoopPersistent too

* Bug fixes and performance improvements

* Initial currentLanePriority implementation

* Minor updates from review

* Fix typos and enable flag

* Remove higherLanePriority from ReactDOMEventReplaying.js

* Change warning implementation and startTransition update lane priority

* Inject reconciler functions to avoid importing src/

* Fix feature flags and lint

* Fix simple event tests by switching to withSuspenseConfig

* Don't lower the priority of setPending in startTransition below InputContinuous

* Move currentUpdateLanePriority in commit root into the first effect block

* Refactor requestUpdateLane to log for priority mismatches

Also verifies that the update lane priority matches the scheduler lane priority before using it

* Fix four tests by adding ReactDOM.unstable_runWithPriority

* Fix partial hydration when using update lane priority

* Fix partial hydration when using update lane priority

* Rename feature flag and only log for now

* Move unstable_runWithPriority to ReactFiberReconciler

* Bug fixes and performance improvements

* Remove higherLanePriority from ReactDOMEventReplaying.js

* Change warning implementation and startTransition update lane priority

* Inject reconciler functions to avoid importing src/

* Fixes from bad rebase
This commit is contained in:
Ricky 2020-07-06 18:53:42 -04:00 committed by GitHub
parent c3e42a962b
commit 91a2e8173f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 396 additions and 67 deletions

View File

@ -34,6 +34,7 @@ export {
createBlockingRoot as unstable_createBlockingRoot,
unstable_flushControlled,
unstable_scheduleHydration,
unstable_runWithPriority,
unstable_renderSubtreeIntoContainer,
unstable_createPortal,
unstable_createEventHandle,

View File

@ -23,6 +23,8 @@ export {
createBlockingRoot as unstable_createBlockingRoot,
unstable_flushControlled,
unstable_scheduleHydration,
// DO NOT USE: Temporarily exposing this to migrate off of Scheduler.runWithPriority.
unstable_runWithPriority,
// Disabled behind disableUnstableRenderSubtreeIntoContainer
unstable_renderSubtreeIntoContainer,
// Disabled behind disableUnstableCreatePortal

View File

@ -25,6 +25,7 @@ export {
createBlockingRoot as unstable_createBlockingRoot,
unstable_flushControlled,
unstable_scheduleHydration,
unstable_runWithPriority,
unstable_renderSubtreeIntoContainer,
unstable_createPortal,
unstable_createEventHandle,

View File

@ -19,6 +19,7 @@ export {
createBlockingRoot as unstable_createBlockingRoot,
unstable_flushControlled,
unstable_scheduleHydration,
unstable_runWithPriority,
unstable_createEventHandle,
unstable_isNewReconciler,
} from './src/client/ReactDOM';

View File

@ -35,6 +35,8 @@ import {
attemptUserBlockingHydration,
attemptContinuousHydration,
attemptHydrationAtCurrentPriority,
runWithPriority,
getCurrentUpdatePriority,
} from 'react-reconciler/src/ReactFiberReconciler';
import {createPortal as createPortalImpl} from 'react-reconciler/src/ReactPortal';
import {canUseDOM} from 'shared/ExecutionEnvironment';
@ -58,6 +60,8 @@ import {
setAttemptContinuousHydration,
setAttemptHydrationAtCurrentPriority,
queueExplicitHydrationTarget,
setGetCurrentUpdatePriority,
setAttemptHydrationAtPriority,
} from '../events/ReactDOMEventReplaying';
import {setBatchingImplementation} from '../events/ReactDOMUpdateBatching';
import {
@ -70,6 +74,8 @@ setAttemptSynchronousHydration(attemptSynchronousHydration);
setAttemptUserBlockingHydration(attemptUserBlockingHydration);
setAttemptContinuousHydration(attemptContinuousHydration);
setAttemptHydrationAtCurrentPriority(attemptHydrationAtCurrentPriority);
setGetCurrentUpdatePriority(getCurrentUpdatePriority);
setAttemptHydrationAtPriority(runWithPriority);
let didWarnAboutUnstableCreatePortal = false;
let didWarnAboutUnstableRenderSubtreeIntoContainer = false;
@ -205,6 +211,9 @@ export {
unstable_createPortal,
// enableCreateEventHandleAPI
createEventHandle as unstable_createEventHandle,
// TODO: Remove this once callers migrate to alternatives.
// This should only be used by React internals.
runWithPriority as unstable_runWithPriority,
};
const foundDevTools = injectIntoDevTools({

View File

@ -12,7 +12,10 @@ import type {Container, SuspenseInstance} from '../client/ReactDOMHostConfig';
import type {DOMTopLevelEventType} from '../events/TopLevelEventTypes';
import type {ElementListenerMap} from '../client/ReactDOMComponentTree';
import type {EventSystemFlags} from './EventSystemFlags';
import type {FiberRoot} from 'react-reconciler/src/ReactInternalTypes';
import type {
FiberRoot,
ReactPriorityLevel,
} from 'react-reconciler/src/ReactInternalTypes';
import {
enableDeprecatedFlareAPI,
@ -64,6 +67,23 @@ export function setAttemptHydrationAtCurrentPriority(
attemptHydrationAtCurrentPriority = fn;
}
let getCurrentUpdatePriority: () => ReactPriorityLevel;
export function setGetCurrentUpdatePriority(fn: () => ReactPriorityLevel) {
getCurrentUpdatePriority = fn;
}
let attemptHydrationAtPriority: <T>(
priority: ReactPriorityLevel,
fn: () => T,
) => T;
export function setAttemptHydrationAtPriority(
fn: <T>(priority: ReactPriorityLevel, fn: () => T) => T,
) {
attemptHydrationAtPriority = fn;
}
// TODO: Upgrade this definition once we're on a newer version of Flow that
// has this definition built-in.
type PointerEvent = Event & {
@ -147,6 +167,7 @@ type QueuedHydrationTarget = {|
blockedOn: null | Container | SuspenseInstance,
target: Node,
priority: number,
lanePriority: ReactPriorityLevel,
|};
const queuedExplicitHydrationTargets: Array<QueuedHydrationTarget> = [];
@ -508,9 +529,12 @@ function attemptExplicitHydrationTarget(
// We're blocked on hydrating this boundary.
// Increase its priority.
queuedTarget.blockedOn = instance;
runWithPriority(queuedTarget.priority, () => {
attemptHydrationAtCurrentPriority(nearestMounted);
attemptHydrationAtPriority(queuedTarget.lanePriority, () => {
runWithPriority(queuedTarget.priority, () => {
attemptHydrationAtCurrentPriority(nearestMounted);
});
});
return;
}
} else if (tag === HostRoot) {
@ -529,15 +553,17 @@ function attemptExplicitHydrationTarget(
export function queueExplicitHydrationTarget(target: Node): void {
if (enableSelectiveHydration) {
const priority = getCurrentPriorityLevel();
const schedulerPriority = getCurrentPriorityLevel();
const updateLanePriority = getCurrentUpdatePriority();
const queuedTarget: QueuedHydrationTarget = {
blockedOn: null,
target: target,
priority: priority,
priority: schedulerPriority,
lanePriority: updateLanePriority,
};
let i = 0;
for (; i < queuedExplicitHydrationTargets.length; i++) {
if (priority <= queuedExplicitHydrationTargets[i].priority) {
if (schedulerPriority <= queuedExplicitHydrationTargets[i].priority) {
break;
}
}

View File

@ -231,6 +231,8 @@ describe('SimpleEventPlugin', function() {
describe('interactive events, in concurrent mode', () => {
beforeEach(() => {
jest.resetModules();
React = require('react');
ReactDOM = require('react-dom');
Scheduler = require('scheduler');
});
@ -377,11 +379,14 @@ describe('SimpleEventPlugin', function() {
<button
ref={el => (button = el)}
onClick={() => {
Scheduler.unstable_next(() => {
this.setState(state => ({
lowPriCount: state.lowPriCount + 1,
}));
});
React.unstable_withSuspenseConfig(
() => {
this.setState(state => ({
lowPriCount: state.lowPriCount + 1,
}));
},
{timeoutMs: 5000},
);
}}>
{text}
</button>

View File

@ -47,6 +47,9 @@ export const {
act,
dumpTree,
getRoot,
// TODO: Remove this once callers migrate to alternatives.
// This should only be used by React internals.
unstable_runWithPriority,
} = createReactNoop(
ReactFiberReconciler, // reconciler
true, // useMutation

View File

@ -47,6 +47,9 @@ export const {
act,
dumpTree,
getRoot,
// TODO: Remove this once callers migrate to alternatives.
// This should only be used by React internals.
unstable_runWithPriority,
} = createReactNoop(
ReactFiberReconciler, // reconciler
false, // useMutation

View File

@ -954,6 +954,8 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
return Scheduler.unstable_flushExpired();
},
unstable_runWithPriority: NoopRenderer.runWithPriority,
batchedUpdates: NoopRenderer.batchedUpdates,
deferredUpdates: NoopRenderer.deferredUpdates,

View File

@ -30,11 +30,16 @@ import {NoMode, BlockingMode} from './ReactTypeOfMode';
import {
NoLane,
NoLanes,
InputContinuousLanePriority,
isSubsetOfLanes,
mergeLanes,
removeLanes,
markRootEntangled,
markRootMutableRead,
getCurrentUpdateLanePriority,
setCurrentUpdateLanePriority,
higherLanePriority,
DefaultLanePriority,
} from './ReactFiberLane';
import {readContext} from './ReactFiberNewContext.new';
import {createDeprecatedResponderListener} from './ReactFiberDeprecatedEvents.new';
@ -1498,12 +1503,20 @@ function rerenderDeferredValue<T>(
function startTransition(setPending, config, callback) {
const priorityLevel = getCurrentPriorityLevel();
const previousLanePriority = getCurrentUpdateLanePriority();
setCurrentUpdateLanePriority(
higherLanePriority(previousLanePriority, InputContinuousLanePriority),
);
runWithPriority(
priorityLevel < UserBlockingPriority ? UserBlockingPriority : priorityLevel,
() => {
setPending(true);
},
);
// If there's no SuspenseConfig set, we'll use the DefaultLanePriority for this transition.
setCurrentUpdateLanePriority(DefaultLanePriority);
runWithPriority(
priorityLevel > NormalPriority ? NormalPriority : priorityLevel,
() => {
@ -1513,6 +1526,7 @@ function startTransition(setPending, config, callback) {
setPending(false);
callback();
} finally {
setCurrentUpdateLanePriority(previousLanePriority);
ReactCurrentBatchConfig.suspense = previousConfig;
}
},

View File

@ -33,11 +33,16 @@ import {NoMode, BlockingMode, DebugTracingMode} from './ReactTypeOfMode';
import {
NoLane,
NoLanes,
InputContinuousLanePriority,
isSubsetOfLanes,
mergeLanes,
removeLanes,
markRootEntangled,
markRootMutableRead,
getCurrentUpdateLanePriority,
setCurrentUpdateLanePriority,
higherLanePriority,
DefaultLanePriority,
} from './ReactFiberLane';
import {readContext} from './ReactFiberNewContext.old';
import {createDeprecatedResponderListener} from './ReactFiberDeprecatedEvents.old';
@ -1502,12 +1507,20 @@ function rerenderDeferredValue<T>(
function startTransition(setPending, config, callback) {
const priorityLevel = getCurrentPriorityLevel();
const previousLanePriority = getCurrentUpdateLanePriority();
setCurrentUpdateLanePriority(
higherLanePriority(previousLanePriority, InputContinuousLanePriority),
);
runWithPriority(
priorityLevel < UserBlockingPriority ? UserBlockingPriority : priorityLevel,
() => {
setPending(true);
},
);
// If there's no SuspenseConfig set, we'll use the DefaultLanePriority for this transition.
setCurrentUpdateLanePriority(DefaultLanePriority);
runWithPriority(
priorityLevel > NormalPriority ? NormalPriority : priorityLevel,
() => {
@ -1517,6 +1530,7 @@ function startTransition(setPending, config, callback) {
setPending(false);
callback();
} finally {
setCurrentUpdateLanePriority(previousLanePriority);
ReactCurrentBatchConfig.suspense = previousConfig;
}
},

View File

@ -49,10 +49,10 @@ const InputDiscreteHydrationLanePriority: LanePriority = 14;
export const InputDiscreteLanePriority: LanePriority = 13;
const InputContinuousHydrationLanePriority: LanePriority = 12;
const InputContinuousLanePriority: LanePriority = 11;
export const InputContinuousLanePriority: LanePriority = 11;
const DefaultHydrationLanePriority: LanePriority = 10;
const DefaultLanePriority: LanePriority = 9;
export const DefaultLanePriority: LanePriority = 9;
const TransitionShortHydrationLanePriority: LanePriority = 8;
export const TransitionShortLanePriority: LanePriority = 7;
@ -120,6 +120,16 @@ export const OffscreenLane: Lane = /* */ 0b1000000000000000000
export const NoTimestamp = -1;
let currentUpdateLanePriority: LanePriority = NoLanePriority;
export function getCurrentUpdateLanePriority(): LanePriority {
return currentUpdateLanePriority;
}
export function setCurrentUpdateLanePriority(newLanePriority: LanePriority) {
currentUpdateLanePriority = newLanePriority;
}
// "Registers" used to "return" multiple values
// Used by getHighestPriorityLanes and getNextLanes:
let return_highestLanePriority: LanePriority = DefaultLanePriority;
@ -651,6 +661,13 @@ export function higherPriorityLane(a: Lane, b: Lane) {
return a !== NoLane && a < b ? a : b;
}
export function higherLanePriority(
a: LanePriority,
b: LanePriority,
): LanePriority {
return a !== NoLanePriority && a > b ? a : b;
}
export function createLaneMap<T>(initial: T): LaneMap<T> {
return new Array(TotalLanes).fill(initial);
}

View File

@ -50,6 +50,8 @@ import {
focusWithin as focusWithin_old,
observeVisibleRects as observeVisibleRects_old,
registerMutableSourceForHydration as registerMutableSourceForHydration_old,
runWithPriority as runWithPriority_old,
getCurrentUpdatePriority as getCurrentUpdatePriority_old,
} from './ReactFiberReconciler.old';
import {
@ -88,6 +90,8 @@ import {
focusWithin as focusWithin_new,
observeVisibleRects as observeVisibleRects_new,
registerMutableSourceForHydration as registerMutableSourceForHydration_new,
runWithPriority as runWithPriority_new,
getCurrentUpdatePriority as getCurrentUpdatePriority_new,
} from './ReactFiberReconciler.new';
export const createContainer = enableNewReconciler
@ -139,6 +143,9 @@ export const attemptContinuousHydration = enableNewReconciler
export const attemptHydrationAtCurrentPriority = enableNewReconciler
? attemptHydrationAtCurrentPriority_new
: attemptHydrationAtCurrentPriority_old;
export const getCurrentUpdatePriority = enableNewReconciler
? getCurrentUpdatePriority_new
: getCurrentUpdatePriority_old;
export const findHostInstance = enableNewReconciler
? findHostInstance_new
: findHostInstance_old;
@ -194,3 +201,6 @@ export const observeVisibleRects = enableNewReconciler
export const registerMutableSourceForHydration = enableNewReconciler
? registerMutableSourceForHydration_new
: registerMutableSourceForHydration_old;
export const runWithPriority = enableNewReconciler
? runWithPriority_new
: runWithPriority_old;

View File

@ -7,7 +7,11 @@
* @flow
*/
import type {Fiber, SuspenseHydrationCallbacks} from './ReactInternalTypes';
import type {
Fiber,
ReactPriorityLevel,
SuspenseHydrationCallbacks,
} from './ReactInternalTypes';
import type {FiberRoot} from './ReactInternalTypes';
import type {RootTag} from './ReactRootTags';
import type {
@ -79,6 +83,10 @@ import {
NoTimestamp,
getHighestPriorityPendingLanes,
higherPriorityLane,
getCurrentUpdateLanePriority,
setCurrentUpdateLanePriority,
schedulerPriorityToLanePriority,
lanePriorityToSchedulerPriority,
} from './ReactFiberLane';
import {requestCurrentSuspenseConfig} from './ReactFiberSuspenseConfig';
import {
@ -424,6 +432,20 @@ export function attemptHydrationAtCurrentPriority(fiber: Fiber): void {
markRetryLaneIfNotHydrated(fiber, lane);
}
export function runWithPriority<T>(priority: ReactPriorityLevel, fn: () => T) {
const previousPriority = getCurrentUpdateLanePriority();
try {
setCurrentUpdateLanePriority(schedulerPriorityToLanePriority(priority));
return fn();
} finally {
setCurrentUpdateLanePriority(previousPriority);
}
}
export function getCurrentUpdatePriority(): ReactPriorityLevel {
return lanePriorityToSchedulerPriority(getCurrentUpdateLanePriority());
}
export {findHostInstance};
export {findHostInstanceWithWarning};

View File

@ -7,7 +7,11 @@
* @flow
*/
import type {Fiber, SuspenseHydrationCallbacks} from './ReactInternalTypes';
import type {
Fiber,
ReactPriorityLevel,
SuspenseHydrationCallbacks,
} from './ReactInternalTypes';
import type {FiberRoot} from './ReactInternalTypes';
import type {RootTag} from './ReactRootTags';
import type {
@ -79,6 +83,10 @@ import {
NoTimestamp,
getHighestPriorityPendingLanes,
higherPriorityLane,
getCurrentUpdateLanePriority,
setCurrentUpdateLanePriority,
schedulerPriorityToLanePriority,
lanePriorityToSchedulerPriority,
} from './ReactFiberLane';
import {requestCurrentSuspenseConfig} from './ReactFiberSuspenseConfig';
import {
@ -424,6 +432,20 @@ export function attemptHydrationAtCurrentPriority(fiber: Fiber): void {
markRetryLaneIfNotHydrated(fiber, lane);
}
export function runWithPriority<T>(priority: ReactPriorityLevel, fn: () => T) {
const previousPriority = getCurrentUpdateLanePriority();
try {
setCurrentUpdateLanePriority(schedulerPriorityToLanePriority(priority));
return fn();
} finally {
setCurrentUpdateLanePriority(previousPriority);
}
}
export function getCurrentUpdatePriority(): ReactPriorityLevel {
return lanePriorityToSchedulerPriority(getCurrentUpdateLanePriority());
}
export {findHostInstance};
export {findHostInstanceWithWarning};

View File

@ -26,6 +26,7 @@ import {
enableSchedulerTracing,
warnAboutUnmockedScheduler,
deferRenderPhaseUpdateToNextBatch,
decoupleUpdatePriorityFromScheduler,
} from 'shared/ReactFeatureFlags';
import ReactSharedInternals from 'shared/ReactSharedInternals';
import invariant from 'shared/invariant';
@ -113,6 +114,7 @@ import {
InputDiscreteLanePriority,
TransitionShortLanePriority,
TransitionLongLanePriority,
DefaultLanePriority,
NoLanes,
NoLane,
SyncLane,
@ -130,6 +132,8 @@ import {
hasUpdatePriority,
getNextLanes,
returnNextLanesPriority,
setCurrentUpdateLanePriority,
getCurrentUpdateLanePriority,
markStarvedLanesAsExpired,
getLanesToRetrySynchronouslyOnError,
markRootUpdated,
@ -394,7 +398,6 @@ export function requestUpdateLane(
currentEventWipLanes = workInProgressRootIncludedLanes;
}
let lane;
if (suspenseConfig !== null) {
// Use the size of the timeout as a heuristic to prioritize shorter
// transitions over longer ones.
@ -412,26 +415,56 @@ export function requestUpdateLane(
: NoLanes;
}
lane = findTransitionLane(
return findTransitionLane(
transitionLanePriority,
currentEventWipLanes,
currentEventPendingLanes,
);
} else {
// TODO: If we're not inside `runWithPriority`, this returns the priority
// of the currently running task. That's probably not what we want.
const schedulerPriority = getCurrentPriorityLevel();
}
if (
// TODO: Temporary. We're removing the concept of discrete updates.
(executionContext & DiscreteEventContext) !== NoContext &&
schedulerPriority === UserBlockingSchedulerPriority
) {
lane = findUpdateLane(InputDiscreteLanePriority, currentEventWipLanes);
} else {
const lanePriority = schedulerPriorityToLanePriority(schedulerPriority);
lane = findUpdateLane(lanePriority, currentEventWipLanes);
// TODO: Remove this dependency on the Scheduler priority.
// To do that, we're replacing it with an update lane priority.
const schedulerPriority = getCurrentPriorityLevel();
// The old behavior was using the priority level of the Scheduler.
// This couples React to the Scheduler internals, so we're replacing it
// with the currentUpdateLanePriority above. As an example of how this
// could be problematic, if we're not inside `Scheduler.runWithPriority`,
// then we'll get the priority of the current running Scheduler task,
// which is probably not what we want.
let lane;
if (
// TODO: Temporary. We're removing the concept of discrete updates.
(executionContext & DiscreteEventContext) !== NoContext &&
schedulerPriority === UserBlockingSchedulerPriority
) {
lane = findUpdateLane(InputDiscreteLanePriority, currentEventWipLanes);
} else {
const schedulerLanePriority = schedulerPriorityToLanePriority(
schedulerPriority,
);
if (decoupleUpdatePriorityFromScheduler) {
// In the new strategy, we will track the current update lane priority
// inside React and use that priority to select a lane for this update.
// For now, we're just logging when they're different so we can assess.
const currentUpdateLanePriority = getCurrentUpdateLanePriority();
if (
schedulerLanePriority !== currentUpdateLanePriority &&
currentUpdateLanePriority !== NoLanePriority
) {
if (__DEV__) {
console.error(
'Expected current scheduler lane priority %s to match current update lane priority %s',
schedulerLanePriority,
currentUpdateLanePriority,
);
}
}
}
lane = findUpdateLane(schedulerLanePriority, currentEventWipLanes);
}
return lane;
@ -1068,7 +1101,13 @@ export function flushDiscreteUpdates() {
export function deferredUpdates<A>(fn: () => A): A {
// TODO: Remove in favor of Scheduler.next
return runWithPriority(NormalSchedulerPriority, fn);
const previousLanePriority = getCurrentUpdateLanePriority();
try {
setCurrentUpdateLanePriority(DefaultLanePriority);
return runWithPriority(NormalSchedulerPriority, fn);
} finally {
setCurrentUpdateLanePriority(previousLanePriority);
}
}
function flushPendingDiscreteUpdates() {
@ -1123,13 +1162,16 @@ export function discreteUpdates<A, B, C, D, R>(
): R {
const prevExecutionContext = executionContext;
executionContext |= DiscreteEventContext;
const previousLanePriority = getCurrentUpdateLanePriority();
try {
setCurrentUpdateLanePriority(InputDiscreteLanePriority);
// Should this
return runWithPriority(
UserBlockingSchedulerPriority,
fn.bind(null, a, b, c, d),
);
} finally {
setCurrentUpdateLanePriority(previousLanePriority);
executionContext = prevExecutionContext;
if (executionContext === NoContext) {
// Flush the immediate callbacks that were scheduled during this batch
@ -1166,13 +1208,16 @@ export function flushSync<A, R>(fn: A => R, a: A): R {
return fn(a);
}
executionContext |= BatchedContext;
const previousLanePriority = getCurrentUpdateLanePriority();
try {
setCurrentUpdateLanePriority(SyncLanePriority);
if (fn) {
return runWithPriority(ImmediateSchedulerPriority, fn.bind(null, a));
} else {
return (undefined: $FlowFixMe);
}
} finally {
setCurrentUpdateLanePriority(previousLanePriority);
executionContext = prevExecutionContext;
// Flush the immediate callbacks that were scheduled during this batch.
// Note that this will happen even if batchedUpdates is higher up
@ -1184,9 +1229,12 @@ export function flushSync<A, R>(fn: A => R, a: A): R {
export function flushControlled(fn: () => mixed): void {
const prevExecutionContext = executionContext;
executionContext |= BatchedContext;
const previousLanePriority = getCurrentUpdateLanePriority();
try {
setCurrentUpdateLanePriority(SyncLanePriority);
runWithPriority(ImmediateSchedulerPriority, fn);
} finally {
setCurrentUpdateLanePriority(previousLanePriority);
executionContext = prevExecutionContext;
if (executionContext === NoContext) {
// Flush the immediate callbacks that were scheduled during this batch
@ -1867,6 +1915,9 @@ function commitRootImpl(root, renderPriorityLevel) {
}
if (firstEffect !== null) {
const previousLanePriority = getCurrentUpdateLanePriority();
setCurrentUpdateLanePriority(SyncLanePriority);
const prevExecutionContext = executionContext;
executionContext |= CommitContext;
const prevInteractions = pushInteractions(root);
@ -1987,6 +2038,9 @@ function commitRootImpl(root, renderPriorityLevel) {
popInteractions(((prevInteractions: any): Set<Interaction>));
}
executionContext = prevExecutionContext;
// Reset the priority to the previous non-sync value.
setCurrentUpdateLanePriority(previousLanePriority);
} else {
// No effects.
root.current = finishedWork;
@ -2249,7 +2303,15 @@ export function flushPassiveEffects() {
? NormalSchedulerPriority
: pendingPassiveEffectsRenderPriority;
pendingPassiveEffectsRenderPriority = NoSchedulerPriority;
return runWithPriority(priorityLevel, flushPassiveEffectsImpl);
const previousLanePriority = getCurrentUpdateLanePriority();
try {
setCurrentUpdateLanePriority(
schedulerPriorityToLanePriority(priorityLevel),
);
return runWithPriority(priorityLevel, flushPassiveEffectsImpl);
} finally {
setCurrentUpdateLanePriority(previousLanePriority);
}
}
}

View File

@ -26,6 +26,7 @@ import {
enableSchedulerTracing,
warnAboutUnmockedScheduler,
deferRenderPhaseUpdateToNextBatch,
decoupleUpdatePriorityFromScheduler,
enableDebugTracing,
} from 'shared/ReactFeatureFlags';
import ReactSharedInternals from 'shared/ReactSharedInternals';
@ -124,6 +125,7 @@ import {
InputDiscreteLanePriority,
TransitionShortLanePriority,
TransitionLongLanePriority,
DefaultLanePriority,
NoLanes,
NoLane,
SyncLane,
@ -141,6 +143,8 @@ import {
hasUpdatePriority,
getNextLanes,
returnNextLanesPriority,
setCurrentUpdateLanePriority,
getCurrentUpdateLanePriority,
markStarvedLanesAsExpired,
getLanesToRetrySynchronouslyOnError,
markRootUpdated,
@ -405,7 +409,6 @@ export function requestUpdateLane(
currentEventWipLanes = workInProgressRootIncludedLanes;
}
let lane;
if (suspenseConfig !== null) {
// Use the size of the timeout as a heuristic to prioritize shorter
// transitions over longer ones.
@ -423,26 +426,56 @@ export function requestUpdateLane(
: NoLanes;
}
lane = findTransitionLane(
return findTransitionLane(
transitionLanePriority,
currentEventWipLanes,
currentEventPendingLanes,
);
} else {
// TODO: If we're not inside `runWithPriority`, this returns the priority
// of the currently running task. That's probably not what we want.
const schedulerPriority = getCurrentPriorityLevel();
}
if (
// TODO: Temporary. We're removing the concept of discrete updates.
(executionContext & DiscreteEventContext) !== NoContext &&
schedulerPriority === UserBlockingSchedulerPriority
) {
lane = findUpdateLane(InputDiscreteLanePriority, currentEventWipLanes);
} else {
const lanePriority = schedulerPriorityToLanePriority(schedulerPriority);
lane = findUpdateLane(lanePriority, currentEventWipLanes);
// TODO: Remove this dependency on the Scheduler priority.
// To do that, we're replacing it with an update lane priority.
const schedulerPriority = getCurrentPriorityLevel();
// The old behavior was using the priority level of the Scheduler.
// This couples React to the Scheduler internals, so we're replacing it
// with the currentUpdateLanePriority above. As an example of how this
// could be problematic, if we're not inside `Scheduler.runWithPriority`,
// then we'll get the priority of the current running Scheduler task,
// which is probably not what we want.
let lane;
if (
// TODO: Temporary. We're removing the concept of discrete updates.
(executionContext & DiscreteEventContext) !== NoContext &&
schedulerPriority === UserBlockingSchedulerPriority
) {
lane = findUpdateLane(InputDiscreteLanePriority, currentEventWipLanes);
} else {
const schedulerLanePriority = schedulerPriorityToLanePriority(
schedulerPriority,
);
if (decoupleUpdatePriorityFromScheduler) {
// In the new strategy, we will track the current update lane priority
// inside React and use that priority to select a lane for this update.
// For now, we're just logging when they're different so we can assess.
const currentUpdateLanePriority = getCurrentUpdateLanePriority();
if (
schedulerLanePriority !== currentUpdateLanePriority &&
currentUpdateLanePriority !== NoLanePriority
) {
if (__DEV__) {
console.error(
'Expected current scheduler lane priority %s to match current update lane priority %s',
schedulerLanePriority,
currentUpdateLanePriority,
);
}
}
}
lane = findUpdateLane(schedulerLanePriority, currentEventWipLanes);
}
return lane;
@ -1079,7 +1112,13 @@ export function flushDiscreteUpdates() {
export function deferredUpdates<A>(fn: () => A): A {
// TODO: Remove in favor of Scheduler.next
return runWithPriority(NormalSchedulerPriority, fn);
const previousLanePriority = getCurrentUpdateLanePriority();
try {
setCurrentUpdateLanePriority(DefaultLanePriority);
return runWithPriority(NormalSchedulerPriority, fn);
} finally {
setCurrentUpdateLanePriority(previousLanePriority);
}
}
function flushPendingDiscreteUpdates() {
@ -1134,13 +1173,16 @@ export function discreteUpdates<A, B, C, D, R>(
): R {
const prevExecutionContext = executionContext;
executionContext |= DiscreteEventContext;
const previousLanePriority = getCurrentUpdateLanePriority();
try {
setCurrentUpdateLanePriority(InputDiscreteLanePriority);
// Should this
return runWithPriority(
UserBlockingSchedulerPriority,
fn.bind(null, a, b, c, d),
);
} finally {
setCurrentUpdateLanePriority(previousLanePriority);
executionContext = prevExecutionContext;
if (executionContext === NoContext) {
// Flush the immediate callbacks that were scheduled during this batch
@ -1177,13 +1219,16 @@ export function flushSync<A, R>(fn: A => R, a: A): R {
return fn(a);
}
executionContext |= BatchedContext;
const previousLanePriority = getCurrentUpdateLanePriority();
try {
setCurrentUpdateLanePriority(SyncLanePriority);
if (fn) {
return runWithPriority(ImmediateSchedulerPriority, fn.bind(null, a));
} else {
return (undefined: $FlowFixMe);
}
} finally {
setCurrentUpdateLanePriority(previousLanePriority);
executionContext = prevExecutionContext;
// Flush the immediate callbacks that were scheduled during this batch.
// Note that this will happen even if batchedUpdates is higher up
@ -1195,9 +1240,12 @@ export function flushSync<A, R>(fn: A => R, a: A): R {
export function flushControlled(fn: () => mixed): void {
const prevExecutionContext = executionContext;
executionContext |= BatchedContext;
const previousLanePriority = getCurrentUpdateLanePriority();
try {
setCurrentUpdateLanePriority(SyncLanePriority);
runWithPriority(ImmediateSchedulerPriority, fn);
} finally {
setCurrentUpdateLanePriority(previousLanePriority);
executionContext = prevExecutionContext;
if (executionContext === NoContext) {
// Flush the immediate callbacks that were scheduled during this batch
@ -1915,6 +1963,9 @@ function commitRootImpl(root, renderPriorityLevel) {
}
if (firstEffect !== null) {
const previousLanePriority = getCurrentUpdateLanePriority();
setCurrentUpdateLanePriority(SyncLanePriority);
const prevExecutionContext = executionContext;
executionContext |= CommitContext;
const prevInteractions = pushInteractions(root);
@ -2035,6 +2086,9 @@ function commitRootImpl(root, renderPriorityLevel) {
popInteractions(((prevInteractions: any): Set<Interaction>));
}
executionContext = prevExecutionContext;
// Reset the priority to the previous non-sync value.
setCurrentUpdateLanePriority(previousLanePriority);
} else {
// No effects.
root.current = finishedWork;
@ -2321,7 +2375,15 @@ export function flushPassiveEffects() {
? NormalSchedulerPriority
: pendingPassiveEffectsRenderPriority;
pendingPassiveEffectsRenderPriority = NoSchedulerPriority;
return runWithPriority(priorityLevel, flushPassiveEffectsImpl);
const previousLanePriority = getCurrentUpdateLanePriority();
try {
setCurrentUpdateLanePriority(
schedulerPriorityToLanePriority(priorityLevel),
);
return runWithPriority(priorityLevel, flushPassiveEffectsImpl);
} finally {
setCurrentUpdateLanePriority(previousLanePriority);
}
}
}

View File

@ -15,6 +15,11 @@ import * as Scheduler from 'scheduler';
import {__interactionsRef} from 'scheduler/tracing';
import {enableSchedulerTracing} from 'shared/ReactFeatureFlags';
import invariant from 'shared/invariant';
import {
SyncLanePriority,
getCurrentUpdateLanePriority,
setCurrentUpdateLanePriority,
} from './ReactFiberLane';
const {
unstable_runWithPriority: Scheduler_runWithPriority,
@ -171,9 +176,11 @@ function flushSyncCallbackQueueImpl() {
// Prevent re-entrancy.
isFlushingSyncQueue = true;
let i = 0;
const previousLanePriority = getCurrentUpdateLanePriority();
try {
const isSync = true;
const queue = syncQueue;
setCurrentUpdateLanePriority(SyncLanePriority);
runWithPriority(ImmediatePriority, () => {
for (; i < queue.length; i++) {
let callback = queue[i];
@ -195,6 +202,7 @@ function flushSyncCallbackQueueImpl() {
);
throw error;
} finally {
setCurrentUpdateLanePriority(previousLanePriority);
isFlushingSyncQueue = false;
}
}

View File

@ -15,6 +15,11 @@ import * as Scheduler from 'scheduler';
import {__interactionsRef} from 'scheduler/tracing';
import {enableSchedulerTracing} from 'shared/ReactFeatureFlags';
import invariant from 'shared/invariant';
import {
SyncLanePriority,
getCurrentUpdateLanePriority,
setCurrentUpdateLanePriority,
} from './ReactFiberLane';
const {
unstable_runWithPriority: Scheduler_runWithPriority,
@ -171,9 +176,11 @@ function flushSyncCallbackQueueImpl() {
// Prevent re-entrancy.
isFlushingSyncQueue = true;
let i = 0;
const previousLanePriority = getCurrentUpdateLanePriority();
try {
const isSync = true;
const queue = syncQueue;
setCurrentUpdateLanePriority(SyncLanePriority);
runWithPriority(ImmediatePriority, () => {
for (; i < queue.length; i++) {
let callback = queue[i];
@ -195,6 +202,7 @@ function flushSyncCallbackQueueImpl() {
);
throw error;
} finally {
setCurrentUpdateLanePriority(previousLanePriority);
isFlushingSyncQueue = false;
}
}

View File

@ -516,11 +516,16 @@ describe('ReactIncrementalUpdates', () => {
Scheduler.unstable_yieldValue('Committed: ' + log);
if (log === 'B') {
// Right after B commits, schedule additional updates.
Scheduler.unstable_runWithPriority(
// TODO: Double wrapping is temporary while we remove Scheduler runWithPriority.
ReactNoop.unstable_runWithPriority(
Scheduler.unstable_UserBlockingPriority,
() => {
pushToLog('C');
},
() =>
Scheduler.unstable_runWithPriority(
Scheduler.unstable_UserBlockingPriority,
() => {
pushToLog('C');
},
),
);
setLog(prevLog => prevLog + 'D');
}
@ -538,11 +543,17 @@ describe('ReactIncrementalUpdates', () => {
await ReactNoop.act(async () => {
pushToLog('A');
Scheduler.unstable_runWithPriority(
// TODO: Double wrapping is temporary while we remove Scheduler runWithPriority.
ReactNoop.unstable_runWithPriority(
Scheduler.unstable_UserBlockingPriority,
() => {
pushToLog('B');
},
() =>
Scheduler.unstable_runWithPriority(
Scheduler.unstable_UserBlockingPriority,
() => {
pushToLog('B');
},
),
);
});
expect(Scheduler).toHaveYielded([
@ -574,11 +585,16 @@ describe('ReactIncrementalUpdates', () => {
Scheduler.unstable_yieldValue('Committed: ' + this.state.log);
if (this.state.log === 'B') {
// Right after B commits, schedule additional updates.
Scheduler.unstable_runWithPriority(
// TODO: Double wrapping is temporary while we remove Scheduler runWithPriority.
ReactNoop.unstable_runWithPriority(
Scheduler.unstable_UserBlockingPriority,
() => {
this.pushToLog('C');
},
() =>
Scheduler.unstable_runWithPriority(
Scheduler.unstable_UserBlockingPriority,
() => {
this.pushToLog('C');
},
),
);
this.pushToLog('D');
}
@ -598,11 +614,16 @@ describe('ReactIncrementalUpdates', () => {
await ReactNoop.act(async () => {
pushToLog('A');
Scheduler.unstable_runWithPriority(
// TODO: Double wrapping is temporary while we remove Scheduler runWithPriority.
ReactNoop.unstable_runWithPriority(
Scheduler.unstable_UserBlockingPriority,
() => {
pushToLog('B');
},
() =>
Scheduler.unstable_runWithPriority(
Scheduler.unstable_UserBlockingPriority,
() => {
pushToLog('B');
},
),
);
});
expect(Scheduler).toHaveYielded([

View File

@ -232,9 +232,14 @@ describe('ReactDOMTracing', () => {
Scheduler.unstable_yieldValue('Child:update');
} else {
Scheduler.unstable_yieldValue('Child:mount');
Scheduler.unstable_runWithPriority(
// TODO: Double wrapping is temporary while we remove Scheduler runWithPriority.
ReactDOM.unstable_runWithPriority(
Scheduler.unstable_IdlePriority,
() => setDidMount(true),
() =>
Scheduler.unstable_runWithPriority(
Scheduler.unstable_IdlePriority,
() => setDidMount(true),
),
);
}
}, [didMount]);

View File

@ -120,3 +120,6 @@ export const enableLegacyFBSupport = false;
// interleaved event. Remove this flag once we have migrated to the
// new behavior.
export const deferRenderPhaseUpdateToNextBatch = true;
// Replacement for runWithPriority in React internals.
export const decoupleUpdatePriorityFromScheduler = false;

View File

@ -46,6 +46,7 @@ export const enableFilterEmptyStringAttributesDOM = false;
export const enableNewReconciler = false;
export const deferRenderPhaseUpdateToNextBatch = true;
export const decoupleUpdatePriorityFromScheduler = false;
// Flow magic to verify the exports of this file match the original version.
// eslint-disable-next-line no-unused-vars

View File

@ -45,6 +45,7 @@ export const enableFilterEmptyStringAttributesDOM = false;
export const enableNewReconciler = false;
export const deferRenderPhaseUpdateToNextBatch = true;
export const decoupleUpdatePriorityFromScheduler = false;
// Flow magic to verify the exports of this file match the original version.
// eslint-disable-next-line no-unused-vars

View File

@ -45,6 +45,7 @@ export const enableFilterEmptyStringAttributesDOM = false;
export const enableNewReconciler = false;
export const deferRenderPhaseUpdateToNextBatch = true;
export const decoupleUpdatePriorityFromScheduler = false;
// Flow magic to verify the exports of this file match the original version.
// eslint-disable-next-line no-unused-vars

View File

@ -45,6 +45,7 @@ export const enableFilterEmptyStringAttributesDOM = false;
export const enableNewReconciler = false;
export const deferRenderPhaseUpdateToNextBatch = true;
export const decoupleUpdatePriorityFromScheduler = false;
// Flow magic to verify the exports of this file match the original version.
// eslint-disable-next-line no-unused-vars

View File

@ -45,6 +45,7 @@ export const enableFilterEmptyStringAttributesDOM = false;
export const enableNewReconciler = false;
export const deferRenderPhaseUpdateToNextBatch = true;
export const decoupleUpdatePriorityFromScheduler = false;
// Flow magic to verify the exports of this file match the original version.
// eslint-disable-next-line no-unused-vars

View File

@ -45,6 +45,7 @@ export const enableFilterEmptyStringAttributesDOM = false;
export const enableNewReconciler = false;
export const deferRenderPhaseUpdateToNextBatch = true;
export const decoupleUpdatePriorityFromScheduler = false;
// Flow magic to verify the exports of this file match the original version.
// eslint-disable-next-line no-unused-vars

View File

@ -18,6 +18,7 @@ export const disableInputAttributeSyncing = __VARIANT__;
export const enableFilterEmptyStringAttributesDOM = __VARIANT__;
export const enableLegacyFBSupport = __VARIANT__;
export const enableDebugTracing = !__VARIANT__;
export const decoupleUpdatePriorityFromScheduler = __VARIANT__;
// This only has an effect in the new reconciler. But also, the new reconciler
// is only enabled when __VARIANT__ is true. So this is set to the opposite of

View File

@ -75,6 +75,7 @@ export const warnUnstableRenderSubtreeIntoContainer = false;
// don't have to add another test dimension. The build system will compile this
// to the correct value.
export const enableNewReconciler = __VARIANT__;
export const decoupleUpdatePriorityFromScheduler = __VARIANT__;
// TODO: This does not currently exist in the new reconciler fork.
export const enableDebugTracing = !__VARIANT__;