Skip to content

[Flight/Fizz] Use Constructors for Large Request/Response Objects in Flight/Fizz #29858

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 41 additions & 22 deletions packages/react-client/src/ReactFlightClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -1136,46 +1136,65 @@ function missingCall() {
);
}

export function createResponse(
function ResponseInstance(
this: $FlowFixMe,
bundlerConfig: SSRModuleMap,
moduleLoading: ModuleLoading,
callServer: void | CallServerCallback,
encodeFormAction: void | EncodeFormActionCallback,
nonce: void | string,
temporaryReferences: void | TemporaryReferenceSet,
findSourceMapURL: void | FindSourceMapURLCallback,
): Response {
) {
const chunks: Map<number, SomeChunk<any>> = new Map();
const response: Response = {
_bundlerConfig: bundlerConfig,
_moduleLoading: moduleLoading,
_callServer: callServer !== undefined ? callServer : missingCall,
_encodeFormAction: encodeFormAction,
_nonce: nonce,
_chunks: chunks,
_stringDecoder: createStringDecoder(),
_fromJSON: (null: any),
_rowState: 0,
_rowID: 0,
_rowTag: 0,
_rowLength: 0,
_buffer: [],
_tempRefs: temporaryReferences,
};
this._bundlerConfig = bundlerConfig;
this._moduleLoading = moduleLoading;
this._callServer = callServer !== undefined ? callServer : missingCall;
this._encodeFormAction = encodeFormAction;
this._nonce = nonce;
this._chunks = chunks;
this._stringDecoder = createStringDecoder();
this._fromJSON = (null: any);
this._rowState = 0;
this._rowID = 0;
this._rowTag = 0;
this._rowLength = 0;
this._buffer = [];
this._tempRefs = temporaryReferences;
if (supportsCreateTask) {
// Any stacks that appear on the server need to be rooted somehow on the client
// so we create a root Task for this response which will be the root owner for any
// elements created by the server. We use the "use server" string to indicate that
// this is where we enter the server from the client.
// TODO: Make this string configurable.
response._debugRootTask = (console: any).createTask('"use server"');
this._debugRootTask = (console: any).createTask('"use server"');
}
if (__DEV__) {
response._debugFindSourceMapURL = findSourceMapURL;
this._debugFindSourceMapURL = findSourceMapURL;
}
// Don't inline this call because it causes closure to outline the call above.
response._fromJSON = createFromJSONCallback(response);
return response;
this._fromJSON = createFromJSONCallback(this);
}

export function createResponse(
bundlerConfig: SSRModuleMap,
moduleLoading: ModuleLoading,
callServer: void | CallServerCallback,
encodeFormAction: void | EncodeFormActionCallback,
nonce: void | string,
temporaryReferences: void | TemporaryReferenceSet,
findSourceMapURL: void | FindSourceMapURLCallback,
): Response {
// $FlowFixMe[invalid-constructor]: the shapes are exact here but Flow doesn't like constructors
return new ResponseInstance(
bundlerConfig,
moduleLoading,
callServer,
encodeFormAction,
nonce,
temporaryReferences,
findSourceMapURL,
);
}

function resolveModel(
Expand Down
105 changes: 69 additions & 36 deletions packages/react-server/src/ReactFizzServer.js
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,8 @@ type RenderTask = {
componentStack: null | ComponentStackNode, // stack frame description of the currently rendering component
thenableState: null | ThenableState,
isFallback: boolean, // whether this task is rendering inside a fallback tree
// DON'T ANY MORE FIELDS. We at 16 already which otherwise requires converting to a constructor.
// Consider splitting into multiple objects or consolidating some fields.
};

type ReplaySet = {
Expand Down Expand Up @@ -264,6 +266,8 @@ type ReplayTask = {
componentStack: null | ComponentStackNode, // stack frame description of the currently rendering component
thenableState: null | ThenableState,
isFallback: boolean, // whether this task is rendering inside a fallback tree
// DON'T ANY MORE FIELDS. We at 16 already which otherwise requires converting to a constructor.
// Consider splitting into multiple objects or consolidating some fields.
};

export type Task = RenderTask | ReplayTask;
Expand Down Expand Up @@ -365,7 +369,8 @@ function defaultErrorHandler(error: mixed) {

function noop(): void {}

export function createRequest(
function RequestInstance(
this: $FlowFixMe,
children: ReactNodeList,
resumableState: ResumableState,
renderState: RenderState,
Expand All @@ -378,45 +383,43 @@ export function createRequest(
onFatalError: void | ((error: mixed) => void),
onPostpone: void | ((reason: string, postponeInfo: PostponeInfo) => void),
formState: void | null | ReactFormState<any, any>,
): Request {
) {
const pingedTasks: Array<Task> = [];
const abortSet: Set<Task> = new Set();
const request: Request = {
destination: null,
flushScheduled: false,
resumableState,
renderState,
rootFormatContext,
progressiveChunkSize:
progressiveChunkSize === undefined
? DEFAULT_PROGRESSIVE_CHUNK_SIZE
: progressiveChunkSize,
status: OPEN,
fatalError: null,
nextSegmentId: 0,
allPendingTasks: 0,
pendingRootTasks: 0,
completedRootSegment: null,
abortableTasks: abortSet,
pingedTasks: pingedTasks,
clientRenderedBoundaries: ([]: Array<SuspenseBoundary>),
completedBoundaries: ([]: Array<SuspenseBoundary>),
partialBoundaries: ([]: Array<SuspenseBoundary>),
trackedPostpones: null,
onError: onError === undefined ? defaultErrorHandler : onError,
onPostpone: onPostpone === undefined ? noop : onPostpone,
onAllReady: onAllReady === undefined ? noop : onAllReady,
onShellReady: onShellReady === undefined ? noop : onShellReady,
onShellError: onShellError === undefined ? noop : onShellError,
onFatalError: onFatalError === undefined ? noop : onFatalError,
formState: formState === undefined ? null : formState,
};
this.destination = null;
this.flushScheduled = false;
this.resumableState = resumableState;
this.renderState = renderState;
this.rootFormatContext = rootFormatContext;
this.progressiveChunkSize =
progressiveChunkSize === undefined
? DEFAULT_PROGRESSIVE_CHUNK_SIZE
: progressiveChunkSize;
this.status = OPEN;
this.fatalError = null;
this.nextSegmentId = 0;
this.allPendingTasks = 0;
this.pendingRootTasks = 0;
this.completedRootSegment = null;
this.abortableTasks = abortSet;
this.pingedTasks = pingedTasks;
this.clientRenderedBoundaries = ([]: Array<SuspenseBoundary>);
this.completedBoundaries = ([]: Array<SuspenseBoundary>);
this.partialBoundaries = ([]: Array<SuspenseBoundary>);
this.trackedPostpones = null;
this.onError = onError === undefined ? defaultErrorHandler : onError;
this.onPostpone = onPostpone === undefined ? noop : onPostpone;
this.onAllReady = onAllReady === undefined ? noop : onAllReady;
this.onShellReady = onShellReady === undefined ? noop : onShellReady;
this.onShellError = onShellError === undefined ? noop : onShellError;
this.onFatalError = onFatalError === undefined ? noop : onFatalError;
this.formState = formState === undefined ? null : formState;
if (__DEV__) {
request.didWarnForKey = null;
this.didWarnForKey = null;
}
// This segment represents the root fallback.
const rootSegment = createPendingSegment(
request,
this,
0,
null,
rootFormatContext,
Expand All @@ -427,7 +430,7 @@ export function createRequest(
// There is no parent so conceptually, we're unblocked to flush this segment.
rootSegment.parentFlushed = true;
const rootTask = createRenderTask(
request,
this,
null,
children,
-1,
Expand All @@ -444,7 +447,37 @@ export function createRequest(
false,
);
pingedTasks.push(rootTask);
return request;
}

export function createRequest(
children: ReactNodeList,
resumableState: ResumableState,
renderState: RenderState,
rootFormatContext: FormatContext,
progressiveChunkSize: void | number,
onError: void | ((error: mixed, errorInfo: ErrorInfo) => ?string),
onAllReady: void | (() => void),
onShellReady: void | (() => void),
onShellError: void | ((error: mixed) => void),
onFatalError: void | ((error: mixed) => void),
onPostpone: void | ((reason: string, postponeInfo: PostponeInfo) => void),
formState: void | null | ReactFormState<any, any>,
): Request {
// $FlowFixMe[invalid-constructor]: the shapes are exact here but Flow doesn't like constructors
return new RequestInstance(
children,
resumableState,
renderState,
rootFormatContext,
progressiveChunkSize,
onError,
onAllReady,
onShellReady,
onShellError,
onFatalError,
onPostpone,
formState,
);
}

export function createPrerenderRequest(
Expand Down
89 changes: 55 additions & 34 deletions packages/react-server/src/ReactFlightServer.js
Original file line number Diff line number Diff line change
Expand Up @@ -473,15 +473,16 @@ const ABORTING = 1;
const CLOSING = 2;
const CLOSED = 3;

export function createRequest(
function RequestInstance(
this: $FlowFixMe,
model: ReactClientValue,
bundlerConfig: ClientManifest,
onError: void | ((error: mixed) => ?string),
identifierPrefix?: string,
onPostpone: void | ((reason: string) => void),
environmentName: void | string,
temporaryReferences: void | TemporaryReferenceSet,
): Request {
) {
if (
ReactSharedInternals.A !== null &&
ReactSharedInternals.A !== DefaultAsyncDispatcher
Expand All @@ -499,42 +500,62 @@ export function createRequest(
TaintRegistryPendingRequests.add(cleanupQueue);
}
const hints = createHints();
const request: Request = ({
status: OPEN,
flushScheduled: false,
fatalError: null,
destination: null,
bundlerConfig,
cache: new Map(),
nextChunkId: 0,
pendingChunks: 0,
hints,
abortListeners: new Set(),
abortableTasks: abortSet,
pingedTasks: pingedTasks,
completedImportChunks: ([]: Array<Chunk>),
completedHintChunks: ([]: Array<Chunk>),
completedRegularChunks: ([]: Array<Chunk | BinaryChunk>),
completedErrorChunks: ([]: Array<Chunk>),
writtenSymbols: new Map(),
writtenClientReferences: new Map(),
writtenServerReferences: new Map(),
writtenObjects: new WeakMap(),
temporaryReferences: temporaryReferences,
identifierPrefix: identifierPrefix || '',
identifierCount: 1,
taintCleanupQueue: cleanupQueue,
onError: onError === undefined ? defaultErrorHandler : onError,
onPostpone: onPostpone === undefined ? defaultPostponeHandler : onPostpone,
}: any);
this.status = OPEN;
this.flushScheduled = false;
this.fatalError = null;
this.destination = null;
this.bundlerConfig = bundlerConfig;
this.cache = new Map();
this.nextChunkId = 0;
this.pendingChunks = 0;
this.hints = hints;
this.abortListeners = new Set();
this.abortableTasks = abortSet;
this.pingedTasks = pingedTasks;
this.completedImportChunks = ([]: Array<Chunk>);
this.completedHintChunks = ([]: Array<Chunk>);
this.completedRegularChunks = ([]: Array<Chunk | BinaryChunk>);
this.completedErrorChunks = ([]: Array<Chunk>);
this.writtenSymbols = new Map();
this.writtenClientReferences = new Map();
this.writtenServerReferences = new Map();
this.writtenObjects = new WeakMap();
this.temporaryReferences = temporaryReferences;
this.identifierPrefix = identifierPrefix || '';
this.identifierCount = 1;
this.taintCleanupQueue = cleanupQueue;
this.onError = onError === undefined ? defaultErrorHandler : onError;
this.onPostpone =
onPostpone === undefined ? defaultPostponeHandler : onPostpone;

if (__DEV__) {
request.environmentName =
this.environmentName =
environmentName === undefined ? 'Server' : environmentName;
request.didWarnForKey = null;
this.didWarnForKey = null;
}
const rootTask = createTask(request, model, null, false, abortSet);
const rootTask = createTask(this, model, null, false, abortSet);
pingedTasks.push(rootTask);
return request;
}

export function createRequest(
model: ReactClientValue,
bundlerConfig: ClientManifest,
onError: void | ((error: mixed) => ?string),
identifierPrefix?: string,
onPostpone: void | ((reason: string) => void),
environmentName: void | string,
temporaryReferences: void | TemporaryReferenceSet,
): Request {
// $FlowFixMe[invalid-constructor]: the shapes are exact here but Flow doesn't like constructors
return new RequestInstance(
model,
bundlerConfig,
onError,
identifierPrefix,
onPostpone,
environmentName,
temporaryReferences,
);
}

let currentRequest: null | Request = null;
Expand Down