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

[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;