Skip to content

Commit 99918f1

Browse files
authored
change: pool.ts: assign unique IDs to clients and include those IDs in log messages (#2416)
1 parent c7ee682 commit 99918f1

File tree

2 files changed

+99
-20
lines changed

2 files changed

+99
-20
lines changed

dev/src/pool.ts

Lines changed: 90 additions & 11 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
@@ -80,7 +94,13 @@ export class ClientPool<T> {
8094
private readonly clientFactory: (requiresGrpc: boolean) => T,
8195
private readonly clientDestructor: (client: T) => Promise<void> = () =>
8296
Promise.resolve()
83-
) {}
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',
142+
'Re-using existing client [%s] with %s remaining operations',
143+
selectedClientId,
122144
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)',
151+
'Creating a new client [%s] (requiresGrpc: %s)',
152+
newClientId,
129153
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',
300-
this.opCount
338+
'Waiting for %s pending operations to complete before terminating (%s)',
339+
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: 9 additions & 9 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
@@ -341,8 +341,8 @@ describe('Client pool', () => {
341341

342342
it('bin packs operations', async () => {
343343
let clientCount = 0;
344-
const clientPool = new ClientPool<number>(2, 0, () => {
345-
return ++clientCount;
344+
const clientPool = new ClientPool<{count: number}>(2, 0, () => {
345+
return {count: ++clientCount};
346346
});
347347

348348
expect(clientPool.size).to.equal(0);
@@ -351,23 +351,23 @@ describe('Client pool', () => {
351351
// client, 2 on the second and 1 on the third.
352352
const operationPromises = deferredPromises(7);
353353
clientPool.run(REQUEST_TAG, USE_REST, client => {
354-
expect(client).to.be.equal(1);
354+
expect(client.count).to.be.equal(1);
355355
return operationPromises[0].promise;
356356
});
357357
clientPool.run(REQUEST_TAG, USE_REST, client => {
358-
expect(client).to.be.equal(1);
358+
expect(client.count).to.be.equal(1);
359359
return operationPromises[1].promise;
360360
});
361361
const thirdOperation = clientPool.run(REQUEST_TAG, USE_REST, client => {
362-
expect(client).to.be.equal(2);
362+
expect(client.count).to.be.equal(2);
363363
return operationPromises[2].promise;
364364
});
365365
clientPool.run(REQUEST_TAG, USE_REST, client => {
366-
expect(client).to.be.equal(2);
366+
expect(client.count).to.be.equal(2);
367367
return operationPromises[3].promise;
368368
});
369369
clientPool.run(REQUEST_TAG, USE_REST, client => {
370-
expect(client).to.be.equal(3);
370+
expect(client.count).to.be.equal(3);
371371
return operationPromises[4].promise;
372372
});
373373

@@ -378,7 +378,7 @@ describe('Client pool', () => {
378378
// A newly scheduled operation should use the first client that has a free
379379
// slot.
380380
clientPool.run(REQUEST_TAG, USE_REST, async client => {
381-
expect(client).to.be.equal(2);
381+
expect(client.count).to.be.equal(2);
382382
});
383383
});
384384

0 commit comments

Comments
 (0)