@samitouri / QOS-React-2 / commits / 05ca66ad9c

[Fizz] model fb bundle's external work scheduling explicitly (#36576)

There are parts of Fizz that need to schedule work regardless of whether the primary rendering pathway is drive externally through performWork. Historically scheduleWork and later s cheduleMicrotask were noops in this bundle but it makes it hard to reason about the code because you cannot be assured that calling scheduleWork will actually result in the function ever executing. Now we model this explicitly through config. For builds that drive work through external calls to performaWork we simply omit any work scheduling in startWork or pi ngTask. Now that this is modeled explicitly we can implement scheduleMicrotask and scheduleWork to actually provide a guarantee that the callbacks will get invoked. For now I've implemented these two for the fb build as synchronous however it is likely that queueMicrotask and setTimeout or similar are preferred.

Josh Story committed May 30, 2026 at 13:30 UTC 05ca66ad9c6f07b9ee2cc6a8f2129750e8f96fa4
11 files changed +91 -46
packages/react-server/src/ReactFizzServer.js
+49 -38
@@ -88,6 +88,7 @@ import {
88 hoistHoistables,
89 createHoistableState,
90 createPreambleState,
91 + isWorkLoopExternallyDriven,
92 supportsRequestStorage,
93 requestStorage,
94 pushFormStateMarkerIsMatching,
@@ -527,7 +528,7 @@ function RequestInstance(
528 progressiveChunkSize === undefined
529 ? DEFAULT_PROGRESSIVE_CHUNK_SIZE
530 : progressiveChunkSize;
530 - this.status = OPENING;
531 + this.status = isWorkLoopExternallyDriven ? OPEN : OPENING;
532 this.fatalError = null;
533 this.nextSegmentId = 0;
534 this.allPendingTasks = 0;
@@ -790,12 +791,16 @@ export function resolveRequest(): null | Request {
791 function pingTask(request: Request, task: Task): void {
792 const pingedTasks = request.pingedTasks;
793 pingedTasks.push(task);
793 - if (request.pingedTasks.length === 1) {
794 - request.flushScheduled = request.destination !== null;
795 - if (request.trackedPostpones !== null || request.status === OPENING) {
796 - scheduleMicrotask(() => performWork(request));
797 - } else {
798 - scheduleWork(() => performWork(request));
794 + if (isWorkLoopExternallyDriven) {
795 + return;
796 + } else {
797 + if (request.pingedTasks.length === 1) {
798 + request.flushScheduled = request.destination !== null;
799 + if (request.trackedPostpones !== null || request.status === OPENING) {
800 + scheduleMicrotask(() => performWork(request));
801 + } else {
802 + scheduleWork(() => performWork(request));
803 + }
804 }
805 }
806 }
@@ -6030,39 +6035,45 @@ function flushCompletedQueues(
6035 }
6036
6037 export function startWork(request: Request): void {
6033 - request.flushScheduled = request.destination !== null;
6034 - // When prerendering we use microtasks for pinging work
6035 - if (supportsRequestStorage) {
6036 - scheduleMicrotask(() => requestStorage.run(request, performWork, request));
6038 + if (isWorkLoopExternallyDriven) {
6039 + return;
6040 } else {
6038 - scheduleMicrotask(() => performWork(request));
6039 - }
6040 - scheduleWork(() => {
6041 - if (request.status === OPENING) {
6042 - request.status = OPEN;
6043 - }
6044 -
6045 - if (request.trackedPostpones === null) {
6046 - // this is either a regular render or a resume. For regular render we want
6047 - // to call emitEarlyPreloads after the first performWork because we want
6048 - // are responding to a live request and need to balance sending something early
6049 - // (i.e. don't want for the shell to finish) but we need something to send.
6050 - // The only implementation of this is for DOM at the moment and during resumes nothing
6051 - // actually emits but the code paths here are the same.
6052 - // During a prerender we don't want to be too aggressive in emitting early preloads
6053 - // because we aren't responding to a live request and we can wait for the prerender to
6054 - // postpone before we emit anything.
6055 - if (supportsRequestStorage) {
6056 - requestStorage.run(
6057 - request,
6058 - enqueueEarlyPreloadsAfterInitialWork,
6059 - request,
6060 - );
6061 - } else {
6062 - enqueueEarlyPreloadsAfterInitialWork(request);
6063 - }
6041 + request.flushScheduled = request.destination !== null;
6042 + // When prerendering we use microtasks for pinging work
6043 + if (supportsRequestStorage) {
6044 + scheduleMicrotask(() =>
6045 + requestStorage.run(request, performWork, request),
6046 + );
6047 + } else {
6048 + scheduleMicrotask(() => performWork(request));
6049 }
6065 - });
6050 + scheduleWork(() => {
6051 + if (request.status === OPENING) {
6052 + request.status = OPEN;
6053 + }
6054 +
6055 + if (request.trackedPostpones === null) {
6056 + // this is either a regular render or a resume. For regular render we want
6057 + // to call emitEarlyPreloads after the first performWork because we want
6058 + // are responding to a live request and need to balance sending something early
6059 + // (i.e. don't want for the shell to finish) but we need something to send.
6060 + // The only implementation of this is for DOM at the moment and during resumes nothing
6061 + // actually emits but the code paths here are the same.
6062 + // During a prerender we don't want to be too aggressive in emitting early preloads
6063 + // because we aren't responding to a live request and we can wait for the prerender to
6064 + // postpone before we emit anything.
6065 + if (supportsRequestStorage) {
6066 + requestStorage.run(
6067 + request,
6068 + enqueueEarlyPreloadsAfterInitialWork,
6069 + request,
6070 + );
6071 + } else {
6072 + enqueueEarlyPreloadsAfterInitialWork(request);
6073 + }
6074 + }
6075 + });
6076 + }
6077 }
6078
6079 function enqueueEarlyPreloadsAfterInitialWork(request: Request) {
packages/react-server/src/ReactServerStreamConfigFB.js
+12
@@ -18,6 +18,18 @@ export opaque type PrecomputedChunk = string;
18 export opaque type Chunk = string;
19 export opaque type BinaryChunk = string;
20
21 +export function scheduleMicrotask(callback: () => void) {
22 + // TODO: Consider unifying this with the FB Flight stream config and
23 + // adopting its microtask scheduling implementation.
24 + callback();
25 +}
26 +
27 +export function scheduleWork(callback: () => void) {
28 + // TODO: Consider unifying this with the FB Flight stream config and
29 + // adopting its work scheduling implementation.
30 + callback();
31 +}
32 +
33 export function flushBuffered(destination: Destination) {}
34
35 export const supportsRequestStorage = false;
packages/react-server/src/forks/ReactFizzConfig.custom.js
+2
@@ -40,6 +40,8 @@ export const isPrimaryRenderer = false;
40
41 export const supportsClientAPIs = true;
42
43 +export const isWorkLoopExternallyDriven =
44 + $$$config.isWorkLoopExternallyDriven === true;
45 export const supportsRequestStorage = false;
46 export const requestStorage: AsyncLocalStorage<Request | void> = (null: any);
47
packages/react-server/src/forks/ReactFizzConfig.dom-edge.js
+2
@@ -12,6 +12,8 @@ export * from 'react-dom-bindings/src/server/ReactFizzConfigDOM';
12
13 export * from 'react-client/src/ReactClientConsoleConfigServer';
14
15 +export const isWorkLoopExternallyDriven = false;
16 +
17 // For now, we get this from the global scope, but this will likely move to a module.
18 export const supportsRequestStorage = typeof AsyncLocalStorage === 'function';
19 export const requestStorage: AsyncLocalStorage<Request | void> =
packages/react-server/src/forks/ReactFizzConfig.dom-fb.js new
+20
@@ -0,0 +1,20 @@
1 +/**
2 + * Copyright (c) Meta Platforms, Inc. and affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +import type {Request} from 'react-server/src/ReactFizzServer';
10 +
11 +export * from 'react-dom-bindings/src/server/ReactFizzConfigDOM';
12 +
13 +export * from 'react-client/src/ReactClientConsoleConfigBrowser';
14 +
15 +// This renderer is pulled from the outside through renderNextChunk. Promises
16 +// can ping tasks, but the outer caller decides when to process them.
17 +export const isWorkLoopExternallyDriven = true;
18 +
19 +export const supportsRequestStorage = false;
20 +export const requestStorage: AsyncLocalStorage<Request | void> = (null: any);
packages/react-server/src/forks/ReactFizzConfig.dom-legacy.js
+1
@@ -12,5 +12,6 @@ export * from 'react-dom-bindings/src/server/ReactFizzConfigDOMLegacy';
12
13 export * from 'react-client/src/ReactClientConsoleConfigPlain';
14
15 +export const isWorkLoopExternallyDriven = false;
16 export const supportsRequestStorage = false;
17 export const requestStorage: AsyncLocalStorage<Request | void> = (null: any);
packages/react-server/src/forks/ReactFizzConfig.dom-node.js
+1
@@ -15,6 +15,7 @@ export * from 'react-dom-bindings/src/server/ReactFizzConfigDOM';
15
16 export * from 'react-client/src/ReactClientConsoleConfigServer';
17
18 +export const isWorkLoopExternallyDriven = false;
19 export const supportsRequestStorage = true;
20 export const requestStorage: AsyncLocalStorage<Request | void> =
21 new AsyncLocalStorage();
packages/react-server/src/forks/ReactFizzConfig.dom.js
+1
@@ -12,5 +12,6 @@ export * from 'react-dom-bindings/src/server/ReactFizzConfigDOM';
12
13 export * from 'react-client/src/ReactClientConsoleConfigBrowser';
14
15 +export const isWorkLoopExternallyDriven = false;
16 export const supportsRequestStorage = false;
17 export const requestStorage: AsyncLocalStorage<Request | void> = (null: any);
packages/react-server/src/forks/ReactFizzConfig.markup.js
+1
@@ -12,5 +12,6 @@ export * from 'react-markup/src/ReactFizzConfigMarkup.js';
12
13 export * from 'react-client/src/ReactClientConsoleConfigPlain';
14
15 +export const isWorkLoopExternallyDriven = false;
16 export const supportsRequestStorage = false;
17 export const requestStorage: AsyncLocalStorage<Request | void> = (null: any);
packages/react-server/src/forks/ReactFizzConfig.noop.js
+2
@@ -40,6 +40,8 @@ export const isPrimaryRenderer = false;
40
41 export const supportsClientAPIs = true;
42
43 +export const isWorkLoopExternallyDriven =
44 + $$$config.isWorkLoopExternallyDriven === true;
45 export const supportsRequestStorage = false;
46 export const requestStorage: AsyncLocalStorage<Request | void> = (null: any);
47
packages/react-server/src/forks/ReactServerStreamConfig.dom-fb.js
-8
@@ -8,11 +8,3 @@
8 */
9
10 export * from '../ReactServerStreamConfigFB';
11 -
12 -export function scheduleMicrotask(callback: () => void) {
13 - // We don't schedule work in this model, and instead expect performWork to always be called repeatedly.
14 -}
15 -
16 -export function scheduleWork(callback: () => void) {
17 - // We don't schedule work in this model, and instead expect performWork to always be called repeatedly.
18 -}