[Scheduler] Always yield to native macro tasks when a virtual task completes (#31787)
As an alternative to #31784. We should really just always yield each virtual task to a native task. So that it's 1:1 with native tasks. This affects when microtasks within each task happens. This brings us closer to native `postTask` semantics which makes it more seamless to just use that when available. This still doesn't yield when a task expires to protect against starvation.
Sebastian Markbåge committed
Dec 17, 2024 at 16:49 UTC
f5077bcc925aa6d0ba2ca4041c875d35e24f6266
5 files changed
+53
-11
packages/scheduler/src/SchedulerFeatureFlags.js
+2
@@ -15,3 +15,5 @@ export const userBlockingPriorityTimeout = 250;
15
export const normalPriorityTimeout = 5000;
16
export const lowPriorityTimeout = 10000;
17
export const enableRequestPaint = true;
18
+
19
+export const enableAlwaysYieldScheduler = __EXPERIMENTAL__;
packages/scheduler/src/__tests__/Scheduler-test.js
+15
-4
@@ -201,9 +201,10 @@ describe('SchedulerBrowser', () => {
201
runtime.assertLog([
202
'Message Event',
203
'Task',
204
- SchedulerFeatureFlags.enableRequestPaint
205
- ? 'Yield at 0ms'
206
- : `Yield at ${SchedulerFeatureFlags.frameYieldMs}ms`,
204
+ gate(flags => flags.enableAlwaysYieldScheduler) ||
205
+ !SchedulerFeatureFlags.enableRequestPaint
206
+ ? gate(flags => (flags.www ? 'Yield at 10ms' : 'Yield at 5ms'))
207
+ : 'Yield at 0ms',
208
'Post Message',
209
]);
210
@@ -220,7 +221,13 @@ describe('SchedulerBrowser', () => {
221
});
222
runtime.assertLog(['Post Message']);
223
runtime.fireMessageEvent();
223
- runtime.assertLog(['Message Event', 'A', 'B']);
224
+ if (gate(flags => flags.enableAlwaysYieldScheduler)) {
225
+ runtime.assertLog(['Message Event', 'A', 'Post Message']);
226
+ runtime.fireMessageEvent();
227
+ runtime.assertLog(['Message Event', 'B']);
228
+ } else {
229
+ runtime.assertLog(['Message Event', 'A', 'B']);
230
+ }
231
});
232
233
it('multiple tasks with a yield in between', () => {
@@ -267,6 +274,10 @@ describe('SchedulerBrowser', () => {
274
runtime.assertLog(['Message Event', 'Oops!', 'Post Message']);
275
276
runtime.fireMessageEvent();
277
+ if (gate(flags => flags.enableAlwaysYieldScheduler)) {
278
+ runtime.assertLog(['Message Event', 'Post Message']);
279
+ runtime.fireMessageEvent();
280
+ }
281
runtime.assertLog(['Message Event', 'Yay']);
282
});
283
packages/scheduler/src/__tests__/SchedulerSetImmediate-test.js
+21
-3
@@ -188,7 +188,13 @@ describe('SchedulerDOMSetImmediate', () => {
188
});
189
runtime.assertLog(['Set Immediate']);
190
runtime.fireSetImmediate();
191
- runtime.assertLog(['setImmediate Callback', 'A', 'B']);
191
+ if (gate(flags => flags.enableAlwaysYieldScheduler)) {
192
+ runtime.assertLog(['setImmediate Callback', 'A', 'Set Immediate']);
193
+ runtime.fireSetImmediate();
194
+ runtime.assertLog(['setImmediate Callback', 'B']);
195
+ } else {
196
+ runtime.assertLog(['setImmediate Callback', 'A', 'B']);
197
+ }
198
});
199
200
it('multiple tasks at different priority', () => {
@@ -200,7 +206,13 @@ describe('SchedulerDOMSetImmediate', () => {
206
});
207
runtime.assertLog(['Set Immediate']);
208
runtime.fireSetImmediate();
203
- runtime.assertLog(['setImmediate Callback', 'B', 'A']);
209
+ if (gate(flags => flags.enableAlwaysYieldScheduler)) {
210
+ runtime.assertLog(['setImmediate Callback', 'B', 'Set Immediate']);
211
+ runtime.fireSetImmediate();
212
+ runtime.assertLog(['setImmediate Callback', 'A']);
213
+ } else {
214
+ runtime.assertLog(['setImmediate Callback', 'B', 'A']);
215
+ }
216
});
217
218
it('multiple tasks with a yield in between', () => {
@@ -246,7 +258,13 @@ describe('SchedulerDOMSetImmediate', () => {
258
runtime.assertLog(['setImmediate Callback', 'Oops!', 'Set Immediate']);
259
260
runtime.fireSetImmediate();
249
- runtime.assertLog(['setImmediate Callback', 'Yay']);
261
+ if (gate(flags => flags.enableAlwaysYieldScheduler)) {
262
+ runtime.assertLog(['setImmediate Callback', 'Set Immediate']);
263
+ runtime.fireSetImmediate();
264
+ runtime.assertLog(['setImmediate Callback', 'Yay']);
265
+ } else {
266
+ runtime.assertLog(['setImmediate Callback', 'Yay']);
267
+ }
268
});
269
270
it('schedule new task after queue has emptied', () => {
packages/scheduler/src/forks/Scheduler.js
+13
-4
@@ -19,6 +19,7 @@ import {
19
lowPriorityTimeout,
20
normalPriorityTimeout,
21
enableRequestPaint,
22
+ enableAlwaysYieldScheduler,
23
} from '../SchedulerFeatureFlags';
24
25
import {push, pop, peek} from '../SchedulerMinHeap';
@@ -196,9 +197,11 @@ function workLoop(initialTime: number) {
197
currentTask !== null &&
198
!(enableSchedulerDebugging && isSchedulerPaused)
199
) {
199
- if (currentTask.expirationTime > currentTime && shouldYieldToHost()) {
200
- // This currentTask hasn't expired, and we've reached the deadline.
201
- break;
200
+ if (!enableAlwaysYieldScheduler) {
201
+ if (currentTask.expirationTime > currentTime && shouldYieldToHost()) {
202
+ // This currentTask hasn't expired, and we've reached the deadline.
203
+ break;
204
+ }
205
}
206
// $FlowFixMe[incompatible-use] found when upgrading Flow
207
const callback = currentTask.callback;
@@ -242,6 +245,12 @@ function workLoop(initialTime: number) {
245
pop(taskQueue);
246
}
247
currentTask = peek(taskQueue);
248
+ if (enableAlwaysYieldScheduler) {
249
+ if (currentTask === null || currentTask.expirationTime > currentTime) {
250
+ // This currentTask hasn't expired we yield to the browser task.
251
+ break;
252
+ }
253
+ }
254
}
255
// Return whether there's additional work
256
if (currentTask !== null) {
@@ -459,7 +468,7 @@ let frameInterval = frameYieldMs;
468
let startTime = -1;
469
470
function shouldYieldToHost(): boolean {
462
- if (enableRequestPaint && needsPaint) {
471
+ if (!enableAlwaysYieldScheduler && enableRequestPaint && needsPaint) {
472
// Yield now.
473
return true;
474
}
packages/scheduler/src/forks/SchedulerFeatureFlags.www.js
+2
@@ -19,3 +19,5 @@ export const frameYieldMs = 10;
19
export const userBlockingPriorityTimeout = 250;
20
export const normalPriorityTimeout = 5000;
21
export const lowPriorityTimeout = 10000;
22
+
23
+export const enableAlwaysYieldScheduler = false;