Skip to content

Commit 7481098

Browse files
Fix: Leak of grpc resources on terminate. (#7847)
* Fix: Leak of grpc resources on terminate.
1 parent 03ba262 commit 7481098

File tree

8 files changed

+63
-2
lines changed

8 files changed

+63
-2
lines changed

.changeset/cold-carpets-sing.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@firebase/firestore": patch
3+
---
4+
5+
Fixed leak of grpc-js resources on terminate.

packages/firestore/src/core/component_provider.ts

+6-2
Original file line numberDiff line numberDiff line change
@@ -483,7 +483,11 @@ export class OnlineComponentProvider {
483483
);
484484
}
485485

486-
terminate(): Promise<void> {
487-
return remoteStoreShutdown(this.remoteStore);
486+
async terminate(): Promise<void> {
487+
await remoteStoreShutdown(this.remoteStore);
488+
489+
if (this.datastore) {
490+
await this.datastore.terminate();
491+
}
488492
}
489493
}

packages/firestore/src/platform/node/grpc_connection.ts

+13
Original file line numberDiff line numberDiff line change
@@ -325,4 +325,17 @@ export class GrpcConnection implements Connection {
325325

326326
return stream;
327327
}
328+
329+
/**
330+
* Closes and cleans up any resources associated with the GrpcConnection.
331+
* If a gRPC client has been generated for this connection, the gRPC client
332+
* is closed. Failure to call terminate on a GrpcConnection can result
333+
* in leaked resources of the gRPC client.
334+
*/
335+
terminate(): void {
336+
if (this.cachedStub) {
337+
this.cachedStub.close();
338+
this.cachedStub = undefined;
339+
}
340+
}
328341
}

packages/firestore/src/remote/connection.ts

+7
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,13 @@ export interface Connection {
9292
*/
9393
readonly shouldResourcePathBeIncludedInRequest: boolean;
9494

95+
/**
96+
* Closes and cleans up any resources associated with the connection. Actual
97+
* resources cleaned are implementation specific. Failure to call `terminate`
98+
* on a connection may result in resource leaks.
99+
*/
100+
terminate(): void;
101+
95102
// TODO(mcg): subscribe to connection state changes.
96103
}
97104

packages/firestore/src/remote/datastore.ts

+1
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@ class DatastoreImpl extends Datastore {
161161

162162
terminate(): void {
163163
this.terminated = true;
164+
this.connection.terminate();
164165
}
165166
}
166167

packages/firestore/src/remote/rest_connection.ts

+9
Original file line numberDiff line numberDiff line change
@@ -189,4 +189,13 @@ export abstract class RestConnection implements Connection {
189189
);
190190
return `${this.baseUrl}/${RPC_URL_VERSION}/${path}:${urlRpcName}`;
191191
}
192+
193+
/**
194+
* Closes and cleans up any resources associated with the connection. This
195+
* implementation is a no-op because there are no resources associated
196+
* with the RestConnection that need to be cleaned up.
197+
*/
198+
terminate(): void {
199+
// No-op
200+
}
192201
}

packages/firestore/test/unit/remote/datastore.test.ts

+18
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,12 @@ use(chaiAsPromised);
4141
// `invokeRPC()` and `invokeStreamingRPC()`.
4242
describe('Datastore', () => {
4343
class MockConnection implements Connection {
44+
terminateInvoked = false;
45+
46+
terminate(): void {
47+
this.terminateInvoked = true;
48+
}
49+
4450
invokeRPC<Req, Resp>(
4551
rpcName: string,
4652
path: string,
@@ -131,6 +137,18 @@ describe('Datastore', () => {
131137
});
132138
});
133139

140+
it('Connection.terminate() invoked if Datastore terminated', async () => {
141+
const connection = new MockConnection();
142+
const datastore = newDatastore(
143+
new EmptyAuthCredentialsProvider(),
144+
new EmptyAppCheckTokenProvider(),
145+
connection,
146+
serializer
147+
);
148+
datastore.terminate();
149+
await expect(connection.terminateInvoked).to.equal(true);
150+
});
151+
134152
it('DatastoreImpl.invokeRPC() rethrows a FirestoreError', async () => {
135153
const connection = new MockConnection();
136154
connection.invokeRPC = () =>

packages/firestore/test/unit/specs/spec_test_components.ts

+4
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,10 @@ export class MockConnection implements Connection {
254254

255255
constructor(private queue: AsyncQueue) {}
256256

257+
terminate(): void {
258+
// no-op
259+
}
260+
257261
shouldResourcePathBeIncludedInRequest: boolean = false;
258262

259263
/**

0 commit comments

Comments
 (0)