Skip to content

Commit cec047e

Browse files
Merge branch 'main' into node-18
2 parents fc4c934 + 0ab21b3 commit cec047e

File tree

5 files changed

+115
-29
lines changed

5 files changed

+115
-29
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,13 @@
44

55
[1]: https://www.npmjs.com/package/@google-cloud/firestore?activeTab=versions
66

7+
## [7.11.4](https://github.com/googleapis/nodejs-firestore/compare/v7.11.3...v7.11.4) (2025-09-16)
8+
9+
10+
### Bug Fixes
11+
12+
* Improve debug logging for the internal client pool. Added client IDs to debug log statements for client management. ([99918f1](https://github.com/googleapis/nodejs-firestore/commit/99918f1794adee706c4f2685cd3f8aea6dff895e))
13+
714
## [7.11.3](https://github.com/googleapis/nodejs-firestore/compare/v7.11.2...v7.11.3) (2025-07-09)
815

916

dev/src/pool.ts

Lines changed: 92 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import {GoogleError} from 'google-gax';
1818
import * as assert from 'assert';
1919

2020
import {logger} from './logger';
21-
import {Deferred} from './util';
21+
import {Deferred, requestTag as generateClientId} from './util';
2222

2323
export const CLIENT_TERMINATED_ERROR_MSG =
2424
'The client has already been terminated';
@@ -34,13 +34,13 @@ export const CLIENT_TERMINATED_ERROR_MSG =
3434
* @private
3535
* @internal
3636
*/
37-
export class ClientPool<T> {
37+
export class ClientPool<T extends object> {
3838
private grpcEnabled = false;
3939

4040
/**
4141
* Stores each active clients and how many operations it has outstanding.
4242
*/
43-
private activeClients = new Map<
43+
private readonly activeClients = new Map<
4444
T,
4545
{activeRequestCount: number; grpcEnabled: boolean}
4646
>();
@@ -50,7 +50,21 @@ export class ClientPool<T> {
5050
* https://github.com/googleapis/nodejs-firestore/issues/1023) and should
5151
* no longer be used.
5252
*/
53-
private failedClients = new Set<T>();
53+
private readonly failedClients = new Set<T>();
54+
55+
/**
56+
* A mapping from "client" objects to their corresponding IDs. These IDs have
57+
* no semantic meaning but are used for logging to enable tracing the events
58+
* of a particular client over time (such as creating, acquiring, and
59+
* releasing).
60+
*/
61+
private readonly clientIdByClient = new WeakMap<T, string>();
62+
63+
/**
64+
* An object that can be specified to `logger()` to lazily calculate a long
65+
* log message that includes all client IDs of active and pending clients.
66+
*/
67+
private readonly lazyLogStringForAllClientIds: unknown;
5468

5569
/**
5670
* Whether the Firestore instance has been terminated. Once terminated, the
@@ -62,7 +76,7 @@ export class ClientPool<T> {
6276
* Deferred promise that is resolved when there are no active operations on
6377
* the client pool after terminate() has been called.
6478
*/
65-
private terminateDeferred = new Deferred<void>();
79+
private readonly terminateDeferred = new Deferred<void>();
6680

6781
/**
6882
* @param concurrentOperationLimit The number of operations that each client
@@ -79,8 +93,14 @@ export class ClientPool<T> {
7993
private readonly maxIdleClients: number,
8094
private readonly clientFactory: (requiresGrpc: boolean) => T,
8195
private readonly clientDestructor: (client: T) => Promise<void> = () =>
82-
Promise.resolve(),
83-
) {}
96+
Promise.resolve()
97+
) {
98+
this.lazyLogStringForAllClientIds = new LazyLogStringForAllClientIds({
99+
activeClients: this.activeClients,
100+
failedClients: this.failedClients,
101+
clientIdByClient: this.clientIdByClient,
102+
});
103+
}
84104

85105
/**
86106
* Returns an already existing client if it has less than the maximum number
@@ -115,20 +135,25 @@ export class ClientPool<T> {
115135
}
116136

117137
if (selectedClient) {
138+
const selectedClientId = this.clientIdByClient.get(selectedClient);
118139
logger(
119140
'ClientPool.acquire',
120141
requestTag,
121-
'Re-using existing client with %s remaining operations',
122-
this.concurrentOperationLimit - selectedClientRequestCount,
142+
'Re-using existing client [%s] with %s remaining operations',
143+
selectedClientId,
144+
this.concurrentOperationLimit - selectedClientRequestCount
123145
);
124146
} else {
147+
const newClientId = 'cli' + generateClientId();
125148
logger(
126149
'ClientPool.acquire',
127150
requestTag,
128-
'Creating a new client (requiresGrpc: %s)',
129-
requiresGrpc,
151+
'Creating a new client [%s] (requiresGrpc: %s)',
152+
newClientId,
153+
requiresGrpc
130154
);
131155
selectedClient = this.clientFactory(requiresGrpc);
156+
this.clientIdByClient.set(selectedClient, newClientId);
132157
selectedClientRequestCount = 0;
133158
assert(
134159
!this.activeClients.has(selectedClient),
@@ -162,10 +187,24 @@ export class ClientPool<T> {
162187
}
163188

164189
if (this.shouldGarbageCollectClient(client)) {
190+
const clientId = this.clientIdByClient.get(client);
191+
logger(
192+
'ClientPool.release',
193+
requestTag,
194+
'Garbage collecting client [%s] (%s)',
195+
clientId,
196+
this.lazyLogStringForAllClientIds
197+
);
165198
this.activeClients.delete(client);
166199
this.failedClients.delete(client);
167200
await this.clientDestructor(client);
168-
logger('ClientPool.release', requestTag, 'Garbage collected 1 client');
201+
logger(
202+
'ClientPool.release',
203+
requestTag,
204+
'Garbage collected client [%s] (%s)',
205+
clientId,
206+
this.lazyLogStringForAllClientIds
207+
);
169208
}
170209
}
171210

@@ -296,8 +335,9 @@ export class ClientPool<T> {
296335
logger(
297336
'ClientPool.terminate',
298337
/* requestTag= */ null,
299-
'Waiting for %s pending operations to complete before terminating',
338+
'Waiting for %s pending operations to complete before terminating (%s)',
300339
this.opCount,
340+
this.lazyLogStringForAllClientIds
301341
);
302342
await this.terminateDeferred.promise;
303343
}
@@ -307,3 +347,42 @@ export class ClientPool<T> {
307347
}
308348
}
309349
}
350+
351+
/**
352+
* Helper class that, when logged as a direct argument of `logger()`, will
353+
* lazily evaluate to a long string that contains all IDs of both active and
354+
* failed clients.
355+
*/
356+
class LazyLogStringForAllClientIds<T extends object> {
357+
private readonly activeClients: Map<T, unknown>;
358+
private readonly failedClients: Set<T>;
359+
private readonly clientIdByClient: WeakMap<T, string>;
360+
361+
constructor(config: {
362+
activeClients: Map<T, unknown>;
363+
failedClients: Set<T>;
364+
clientIdByClient: WeakMap<T, string>;
365+
}) {
366+
this.activeClients = config.activeClients;
367+
this.failedClients = config.failedClients;
368+
this.clientIdByClient = config.clientIdByClient;
369+
}
370+
371+
toString(): string {
372+
return (
373+
`${this.activeClients.size} active clients: {` +
374+
this.logStringFromClientIds(this.activeClients.keys()) +
375+
'}, ' +
376+
`${this.failedClients.size} failed clients: {` +
377+
this.logStringFromClientIds(this.failedClients) +
378+
'}'
379+
);
380+
}
381+
382+
private logStringFromClientIds(clients: Iterable<T>): string {
383+
return Array.from(clients)
384+
.map(client => this.clientIdByClient.get(client) ?? '<unknown>')
385+
.sort()
386+
.join(', ');
387+
}
388+
}

dev/test/pool.ts

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ function deferredPromises(count: number): Array<Deferred<void>> {
3434
return deferred;
3535
}
3636

37-
function assertOpCount<T>(
37+
function assertOpCount<T extends object>(
3838
pool: ClientPool<T>,
3939
grpcClientOpCount: number,
4040
restClientOpCount: number,
@@ -372,33 +372,33 @@ describe('Client pool', () => {
372372

373373
it('bin packs operations', async () => {
374374
let clientCount = 0;
375-
const clientPool = new ClientPool<number>(2, 0, () => {
376-
return ++clientCount;
375+
const clientPool = new ClientPool<{count: number}>(2, 0, () => {
376+
return {count: ++clientCount};
377377
});
378378

379379
expect(clientPool.size).to.equal(0);
380380

381381
// Create 5 operations, which should schedule 2 operations on the first
382382
// client, 2 on the second and 1 on the third.
383383
const operationPromises = deferredPromises(7);
384-
void clientPool.run(REQUEST_TAG, USE_REST, client => {
385-
expect(client).to.be.equal(1);
384+
clientPool.run(REQUEST_TAG, USE_REST, client => {
385+
expect(client.count).to.be.equal(1);
386386
return operationPromises[0].promise;
387387
});
388-
void clientPool.run(REQUEST_TAG, USE_REST, client => {
389-
expect(client).to.be.equal(1);
388+
clientPool.run(REQUEST_TAG, USE_REST, client => {
389+
expect(client.count).to.be.equal(1);
390390
return operationPromises[1].promise;
391391
});
392392
const thirdOperation = clientPool.run(REQUEST_TAG, USE_REST, client => {
393-
expect(client).to.be.equal(2);
393+
expect(client.count).to.be.equal(2);
394394
return operationPromises[2].promise;
395395
});
396-
void clientPool.run(REQUEST_TAG, USE_REST, client => {
397-
expect(client).to.be.equal(2);
396+
clientPool.run(REQUEST_TAG, USE_REST, client => {
397+
expect(client.count).to.be.equal(2);
398398
return operationPromises[3].promise;
399399
});
400-
void clientPool.run(REQUEST_TAG, USE_REST, client => {
401-
expect(client).to.be.equal(3);
400+
clientPool.run(REQUEST_TAG, USE_REST, client => {
401+
expect(client.count).to.be.equal(3);
402402
return operationPromises[4].promise;
403403
});
404404

@@ -408,8 +408,8 @@ describe('Client pool', () => {
408408

409409
// A newly scheduled operation should use the first client that has a free
410410
// slot.
411-
void clientPool.run(REQUEST_TAG, USE_REST, async client => {
412-
expect(client).to.be.equal(2);
411+
clientPool.run(REQUEST_TAG, USE_REST, async client => {
412+
expect(client.count).to.be.equal(2);
413413
});
414414
});
415415

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@google-cloud/firestore",
33
"description": "Firestore Client Library for Node.js",
4-
"version": "7.11.3",
4+
"version": "7.11.4",
55
"license": "Apache-2.0",
66
"author": "Google Inc.",
77
"engines": {

samples/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
"test": "mocha --timeout 600000"
1212
},
1313
"dependencies": {
14-
"@google-cloud/firestore": "^7.11.3"
14+
"@google-cloud/firestore": "^7.11.4"
1515
},
1616
"devDependencies": {
1717
"chai": "^4.2.0",

0 commit comments

Comments
 (0)