Skip to content

Commit 8df4d59

Browse files
mrkevacdlite
authored andcommitted
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
1 parent 5bb4ad7 commit 8df4d59

16 files changed

+197
-4
lines changed

fixtures/scheduler/index.html

+74-1
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,21 @@ <h2>Tests:</h2>
9090
</div>
9191
<div> If the counter advanced while you were away from this tab, it's correct.</div>
9292
</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>
93108
</ol>
94109
<script src="../../build/node_modules/react/umd/react.development.js"></script>
95110
<script src="../../build/node_modules/scheduler/umd/scheduler.development.js"></script>
@@ -98,7 +113,10 @@ <h2>Tests:</h2>
98113
const {
99114
unstable_scheduleCallback: scheduleCallback,
100115
unstable_cancelCallback: cancelCallback,
101-
unstable_now: now
116+
unstable_now: now,
117+
unstable_getFirstCallbackNode: getFirstCallbackNode,
118+
unstable_pauseExecution: pauseExecution,
119+
unstable_continueExecution: continueExecution,
102120
} = Scheduler;
103121
function displayTestResult(testNumber) {
104122
const expectationNode = document.getElementById('test-' + testNumber + '-expected');
@@ -215,6 +233,16 @@ <h2>Tests:</h2>
215233
[
216234
// ... TODO
217235
],
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+
],
218246
];
219247
function runTestOne() {
220248
// Test 1
@@ -496,6 +524,51 @@ <h2>Tests:</h2>
496524
}
497525
scheduleCallback(incrementCounterAndScheduleNextCallback);
498526
}
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+
499572
</script type="text/babel">
500573
</body>
501574
</html>

packages/react/src/ReactSharedInternals.js

+6
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ import {
1212
unstable_now,
1313
unstable_scheduleCallback,
1414
unstable_runWithPriority,
15+
unstable_getFirstCallbackNode,
16+
unstable_pauseExecution,
17+
unstable_continueExecution,
1518
unstable_wrapCallback,
1619
unstable_getCurrentPriorityLevel,
1720
} from 'scheduler';
@@ -49,6 +52,9 @@ if (__UMD__) {
4952
unstable_scheduleCallback,
5053
unstable_runWithPriority,
5154
unstable_wrapCallback,
55+
unstable_getFirstCallbackNode,
56+
unstable_pauseExecution,
57+
unstable_continueExecution,
5258
unstable_getCurrentPriorityLevel,
5359
},
5460
SchedulerTracing: {

packages/scheduler/npm/umd/scheduler.development.js

+24
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,27 @@
6868
);
6969
}
7070

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+
7192
return Object.freeze({
7293
unstable_now: unstable_now,
7394
unstable_scheduleCallback: unstable_scheduleCallback,
@@ -76,5 +97,8 @@
7697
unstable_runWithPriority: unstable_runWithPriority,
7798
unstable_wrapCallback: unstable_wrapCallback,
7899
unstable_getCurrentPriorityLevel: unstable_getCurrentPriorityLevel,
100+
unstable_continueExecution: unstable_continueExecution,
101+
unstable_pauseExecution: unstable_pauseExecution,
102+
unstable_getFirstCallbackNode: unstable_getFirstCallbackNode,
79103
});
80104
});

packages/scheduler/npm/umd/scheduler.production.min.js

+18
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,21 @@
6868
);
6969
}
7070

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+
7186
return Object.freeze({
7287
unstable_now: unstable_now,
7388
unstable_scheduleCallback: unstable_scheduleCallback,
@@ -76,5 +91,8 @@
7691
unstable_runWithPriority: unstable_runWithPriority,
7792
unstable_wrapCallback: unstable_wrapCallback,
7893
unstable_getCurrentPriorityLevel: unstable_getCurrentPriorityLevel,
94+
unstable_continueExecution: unstable_continueExecution,
95+
unstable_pauseExecution: unstable_pauseExecution,
96+
unstable_getFirstCallbackNode: unstable_getFirstCallbackNode,
7997
});
8098
});

packages/scheduler/npm/umd/scheduler.profiling.min.js

+18
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,21 @@
6868
);
6969
}
7070

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+
7186
return Object.freeze({
7287
unstable_now: unstable_now,
7388
unstable_scheduleCallback: unstable_scheduleCallback,
@@ -76,5 +91,8 @@
7691
unstable_runWithPriority: unstable_runWithPriority,
7792
unstable_wrapCallback: unstable_wrapCallback,
7893
unstable_getCurrentPriorityLevel: unstable_getCurrentPriorityLevel,
94+
unstable_continueExecution: unstable_continueExecution,
95+
unstable_pauseExecution: unstable_pauseExecution,
96+
unstable_getFirstCallbackNode: unstable_getFirstCallbackNode,
7997
});
8098
});

packages/scheduler/src/Scheduler.js

+39-2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88

99
/* eslint-disable no-var */
1010

11+
import {enableSchedulerDebugging} from 'shared/ReactFeatureFlags';
12+
1113
// TODO: Use symbols?
1214
var ImmediatePriority = 1;
1315
var UserBlockingPriority = 2;
@@ -33,6 +35,9 @@ var IDLE_PRIORITY = maxSigned31BitInt;
3335
var firstCallbackNode = null;
3436

3537
var currentDidTimeout = false;
38+
// Pausing the scheduler is useful for debugging.
39+
var isSchedulerPaused = false;
40+
3641
var currentPriorityLevel = NormalPriority;
3742
var currentEventStartTime = -1;
3843
var currentExpirationTime = -1;
@@ -173,13 +178,23 @@ function flushImmediateWork() {
173178
}
174179

175180
function flushWork(didTimeout) {
181+
// Exit right away if we're currently paused
182+
183+
if (enableSchedulerDebugging && isSchedulerPaused) {
184+
return;
185+
}
186+
176187
isExecutingCallback = true;
177188
const previousDidTimeout = currentDidTimeout;
178189
currentDidTimeout = didTimeout;
179190
try {
180191
if (didTimeout) {
181192
// 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
183198
// Read the current time. Flush all the callbacks that expire at or
184199
// earlier than that time. Then read the current time again and repeat.
185200
// This optimizes for as few performance.now calls as possible.
@@ -189,7 +204,8 @@ function flushWork(didTimeout) {
189204
flushFirstCallback();
190205
} while (
191206
firstCallbackNode !== null &&
192-
firstCallbackNode.expirationTime <= currentTime
207+
firstCallbackNode.expirationTime <= currentTime &&
208+
!(enableSchedulerDebugging && isSchedulerPaused)
193209
);
194210
continue;
195211
}
@@ -199,6 +215,9 @@ function flushWork(didTimeout) {
199215
// Keep flushing callbacks until we run out of time in the frame.
200216
if (firstCallbackNode !== null) {
201217
do {
218+
if (enableSchedulerDebugging && isSchedulerPaused) {
219+
break;
220+
}
202221
flushFirstCallback();
203222
} while (firstCallbackNode !== null && !shouldYieldToHost());
204223
}
@@ -342,6 +361,21 @@ function unstable_scheduleCallback(callback, deprecated_options) {
342361
return newNode;
343362
}
344363

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+
345379
function unstable_cancelCallback(callbackNode) {
346380
var next = callbackNode.next;
347381
if (next === null) {
@@ -659,5 +693,8 @@ export {
659693
unstable_wrapCallback,
660694
unstable_getCurrentPriorityLevel,
661695
unstable_shouldYield,
696+
unstable_continueExecution,
697+
unstable_pauseExecution,
698+
unstable_getFirstCallbackNode,
662699
getCurrentTime as unstable_now,
663700
};

packages/shared/ReactFeatureFlags.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,10 @@ export const enableProfilerTimer = __PROFILE__;
3333
export const enableSchedulerTracing = __PROFILE__;
3434

3535
// 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__;
3740

3841
// Only used in www builds.
3942
export function addUserTimingListener() {

packages/shared/forks/ReactFeatureFlags.native-fabric-fb.js

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export const enableSuspenseServerRenderer = false;
2424
export const disableInputAttributeSyncing = false;
2525
export const enableStableConcurrentModeAPIs = false;
2626
export const warnAboutShorthandPropertyCollision = false;
27+
export const enableSchedulerDebugging = false;
2728

2829
// Only used in www builds.
2930
export function addUserTimingListener() {

packages/shared/forks/ReactFeatureFlags.native-fabric-oss.js

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export const enableSuspenseServerRenderer = false;
2424
export const disableInputAttributeSyncing = false;
2525
export const enableStableConcurrentModeAPIs = false;
2626
export const warnAboutShorthandPropertyCollision = false;
27+
export const enableSchedulerDebugging = false;
2728

2829
// Only used in www builds.
2930
export function addUserTimingListener() {

packages/shared/forks/ReactFeatureFlags.native-fb.js

+1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ export const enableSchedulerTracing = __PROFILE__;
2929
export const enableSuspenseServerRenderer = false;
3030
export const enableStableConcurrentModeAPIs = false;
3131
export const warnAboutShorthandPropertyCollision = false;
32+
export const enableSchedulerDebugging = false;
3233

3334
// Only used in www builds.
3435
export function addUserTimingListener() {

packages/shared/forks/ReactFeatureFlags.native-oss.js

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export const enableSuspenseServerRenderer = false;
2424
export const disableInputAttributeSyncing = false;
2525
export const enableStableConcurrentModeAPIs = false;
2626
export const warnAboutShorthandPropertyCollision = false;
27+
export const enableSchedulerDebugging = false;
2728

2829
// Only used in www builds.
2930
export function addUserTimingListener() {

packages/shared/forks/ReactFeatureFlags.persistent.js

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export const enableSuspenseServerRenderer = false;
2424
export const disableInputAttributeSyncing = false;
2525
export const enableStableConcurrentModeAPIs = false;
2626
export const warnAboutShorthandPropertyCollision = false;
27+
export const enableSchedulerDebugging = false;
2728

2829
// Only used in www builds.
2930
export function addUserTimingListener() {

packages/shared/forks/ReactFeatureFlags.test-renderer.js

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export const enableSuspenseServerRenderer = false;
2424
export const disableInputAttributeSyncing = false;
2525
export const enableStableConcurrentModeAPIs = false;
2626
export const warnAboutShorthandPropertyCollision = false;
27+
export const enableSchedulerDebugging = false;
2728

2829
// Only used in www builds.
2930
export function addUserTimingListener() {

packages/shared/forks/ReactFeatureFlags.test-renderer.www.js

+1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export const enableProfilerTimer = false;
2222
export const enableSchedulerTracing = false;
2323
export const enableSuspenseServerRenderer = false;
2424
export const enableStableConcurrentModeAPIs = false;
25+
export const enableSchedulerDebugging = false;
2526

2627
// Only used in www builds.
2728
export function addUserTimingListener() {

packages/shared/forks/ReactFeatureFlags.www.js

+1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ export let enableUserTimingAPI = __DEV__;
3434

3535
export const enableProfilerTimer = __PROFILE__;
3636
export const enableSchedulerTracing = __PROFILE__;
37+
export const enableSchedulerDebugging = __DEV__; // TODO or just true
3738

3839
export const enableStableConcurrentModeAPIs = false;
3940

0 commit comments

Comments
 (0)