fix(common): cancel reader when app is destroyed (#61528)

Streams left in a pending state (due to `break` without cancel) may continue consuming or holding onto data behind the scenes. Calling `reader.cancel()` allows the browser or the underlying system to release any network or memory resources associated with the stream.

PR Close #61528
This commit is contained in:
arturovt
2025-05-20 19:56:29 +03:00
committed by Jessica Janiuk
parent 350776b412
commit af7881abac
+17
View File
@@ -147,6 +147,8 @@ export class FetchBackend implements HttpBackend {
// when the zone is nooped.
const reqZone = typeof Zone !== 'undefined' && Zone.current;
let canceled = false;
// Perform response processing outside of Angular zone to
// ensure no excessive change detection runs are executed
// Here calling the async ReadableStreamDefaultReader.read() is responsible for triggering CD
@@ -157,6 +159,12 @@ export class FetchBackend implements HttpBackend {
// This may happen if the app was explicitly destroyed before
// the response returned entirely.
if (this.appRef.destroyed) {
// Streams left in a pending state (due to `break` without cancel) may
// continue consuming or holding onto data behind the scenes.
// Calling `reader.cancel()` allows the browser or the underlying
// system to release any network or memory resources associated with the stream.
await reader.cancel();
canceled = true;
break;
}
@@ -188,6 +196,15 @@ export class FetchBackend implements HttpBackend {
}
});
// We need to manage the canceled state — because the Streams API does not
// expose a direct `.state` property on the reader.
// We need to `return` because `parseBody` may not be able to parse chunks
// that were only partially read (due to cancellation caused by app destruction).
if (canceled) {
observer.complete();
return;
}
// Combine all chunks.
const chunksAll = this.concatChunks(chunks, receivedLength);
try {