Skip to content

Commit 722bc04

Browse files
authored
Don't rely on didTimeout for SyncBatched (facebook#19469)
Tasks with SyncBatchedPriority — used by Blocking Mode — should always be rendered by the `peformSyncWorkOnRoot` path, not `performConcurrentWorkOnRoot`. Currently, they go through the `performConcurrentWorkOnRoot` callback. Then, we check `didTimeout` to see if the task expired. Since SyncBatchedPriority translates to ImmediatePriority in the Scheduler, `didTimeout` is always `true`, so we mark it as expired. Then it exits and re-enters in the `performSyncWorkOnRoot` path. Aside from being overly convoluted, we shouldn't rely on Scheduler to tell us that SyncBatchedPriority work is synchronous. We should handle that ourselves. This will allow us to remove the `didTimeout` check. And it further decouples us from the Scheduler priority, so we can eventually remove that, too.
1 parent feb134c commit 722bc04

File tree

3 files changed

+49
-9
lines changed

3 files changed

+49
-9
lines changed

packages/react-reconciler/src/ReactFiberLane.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ import {
4444
} from './SchedulerWithReactIntegration.new';
4545

4646
export const SyncLanePriority: LanePriority = 17;
47-
const SyncBatchedLanePriority: LanePriority = 16;
47+
export const SyncBatchedLanePriority: LanePriority = 16;
4848

4949
const InputDiscreteHydrationLanePriority: LanePriority = 15;
5050
export const InputDiscreteLanePriority: LanePriority = 14;

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

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ import {
144144
import {
145145
NoLanePriority,
146146
SyncLanePriority,
147+
SyncBatchedLanePriority,
147148
InputDiscreteLanePriority,
148149
TransitionShortLanePriority,
149150
TransitionLongLanePriority,
@@ -726,6 +727,11 @@ function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {
726727
newCallbackNode = scheduleSyncCallback(
727728
performSyncWorkOnRoot.bind(null, root),
728729
);
730+
} else if (newCallbackPriority === SyncBatchedLanePriority) {
731+
newCallbackNode = scheduleCallback(
732+
ImmediateSchedulerPriority,
733+
performSyncWorkOnRoot.bind(null, root),
734+
);
729735
} else {
730736
const schedulerPriorityLevel = lanePriorityToSchedulerPriority(
731737
newCallbackPriority,
@@ -756,7 +762,20 @@ function performConcurrentWorkOnRoot(root, didTimeout) {
756762

757763
// Flush any pending passive effects before deciding which lanes to work on,
758764
// in case they schedule additional work.
759-
flushPassiveEffects();
765+
const originalCallbackNode = root.callbackNode;
766+
const didFlushPassiveEffects = flushPassiveEffects();
767+
if (didFlushPassiveEffects) {
768+
// Something in the passive effect phase may have canceled the current task.
769+
// Check if the task node for this root was changed.
770+
if (root.callbackNode !== originalCallbackNode) {
771+
// The current task was canceled. Exit. We don't need to call
772+
// `ensureRootIsScheduled` because the check above implies either that
773+
// there's a new task, or that there's no remaining work on this root.
774+
return null;
775+
} else {
776+
// Current task was not canceled. Continue.
777+
}
778+
}
760779

761780
// Determine the next expiration time to work on, using the fields stored
762781
// on the root.
@@ -765,6 +784,7 @@ function performConcurrentWorkOnRoot(root, didTimeout) {
765784
root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,
766785
);
767786
if (lanes === NoLanes) {
787+
// Defensive coding. This is never expected to happen.
768788
return null;
769789
}
770790

@@ -781,8 +801,6 @@ function performConcurrentWorkOnRoot(root, didTimeout) {
781801
return null;
782802
}
783803

784-
const originalCallbackNode = root.callbackNode;
785-
786804
let exitStatus = renderRootConcurrent(root, lanes);
787805

788806
if (
@@ -2593,7 +2611,8 @@ function commitLayoutEffectsImpl(
25932611
resetCurrentDebugFiberInDEV();
25942612
}
25952613

2596-
export function flushPassiveEffects() {
2614+
export function flushPassiveEffects(): boolean {
2615+
// Returns whether passive effects were flushed.
25972616
if (pendingPassiveEffectsRenderPriority !== NoSchedulerPriority) {
25982617
const priorityLevel =
25992618
pendingPassiveEffectsRenderPriority > NormalSchedulerPriority
@@ -2610,6 +2629,7 @@ export function flushPassiveEffects() {
26102629
setCurrentUpdateLanePriority(previousLanePriority);
26112630
}
26122631
}
2632+
return false;
26132633
}
26142634

26152635
export function enqueuePendingPassiveProfilerEffect(fiber: Fiber): void {

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

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ import {
136136
import {
137137
NoLanePriority,
138138
SyncLanePriority,
139+
SyncBatchedLanePriority,
139140
InputDiscreteLanePriority,
140141
TransitionShortLanePriority,
141142
TransitionLongLanePriority,
@@ -719,6 +720,11 @@ function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {
719720
newCallbackNode = scheduleSyncCallback(
720721
performSyncWorkOnRoot.bind(null, root),
721722
);
723+
} else if (newCallbackPriority === SyncBatchedLanePriority) {
724+
newCallbackNode = scheduleCallback(
725+
ImmediateSchedulerPriority,
726+
performSyncWorkOnRoot.bind(null, root),
727+
);
722728
} else {
723729
const schedulerPriorityLevel = lanePriorityToSchedulerPriority(
724730
newCallbackPriority,
@@ -749,7 +755,20 @@ function performConcurrentWorkOnRoot(root, didTimeout) {
749755

750756
// Flush any pending passive effects before deciding which lanes to work on,
751757
// in case they schedule additional work.
752-
flushPassiveEffects();
758+
const originalCallbackNode = root.callbackNode;
759+
const didFlushPassiveEffects = flushPassiveEffects();
760+
if (didFlushPassiveEffects) {
761+
// Something in the passive effect phase may have canceled the current task.
762+
// Check if the task node for this root was changed.
763+
if (root.callbackNode !== originalCallbackNode) {
764+
// The current task was canceled. Exit. We don't need to call
765+
// `ensureRootIsScheduled` because the check above implies either that
766+
// there's a new task, or that there's no remaining work on this root.
767+
return null;
768+
} else {
769+
// Current task was not canceled. Continue.
770+
}
771+
}
753772

754773
// Determine the next expiration time to work on, using the fields stored
755774
// on the root.
@@ -758,6 +777,7 @@ function performConcurrentWorkOnRoot(root, didTimeout) {
758777
root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,
759778
);
760779
if (lanes === NoLanes) {
780+
// Defensive coding. This is never expected to happen.
761781
return null;
762782
}
763783

@@ -774,8 +794,6 @@ function performConcurrentWorkOnRoot(root, didTimeout) {
774794
return null;
775795
}
776796

777-
const originalCallbackNode = root.callbackNode;
778-
779797
let exitStatus = renderRootConcurrent(root, lanes);
780798

781799
if (
@@ -2437,7 +2455,8 @@ function commitLayoutEffects(root: FiberRoot, committedLanes: Lanes) {
24372455
}
24382456
}
24392457

2440-
export function flushPassiveEffects() {
2458+
export function flushPassiveEffects(): boolean {
2459+
// Returns whether passive effects were flushed.
24412460
if (pendingPassiveEffectsRenderPriority !== NoSchedulerPriority) {
24422461
const priorityLevel =
24432462
pendingPassiveEffectsRenderPriority > NormalSchedulerPriority
@@ -2454,6 +2473,7 @@ export function flushPassiveEffects() {
24542473
setCurrentUpdateLanePriority(previousLanePriority);
24552474
}
24562475
}
2476+
return false;
24572477
}
24582478

24592479
export function enqueuePendingPassiveProfilerEffect(fiber: Fiber): void {

0 commit comments

Comments
 (0)