Skip to content

Commit c80c69f

Browse files
authored
[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.
1 parent aab72cb commit c80c69f

File tree

1 file changed

+34
-39
lines changed

1 file changed

+34
-39
lines changed

packages/react-client/src/ReactFlightClient.js

Lines changed: 34 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,6 @@ type PendingChunk<T> = {
165165
status: 'pending',
166166
value: null | Array<(T) => mixed>,
167167
reason: null | Array<(mixed) => mixed>,
168-
_response: Response,
169168
_children: Array<SomeChunk<any>> | ProfilingResult, // Profiling-only
170169
_debugInfo?: null | ReactDebugInfo, // DEV-only
171170
then(resolve: (T) => mixed, reject?: (mixed) => mixed): void,
@@ -174,16 +173,14 @@ type BlockedChunk<T> = {
174173
status: 'blocked',
175174
value: null | Array<(T) => mixed>,
176175
reason: null | Array<(mixed) => mixed>,
177-
_response: Response,
178176
_children: Array<SomeChunk<any>> | ProfilingResult, // Profiling-only
179177
_debugInfo?: null | ReactDebugInfo, // DEV-only
180178
then(resolve: (T) => mixed, reject?: (mixed) => mixed): void,
181179
};
182180
type ResolvedModelChunk<T> = {
183181
status: 'resolved_model',
184182
value: UninitializedModel,
185-
reason: null,
186-
_response: Response,
183+
reason: Response,
187184
_children: Array<SomeChunk<any>> | ProfilingResult, // Profiling-only
188185
_debugInfo?: null | ReactDebugInfo, // DEV-only
189186
then(resolve: (T) => mixed, reject?: (mixed) => mixed): void,
@@ -192,7 +189,6 @@ type ResolvedModuleChunk<T> = {
192189
status: 'resolved_module',
193190
value: ClientReference<T>,
194191
reason: null,
195-
_response: Response,
196192
_children: Array<SomeChunk<any>> | ProfilingResult, // Profiling-only
197193
_debugInfo?: null | ReactDebugInfo, // DEV-only
198194
then(resolve: (T) => mixed, reject?: (mixed) => mixed): void,
@@ -201,7 +197,6 @@ type InitializedChunk<T> = {
201197
status: 'fulfilled',
202198
value: T,
203199
reason: null | FlightStreamController,
204-
_response: Response,
205200
_children: Array<SomeChunk<any>> | ProfilingResult, // Profiling-only
206201
_debugInfo?: null | ReactDebugInfo, // DEV-only
207202
then(resolve: (T) => mixed, reject?: (mixed) => mixed): void,
@@ -212,7 +207,6 @@ type InitializedStreamChunk<
212207
status: 'fulfilled',
213208
value: T,
214209
reason: FlightStreamController,
215-
_response: Response,
216210
_children: Array<SomeChunk<any>> | ProfilingResult, // Profiling-only
217211
_debugInfo?: null | ReactDebugInfo, // DEV-only
218212
then(resolve: (ReadableStream) => mixed, reject?: (mixed) => mixed): void,
@@ -221,7 +215,6 @@ type ErroredChunk<T> = {
221215
status: 'rejected',
222216
value: null,
223217
reason: mixed,
224-
_response: Response,
225218
_children: Array<SomeChunk<any>> | ProfilingResult, // Profiling-only
226219
_debugInfo?: null | ReactDebugInfo, // DEV-only
227220
then(resolve: (T) => mixed, reject?: (mixed) => mixed): void,
@@ -230,7 +223,6 @@ type HaltedChunk<T> = {
230223
status: 'halted',
231224
value: null,
232225
reason: null,
233-
_response: Response,
234226
_children: Array<SomeChunk<any>> | ProfilingResult, // Profiling-only
235227
_debugInfo?: null | ReactDebugInfo, // DEV-only
236228
then(resolve: (T) => mixed, reject?: (mixed) => mixed): void,
@@ -245,16 +237,10 @@ type SomeChunk<T> =
245237
| HaltedChunk<T>;
246238

247239
// $FlowFixMe[missing-this-annot]
248-
function ReactPromise(
249-
status: any,
250-
value: any,
251-
reason: any,
252-
response: Response,
253-
) {
240+
function ReactPromise(status: any, value: any, reason: any) {
254241
this.status = status;
255242
this.value = value;
256243
this.reason = reason;
257-
this._response = response;
258244
if (enableProfilerTimer && enableComponentPerformanceTrack) {
259245
this._children = [];
260246
}
@@ -401,20 +387,20 @@ export function getRoot<T>(response: Response): Thenable<T> {
401387

402388
function createPendingChunk<T>(response: Response): PendingChunk<T> {
403389
// $FlowFixMe[invalid-constructor] Flow doesn't support functions as constructors
404-
return new ReactPromise(PENDING, null, null, response);
390+
return new ReactPromise(PENDING, null, null);
405391
}
406392

407393
function createBlockedChunk<T>(response: Response): BlockedChunk<T> {
408394
// $FlowFixMe[invalid-constructor] Flow doesn't support functions as constructors
409-
return new ReactPromise(BLOCKED, null, null, response);
395+
return new ReactPromise(BLOCKED, null, null);
410396
}
411397

412398
function createErrorChunk<T>(
413399
response: Response,
414400
error: mixed,
415401
): ErroredChunk<T> {
416402
// $FlowFixMe[invalid-constructor] Flow doesn't support functions as constructors
417-
return new ReactPromise(ERRORED, null, error, response);
403+
return new ReactPromise(ERRORED, null, error);
418404
}
419405

420406
function wakeChunk<T>(listeners: Array<(T) => mixed>, value: T): void {
@@ -486,31 +472,31 @@ function createResolvedModelChunk<T>(
486472
value: UninitializedModel,
487473
): ResolvedModelChunk<T> {
488474
// $FlowFixMe[invalid-constructor] Flow doesn't support functions as constructors
489-
return new ReactPromise(RESOLVED_MODEL, value, null, response);
475+
return new ReactPromise(RESOLVED_MODEL, value, response);
490476
}
491477

492478
function createResolvedModuleChunk<T>(
493479
response: Response,
494480
value: ClientReference<T>,
495481
): ResolvedModuleChunk<T> {
496482
// $FlowFixMe[invalid-constructor] Flow doesn't support functions as constructors
497-
return new ReactPromise(RESOLVED_MODULE, value, null, response);
483+
return new ReactPromise(RESOLVED_MODULE, value, null);
498484
}
499485

500486
function createInitializedTextChunk(
501487
response: Response,
502488
value: string,
503489
): InitializedChunk<string> {
504490
// $FlowFixMe[invalid-constructor] Flow doesn't support functions as constructors
505-
return new ReactPromise(INITIALIZED, value, null, response);
491+
return new ReactPromise(INITIALIZED, value, null);
506492
}
507493

508494
function createInitializedBufferChunk(
509495
response: Response,
510496
value: $ArrayBufferView | ArrayBuffer,
511497
): InitializedChunk<Uint8Array> {
512498
// $FlowFixMe[invalid-constructor] Flow doesn't support functions as constructors
513-
return new ReactPromise(INITIALIZED, value, null, response);
499+
return new ReactPromise(INITIALIZED, value, null);
514500
}
515501

516502
function createInitializedIteratorResultChunk<T>(
@@ -519,12 +505,7 @@ function createInitializedIteratorResultChunk<T>(
519505
done: boolean,
520506
): InitializedChunk<IteratorResult<T, T>> {
521507
// $FlowFixMe[invalid-constructor] Flow doesn't support functions as constructors
522-
return new ReactPromise(
523-
INITIALIZED,
524-
{done: done, value: value},
525-
null,
526-
response,
527-
);
508+
return new ReactPromise(INITIALIZED, {done: done, value: value}, null);
528509
}
529510

530511
function createInitializedStreamChunk<
@@ -537,7 +518,7 @@ function createInitializedStreamChunk<
537518
// We use the reason field to stash the controller since we already have that
538519
// field. It's a bit of a hack but efficient.
539520
// $FlowFixMe[invalid-constructor] Flow doesn't support functions as constructors
540-
return new ReactPromise(INITIALIZED, value, controller, response);
521+
return new ReactPromise(INITIALIZED, value, controller);
541522
}
542523

543524
function createResolvedIteratorResultChunk<T>(
@@ -549,21 +530,23 @@ function createResolvedIteratorResultChunk<T>(
549530
const iteratorResultJSON =
550531
(done ? '{"done":true,"value":' : '{"done":false,"value":') + value + '}';
551532
// $FlowFixMe[invalid-constructor] Flow doesn't support functions as constructors
552-
return new ReactPromise(RESOLVED_MODEL, iteratorResultJSON, null, response);
533+
return new ReactPromise(RESOLVED_MODEL, iteratorResultJSON, response);
553534
}
554535

555536
function resolveIteratorResultChunk<T>(
537+
response: Response,
556538
chunk: SomeChunk<IteratorResult<T, T>>,
557539
value: UninitializedModel,
558540
done: boolean,
559541
): void {
560542
// To reuse code as much code as possible we add the wrapper element as part of the JSON.
561543
const iteratorResultJSON =
562544
(done ? '{"done":true,"value":' : '{"done":false,"value":') + value + '}';
563-
resolveModelChunk(chunk, iteratorResultJSON);
545+
resolveModelChunk(response, chunk, iteratorResultJSON);
564546
}
565547

566548
function resolveModelChunk<T>(
549+
response: Response,
567550
chunk: SomeChunk<T>,
568551
value: UninitializedModel,
569552
): void {
@@ -580,6 +563,7 @@ function resolveModelChunk<T>(
580563
const resolvedChunk: ResolvedModelChunk<T> = (chunk: any);
581564
resolvedChunk.status = RESOLVED_MODEL;
582565
resolvedChunk.value = value;
566+
resolvedChunk.reason = response;
583567
if (resolveListeners !== null) {
584568
// This is unfortunate that we're reading this eagerly if
585569
// we already have listeners attached since they might no
@@ -625,6 +609,7 @@ function initializeModelChunk<T>(chunk: ResolvedModelChunk<T>): void {
625609
initializingHandler = null;
626610

627611
const resolvedModel = chunk.value;
612+
const response = chunk.reason;
628613

629614
// We go to the BLOCKED state until we've fully resolved this.
630615
// 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 {
639624
}
640625

641626
try {
642-
const value: T = parseModel(chunk._response, resolvedModel);
627+
const value: T = parseModel(response, resolvedModel);
643628
// Invoke any listeners added while resolving this model. I.e. cyclic
644629
// references. This may or may not fully resolve the model depending on
645630
// if they were blocked.
@@ -1862,7 +1847,7 @@ function resolveModel(
18621847
if (!chunk) {
18631848
chunks.set(id, createResolvedModelChunk(response, model));
18641849
} else {
1865-
resolveModelChunk(chunk, model);
1850+
resolveModelChunk(response, chunk, model);
18661851
}
18671852
}
18681853

@@ -2036,7 +2021,7 @@ function startReadableStream<T>(
20362021
// to synchronous emitting.
20372022
previousBlockedChunk = null;
20382023
}
2039-
resolveModelChunk(chunk, json);
2024+
resolveModelChunk(response, chunk, json);
20402025
});
20412026
}
20422027
},
@@ -2124,7 +2109,12 @@ function startAsyncIterable<T>(
21242109
false,
21252110
);
21262111
} else {
2127-
resolveIteratorResultChunk(buffer[nextWriteIndex], value, false);
2112+
resolveIteratorResultChunk(
2113+
response,
2114+
buffer[nextWriteIndex],
2115+
value,
2116+
false,
2117+
);
21282118
}
21292119
nextWriteIndex++;
21302120
},
@@ -2137,12 +2127,18 @@ function startAsyncIterable<T>(
21372127
true,
21382128
);
21392129
} else {
2140-
resolveIteratorResultChunk(buffer[nextWriteIndex], value, true);
2130+
resolveIteratorResultChunk(
2131+
response,
2132+
buffer[nextWriteIndex],
2133+
value,
2134+
true,
2135+
);
21412136
}
21422137
nextWriteIndex++;
21432138
while (nextWriteIndex < buffer.length) {
21442139
// In generators, any extra reads from the iterator have the value undefined.
21452140
resolveIteratorResultChunk(
2141+
response,
21462142
buffer[nextWriteIndex++],
21472143
'"$undefined"',
21482144
true,
@@ -2178,7 +2174,6 @@ function startAsyncIterable<T>(
21782174
INITIALIZED,
21792175
{done: true, value: undefined},
21802176
null,
2181-
response,
21822177
);
21832178
}
21842179
buffer[nextReadIndex] =
@@ -2946,7 +2941,7 @@ function resolveIOInfo(
29462941
chunks.set(id, chunk);
29472942
initializeModelChunk(chunk);
29482943
} else {
2949-
resolveModelChunk(chunk, model);
2944+
resolveModelChunk(response, chunk, model);
29502945
if (chunk.status === RESOLVED_MODEL) {
29512946
initializeModelChunk(chunk);
29522947
}

0 commit comments

Comments
 (0)