Implement pauseExecution, continueExecution, dumpQueue for Scheduler (#14053)
* Implement pauseExecution, continueExecution, dumpQueue * Expose firstCallbackNode. Fix tests. Revert results.json * Put scheduler pausing behind a feature flag
Kevin Chavez committed
Dec 6, 2018 at 13:57 UTC
8df4d59be532e56b6513815184094f57f566df09
16 files changed
+197
-4
fixtures/scheduler/index.html
+74
-1
@@ -90,6 +90,21 @@
90
</div>
91
<div> If the counter advanced while you were away from this tab, it's correct.</div>
92
</li>
93
+ <li>
94
+ <p>Can pause execution, dump scheduled callbacks, and continue where it left off</p>
95
+ <button onClick="runTestEight()">Run Test 8</button>
96
+ <div><b>Click the button above, press "continue" to finish the test after it pauses:</b></div>
97
+ <button onClick="continueTestEight()">continue</button>
98
+ <div><b>Expected:</b></div>
99
+ <div id="test-8-expected">
100
+ </div>
101
+ <div> -------------------------------------------------</div>
102
+ <div> If the test didn't progress until you hit "continue" and </div>
103
+ <div> you see the same above and below afterwards it's correct.
104
+ <div> -------------------------------------------------</div>
105
+ <div><b>Actual:</b></div>
106
+ <div id="test-8"></div>
107
+ </li>
108
</ol>
109
<script src="../../build/node_modules/react/umd/react.development.js"></script>
110
<script src="../../build/node_modules/scheduler/umd/scheduler.development.js"></script>
@@ -98,7 +113,10 @@
113
const {
114
unstable_scheduleCallback: scheduleCallback,
115
unstable_cancelCallback: cancelCallback,
101
- unstable_now: now
116
+ unstable_now: now,
117
+ unstable_getFirstCallbackNode: getFirstCallbackNode,
118
+ unstable_pauseExecution: pauseExecution,
119
+ unstable_continueExecution: continueExecution,
120
} = Scheduler;
121
function displayTestResult(testNumber) {
122
const expectationNode = document.getElementById('test-' + testNumber + '-expected');
@@ -215,6 +233,16 @@ const expectedResults = [
233
[
234
// ... TODO
235
],
236
+ [],
237
+ [],
238
+ // Test 8
239
+ [
240
+ 'Queue size: 0.',
241
+ 'Pausing... press continue to resume.',
242
+ 'Queue size: 2.',
243
+ 'Finishing...',
244
+ 'Done!',
245
+ ],
246
];
247
function runTestOne() {
248
// Test 1
@@ -496,6 +524,51 @@ function runTestSeven() {
524
}
525
scheduleCallback(incrementCounterAndScheduleNextCallback);
526
}
527
+
528
+function runTestEight() {
529
+ // Test 8
530
+ // Pauses execution, dumps the queue, and continues execution
531
+ clearTestResult(8);
532
+
533
+ function countNodesInStack(firstCallbackNode) {
534
+ var node = firstCallbackNode;
535
+ var count = 0;
536
+ if (node !== null) {
537
+ do {
538
+ count = count + 1;
539
+ node = node.next;
540
+ } while (node !== firstCallbackNode);
541
+ }
542
+ return count;
543
+ }
544
+
545
+ scheduleCallback(() => {
546
+
547
+ // size should be 0
548
+ updateTestResult(8, `Queue size: ${countNodesInStack(getFirstCallbackNode())}.`);
549
+ updateTestResult(8, 'Pausing... press continue to resume.');
550
+ pauseExecution();
551
+
552
+ scheduleCallback(function () {
553
+ updateTestResult(8, 'Finishing...');
554
+ displayTestResult(8);
555
+ })
556
+ scheduleCallback(function () {
557
+ updateTestResult(8, 'Done!');
558
+ displayTestResult(8);
559
+ checkTestResult(8);
560
+ })
561
+
562
+ // new size should be 2 now
563
+ updateTestResult(8, `Queue size: ${countNodesInStack(getFirstCallbackNode())}.`);
564
+ displayTestResult(8);
565
+ });
566
+}
567
+
568
+function continueTestEight() {
569
+ continueExecution();
570
+}
571
+
572
</script type="text/babel">
573
</body>
574
</html>
\ No newline at end of file
packages/react/src/ReactSharedInternals.js
+6
@@ -12,6 +12,9 @@ import {
12
unstable_now,
13
unstable_scheduleCallback,
14
unstable_runWithPriority,
15
+ unstable_getFirstCallbackNode,
16
+ unstable_pauseExecution,
17
+ unstable_continueExecution,
18
unstable_wrapCallback,
19
unstable_getCurrentPriorityLevel,
20
} from 'scheduler';
@@ -49,6 +52,9 @@ if (__UMD__) {
52
unstable_scheduleCallback,
53
unstable_runWithPriority,
54
unstable_wrapCallback,
55
+ unstable_getFirstCallbackNode,
56
+ unstable_pauseExecution,
57
+ unstable_continueExecution,
58
unstable_getCurrentPriorityLevel,
59
},
60
SchedulerTracing: {
packages/scheduler/npm/umd/scheduler.development.js
+24
@@ -68,6 +68,27 @@
68
);
69
}
70
71
+ function unstable_getFirstCallbackNode() {
72
+ return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Scheduler.unstable_getFirstCallbackNode.apply(
73
+ this,
74
+ arguments
75
+ );
76
+ }
77
+
78
+ function unstable_pauseExecution() {
79
+ return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Scheduler.unstable_pauseExecution.apply(
80
+ this,
81
+ arguments
82
+ );
83
+ }
84
+
85
+ function unstable_continueExecution() {
86
+ return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Scheduler.unstable_continueExecution.apply(
87
+ this,
88
+ arguments
89
+ );
90
+ }
91
+
92
return Object.freeze({
93
unstable_now: unstable_now,
94
unstable_scheduleCallback: unstable_scheduleCallback,
@@ -76,5 +97,8 @@
97
unstable_runWithPriority: unstable_runWithPriority,
98
unstable_wrapCallback: unstable_wrapCallback,
99
unstable_getCurrentPriorityLevel: unstable_getCurrentPriorityLevel,
100
+ unstable_continueExecution: unstable_continueExecution,
101
+ unstable_pauseExecution: unstable_pauseExecution,
102
+ unstable_getFirstCallbackNode: unstable_getFirstCallbackNode,
103
});
104
});
packages/scheduler/npm/umd/scheduler.production.min.js
+18
@@ -68,6 +68,21 @@
68
);
69
}
70
71
+ function unstable_getFirstCallbackNode() {
72
+ return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Scheduler.unstable_getFirstCallbackNode.apply(
73
+ this,
74
+ arguments
75
+ );
76
+ }
77
+
78
+ function unstable_pauseExecution() {
79
+ return undefined;
80
+ }
81
+
82
+ function unstable_continueExecution() {
83
+ return undefined;
84
+ }
85
+
86
return Object.freeze({
87
unstable_now: unstable_now,
88
unstable_scheduleCallback: unstable_scheduleCallback,
@@ -76,5 +91,8 @@
91
unstable_runWithPriority: unstable_runWithPriority,
92
unstable_wrapCallback: unstable_wrapCallback,
93
unstable_getCurrentPriorityLevel: unstable_getCurrentPriorityLevel,
94
+ unstable_continueExecution: unstable_continueExecution,
95
+ unstable_pauseExecution: unstable_pauseExecution,
96
+ unstable_getFirstCallbackNode: unstable_getFirstCallbackNode,
97
});
98
});
packages/scheduler/npm/umd/scheduler.profiling.min.js
+18
@@ -68,6 +68,21 @@
68
);
69
}
70
71
+ function unstable_getFirstCallbackNode() {
72
+ return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Scheduler.unstable_getFirstCallbackNode.apply(
73
+ this,
74
+ arguments
75
+ );
76
+ }
77
+
78
+ function unstable_pauseExecution() {
79
+ return undefined;
80
+ }
81
+
82
+ function unstable_continueExecution() {
83
+ return undefined;
84
+ }
85
+
86
return Object.freeze({
87
unstable_now: unstable_now,
88
unstable_scheduleCallback: unstable_scheduleCallback,
@@ -76,5 +91,8 @@
91
unstable_runWithPriority: unstable_runWithPriority,
92
unstable_wrapCallback: unstable_wrapCallback,
93
unstable_getCurrentPriorityLevel: unstable_getCurrentPriorityLevel,
94
+ unstable_continueExecution: unstable_continueExecution,
95
+ unstable_pauseExecution: unstable_pauseExecution,
96
+ unstable_getFirstCallbackNode: unstable_getFirstCallbackNode,
97
});
98
});
packages/scheduler/src/Scheduler.js
+39
-2
@@ -8,6 +8,8 @@
8
9
/* eslint-disable no-var */
10
11
+import {enableSchedulerDebugging} from 'shared/ReactFeatureFlags';
12
+
13
// TODO: Use symbols?
14
var ImmediatePriority = 1;
15
var UserBlockingPriority = 2;
@@ -33,6 +35,9 @@ var IDLE_PRIORITY = maxSigned31BitInt;
35
var firstCallbackNode = null;
36
37
var currentDidTimeout = false;
38
+// Pausing the scheduler is useful for debugging.
39
+var isSchedulerPaused = false;
40
+
41
var currentPriorityLevel = NormalPriority;
42
var currentEventStartTime = -1;
43
var currentExpirationTime = -1;
@@ -173,13 +178,23 @@ function flushImmediateWork() {
178
}
179
180
function flushWork(didTimeout) {
181
+ // Exit right away if we're currently paused
182
+
183
+ if (enableSchedulerDebugging && isSchedulerPaused) {
184
+ return;
185
+ }
186
+
187
isExecutingCallback = true;
188
const previousDidTimeout = currentDidTimeout;
189
currentDidTimeout = didTimeout;
190
try {
191
if (didTimeout) {
192
// Flush all the expired callbacks without yielding.
182
- while (firstCallbackNode !== null) {
193
+ while (
194
+ firstCallbackNode !== null &&
195
+ !(enableSchedulerDebugging && isSchedulerPaused)
196
+ ) {
197
+ // TODO Wrap i nfeature flag
198
// Read the current time. Flush all the callbacks that expire at or
199
// earlier than that time. Then read the current time again and repeat.
200
// This optimizes for as few performance.now calls as possible.
@@ -189,7 +204,8 @@ function flushWork(didTimeout) {
204
flushFirstCallback();
205
} while (
206
firstCallbackNode !== null &&
192
- firstCallbackNode.expirationTime <= currentTime
207
+ firstCallbackNode.expirationTime <= currentTime &&
208
+ !(enableSchedulerDebugging && isSchedulerPaused)
209
);
210
continue;
211
}
@@ -199,6 +215,9 @@ function flushWork(didTimeout) {
215
// Keep flushing callbacks until we run out of time in the frame.
216
if (firstCallbackNode !== null) {
217
do {
218
+ if (enableSchedulerDebugging && isSchedulerPaused) {
219
+ break;
220
+ }
221
flushFirstCallback();
222
} while (firstCallbackNode !== null && !shouldYieldToHost());
223
}
@@ -342,6 +361,21 @@ function unstable_scheduleCallback(callback, deprecated_options) {
361
return newNode;
362
}
363
364
+function unstable_pauseExecution() {
365
+ isSchedulerPaused = true;
366
+}
367
+
368
+function unstable_continueExecution() {
369
+ isSchedulerPaused = false;
370
+ if (firstCallbackNode !== null) {
371
+ ensureHostCallbackIsScheduled();
372
+ }
373
+}
374
+
375
+function unstable_getFirstCallbackNode() {
376
+ return firstCallbackNode;
377
+}
378
+
379
function unstable_cancelCallback(callbackNode) {
380
var next = callbackNode.next;
381
if (next === null) {
@@ -659,5 +693,8 @@ export {
693
unstable_wrapCallback,
694
unstable_getCurrentPriorityLevel,
695
unstable_shouldYield,
696
+ unstable_continueExecution,
697
+ unstable_pauseExecution,
698
+ unstable_getFirstCallbackNode,
699
getCurrentTime as unstable_now,
700
};
packages/shared/ReactFeatureFlags.js
+4
-1
@@ -33,7 +33,10 @@ export const enableProfilerTimer = __PROFILE__;
33
export const enableSchedulerTracing = __PROFILE__;
34
35
// Only used in www builds.
36
-export const enableSuspenseServerRenderer = false;
36
+export const enableSuspenseServerRenderer = false; // TODO: __DEV__? Here it might just be false.
37
+
38
+// Only used in www builds.
39
+export const enableSchedulerDebugging = __DEV__;
40
41
// Only used in www builds.
42
export function addUserTimingListener() {
packages/shared/forks/ReactFeatureFlags.native-fabric-fb.js
+1
@@ -24,6 +24,7 @@ export const enableSuspenseServerRenderer = false;
24
export const disableInputAttributeSyncing = false;
25
export const enableStableConcurrentModeAPIs = false;
26
export const warnAboutShorthandPropertyCollision = false;
27
+export const enableSchedulerDebugging = false;
28
29
// Only used in www builds.
30
export function addUserTimingListener() {
packages/shared/forks/ReactFeatureFlags.native-fabric-oss.js
+1
@@ -24,6 +24,7 @@ export const enableSuspenseServerRenderer = false;
24
export const disableInputAttributeSyncing = false;
25
export const enableStableConcurrentModeAPIs = false;
26
export const warnAboutShorthandPropertyCollision = false;
27
+export const enableSchedulerDebugging = false;
28
29
// Only used in www builds.
30
export function addUserTimingListener() {
packages/shared/forks/ReactFeatureFlags.native-fb.js
+1
@@ -29,6 +29,7 @@ export const enableSchedulerTracing = __PROFILE__;
29
export const enableSuspenseServerRenderer = false;
30
export const enableStableConcurrentModeAPIs = false;
31
export const warnAboutShorthandPropertyCollision = false;
32
+export const enableSchedulerDebugging = false;
33
34
// Only used in www builds.
35
export function addUserTimingListener() {
packages/shared/forks/ReactFeatureFlags.native-oss.js
+1
@@ -24,6 +24,7 @@ export const enableSuspenseServerRenderer = false;
24
export const disableInputAttributeSyncing = false;
25
export const enableStableConcurrentModeAPIs = false;
26
export const warnAboutShorthandPropertyCollision = false;
27
+export const enableSchedulerDebugging = false;
28
29
// Only used in www builds.
30
export function addUserTimingListener() {
packages/shared/forks/ReactFeatureFlags.persistent.js
+1
@@ -24,6 +24,7 @@ export const enableSuspenseServerRenderer = false;
24
export const disableInputAttributeSyncing = false;
25
export const enableStableConcurrentModeAPIs = false;
26
export const warnAboutShorthandPropertyCollision = false;
27
+export const enableSchedulerDebugging = false;
28
29
// Only used in www builds.
30
export function addUserTimingListener() {
packages/shared/forks/ReactFeatureFlags.test-renderer.js
+1
@@ -24,6 +24,7 @@ export const enableSuspenseServerRenderer = false;
24
export const disableInputAttributeSyncing = false;
25
export const enableStableConcurrentModeAPIs = false;
26
export const warnAboutShorthandPropertyCollision = false;
27
+export const enableSchedulerDebugging = false;
28
29
// Only used in www builds.
30
export function addUserTimingListener() {
packages/shared/forks/ReactFeatureFlags.test-renderer.www.js
+1
@@ -22,6 +22,7 @@ export const enableProfilerTimer = false;
22
export const enableSchedulerTracing = false;
23
export const enableSuspenseServerRenderer = false;
24
export const enableStableConcurrentModeAPIs = false;
25
+export const enableSchedulerDebugging = false;
26
27
// Only used in www builds.
28
export function addUserTimingListener() {
packages/shared/forks/ReactFeatureFlags.www.js
+1
@@ -34,6 +34,7 @@ export let enableUserTimingAPI = __DEV__;
34
35
export const enableProfilerTimer = __PROFILE__;
36
export const enableSchedulerTracing = __PROFILE__;
37
+export const enableSchedulerDebugging = __DEV__; // TODO or just true
38
39
export const enableStableConcurrentModeAPIs = false;
40
packages/shared/forks/Scheduler.umd.js
+6
@@ -16,6 +16,9 @@ const {
16
unstable_now,
17
unstable_scheduleCallback,
18
unstable_shouldYield,
19
+ unstable_getFirstCallbackNode,
20
+ unstable_continueExecution,
21
+ unstable_pauseExecution,
22
} = ReactInternals.Scheduler;
23
24
export {
@@ -23,4 +26,7 @@ export {
26
unstable_now,
27
unstable_scheduleCallback,
28
unstable_shouldYield,
29
+ unstable_getFirstCallbackNode,
30
+ unstable_continueExecution,
31
+ unstable_pauseExecution,
32
};