@samitouri / QOS-React / commits / 21f6d4145f

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