mirror of
https://github.com/zebrajr/react.git
synced 2025-12-06 12:20:20 +01:00
[Flight] Remove back pointers to the Response from the Chunks (#33620)
This frees some memory that will be even more important in a follow up. Currently, all `ReactPromise` instances hold onto their original `Response`. The `Response` holds onto all objects that were in that response since they're needed in case the parsed content ends up referring to an existing object. If everything you retain are plain objects then that's fine and the `Response` gets GC:ed, but if you're retaining a `Promise` itself then it holds onto the whole `Response`. The only thing that needs this reference at all is a `ResolvedModelChunk` since it will lazily initialize e.g. by calling `.then` on itself and so we need to know where to find any sibling chunks it may refer to. However, we can just store the `Response` on the `reason` field for this particular state. That way when all lazy values are touched and initialized the `Response` is freed. We also free up some memory by getting rid of the extra field.
This commit is contained in:
parent
aab72cb1cb
commit
c80c69fa96
73
packages/react-client/src/ReactFlightClient.js
vendored
73
packages/react-client/src/ReactFlightClient.js
vendored
|
|
@ -165,7 +165,6 @@ type PendingChunk<T> = {
|
||||||
status: 'pending',
|
status: 'pending',
|
||||||
value: null | Array<(T) => mixed>,
|
value: null | Array<(T) => mixed>,
|
||||||
reason: null | Array<(mixed) => mixed>,
|
reason: null | Array<(mixed) => mixed>,
|
||||||
_response: Response,
|
|
||||||
_children: Array<SomeChunk<any>> | ProfilingResult, // Profiling-only
|
_children: Array<SomeChunk<any>> | ProfilingResult, // Profiling-only
|
||||||
_debugInfo?: null | ReactDebugInfo, // DEV-only
|
_debugInfo?: null | ReactDebugInfo, // DEV-only
|
||||||
then(resolve: (T) => mixed, reject?: (mixed) => mixed): void,
|
then(resolve: (T) => mixed, reject?: (mixed) => mixed): void,
|
||||||
|
|
@ -174,7 +173,6 @@ type BlockedChunk<T> = {
|
||||||
status: 'blocked',
|
status: 'blocked',
|
||||||
value: null | Array<(T) => mixed>,
|
value: null | Array<(T) => mixed>,
|
||||||
reason: null | Array<(mixed) => mixed>,
|
reason: null | Array<(mixed) => mixed>,
|
||||||
_response: Response,
|
|
||||||
_children: Array<SomeChunk<any>> | ProfilingResult, // Profiling-only
|
_children: Array<SomeChunk<any>> | ProfilingResult, // Profiling-only
|
||||||
_debugInfo?: null | ReactDebugInfo, // DEV-only
|
_debugInfo?: null | ReactDebugInfo, // DEV-only
|
||||||
then(resolve: (T) => mixed, reject?: (mixed) => mixed): void,
|
then(resolve: (T) => mixed, reject?: (mixed) => mixed): void,
|
||||||
|
|
@ -182,8 +180,7 @@ type BlockedChunk<T> = {
|
||||||
type ResolvedModelChunk<T> = {
|
type ResolvedModelChunk<T> = {
|
||||||
status: 'resolved_model',
|
status: 'resolved_model',
|
||||||
value: UninitializedModel,
|
value: UninitializedModel,
|
||||||
reason: null,
|
reason: Response,
|
||||||
_response: Response,
|
|
||||||
_children: Array<SomeChunk<any>> | ProfilingResult, // Profiling-only
|
_children: Array<SomeChunk<any>> | ProfilingResult, // Profiling-only
|
||||||
_debugInfo?: null | ReactDebugInfo, // DEV-only
|
_debugInfo?: null | ReactDebugInfo, // DEV-only
|
||||||
then(resolve: (T) => mixed, reject?: (mixed) => mixed): void,
|
then(resolve: (T) => mixed, reject?: (mixed) => mixed): void,
|
||||||
|
|
@ -192,7 +189,6 @@ type ResolvedModuleChunk<T> = {
|
||||||
status: 'resolved_module',
|
status: 'resolved_module',
|
||||||
value: ClientReference<T>,
|
value: ClientReference<T>,
|
||||||
reason: null,
|
reason: null,
|
||||||
_response: Response,
|
|
||||||
_children: Array<SomeChunk<any>> | ProfilingResult, // Profiling-only
|
_children: Array<SomeChunk<any>> | ProfilingResult, // Profiling-only
|
||||||
_debugInfo?: null | ReactDebugInfo, // DEV-only
|
_debugInfo?: null | ReactDebugInfo, // DEV-only
|
||||||
then(resolve: (T) => mixed, reject?: (mixed) => mixed): void,
|
then(resolve: (T) => mixed, reject?: (mixed) => mixed): void,
|
||||||
|
|
@ -201,7 +197,6 @@ type InitializedChunk<T> = {
|
||||||
status: 'fulfilled',
|
status: 'fulfilled',
|
||||||
value: T,
|
value: T,
|
||||||
reason: null | FlightStreamController,
|
reason: null | FlightStreamController,
|
||||||
_response: Response,
|
|
||||||
_children: Array<SomeChunk<any>> | ProfilingResult, // Profiling-only
|
_children: Array<SomeChunk<any>> | ProfilingResult, // Profiling-only
|
||||||
_debugInfo?: null | ReactDebugInfo, // DEV-only
|
_debugInfo?: null | ReactDebugInfo, // DEV-only
|
||||||
then(resolve: (T) => mixed, reject?: (mixed) => mixed): void,
|
then(resolve: (T) => mixed, reject?: (mixed) => mixed): void,
|
||||||
|
|
@ -212,7 +207,6 @@ type InitializedStreamChunk<
|
||||||
status: 'fulfilled',
|
status: 'fulfilled',
|
||||||
value: T,
|
value: T,
|
||||||
reason: FlightStreamController,
|
reason: FlightStreamController,
|
||||||
_response: Response,
|
|
||||||
_children: Array<SomeChunk<any>> | ProfilingResult, // Profiling-only
|
_children: Array<SomeChunk<any>> | ProfilingResult, // Profiling-only
|
||||||
_debugInfo?: null | ReactDebugInfo, // DEV-only
|
_debugInfo?: null | ReactDebugInfo, // DEV-only
|
||||||
then(resolve: (ReadableStream) => mixed, reject?: (mixed) => mixed): void,
|
then(resolve: (ReadableStream) => mixed, reject?: (mixed) => mixed): void,
|
||||||
|
|
@ -221,7 +215,6 @@ type ErroredChunk<T> = {
|
||||||
status: 'rejected',
|
status: 'rejected',
|
||||||
value: null,
|
value: null,
|
||||||
reason: mixed,
|
reason: mixed,
|
||||||
_response: Response,
|
|
||||||
_children: Array<SomeChunk<any>> | ProfilingResult, // Profiling-only
|
_children: Array<SomeChunk<any>> | ProfilingResult, // Profiling-only
|
||||||
_debugInfo?: null | ReactDebugInfo, // DEV-only
|
_debugInfo?: null | ReactDebugInfo, // DEV-only
|
||||||
then(resolve: (T) => mixed, reject?: (mixed) => mixed): void,
|
then(resolve: (T) => mixed, reject?: (mixed) => mixed): void,
|
||||||
|
|
@ -230,7 +223,6 @@ type HaltedChunk<T> = {
|
||||||
status: 'halted',
|
status: 'halted',
|
||||||
value: null,
|
value: null,
|
||||||
reason: null,
|
reason: null,
|
||||||
_response: Response,
|
|
||||||
_children: Array<SomeChunk<any>> | ProfilingResult, // Profiling-only
|
_children: Array<SomeChunk<any>> | ProfilingResult, // Profiling-only
|
||||||
_debugInfo?: null | ReactDebugInfo, // DEV-only
|
_debugInfo?: null | ReactDebugInfo, // DEV-only
|
||||||
then(resolve: (T) => mixed, reject?: (mixed) => mixed): void,
|
then(resolve: (T) => mixed, reject?: (mixed) => mixed): void,
|
||||||
|
|
@ -245,16 +237,10 @@ type SomeChunk<T> =
|
||||||
| HaltedChunk<T>;
|
| HaltedChunk<T>;
|
||||||
|
|
||||||
// $FlowFixMe[missing-this-annot]
|
// $FlowFixMe[missing-this-annot]
|
||||||
function ReactPromise(
|
function ReactPromise(status: any, value: any, reason: any) {
|
||||||
status: any,
|
|
||||||
value: any,
|
|
||||||
reason: any,
|
|
||||||
response: Response,
|
|
||||||
) {
|
|
||||||
this.status = status;
|
this.status = status;
|
||||||
this.value = value;
|
this.value = value;
|
||||||
this.reason = reason;
|
this.reason = reason;
|
||||||
this._response = response;
|
|
||||||
if (enableProfilerTimer && enableComponentPerformanceTrack) {
|
if (enableProfilerTimer && enableComponentPerformanceTrack) {
|
||||||
this._children = [];
|
this._children = [];
|
||||||
}
|
}
|
||||||
|
|
@ -401,12 +387,12 @@ export function getRoot<T>(response: Response): Thenable<T> {
|
||||||
|
|
||||||
function createPendingChunk<T>(response: Response): PendingChunk<T> {
|
function createPendingChunk<T>(response: Response): PendingChunk<T> {
|
||||||
// $FlowFixMe[invalid-constructor] Flow doesn't support functions as constructors
|
// $FlowFixMe[invalid-constructor] Flow doesn't support functions as constructors
|
||||||
return new ReactPromise(PENDING, null, null, response);
|
return new ReactPromise(PENDING, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
function createBlockedChunk<T>(response: Response): BlockedChunk<T> {
|
function createBlockedChunk<T>(response: Response): BlockedChunk<T> {
|
||||||
// $FlowFixMe[invalid-constructor] Flow doesn't support functions as constructors
|
// $FlowFixMe[invalid-constructor] Flow doesn't support functions as constructors
|
||||||
return new ReactPromise(BLOCKED, null, null, response);
|
return new ReactPromise(BLOCKED, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
function createErrorChunk<T>(
|
function createErrorChunk<T>(
|
||||||
|
|
@ -414,7 +400,7 @@ function createErrorChunk<T>(
|
||||||
error: mixed,
|
error: mixed,
|
||||||
): ErroredChunk<T> {
|
): ErroredChunk<T> {
|
||||||
// $FlowFixMe[invalid-constructor] Flow doesn't support functions as constructors
|
// $FlowFixMe[invalid-constructor] Flow doesn't support functions as constructors
|
||||||
return new ReactPromise(ERRORED, null, error, response);
|
return new ReactPromise(ERRORED, null, error);
|
||||||
}
|
}
|
||||||
|
|
||||||
function wakeChunk<T>(listeners: Array<(T) => mixed>, value: T): void {
|
function wakeChunk<T>(listeners: Array<(T) => mixed>, value: T): void {
|
||||||
|
|
@ -486,7 +472,7 @@ function createResolvedModelChunk<T>(
|
||||||
value: UninitializedModel,
|
value: UninitializedModel,
|
||||||
): ResolvedModelChunk<T> {
|
): ResolvedModelChunk<T> {
|
||||||
// $FlowFixMe[invalid-constructor] Flow doesn't support functions as constructors
|
// $FlowFixMe[invalid-constructor] Flow doesn't support functions as constructors
|
||||||
return new ReactPromise(RESOLVED_MODEL, value, null, response);
|
return new ReactPromise(RESOLVED_MODEL, value, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
function createResolvedModuleChunk<T>(
|
function createResolvedModuleChunk<T>(
|
||||||
|
|
@ -494,7 +480,7 @@ function createResolvedModuleChunk<T>(
|
||||||
value: ClientReference<T>,
|
value: ClientReference<T>,
|
||||||
): ResolvedModuleChunk<T> {
|
): ResolvedModuleChunk<T> {
|
||||||
// $FlowFixMe[invalid-constructor] Flow doesn't support functions as constructors
|
// $FlowFixMe[invalid-constructor] Flow doesn't support functions as constructors
|
||||||
return new ReactPromise(RESOLVED_MODULE, value, null, response);
|
return new ReactPromise(RESOLVED_MODULE, value, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
function createInitializedTextChunk(
|
function createInitializedTextChunk(
|
||||||
|
|
@ -502,7 +488,7 @@ function createInitializedTextChunk(
|
||||||
value: string,
|
value: string,
|
||||||
): InitializedChunk<string> {
|
): InitializedChunk<string> {
|
||||||
// $FlowFixMe[invalid-constructor] Flow doesn't support functions as constructors
|
// $FlowFixMe[invalid-constructor] Flow doesn't support functions as constructors
|
||||||
return new ReactPromise(INITIALIZED, value, null, response);
|
return new ReactPromise(INITIALIZED, value, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
function createInitializedBufferChunk(
|
function createInitializedBufferChunk(
|
||||||
|
|
@ -510,7 +496,7 @@ function createInitializedBufferChunk(
|
||||||
value: $ArrayBufferView | ArrayBuffer,
|
value: $ArrayBufferView | ArrayBuffer,
|
||||||
): InitializedChunk<Uint8Array> {
|
): InitializedChunk<Uint8Array> {
|
||||||
// $FlowFixMe[invalid-constructor] Flow doesn't support functions as constructors
|
// $FlowFixMe[invalid-constructor] Flow doesn't support functions as constructors
|
||||||
return new ReactPromise(INITIALIZED, value, null, response);
|
return new ReactPromise(INITIALIZED, value, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
function createInitializedIteratorResultChunk<T>(
|
function createInitializedIteratorResultChunk<T>(
|
||||||
|
|
@ -519,12 +505,7 @@ function createInitializedIteratorResultChunk<T>(
|
||||||
done: boolean,
|
done: boolean,
|
||||||
): InitializedChunk<IteratorResult<T, T>> {
|
): InitializedChunk<IteratorResult<T, T>> {
|
||||||
// $FlowFixMe[invalid-constructor] Flow doesn't support functions as constructors
|
// $FlowFixMe[invalid-constructor] Flow doesn't support functions as constructors
|
||||||
return new ReactPromise(
|
return new ReactPromise(INITIALIZED, {done: done, value: value}, null);
|
||||||
INITIALIZED,
|
|
||||||
{done: done, value: value},
|
|
||||||
null,
|
|
||||||
response,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function createInitializedStreamChunk<
|
function createInitializedStreamChunk<
|
||||||
|
|
@ -537,7 +518,7 @@ function createInitializedStreamChunk<
|
||||||
// We use the reason field to stash the controller since we already have that
|
// We use the reason field to stash the controller since we already have that
|
||||||
// field. It's a bit of a hack but efficient.
|
// field. It's a bit of a hack but efficient.
|
||||||
// $FlowFixMe[invalid-constructor] Flow doesn't support functions as constructors
|
// $FlowFixMe[invalid-constructor] Flow doesn't support functions as constructors
|
||||||
return new ReactPromise(INITIALIZED, value, controller, response);
|
return new ReactPromise(INITIALIZED, value, controller);
|
||||||
}
|
}
|
||||||
|
|
||||||
function createResolvedIteratorResultChunk<T>(
|
function createResolvedIteratorResultChunk<T>(
|
||||||
|
|
@ -549,10 +530,11 @@ function createResolvedIteratorResultChunk<T>(
|
||||||
const iteratorResultJSON =
|
const iteratorResultJSON =
|
||||||
(done ? '{"done":true,"value":' : '{"done":false,"value":') + value + '}';
|
(done ? '{"done":true,"value":' : '{"done":false,"value":') + value + '}';
|
||||||
// $FlowFixMe[invalid-constructor] Flow doesn't support functions as constructors
|
// $FlowFixMe[invalid-constructor] Flow doesn't support functions as constructors
|
||||||
return new ReactPromise(RESOLVED_MODEL, iteratorResultJSON, null, response);
|
return new ReactPromise(RESOLVED_MODEL, iteratorResultJSON, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveIteratorResultChunk<T>(
|
function resolveIteratorResultChunk<T>(
|
||||||
|
response: Response,
|
||||||
chunk: SomeChunk<IteratorResult<T, T>>,
|
chunk: SomeChunk<IteratorResult<T, T>>,
|
||||||
value: UninitializedModel,
|
value: UninitializedModel,
|
||||||
done: boolean,
|
done: boolean,
|
||||||
|
|
@ -560,10 +542,11 @@ function resolveIteratorResultChunk<T>(
|
||||||
// To reuse code as much code as possible we add the wrapper element as part of the JSON.
|
// To reuse code as much code as possible we add the wrapper element as part of the JSON.
|
||||||
const iteratorResultJSON =
|
const iteratorResultJSON =
|
||||||
(done ? '{"done":true,"value":' : '{"done":false,"value":') + value + '}';
|
(done ? '{"done":true,"value":' : '{"done":false,"value":') + value + '}';
|
||||||
resolveModelChunk(chunk, iteratorResultJSON);
|
resolveModelChunk(response, chunk, iteratorResultJSON);
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveModelChunk<T>(
|
function resolveModelChunk<T>(
|
||||||
|
response: Response,
|
||||||
chunk: SomeChunk<T>,
|
chunk: SomeChunk<T>,
|
||||||
value: UninitializedModel,
|
value: UninitializedModel,
|
||||||
): void {
|
): void {
|
||||||
|
|
@ -580,6 +563,7 @@ function resolveModelChunk<T>(
|
||||||
const resolvedChunk: ResolvedModelChunk<T> = (chunk: any);
|
const resolvedChunk: ResolvedModelChunk<T> = (chunk: any);
|
||||||
resolvedChunk.status = RESOLVED_MODEL;
|
resolvedChunk.status = RESOLVED_MODEL;
|
||||||
resolvedChunk.value = value;
|
resolvedChunk.value = value;
|
||||||
|
resolvedChunk.reason = response;
|
||||||
if (resolveListeners !== null) {
|
if (resolveListeners !== null) {
|
||||||
// This is unfortunate that we're reading this eagerly if
|
// This is unfortunate that we're reading this eagerly if
|
||||||
// we already have listeners attached since they might no
|
// we already have listeners attached since they might no
|
||||||
|
|
@ -625,6 +609,7 @@ function initializeModelChunk<T>(chunk: ResolvedModelChunk<T>): void {
|
||||||
initializingHandler = null;
|
initializingHandler = null;
|
||||||
|
|
||||||
const resolvedModel = chunk.value;
|
const resolvedModel = chunk.value;
|
||||||
|
const response = chunk.reason;
|
||||||
|
|
||||||
// We go to the BLOCKED state until we've fully resolved this.
|
// We go to the BLOCKED state until we've fully resolved this.
|
||||||
// We do this before parsing in case we try to initialize the same chunk
|
// We do this before parsing in case we try to initialize the same chunk
|
||||||
|
|
@ -639,7 +624,7 @@ function initializeModelChunk<T>(chunk: ResolvedModelChunk<T>): void {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const value: T = parseModel(chunk._response, resolvedModel);
|
const value: T = parseModel(response, resolvedModel);
|
||||||
// Invoke any listeners added while resolving this model. I.e. cyclic
|
// Invoke any listeners added while resolving this model. I.e. cyclic
|
||||||
// references. This may or may not fully resolve the model depending on
|
// references. This may or may not fully resolve the model depending on
|
||||||
// if they were blocked.
|
// if they were blocked.
|
||||||
|
|
@ -1862,7 +1847,7 @@ function resolveModel(
|
||||||
if (!chunk) {
|
if (!chunk) {
|
||||||
chunks.set(id, createResolvedModelChunk(response, model));
|
chunks.set(id, createResolvedModelChunk(response, model));
|
||||||
} else {
|
} else {
|
||||||
resolveModelChunk(chunk, model);
|
resolveModelChunk(response, chunk, model);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2036,7 +2021,7 @@ function startReadableStream<T>(
|
||||||
// to synchronous emitting.
|
// to synchronous emitting.
|
||||||
previousBlockedChunk = null;
|
previousBlockedChunk = null;
|
||||||
}
|
}
|
||||||
resolveModelChunk(chunk, json);
|
resolveModelChunk(response, chunk, json);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -2124,7 +2109,12 @@ function startAsyncIterable<T>(
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
resolveIteratorResultChunk(buffer[nextWriteIndex], value, false);
|
resolveIteratorResultChunk(
|
||||||
|
response,
|
||||||
|
buffer[nextWriteIndex],
|
||||||
|
value,
|
||||||
|
false,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
nextWriteIndex++;
|
nextWriteIndex++;
|
||||||
},
|
},
|
||||||
|
|
@ -2137,12 +2127,18 @@ function startAsyncIterable<T>(
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
resolveIteratorResultChunk(buffer[nextWriteIndex], value, true);
|
resolveIteratorResultChunk(
|
||||||
|
response,
|
||||||
|
buffer[nextWriteIndex],
|
||||||
|
value,
|
||||||
|
true,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
nextWriteIndex++;
|
nextWriteIndex++;
|
||||||
while (nextWriteIndex < buffer.length) {
|
while (nextWriteIndex < buffer.length) {
|
||||||
// In generators, any extra reads from the iterator have the value undefined.
|
// In generators, any extra reads from the iterator have the value undefined.
|
||||||
resolveIteratorResultChunk(
|
resolveIteratorResultChunk(
|
||||||
|
response,
|
||||||
buffer[nextWriteIndex++],
|
buffer[nextWriteIndex++],
|
||||||
'"$undefined"',
|
'"$undefined"',
|
||||||
true,
|
true,
|
||||||
|
|
@ -2178,7 +2174,6 @@ function startAsyncIterable<T>(
|
||||||
INITIALIZED,
|
INITIALIZED,
|
||||||
{done: true, value: undefined},
|
{done: true, value: undefined},
|
||||||
null,
|
null,
|
||||||
response,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
buffer[nextReadIndex] =
|
buffer[nextReadIndex] =
|
||||||
|
|
@ -2946,7 +2941,7 @@ function resolveIOInfo(
|
||||||
chunks.set(id, chunk);
|
chunks.set(id, chunk);
|
||||||
initializeModelChunk(chunk);
|
initializeModelChunk(chunk);
|
||||||
} else {
|
} else {
|
||||||
resolveModelChunk(chunk, model);
|
resolveModelChunk(response, chunk, model);
|
||||||
if (chunk.status === RESOLVED_MODEL) {
|
if (chunk.status === RESOLVED_MODEL) {
|
||||||
initializeModelChunk(chunk);
|
initializeModelChunk(chunk);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user