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