Skip to content

Commit defffdb

Browse files
authored
[Fiber] Don't work on scheduled tasks while we're in an async commit but flush it eagerly if we're sync (#31987)
This is a follow up to #31930 and a prerequisite for #31975. With View Transitions, the commit phase becomes async which means that other work can sneak in between. We need to be resilient to that. This PR first refactors the flushMutationEffects and flushLayoutEffects to use module scope variables to track its arguments so we can defer them. It shares these with how we were already doing it for flushPendingEffects. We also track how far along the commit phase we are so we know what we have left to flush. Then callers of flushPassiveEffects become flushPendingEffects. That helper synchronously flushes any remaining phases we've yet to commit. That ensure that things are at least consistent if that happens. Finally, when we are using a scheduled task, we don't do any work. This ensures that we're not flushing any work too early if we could've deferred it. This still ensures that we always do flush it before starting any new work on any root so new roots observe the committed state. There are some unfortunate effects that could happen from allowing things to flush eagerly. Such as if a flushSync sneaks in before startViewTransition, it'll skip the animation. If it's during a suspensey font it'll start the transition before the font has loaded which might be better than breaking flushSync. It'll also potentially flush passive effects inside the startViewTransition which should typically be ok.
1 parent 3ce77d5 commit defffdb

File tree

4 files changed

+150
-105
lines changed

4 files changed

+150
-105
lines changed

packages/react-reconciler/src/ReactFiberHotReloading.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import type {ReactNodeList} from 'shared/ReactTypes';
1616
import {
1717
flushSyncWork,
1818
scheduleUpdateOnFiber,
19-
flushPassiveEffects,
19+
flushPendingEffects,
2020
} from './ReactFiberWorkLoop';
2121
import {enqueueConcurrentRenderForLane} from './ReactFiberConcurrentUpdates';
2222
import {updateContainerSync} from './ReactFiberReconciler';
@@ -229,7 +229,7 @@ export const scheduleRefresh: ScheduleRefresh = (
229229
return;
230230
}
231231
const {staleFamilies, updatedFamilies} = update;
232-
flushPassiveEffects();
232+
flushPendingEffects();
233233
scheduleFibersWithFamiliesRecursively(
234234
root.current,
235235
updatedFamilies,

packages/react-reconciler/src/ReactFiberReconciler.js

+5-4
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import isArray from 'shared/isArray';
4141
import {
4242
enableSchedulingProfiler,
4343
enableHydrationLaneScheduling,
44+
disableLegacyMode,
4445
} from 'shared/ReactFeatureFlags';
4546
import ReactSharedInternals from 'shared/ReactSharedInternals';
4647
import {
@@ -75,7 +76,7 @@ import {
7576
isAlreadyRendering,
7677
deferredUpdates,
7778
discreteUpdates,
78-
flushPassiveEffects,
79+
flushPendingEffects,
7980
} from './ReactFiberWorkLoop';
8081
import {enqueueConcurrentRenderForLane} from './ReactFiberConcurrentUpdates';
8182
import {
@@ -364,8 +365,8 @@ export function updateContainerSync(
364365
parentComponent: ?React$Component<any, any>,
365366
callback: ?Function,
366367
): Lane {
367-
if (container.tag === LegacyRoot) {
368-
flushPassiveEffects();
368+
if (!disableLegacyMode && container.tag === LegacyRoot) {
369+
flushPendingEffects();
369370
}
370371
const current = container.current;
371372
updateContainerImpl(
@@ -453,7 +454,7 @@ export {
453454
flushSyncFromReconciler,
454455
flushSyncWork,
455456
isAlreadyRendering,
456-
flushPassiveEffects,
457+
flushPendingEffects as flushPassiveEffects,
457458
};
458459

459460
export function getPublicRootInstance(

packages/react-reconciler/src/ReactFiberRootScheduler.js

+17-3
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,13 @@ import {
3838
CommitContext,
3939
NoContext,
4040
RenderContext,
41-
flushPassiveEffects,
41+
flushPendingEffects,
4242
getExecutionContext,
4343
getWorkInProgressRoot,
4444
getWorkInProgressRootRenderLanes,
4545
getRootWithPendingPassiveEffects,
4646
getPendingPassiveEffectsLanes,
47+
hasPendingCommitEffects,
4748
isWorkLoopSuspendedOnData,
4849
performWorkOnRoot,
4950
} from './ReactFiberWorkLoop';
@@ -466,10 +467,23 @@ function performWorkOnRootViaSchedulerTask(
466467
trackSchedulerEvent();
467468
}
468469

470+
if (hasPendingCommitEffects()) {
471+
// We are currently in the middle of an async committing (such as a View Transition).
472+
// We could force these to flush eagerly but it's better to defer any work until
473+
// it finishes. This may not be the same root as we're waiting on.
474+
// TODO: This relies on the commit eventually calling ensureRootIsScheduled which
475+
// always calls processRootScheduleInMicrotask which in turn always loops through
476+
// all the roots to figure out. This is all a bit inefficient and if optimized
477+
// it'll need to consider rescheduling a task for any skipped roots.
478+
root.callbackNode = null;
479+
root.callbackPriority = NoLane;
480+
return null;
481+
}
482+
469483
// Flush any pending passive effects before deciding which lanes to work on,
470484
// in case they schedule additional work.
471485
const originalCallbackNode = root.callbackNode;
472-
const didFlushPassiveEffects = flushPassiveEffects();
486+
const didFlushPassiveEffects = flushPendingEffects(true);
473487
if (didFlushPassiveEffects) {
474488
// Something in the passive effect phase may have canceled the current task.
475489
// Check if the task node for this root was changed.
@@ -534,7 +548,7 @@ function performWorkOnRootViaSchedulerTask(
534548
function performSyncWorkOnRoot(root: FiberRoot, lanes: Lanes) {
535549
// This is the entry point for synchronous tasks that don't go
536550
// through Scheduler.
537-
const didFlushPassiveEffects = flushPassiveEffects();
551+
const didFlushPassiveEffects = flushPendingEffects();
538552
if (didFlushPassiveEffects) {
539553
// If passive effects were flushed, exit to the outer work loop in the root
540554
// scheduler, so we can recompute the priority.

0 commit comments

Comments
 (0)