@samitouri / QOS-React-2 / commits / bc9818f24d

Scheduler.unstable_next (#14756)

* Add Scheduler.unstable_next * Use Scheduler to prioritize updates Changes the implementation of syncUpdates, deferredUpdates, and interactiveUpdates to use runWithPriority, so This is the minimum integration between Scheduler and React needed to unblock use of the Scheduler.next API. * Add Scheduler.unstable_next * Use Scheduler to prioritize updates Changes the implementation of syncUpdates, deferredUpdates, and interactiveUpdates to use runWithPriority, so This is the minimum integration between Scheduler and React needed to unblock use of the Scheduler.next API.

Andrew Clark committed Feb 6, 2019 at 08:16 UTC bc9818f24d55e681aea39ecae59b8f544318169d
8 files changed +166 -78
packages/react-cache/src/__tests__/ReactCache-test.internal.js
+22
@@ -24,6 +24,8 @@ describe('ReactCache', () => {
24 beforeEach(() => {
25 jest.resetModules();
26
27 + let currentPriorityLevel = 3;
28 +
29 jest.mock('scheduler', () => {
30 let callbacks = [];
31 return {
@@ -38,6 +40,26 @@ describe('ReactCache', () => {
40 callback();
41 }
42 },
43 +
44 + unstable_ImmediatePriority: 1,
45 + unstable_UserBlockingPriority: 2,
46 + unstable_NormalPriority: 3,
47 + unstable_LowPriority: 4,
48 + unstable_IdlePriority: 5,
49 +
50 + unstable_runWithPriority(priorityLevel, fn) {
51 + const prevPriorityLevel = currentPriorityLevel;
52 + currentPriorityLevel = priorityLevel;
53 + try {
54 + return fn();
55 + } finally {
56 + currentPriorityLevel = prevPriorityLevel;
57 + }
58 + },
59 +
60 + unstable_getCurrentPriorityLevel() {
61 + return currentPriorityLevel;
62 + },
63 };
64 });
65
packages/react-reconciler/src/ReactFiberScheduler.js
+70 -78
@@ -15,8 +15,18 @@ import type {Interaction} from 'scheduler/src/Tracing';
15 import {
16 __interactionsRef,
17 __subscriberRef,
18 - unstable_wrap as Schedule_tracing_wrap,
18 + unstable_wrap as Scheduler_tracing_wrap,
19 } from 'scheduler/tracing';
20 +import {
21 + unstable_next as Scheduler_next,
22 + unstable_getCurrentPriorityLevel as getCurrentPriorityLevel,
23 + unstable_runWithPriority as runWithPriority,
24 + unstable_ImmediatePriority as ImmediatePriority,
25 + unstable_UserBlockingPriority as UserBlockingPriority,
26 + unstable_NormalPriority as NormalPriority,
27 + unstable_LowPriority as LowPriority,
28 + unstable_IdlePriority as IdlePriority,
29 +} from 'scheduler';
30 import {
31 invokeGuardedCallback,
32 hasCaughtError,
@@ -122,7 +132,7 @@ import {
132 computeAsyncExpiration,
133 computeInteractiveExpiration,
134 } from './ReactFiberExpirationTime';
125 -import {ConcurrentMode, ProfileMode} from './ReactTypeOfMode';
135 +import {ConcurrentMode, ProfileMode, NoContext} from './ReactTypeOfMode';
136 import {enqueueUpdate, resetCurrentlyProcessingQueue} from './ReactUpdateQueue';
137 import {createCapturedValue} from './ReactCapturedValue';
138 import {
@@ -242,11 +252,6 @@ if (__DEV__) {
252 // Used to ensure computeUniqueAsyncExpiration is monotonically decreasing.
253 let lastUniqueAsyncExpiration: number = Sync - 1;
254
245 -// Represents the expiration time that incoming updates should use. (If this
246 -// is NoWork, use the default strategy: async updates in async mode, sync
247 -// updates in sync mode.)
248 -let expirationContext: ExpirationTime = NoWork;
249 -
255 let isWorking: boolean = false;
256
257 // The next work in progress fiber that we're currently working on.
@@ -793,9 +798,11 @@ function commitRoot(root: FiberRoot, finishedWork: Fiber): void {
798 // TODO: Avoid this extra callback by mutating the tracing ref directly,
799 // like we do at the beginning of commitRoot. I've opted not to do that
800 // here because that code is still in flux.
796 - callback = Schedule_tracing_wrap(callback);
801 + callback = Scheduler_tracing_wrap(callback);
802 }
798 - passiveEffectCallbackHandle = schedulePassiveEffects(callback);
803 + passiveEffectCallbackHandle = runWithPriority(NormalPriority, () => {
804 + return schedulePassiveEffects(callback);
805 + });
806 passiveEffectCallback = callback;
807 }
808
@@ -1579,52 +1586,58 @@ function computeUniqueAsyncExpiration(): ExpirationTime {
1586 }
1587
1588 function computeExpirationForFiber(currentTime: ExpirationTime, fiber: Fiber) {
1589 + const priorityLevel = getCurrentPriorityLevel();
1590 +
1591 let expirationTime;
1583 - if (expirationContext !== NoWork) {
1584 - // An explicit expiration context was set;
1585 - expirationTime = expirationContext;
1586 - } else if (isWorking) {
1587 - if (isCommitting) {
1588 - // Updates that occur during the commit phase should have sync priority
1589 - // by default.
1590 - expirationTime = Sync;
1591 - } else {
1592 - // Updates during the render phase should expire at the same time as
1593 - // the work that is being rendered.
1594 - expirationTime = nextRenderExpirationTime;
1595 - }
1592 + if ((fiber.mode & ConcurrentMode) === NoContext) {
1593 + // Outside of concurrent mode, updates are always synchronous.
1594 + expirationTime = Sync;
1595 + } else if (isWorking && !isCommitting) {
1596 + // During render phase, updates expire during as the current render.
1597 + expirationTime = nextRenderExpirationTime;
1598 } else {
1597 - // No explicit expiration context was set, and we're not currently
1598 - // performing work. Calculate a new expiration time.
1599 - if (fiber.mode & ConcurrentMode) {
1600 - if (isBatchingInteractiveUpdates) {
1601 - // This is an interactive update
1599 + switch (priorityLevel) {
1600 + case ImmediatePriority:
1601 + expirationTime = Sync;
1602 + break;
1603 + case UserBlockingPriority:
1604 expirationTime = computeInteractiveExpiration(currentTime);
1603 - } else {
1604 - // This is an async update
1605 + break;
1606 + case NormalPriority:
1607 + // This is a normal, concurrent update
1608 expirationTime = computeAsyncExpiration(currentTime);
1606 - }
1607 - // If we're in the middle of rendering a tree, do not update at the same
1608 - // expiration time that is already rendering.
1609 - if (nextRoot !== null && expirationTime === nextRenderExpirationTime) {
1610 - expirationTime -= 1;
1611 - }
1612 - } else {
1613 - // This is a sync update
1614 - expirationTime = Sync;
1609 + break;
1610 + case LowPriority:
1611 + case IdlePriority:
1612 + expirationTime = Never;
1613 + break;
1614 + default:
1615 + invariant(
1616 + false,
1617 + 'Unknown priority level. This error is likely caused by a bug in ' +
1618 + 'React. Please file an issue.',
1619 + );
1620 }
1616 - }
1617 - if (isBatchingInteractiveUpdates) {
1618 - // This is an interactive update. Keep track of the lowest pending
1619 - // interactive expiration time. This allows us to synchronously flush
1620 - // all interactive updates when needed.
1621 - if (
1622 - lowestPriorityPendingInteractiveExpirationTime === NoWork ||
1623 - expirationTime < lowestPriorityPendingInteractiveExpirationTime
1624 - ) {
1625 - lowestPriorityPendingInteractiveExpirationTime = expirationTime;
1621 +
1622 + // If we're in the middle of rendering a tree, do not update at the same
1623 + // expiration time that is already rendering.
1624 + if (nextRoot !== null && expirationTime === nextRenderExpirationTime) {
1625 + expirationTime -= 1;
1626 }
1627 }
1628 +
1629 + // Keep track of the lowest pending interactive expiration time. This
1630 + // allows us to synchronously flush all interactive updates
1631 + // when needed.
1632 + // TODO: Move this to renderer?
1633 + if (
1634 + priorityLevel === UserBlockingPriority &&
1635 + (lowestPriorityPendingInteractiveExpirationTime === NoWork ||
1636 + expirationTime < lowestPriorityPendingInteractiveExpirationTime)
1637 + ) {
1638 + lowestPriorityPendingInteractiveExpirationTime = expirationTime;
1639 + }
1640 +
1641 return expirationTime;
1642 }
1643
@@ -1862,20 +1875,6 @@ function scheduleWork(fiber: Fiber, expirationTime: ExpirationTime) {
1875 }
1876 }
1877
1865 -function deferredUpdates<A>(fn: () => A): A {
1866 - const currentTime = requestCurrentTime();
1867 - const previousExpirationContext = expirationContext;
1868 - const previousIsBatchingInteractiveUpdates = isBatchingInteractiveUpdates;
1869 - expirationContext = computeAsyncExpiration(currentTime);
1870 - isBatchingInteractiveUpdates = false;
1871 - try {
1872 - return fn();
1873 - } finally {
1874 - expirationContext = previousExpirationContext;
1875 - isBatchingInteractiveUpdates = previousIsBatchingInteractiveUpdates;
1876 - }
1877 -}
1878 -
1878 function syncUpdates<A, B, C0, D, R>(
1879 fn: (A, B, C0, D) => R,
1880 a: A,
@@ -1883,13 +1882,9 @@ function syncUpdates<A, B, C0, D, R>(
1882 c: C0,
1883 d: D,
1884 ): R {
1886 - const previousExpirationContext = expirationContext;
1887 - expirationContext = Sync;
1888 - try {
1885 + return runWithPriority(ImmediatePriority, () => {
1886 return fn(a, b, c, d);
1890 - } finally {
1891 - expirationContext = previousExpirationContext;
1892 - }
1887 + });
1888 }
1889
1890 // TODO: Everything below this is written as if it has been lifted to the
@@ -1910,7 +1905,6 @@ let unhandledError: mixed | null = null;
1905
1906 let isBatchingUpdates: boolean = false;
1907 let isUnbatchingUpdates: boolean = false;
1913 -let isBatchingInteractiveUpdates: boolean = false;
1908
1909 let completedBatches: Array<Batch> | null = null;
1910
@@ -2441,7 +2435,9 @@ function completeRoot(
2435 lastCommittedRootDuringThisBatch = root;
2436 nestedUpdateCount = 0;
2437 }
2444 - commitRoot(root, finishedWork);
2438 + runWithPriority(ImmediatePriority, () => {
2439 + commitRoot(root, finishedWork);
2440 + });
2441 }
2442
2443 function onUncaughtError(error: mixed) {
@@ -2507,9 +2503,6 @@ function flushSync<A, R>(fn: (a: A) => R, a: A): R {
2503 }
2504
2505 function interactiveUpdates<A, B, R>(fn: (A, B) => R, a: A, b: B): R {
2510 - if (isBatchingInteractiveUpdates) {
2511 - return fn(a, b);
2512 - }
2506 // If there are any pending interactive updates, synchronously flush them.
2507 // This needs to happen before we read any handlers, because the effect of
2508 // the previous event may influence which handlers are called during
@@ -2523,14 +2516,13 @@ function interactiveUpdates<A, B, R>(fn: (A, B) => R, a: A, b: B): R {
2516 performWork(lowestPriorityPendingInteractiveExpirationTime, false);
2517 lowestPriorityPendingInteractiveExpirationTime = NoWork;
2518 }
2526 - const previousIsBatchingInteractiveUpdates = isBatchingInteractiveUpdates;
2519 const previousIsBatchingUpdates = isBatchingUpdates;
2528 - isBatchingInteractiveUpdates = true;
2520 isBatchingUpdates = true;
2521 try {
2531 - return fn(a, b);
2522 + return runWithPriority(UserBlockingPriority, () => {
2523 + return fn(a, b);
2524 + });
2525 } finally {
2533 - isBatchingInteractiveUpdates = previousIsBatchingInteractiveUpdates;
2526 isBatchingUpdates = previousIsBatchingUpdates;
2527 if (!isBatchingUpdates && !isRendering) {
2528 performSyncWork();
@@ -2580,7 +2572,7 @@ export {
2572 unbatchedUpdates,
2573 flushSync,
2574 flushControlled,
2583 - deferredUpdates,
2575 + Scheduler_next as deferredUpdates,
2576 syncUpdates,
2577 interactiveUpdates,
2578 flushInteractiveUpdates,
packages/react/src/ReactSharedInternals.js
+2
@@ -12,6 +12,7 @@ import {
12 unstable_now,
13 unstable_scheduleCallback,
14 unstable_runWithPriority,
15 + unstable_next,
16 unstable_getFirstCallbackNode,
17 unstable_pauseExecution,
18 unstable_continueExecution,
@@ -53,6 +54,7 @@ if (__UMD__) {
54 unstable_now,
55 unstable_scheduleCallback,
56 unstable_runWithPriority,
57 + unstable_next,
58 unstable_wrapCallback,
59 unstable_getFirstCallbackNode,
60 unstable_pauseExecution,
packages/scheduler/npm/umd/scheduler.development.js
+8
@@ -54,6 +54,13 @@
54 );
55 }
56
57 + function unstable_next() {
58 + return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Scheduler.unstable_next.apply(
59 + this,
60 + arguments
61 + );
62 + }
63 +
64 function unstable_wrapCallback() {
65 return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Scheduler.unstable_wrapCallback.apply(
66 this,
@@ -95,6 +102,7 @@
102 unstable_cancelCallback: unstable_cancelCallback,
103 unstable_shouldYield: unstable_shouldYield,
104 unstable_runWithPriority: unstable_runWithPriority,
105 + unstable_next: unstable_next,
106 unstable_wrapCallback: unstable_wrapCallback,
107 unstable_getCurrentPriorityLevel: unstable_getCurrentPriorityLevel,
108 unstable_continueExecution: unstable_continueExecution,
packages/scheduler/npm/umd/scheduler.production.min.js
+8
@@ -54,6 +54,13 @@
54 );
55 }
56
57 + function unstable_next() {
58 + return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Scheduler.unstable_next.apply(
59 + this,
60 + arguments
61 + );
62 + }
63 +
64 function unstable_wrapCallback() {
65 return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Scheduler.unstable_wrapCallback.apply(
66 this,
@@ -89,6 +96,7 @@
96 unstable_cancelCallback: unstable_cancelCallback,
97 unstable_shouldYield: unstable_shouldYield,
98 unstable_runWithPriority: unstable_runWithPriority,
99 + unstable_next: unstable_next,
100 unstable_wrapCallback: unstable_wrapCallback,
101 unstable_getCurrentPriorityLevel: unstable_getCurrentPriorityLevel,
102 unstable_continueExecution: unstable_continueExecution,
packages/scheduler/npm/umd/scheduler.profiling.min.js
+8
@@ -54,6 +54,13 @@
54 );
55 }
56
57 + function unstable_next() {
58 + return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Scheduler.unstable_next.apply(
59 + this,
60 + arguments
61 + );
62 + }
63 +
64 function unstable_wrapCallback() {
65 return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Scheduler.unstable_wrapCallback.apply(
66 this,
@@ -89,6 +96,7 @@
96 unstable_cancelCallback: unstable_cancelCallback,
97 unstable_shouldYield: unstable_shouldYield,
98 unstable_runWithPriority: unstable_runWithPriority,
99 + unstable_next: unstable_next,
100 unstable_wrapCallback: unstable_wrapCallback,
101 unstable_getCurrentPriorityLevel: unstable_getCurrentPriorityLevel,
102 unstable_continueExecution: unstable_continueExecution,
packages/scheduler/src/Scheduler.js
+32
@@ -264,6 +264,37 @@ function unstable_runWithPriority(priorityLevel, eventHandler) {
264 }
265 }
266
267 +function unstable_next(eventHandler) {
268 + let priorityLevel;
269 + switch (currentPriorityLevel) {
270 + case ImmediatePriority:
271 + case UserBlockingPriority:
272 + case NormalPriority:
273 + // Shift down to normal priority
274 + priorityLevel = NormalPriority;
275 + break;
276 + default:
277 + // Anything lower than normal priority should remain at the current level.
278 + priorityLevel = currentPriorityLevel;
279 + break;
280 + }
281 +
282 + var previousPriorityLevel = currentPriorityLevel;
283 + var previousEventStartTime = currentEventStartTime;
284 + currentPriorityLevel = priorityLevel;
285 + currentEventStartTime = getCurrentTime();
286 +
287 + try {
288 + return eventHandler();
289 + } finally {
290 + currentPriorityLevel = previousPriorityLevel;
291 + currentEventStartTime = previousEventStartTime;
292 +
293 + // Before exiting, flush all the immediate work that was scheduled.
294 + flushImmediateWork();
295 + }
296 +}
297 +
298 function unstable_wrapCallback(callback) {
299 var parentPriorityLevel = currentPriorityLevel;
300 return function() {
@@ -688,6 +719,7 @@ export {
719 IdlePriority as unstable_IdlePriority,
720 LowPriority as unstable_LowPriority,
721 unstable_runWithPriority,
722 + unstable_next,
723 unstable_scheduleCallback,
724 unstable_cancelCallback,
725 unstable_wrapCallback,
packages/shared/forks/Scheduler.umd.js
+16
@@ -17,8 +17,16 @@ const {
17 unstable_scheduleCallback,
18 unstable_shouldYield,
19 unstable_getFirstCallbackNode,
20 + unstable_runWithPriority,
21 + unstable_next,
22 unstable_continueExecution,
23 unstable_pauseExecution,
24 + unstable_getCurrentPriorityLevel,
25 + unstable_ImmediatePriority,
26 + unstable_UserBlockingPriority,
27 + unstable_NormalPriority,
28 + unstable_LowPriority,
29 + unstable_IdlePriority,
30 } = ReactInternals.Scheduler;
31
32 export {
@@ -27,6 +35,14 @@ export {
35 unstable_scheduleCallback,
36 unstable_shouldYield,
37 unstable_getFirstCallbackNode,
38 + unstable_runWithPriority,
39 + unstable_next,
40 unstable_continueExecution,
41 unstable_pauseExecution,
42 + unstable_getCurrentPriorityLevel,
43 + unstable_ImmediatePriority,
44 + unstable_UserBlockingPriority,
45 + unstable_NormalPriority,
46 + unstable_LowPriority,
47 + unstable_IdlePriority,
48 };