Skip to content

Commit fbb94d3

Browse files
committed
Yield to main thread if continuation is returned
Instead of using an imperative method `requestYield` to ask Scheduler to yield to the main thread, we can assume that any time a Scheduler task returns a continuation callback, it's because it wants to yield to the main thread. We can assume the task already checked some condition that caused it to return a continuation, so we don't need to do any additional checks — we can immediately yield and schedule a new task for the continuation. The replaces the `requestYield` API that I added in ca990e9.
1 parent 17e2a15 commit fbb94d3

File tree

9 files changed

+71
-110
lines changed

9 files changed

+71
-110
lines changed

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

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -54,13 +54,6 @@
5454
);
5555
}
5656

57-
function unstable_requestYield() {
58-
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Scheduler.unstable_requestYield.apply(
59-
this,
60-
arguments
61-
);
62-
}
63-
6457
function unstable_runWithPriority() {
6558
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Scheduler.unstable_runWithPriority.apply(
6659
this,
@@ -123,7 +116,6 @@
123116
unstable_cancelCallback: unstable_cancelCallback,
124117
unstable_shouldYield: unstable_shouldYield,
125118
unstable_requestPaint: unstable_requestPaint,
126-
unstable_requestYield: unstable_requestYield,
127119
unstable_runWithPriority: unstable_runWithPriority,
128120
unstable_next: unstable_next,
129121
unstable_wrapCallback: unstable_wrapCallback,

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

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -54,13 +54,6 @@
5454
);
5555
}
5656

57-
function unstable_requestYield() {
58-
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Scheduler.unstable_requestYield.apply(
59-
this,
60-
arguments
61-
);
62-
}
63-
6457
function unstable_runWithPriority() {
6558
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Scheduler.unstable_runWithPriority.apply(
6659
this,
@@ -117,7 +110,6 @@
117110
unstable_cancelCallback: unstable_cancelCallback,
118111
unstable_shouldYield: unstable_shouldYield,
119112
unstable_requestPaint: unstable_requestPaint,
120-
unstable_requestYield: unstable_requestYield,
121113
unstable_runWithPriority: unstable_runWithPriority,
122114
unstable_next: unstable_next,
123115
unstable_wrapCallback: unstable_wrapCallback,

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

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -54,13 +54,6 @@
5454
);
5555
}
5656

57-
function unstable_requestYield() {
58-
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Scheduler.unstable_requestYield.apply(
59-
this,
60-
arguments
61-
);
62-
}
63-
6457
function unstable_runWithPriority() {
6558
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Scheduler.unstable_runWithPriority.apply(
6659
this,
@@ -117,7 +110,6 @@
117110
unstable_cancelCallback: unstable_cancelCallback,
118111
unstable_shouldYield: unstable_shouldYield,
119112
unstable_requestPaint: unstable_requestPaint,
120-
unstable_requestYield: unstable_requestYield,
121113
unstable_runWithPriority: unstable_runWithPriority,
122114
unstable_next: unstable_next,
123115
unstable_wrapCallback: unstable_wrapCallback,

packages/scheduler/src/__tests__/Scheduler-test.js

Lines changed: 8 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ let performance;
1818
let cancelCallback;
1919
let scheduleCallback;
2020
let requestPaint;
21-
let requestYield;
2221
let shouldYield;
2322
let NormalPriority;
2423

@@ -44,7 +43,6 @@ describe('SchedulerBrowser', () => {
4443
scheduleCallback = Scheduler.unstable_scheduleCallback;
4544
NormalPriority = Scheduler.unstable_NormalPriority;
4645
requestPaint = Scheduler.unstable_requestPaint;
47-
requestYield = Scheduler.unstable_requestYield;
4846
shouldYield = Scheduler.unstable_shouldYield;
4947
});
5048

@@ -480,19 +478,13 @@ describe('SchedulerBrowser', () => {
480478
]);
481479
});
482480

483-
it('requestYield forces a yield immediately', () => {
481+
it('yielding continues in a new task regardless of how much time is remaining', () => {
484482
scheduleCallback(NormalPriority, () => {
485483
runtime.log('Original Task');
486484
runtime.log('shouldYield: ' + shouldYield());
487-
runtime.log('requestYield');
488-
requestYield();
489-
runtime.log('shouldYield: ' + shouldYield());
485+
runtime.log('Return a continuation');
490486
return () => {
491487
runtime.log('Continuation Task');
492-
runtime.log('shouldYield: ' + shouldYield());
493-
runtime.log('Advance time past frame deadline');
494-
runtime.advanceTime(10000);
495-
runtime.log('shouldYield: ' + shouldYield());
496488
};
497489
});
498490
runtime.assertLog(['Post Message']);
@@ -501,27 +493,20 @@ describe('SchedulerBrowser', () => {
501493
runtime.assertLog([
502494
'Message Event',
503495
'Original Task',
496+
// Immediately before returning a continuation, `shouldYield` returns
497+
// false, which means there must be time remaining in the frame.
504498
'shouldYield: false',
505-
'requestYield',
506-
// Immediately after calling requestYield, shouldYield starts
507-
// returning true, even though no time has elapsed in the frame
508-
'shouldYield: true',
499+
'Return a continuation',
509500

510-
// The continuation should be scheduled in a separate macrotask.
501+
// The continuation should be scheduled in a separate macrotask even
502+
// though there's time remaining.
511503
'Post Message',
512504
]);
513505

514506
// No time has elapsed
515507
expect(performance.now()).toBe(0);
516508

517-
// Subsequent tasks work as normal
518509
runtime.fireMessageEvent();
519-
runtime.assertLog([
520-
'Message Event',
521-
'Continuation Task',
522-
'shouldYield: false',
523-
'Advance time past frame deadline',
524-
'shouldYield: true',
525-
]);
510+
runtime.assertLog(['Message Event', 'Continuation Task']);
526511
});
527512
});

packages/scheduler/src/__tests__/SchedulerMock-test.js

Lines changed: 37 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -726,37 +726,53 @@ describe('Scheduler', () => {
726726
expect(Scheduler).toFlushWithoutYielding();
727727
});
728728

729-
it('requestYield forces a yield immediately', () => {
729+
it('toFlushUntilNextPaint stops if a continuation is returned', () => {
730730
scheduleCallback(NormalPriority, () => {
731731
Scheduler.unstable_yieldValue('Original Task');
732-
Scheduler.unstable_yieldValue(
733-
'shouldYield: ' + Scheduler.unstable_shouldYield(),
734-
);
735-
Scheduler.unstable_yieldValue('requestYield');
736-
Scheduler.unstable_requestYield();
737-
Scheduler.unstable_yieldValue(
738-
'shouldYield: ' + Scheduler.unstable_shouldYield(),
739-
);
732+
Scheduler.unstable_yieldValue('shouldYield: ' + shouldYield());
733+
Scheduler.unstable_yieldValue('Return a continuation');
740734
return () => {
741735
Scheduler.unstable_yieldValue('Continuation Task');
742-
Scheduler.unstable_yieldValue(
743-
'shouldYield: ' + Scheduler.unstable_shouldYield(),
744-
);
745-
Scheduler.unstable_yieldValue('Advance time past frame deadline');
746-
Scheduler.unstable_yieldValue(
747-
'shouldYield: ' + Scheduler.unstable_shouldYield(),
748-
);
749736
};
750737
});
751738

752-
// The continuation should be scheduled in a separate macrotask.
753739
expect(Scheduler).toFlushUntilNextPaint([
754740
'Original Task',
741+
// Immediately before returning a continuation, `shouldYield` returns
742+
// false, which means there must be time remaining in the frame.
755743
'shouldYield: false',
756-
'requestYield',
757-
// Immediately after calling requestYield, shouldYield starts
758-
// returning true
759-
'shouldYield: true',
744+
'Return a continuation',
745+
746+
// The continuation should not flush yet.
747+
]);
748+
749+
// No time has elapsed
750+
expect(Scheduler.unstable_now()).toBe(0);
751+
752+
// Continue the task
753+
expect(Scheduler).toFlushAndYield(['Continuation Task']);
754+
});
755+
756+
it("toFlushAndYield keeps flushing even if there's a continuation", () => {
757+
scheduleCallback(NormalPriority, () => {
758+
Scheduler.unstable_yieldValue('Original Task');
759+
Scheduler.unstable_yieldValue('shouldYield: ' + shouldYield());
760+
Scheduler.unstable_yieldValue('Return a continuation');
761+
return () => {
762+
Scheduler.unstable_yieldValue('Continuation Task');
763+
};
764+
});
765+
766+
expect(Scheduler).toFlushAndYield([
767+
'Original Task',
768+
// Immediately before returning a continuation, `shouldYield` returns
769+
// false, which means there must be time remaining in the frame.
770+
'shouldYield: false',
771+
'Return a continuation',
772+
773+
// The continuation should flush immediately, even though the task
774+
// yielded a continuation.
775+
'Continuation Task',
760776
]);
761777
});
762778
});

packages/scheduler/src/__tests__/SchedulerPostTask-test.js

Lines changed: 8 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ let UserBlockingPriority;
2323
let LowPriority;
2424
let IdlePriority;
2525
let shouldYield;
26-
let requestYield;
2726

2827
// The Scheduler postTask implementation uses a new postTask browser API to
2928
// schedule work on the main thread. This test suite mocks all browser methods
@@ -47,7 +46,6 @@ describe('SchedulerPostTask', () => {
4746
LowPriority = Scheduler.unstable_LowPriority;
4847
IdlePriority = Scheduler.unstable_IdlePriority;
4948
shouldYield = Scheduler.unstable_shouldYield;
50-
requestYield = Scheduler.unstable_requestYield;
5149
});
5250

5351
afterEach(() => {
@@ -301,19 +299,13 @@ describe('SchedulerPostTask', () => {
301299
]);
302300
});
303301

304-
it('requestYield forces a yield immediately', () => {
302+
it('yielding continues in a new task regardless of how much time is remaining', () => {
305303
scheduleCallback(NormalPriority, () => {
306304
runtime.log('Original Task');
307305
runtime.log('shouldYield: ' + shouldYield());
308-
runtime.log('requestYield');
309-
requestYield();
310-
runtime.log('shouldYield: ' + shouldYield());
306+
runtime.log('Return a continuation');
311307
return () => {
312308
runtime.log('Continuation Task');
313-
runtime.log('shouldYield: ' + shouldYield());
314-
runtime.log('Advance time past frame deadline');
315-
runtime.advanceTime(10000);
316-
runtime.log('shouldYield: ' + shouldYield());
317309
};
318310
});
319311
runtime.assertLog(['Post Task 0 [user-visible]']);
@@ -322,27 +314,20 @@ describe('SchedulerPostTask', () => {
322314
runtime.assertLog([
323315
'Task 0 Fired',
324316
'Original Task',
317+
// Immediately before returning a continuation, `shouldYield` returns
318+
// false, which means there must be time remaining in the frame.
325319
'shouldYield: false',
326-
'requestYield',
327-
// Immediately after calling requestYield, shouldYield starts
328-
// returning true, even though no time has elapsed in the frame
329-
'shouldYield: true',
320+
'Return a continuation',
330321

331-
// The continuation should be scheduled in a separate macrotask.
322+
// The continuation should be scheduled in a separate macrotask even
323+
// though there's time remaining.
332324
'Post Task 1 [user-visible]',
333325
]);
334326

335327
// No time has elapsed
336328
expect(performance.now()).toBe(0);
337329

338-
// Subsequent tasks work as normal
339330
runtime.flushTasks();
340-
runtime.assertLog([
341-
'Task 1 Fired',
342-
'Continuation Task',
343-
'shouldYield: false',
344-
'Advance time past frame deadline',
345-
'shouldYield: true',
346-
]);
331+
runtime.assertLog(['Task 1 Fired', 'Continuation Task']);
347332
});
348333
});

packages/scheduler/src/forks/Scheduler.js

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -212,10 +212,14 @@ function workLoop(hasTimeRemaining, initialTime) {
212212
const continuationCallback = callback(didUserCallbackTimeout);
213213
currentTime = getCurrentTime();
214214
if (typeof continuationCallback === 'function') {
215+
// If a continuation is returned, immediately yield to the main thread
216+
// regardless of how much time is left in the current time slice.
215217
currentTask.callback = continuationCallback;
216218
if (enableProfiling) {
217219
markTaskYield(currentTask, currentTime);
218220
}
221+
advanceTimers(currentTime);
222+
return true;
219223
} else {
220224
if (enableProfiling) {
221225
markTaskCompleted(currentTask, currentTime);
@@ -224,8 +228,8 @@ function workLoop(hasTimeRemaining, initialTime) {
224228
if (currentTask === peek(taskQueue)) {
225229
pop(taskQueue);
226230
}
231+
advanceTimers(currentTime);
227232
}
228-
advanceTimers(currentTime);
229233
} else {
230234
pop(taskQueue);
231235
}
@@ -495,11 +499,6 @@ function requestPaint() {
495499
// Since we yield every frame regardless, `requestPaint` has no effect.
496500
}
497501

498-
function requestYield() {
499-
// Force a yield at the next opportunity.
500-
startTime = -99999;
501-
}
502-
503502
function forceFrameRate(fps) {
504503
if (fps < 0 || fps > 125) {
505504
// Using console['error'] to evade Babel and ESLint
@@ -617,7 +616,6 @@ export {
617616
unstable_getCurrentPriorityLevel,
618617
shouldYieldToHost as unstable_shouldYield,
619618
requestPaint as unstable_requestPaint,
620-
requestYield as unstable_requestYield,
621619
unstable_continueExecution,
622620
unstable_pauseExecution,
623621
unstable_getFirstCallbackNode,

packages/scheduler/src/forks/SchedulerMock.js

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -195,10 +195,22 @@ function workLoop(hasTimeRemaining, initialTime) {
195195
const continuationCallback = callback(didUserCallbackTimeout);
196196
currentTime = getCurrentTime();
197197
if (typeof continuationCallback === 'function') {
198+
// If a continuation is returned, immediately yield to the main thread
199+
// regardless of how much time is left in the current time slice.
198200
currentTask.callback = continuationCallback;
199201
if (enableProfiling) {
200202
markTaskYield(currentTask, currentTime);
201203
}
204+
advanceTimers(currentTime);
205+
206+
if (shouldYieldForPaint) {
207+
needsPaint = true;
208+
return true;
209+
} else {
210+
// If `shouldYieldForPaint` is false, we keep flushing synchronously
211+
// without yielding to the main thread. This is the behavior of the
212+
// `toFlushAndYield` and `toFlushAndYieldThrough` testing helpers .
213+
}
202214
} else {
203215
if (enableProfiling) {
204216
markTaskCompleted(currentTask, currentTime);
@@ -207,8 +219,8 @@ function workLoop(hasTimeRemaining, initialTime) {
207219
if (currentTask === peek(taskQueue)) {
208220
pop(taskQueue);
209221
}
222+
advanceTimers(currentTime);
210223
}
211-
advanceTimers(currentTime);
212224
} else {
213225
pop(taskQueue);
214226
}
@@ -608,11 +620,6 @@ function requestPaint() {
608620
needsPaint = true;
609621
}
610622

611-
function requestYield() {
612-
// Force a yield at the next opportunity.
613-
shouldYieldForPaint = needsPaint = true;
614-
}
615-
616623
export {
617624
ImmediatePriority as unstable_ImmediatePriority,
618625
UserBlockingPriority as unstable_UserBlockingPriority,
@@ -627,7 +634,6 @@ export {
627634
unstable_getCurrentPriorityLevel,
628635
shouldYieldToHost as unstable_shouldYield,
629636
requestPaint as unstable_requestPaint,
630-
requestYield as unstable_requestYield,
631637
unstable_continueExecution,
632638
unstable_pauseExecution,
633639
unstable_getFirstCallbackNode,

packages/scheduler/src/forks/SchedulerPostTask.js

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -67,11 +67,6 @@ export function unstable_requestPaint() {
6767
// Since we yield every frame regardless, `requestPaint` has no effect.
6868
}
6969

70-
export function unstable_requestYield() {
71-
// Force a yield at the next opportunity.
72-
deadline = -99999;
73-
}
74-
7570
type SchedulerCallback<T> = (
7671
didTimeout_DEPRECATED: boolean,
7772
) =>

0 commit comments

Comments
 (0)