[Flight] Fix pending chunks count for streams & async iterables in DEV (#35143)
In DEV, we need to prevent the response from being GC'd while there are still pending chunks for ReadableSteams or pending results for AsyncIterables. Co-authored-by: Sebastian "Sebbie" Silbermann <silbermann.sebastian@gmail.com>
Hendrik Liebau committed
Nov 14, 2025 at 23:52 UTC
fb2177c153353621c5e343b0386993e5084f641e
1 file changed
+14
-1
packages/react-client/src/ReactFlightClient.js
+14
-1
@@ -813,6 +813,12 @@ function createInitializedStreamChunk<
813
value: T,
814
controller: FlightStreamController,
815
): InitializedChunk<T> {
816
+ if (__DEV__) {
817
+ // Retain a strong reference to the Response while we wait for chunks.
818
+ if (response._pendingChunks++ === 0) {
819
+ response._weakResponse.response = response;
820
+ }
821
+ }
822
// We use the reason field to stash the controller since we already have that
823
// field. It's a bit of a hack but efficient.
824
// $FlowFixMe[invalid-constructor] Flow doesn't support functions as constructors
@@ -3075,7 +3081,6 @@ function resolveStream<T: ReadableStream | $AsyncIterable<any, any, void>>(
3081
// We already resolved. We didn't expect to see this.
3082
return;
3083
}
3078
- releasePendingChunk(response, chunk);
3084
3085
const resolveListeners = chunk.value;
3086
@@ -3375,6 +3380,14 @@ function stopStream(
3380
// We didn't expect not to have an existing stream;
3381
return;
3382
}
3383
+ if (__DEV__) {
3384
+ if (--response._pendingChunks === 0) {
3385
+ // We're no longer waiting for any more chunks. We can release the strong
3386
+ // reference to the response. We'll regain it if we ask for any more data
3387
+ // later on.
3388
+ response._weakResponse.response = null;
3389
+ }
3390
+ }
3391
const streamChunk: InitializedStreamChunk<any> = (chunk: any);
3392
const controller = streamChunk.reason;
3393
controller.close(row === '' ? '"$undefined"' : row);