Skip to content

Commit

Permalink
[Flight] Schedule work in a microtask
Browse files Browse the repository at this point in the history
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 microta
sks to ping before actually doing a work flush but doesn't force the vm to sping up a new task which is quite common give
n the nature of Server Components
  • Loading branch information
gnoff committed May 23, 2024
1 parent f55d172 commit 87178ee
Show file tree
Hide file tree
Showing 12 changed files with 64 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ export opaque type PrecomputedChunk = string;
export opaque type Chunk = string;
export opaque type BinaryChunk = string;

export function scheduleMicrotask(callback: () => void) {
throw new Error(
'Legacy React Server builds should not be using scheduleMicrotask. This is a bug in React.',
);
}

export function scheduleWork(callback: () => void) {
callback();
}
Expand Down
3 changes: 3 additions & 0 deletions packages/react-noop-renderer/src/ReactNoopFlightServer.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ type Destination = Array<Uint8Array>;
const textEncoder = new TextEncoder();

const ReactNoopFlightServer = ReactFlightServer({
scheduleMicrotask(callback: () => void) {
callback();
},
scheduleWork(callback: () => void) {
callback();
},
Expand Down
3 changes: 3 additions & 0 deletions packages/react-noop-renderer/src/ReactNoopServer.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@ function write(destination: Destination, buffer: Uint8Array): void {
}

const ReactNoopServer = ReactFizzServer({
scheduleMicrotask(callback: () => void) {
callback();
},
scheduleWork(callback: () => void) {
callback();
},
Expand Down
3 changes: 2 additions & 1 deletion packages/react-server/src/ReactFlightServer.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {enableFlightReadableStream} from 'shared/ReactFeatureFlags';

import {
scheduleWork,
scheduleMicrotask,
flushBuffered,
beginWriting,
writeChunkAndReturn,
Expand Down Expand Up @@ -1517,7 +1518,7 @@ function pingTask(request: Request, task: Task): void {
pingedTasks.push(task);
if (pingedTasks.length === 1) {
request.flushScheduled = request.destination !== null;
scheduleWork(() => performWork(request));
scheduleMicrotask(() => performWork(request));
}
}

Expand Down
13 changes: 13 additions & 0 deletions packages/react-server/src/ReactServerStreamConfigBrowser.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,19 @@ export type PrecomputedChunk = Uint8Array;
export opaque type Chunk = Uint8Array;
export type BinaryChunk = Uint8Array;

function handleErrorInNextTick(error: any) {
setTimeout(() => {
throw error;
});
}

export const scheduleMicrotask: (callback: () => void) => void =
typeof queueMicrotask === 'function'
? queueMicrotask
: callback => {
Promise.resolve(null).then(callback).catch(handleErrorInNextTick);
};

export function scheduleWork(callback: () => void) {
callback();
}
Expand Down
2 changes: 2 additions & 0 deletions packages/react-server/src/ReactServerStreamConfigBun.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ export type PrecomputedChunk = string;
export opaque type Chunk = string;
export type BinaryChunk = $ArrayBufferView;

export const scheduleMicrotask = queueMicrotask;

export function scheduleWork(callback: () => void) {
callback();
}
Expand Down
13 changes: 13 additions & 0 deletions packages/react-server/src/ReactServerStreamConfigEdge.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,19 @@ export type PrecomputedChunk = Uint8Array;
export opaque type Chunk = Uint8Array;
export type BinaryChunk = Uint8Array;

function handleErrorInNextTick(error: any) {
setTimeout(() => {
throw error;
});
}

export const scheduleMicrotask: (callback: () => void) => void =
typeof queueMicrotask === 'function'
? queueMicrotask
: callback => {
Promise.resolve(null).then(callback).catch(handleErrorInNextTick);
};

export function scheduleWork(callback: () => void) {
setTimeout(callback, 0);
}
Expand Down
2 changes: 2 additions & 0 deletions packages/react-server/src/ReactServerStreamConfigNode.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ export type PrecomputedChunk = Uint8Array;
export opaque type Chunk = string;
export type BinaryChunk = Uint8Array;

export const scheduleMicrotask = queueMicrotask;

export function scheduleWork(callback: () => void) {
setImmediate(callback);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export opaque type PrecomputedChunk = mixed; // eslint-disable-line no-undef
export opaque type Chunk = mixed; // eslint-disable-line no-undef
export opaque type BinaryChunk = mixed; // eslint-disable-line no-undef

export const scheduleMicrotask = $$$config.scheduleMicrotask;
export const scheduleWork = $$$config.scheduleWork;
export const beginWriting = $$$config.beginWriting;
export const writeChunk = $$$config.writeChunk;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,19 @@ export interface Destination {
onError(error: mixed): void;
}

function handleErrorInNextTick(error: any) {
setTimeout(() => {
throw error;
});
}

export const scheduleMicrotask: (callback: () => void) => void =
typeof queueMicrotask === 'function'
? queueMicrotask
: callback => {
Promise.resolve(null).then(callback).catch(handleErrorInNextTick);
};

export function scheduleWork(callback: () => void) {
callback();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@

export * from '../ReactServerStreamConfigFB';

export function scheduleMicrotask(callback: () => void) {
// We don't schedule work in this model, and instead expect performWork to always be called repeatedly.
}

export function scheduleWork(callback: () => void) {
// We don't schedule work in this model, and instead expect performWork to always be called repeatedly.
}
3 changes: 2 additions & 1 deletion scripts/error-codes/codes.json
Original file line number Diff line number Diff line change
Expand Up @@ -511,5 +511,6 @@
"523": "The render was aborted due to being postponed.",
"524": "Values cannot be passed to next() of AsyncIterables passed to Client Components.",
"525": "A React Element from an older version of React was rendered. This is not supported. It can happen if:\n- Multiple copies of the \"react\" package is used.\n- A library pre-bundled an old copy of \"react\" or \"react/jsx-runtime\".\n- A compiler tries to \"inline\" JSX instead of using the runtime.",
"526": "Could not reference an opaque temporary reference. This is likely due to misconfiguring the temporaryReferences options on the server."
"526": "Could not reference an opaque temporary reference. This is likely due to misconfiguring the temporaryReferences options on the server.",
"527": "Legacy React Server builds should not be using scheduleMicrotask. This is a bug in React."
}

0 comments on commit 87178ee

Please sign in to comment.