syncUpdates prevents Synchronous priority from being downgraded to Task
Typically, a sync update is downgraded to Task priority if it's scheduled within another batch of work, e.g. within a lifecycle like componentDidMount. But syncUpdates should force sync updates to not be downgraded to Task. This will cause performWork to be called recursively.
Andrew Clark committed
Dec 28, 2016 at 21:12 UTC
21f6d4145fdaace4a8e856729b3566e0aaad62d1
1 file changed
+19
-15
src/renderers/shared/fiber/ReactFiberScheduler.js
+19
-15
@@ -107,10 +107,12 @@ module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C
107
// is thrown during reconciliation.
108
let priorityContextBeforeReconciliation : PriorityLevel = NoWork;
109
110
- // Keeps track of whether we're currently in a work loop. Used to batch
111
- // nested updates.
110
+ // Keeps track of whether we're currently in a work loop.
111
let isPerformingWork : boolean = false;
112
113
+ // Keeps track of whether sync updates should be downgraded to task updates.
114
+ let shouldDeferSyncUpdates : boolean = false;
115
+
116
// The next work in progress fiber that we're currently working on.
117
let nextUnitOfWork : ?Fiber = null;
118
let nextPriorityLevel : PriorityLevel = NoWork;
@@ -678,6 +680,7 @@ module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C
680
const previousPriorityContext = priorityContext;
681
const previousPriorityContextBeforeReconciliation = priorityContextBeforeReconciliation;
682
const previousIsPerformingWork = isPerformingWork;
683
+ const previousShouldDeferSyncUpdates = shouldDeferSyncUpdates;
684
const previousNextEffect = nextEffect;
685
const previousCommitPhaseBoundaries = commitPhaseBoundaries;
686
const previousFirstUncaughtError = firstUncaughtError;
@@ -688,6 +691,7 @@ module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C
691
priorityContext = NoWork;
692
priorityContextBeforeReconciliation = NoWork;
693
isPerformingWork = true;
694
+ shouldDeferSyncUpdates = true;
695
nextEffect = null;
696
commitPhaseBoundaries = null;
697
firstUncaughtError = null;
@@ -804,6 +808,7 @@ module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C
808
priorityContext = previousPriorityContext;
809
priorityContextBeforeReconciliation = previousPriorityContextBeforeReconciliation;
810
isPerformingWork = previousIsPerformingWork;
811
+ shouldDeferSyncUpdates = previousShouldDeferSyncUpdates;
812
nextEffect = previousNextEffect;
813
commitPhaseBoundaries = previousCommitPhaseBoundaries;
814
firstUncaughtError = previousFirstUncaughtError;
@@ -1058,15 +1063,11 @@ module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C
1063
// schedule a callback to perform work later.
1064
switch (priorityLevel) {
1065
case SynchronousPriority:
1061
- // Perform work immediately
1066
performWork(SynchronousPriority);
1067
return;
1068
case TaskPriority:
1065
- // If we're already performing work, Task work will be flushed before
1066
- // exiting the current batch. So we can skip it here.
1067
- if (!isPerformingWork) {
1068
- performWork(TaskPriority);
1069
- }
1069
+ // TODO: If we're not already performing work, schedule a
1070
+ // deferred callback.
1071
return;
1072
case AnimationPriority:
1073
scheduleAnimationCallback(performAnimationWork);
@@ -1087,7 +1088,8 @@ module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C
1088
}
1089
1090
function getPriorityContext() : PriorityLevel {
1090
- if (priorityContext === SynchronousPriority && isPerformingWork) {
1091
+ // If we're in a batch, downgrade sync priority to task priority
1092
+ if (priorityContext === SynchronousPriority && shouldDeferSyncUpdates) {
1093
return TaskPriority;
1094
}
1095
return priorityContext;
@@ -1108,16 +1110,15 @@ module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C
1110
}
1111
1112
function batchedUpdates<A, R>(fn : (a: A) => R, a : A) : R {
1111
- const previousIsPerformingWork = isPerformingWork;
1112
- // Simulate that we're performing work so that sync work is batched
1113
- isPerformingWork = true;
1113
+ const previousShouldDeferSyncUpdates = shouldDeferSyncUpdates;
1114
+ shouldDeferSyncUpdates = true;
1115
try {
1116
return fn(a);
1117
} finally {
1117
- isPerformingWork = previousIsPerformingWork;
1118
- // If we're not already performing work, we need to flush any task work
1118
+ shouldDeferSyncUpdates = previousShouldDeferSyncUpdates;
1119
+ // If we're not already inside a batch, we need to flush any task work
1120
// that was created by the user-provided function.
1120
- if (!isPerformingWork) {
1121
+ if (!shouldDeferSyncUpdates) {
1122
performWork(TaskPriority);
1123
}
1124
}
@@ -1125,11 +1126,14 @@ module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C
1126
1127
function syncUpdates<A>(fn : () => A) : A {
1128
const previousPriorityContext = priorityContext;
1129
+ const previousShouldDeferSyncUpdates = shouldDeferSyncUpdates;
1130
priorityContext = SynchronousPriority;
1131
+ shouldDeferSyncUpdates = false;
1132
try {
1133
return fn();
1134
} finally {
1135
priorityContext = previousPriorityContext;
1136
+ shouldDeferSyncUpdates = previousShouldDeferSyncUpdates;
1137
}
1138
}
1139