From 2211693d1dd59be3503e28be1a201e5d312f63da Mon Sep 17 00:00:00 2001 From: Mark Duckworth <1124037+MarkDuckworth@users.noreply.github.com> Date: Wed, 14 May 2025 10:31:49 -0600 Subject: [PATCH 1/4] Explicitly close any unclosed WebChannel instances on terminate --- .../platform/browser/webchannel_connection.ts | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/packages/firestore/src/platform/browser/webchannel_connection.ts b/packages/firestore/src/platform/browser/webchannel_connection.ts index 9a69164457e..f5835408c09 100644 --- a/packages/firestore/src/platform/browser/webchannel_connection.ts +++ b/packages/firestore/src/platform/browser/webchannel_connection.ts @@ -59,6 +59,9 @@ export class WebChannelConnection extends RestConnection { private readonly useFetchStreams: boolean; private readonly longPollingOptions: ExperimentalLongPollingOptions; + /** A collection of open WebChannel instances */ + private webChannels: WebChannel[] = []; + constructor(info: DatabaseInfo) { super(info); this.forceLongPolling = info.forceLongPolling; @@ -239,6 +242,7 @@ export class WebChannelConnection extends RestConnection { request ); const channel = webchannelTransport.createWebChannel(url, request); + this.trackWebChannel(channel); // WebChannel supports sending the first message with the handshake - saving // a network round trip. However, it will have to call send in the same @@ -321,6 +325,7 @@ export class WebChannelConnection extends RestConnection { `RPC '${rpcName}' stream ${streamId} transport closed` ); streamBridge.callOnClose(); + this.untrackWebChannel(channel); } }); @@ -427,4 +432,22 @@ export class WebChannelConnection extends RestConnection { }, 0); return streamBridge; } + + /** + * Closes and cleans up any resources associated with the connection. + */ + terminate(): void { + this.webChannels.forEach(webChannel => webChannel.close()); + this.webChannels = []; + } + + trackWebChannel(webChannel: WebChannel): void { + this.webChannels.push(webChannel); + } + + untrackWebChannel(webChannel: WebChannel): void { + this.webChannels = this.webChannels.filter( + instance => instance === webChannel + ); + } } From 6466b3f3eb8ef57dd48640186e7038b2e5504099 Mon Sep 17 00:00:00 2001 From: Mark Duckworth <1124037+MarkDuckworth@users.noreply.github.com> Date: Wed, 14 May 2025 13:10:19 -0600 Subject: [PATCH 2/4] Comments and naming --- .../platform/browser/webchannel_connection.ts | 28 +++++++++++++------ 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/packages/firestore/src/platform/browser/webchannel_connection.ts b/packages/firestore/src/platform/browser/webchannel_connection.ts index f5835408c09..6f290393fb2 100644 --- a/packages/firestore/src/platform/browser/webchannel_connection.ts +++ b/packages/firestore/src/platform/browser/webchannel_connection.ts @@ -60,7 +60,7 @@ export class WebChannelConnection extends RestConnection { private readonly longPollingOptions: ExperimentalLongPollingOptions; /** A collection of open WebChannel instances */ - private webChannels: WebChannel[] = []; + private openWebChannels: WebChannel[] = []; constructor(info: DatabaseInfo) { super(info); @@ -242,7 +242,7 @@ export class WebChannelConnection extends RestConnection { request ); const channel = webchannelTransport.createWebChannel(url, request); - this.trackWebChannel(channel); + this.addOpenWebChannel(channel); // WebChannel supports sending the first message with the handshake - saving // a network round trip. However, it will have to call send in the same @@ -325,7 +325,7 @@ export class WebChannelConnection extends RestConnection { `RPC '${rpcName}' stream ${streamId} transport closed` ); streamBridge.callOnClose(); - this.untrackWebChannel(channel); + this.removeOpenWebChannel(channel); } }); @@ -437,16 +437,26 @@ export class WebChannelConnection extends RestConnection { * Closes and cleans up any resources associated with the connection. */ terminate(): void { - this.webChannels.forEach(webChannel => webChannel.close()); - this.webChannels = []; + // If the Firestore instance is terminated, we will explicitly + // close any remaining open WebChannel instances. + this.openWebChannels.forEach(webChannel => webChannel.close()); + this.openWebChannels = []; } - trackWebChannel(webChannel: WebChannel): void { - this.webChannels.push(webChannel); + /** + * Add a WebChannel instance to the collection of open instances. + * @param webChannel + */ + addOpenWebChannel(webChannel: WebChannel): void { + this.openWebChannels.push(webChannel); } - untrackWebChannel(webChannel: WebChannel): void { - this.webChannels = this.webChannels.filter( + /** + * Remove a WebChannel instance to the collection of open instances. + * @param webChannel + */ + removeOpenWebChannel(webChannel: WebChannel): void { + this.openWebChannels = this.openWebChannels.filter( instance => instance === webChannel ); } From d5693c12419387a701865df8da6696f9573f9e2c Mon Sep 17 00:00:00 2001 From: Mark Duckworth <1124037+MarkDuckworth@users.noreply.github.com> Date: Wed, 14 May 2025 14:38:21 -0600 Subject: [PATCH 3/4] Create spotty-ghosts-kneel.md --- .changeset/spotty-ghosts-kneel.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/spotty-ghosts-kneel.md diff --git a/.changeset/spotty-ghosts-kneel.md b/.changeset/spotty-ghosts-kneel.md new file mode 100644 index 00000000000..0db91b7bf19 --- /dev/null +++ b/.changeset/spotty-ghosts-kneel.md @@ -0,0 +1,5 @@ +--- +"@firebase/firestore": patch +--- + +Clean up leaked WebChannel instances when the Firestore instance is terminated. From 8bd74f7d7f62316168f6d1e11b10d95ddcd7958d Mon Sep 17 00:00:00 2001 From: Mark Duckworth <1124037+MarkDuckworth@users.noreply.github.com> Date: Wed, 14 May 2025 14:39:32 -0600 Subject: [PATCH 4/4] Update webchannel_connection.ts --- .../firestore/src/platform/browser/webchannel_connection.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/firestore/src/platform/browser/webchannel_connection.ts b/packages/firestore/src/platform/browser/webchannel_connection.ts index 6f290393fb2..56f57aa9595 100644 --- a/packages/firestore/src/platform/browser/webchannel_connection.ts +++ b/packages/firestore/src/platform/browser/webchannel_connection.ts @@ -452,7 +452,7 @@ export class WebChannelConnection extends RestConnection { } /** - * Remove a WebChannel instance to the collection of open instances. + * Remove a WebChannel instance from the collection of open instances. * @param webChannel */ removeOpenWebChannel(webChannel: WebChannel): void {