@@ -18,7 +18,7 @@ import {GoogleError} from 'google-gax';
1818import * as assert from 'assert' ;
1919
2020import { logger } from './logger' ;
21- import { Deferred } from './util' ;
21+ import { Deferred , requestTag as generateClientId } from './util' ;
2222
2323export 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+ }
0 commit comments