@samitouri / QOS-React-2 / commits / 1e1e5cd252

[Flight] Schedule work in a microtask (#29491)

Stacked on #29551 Flight pings much more often than Fizz because async function components will always take at least a microtask to resolve . Rather than scheduling this work as a new macrotask Flight now schedules pings in a microtask. This allows more microtasks to ping before actually doing a work flush but doesn't force the vm to spin up a new task which is quite common give n the nature of Server Components

Josh Story committed Jun 6, 2024 at 10:19 UTC 1e1e5cd25223fddbce0e3fb7889b06df0d93a950
11 files changed +57 -1
packages/react-dom-bindings/src/server/ReactDOMLegacyServerStreamConfig.js
+8
@@ -20,6 +20,14 @@ export function scheduleWork(callback: () => void) {
20 callback();
21 }
22
23 +export function scheduleMicrotask(callback: () => void) {
24 + // While this defies the method name the legacy builds have special
25 + // overrides that make work scheduling sync. At the moment scheduleMicrotask
26 + // isn't used by any legacy APIs so this is somewhat academic but if they
27 + // did in the future we'd probably want to have this be in sync with scheduleWork
28 + callback();
29 +}
30 +
31 export function flushBuffered(destination: Destination) {}
32
33 export function beginWriting(destination: Destination) {}
packages/react-noop-renderer/src/ReactNoopFlightServer.js
+3
@@ -25,6 +25,9 @@ type Destination = Array<Uint8Array>;
25 const textEncoder = new TextEncoder();
26
27 const ReactNoopFlightServer = ReactFlightServer({
28 + scheduleMicrotask(callback: () => void) {
29 + callback();
30 + },
31 scheduleWork(callback: () => void) {
32 callback();
33 },
packages/react-noop-renderer/src/ReactNoopServer.js
+3
@@ -74,6 +74,9 @@ function write(destination: Destination, buffer: Uint8Array): void {
74 }
75
76 const ReactNoopServer = ReactFizzServer({
77 + scheduleMicrotask(callback: () => void) {
78 + callback();
79 + },
80 scheduleWork(callback: () => void) {
81 callback();
82 },
packages/react-server/src/ReactFlightServer.js
+2 -1
@@ -26,6 +26,7 @@ import {enableFlightReadableStream} from 'shared/ReactFeatureFlags';
26
27 import {
28 scheduleWork,
29 + scheduleMicrotask,
30 flushBuffered,
31 beginWriting,
32 writeChunkAndReturn,
@@ -1571,7 +1572,7 @@ function pingTask(request: Request, task: Task): void {
1572 pingedTasks.push(task);
1573 if (pingedTasks.length === 1) {
1574 request.flushScheduled = request.destination !== null;
1574 - scheduleWork(() => performWork(request));
1575 + scheduleMicrotask(() => performWork(request));
1576 }
1577 }
1578
packages/react-server/src/ReactServerStreamConfigBrowser.js
+15
@@ -27,6 +27,21 @@ export function scheduleWork(callback: () => void) {
27 channel.port2.postMessage(null);
28 }
29
30 +function handleErrorInNextTick(error: any) {
31 + setTimeout(() => {
32 + throw error;
33 + });
34 +}
35 +
36 +const LocalPromise = Promise;
37 +
38 +export const scheduleMicrotask: (callback: () => void) => void =
39 + typeof queueMicrotask === 'function'
40 + ? queueMicrotask
41 + : callback => {
42 + LocalPromise.resolve(null).then(callback).catch(handleErrorInNextTick);
43 + };
44 +
45 export function flushBuffered(destination: Destination) {
46 // WHATWG Streams do not yet have a way to flush the underlying
47 // transform streams. https://github.com/whatwg/streams/issues/960
packages/react-server/src/ReactServerStreamConfigBun.js
+2
@@ -25,6 +25,8 @@ export function scheduleWork(callback: () => void) {
25 setTimeout(callback, 0);
26 }
27
28 +export const scheduleMicrotask = queueMicrotask;
29 +
30 export function flushBuffered(destination: Destination) {
31 // Bun direct streams provide a flush function.
32 // If we don't have any more data to send right now.
packages/react-server/src/ReactServerStreamConfigEdge.js
+15
@@ -13,6 +13,21 @@ export type PrecomputedChunk = Uint8Array;
13 export opaque type Chunk = Uint8Array;
14 export type BinaryChunk = Uint8Array;
15
16 +function handleErrorInNextTick(error: any) {
17 + setTimeout(() => {
18 + throw error;
19 + });
20 +}
21 +
22 +const LocalPromise = Promise;
23 +
24 +export const scheduleMicrotask: (callback: () => void) => void =
25 + typeof queueMicrotask === 'function'
26 + ? queueMicrotask
27 + : callback => {
28 + LocalPromise.resolve(null).then(callback).catch(handleErrorInNextTick);
29 + };
30 +
31 export function scheduleWork(callback: () => void) {
32 setTimeout(callback, 0);
33 }
packages/react-server/src/ReactServerStreamConfigNode.js
+2
@@ -26,6 +26,8 @@ export function scheduleWork(callback: () => void) {
26 setImmediate(callback);
27 }
28
29 +export const scheduleMicrotask = queueMicrotask;
30 +
31 export function flushBuffered(destination: Destination) {
32 // If we don't have any more data to send right now.
33 // Flush whatever is in the buffer to the wire.
packages/react-server/src/forks/ReactServerStreamConfig.custom.js
+1
@@ -31,6 +31,7 @@ export opaque type Chunk = mixed; // eslint-disable-line no-undef
31 export opaque type BinaryChunk = mixed; // eslint-disable-line no-undef
32
33 export const scheduleWork = $$$config.scheduleWork;
34 +export const scheduleMicrotask = $$$config.scheduleMicrotask;
35 export const beginWriting = $$$config.beginWriting;
36 export const writeChunk = $$$config.writeChunk;
37 export const writeChunkAndReturn = $$$config.writeChunkAndReturn;
packages/react-server/src/forks/ReactServerStreamConfig.dom-fb-experimental.js
+2
@@ -60,6 +60,8 @@ export function scheduleWork(callback: () => void) {
60 LocalPromise.resolve().then(callback).catch(handleErrorInNextTick);
61 }
62
63 +export const scheduleMicrotask: (callback: () => void) => void = scheduleWork;
64 +
65 export function beginWriting(destination: Destination) {
66 destination.beginWriting();
67 }
packages/react-server/src/forks/ReactServerStreamConfig.dom-fb.js
+4
@@ -9,6 +9,10 @@
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 }