Skip to content
This repository was archived by the owner on Mar 4, 2026. It is now read-only.

Commit 1bbca46

Browse files
authored
fix: pool.ts: add more detailed logging for client garbage collection (#2420)
1 parent 0ab21b3 commit 1bbca46

File tree

1 file changed

+171
-26
lines changed

1 file changed

+171
-26
lines changed

dev/src/pool.ts

Lines changed: 171 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -176,8 +176,10 @@ export class ClientPool<T extends object> {
176176
* @internal
177177
*/
178178
private async release(requestTag: string, client: T): Promise<void> {
179+
const clientId = this.clientIdByClient.get(client);
179180
const metadata = this.activeClients.get(client);
180181
assert(metadata && metadata.activeRequestCount > 0, 'No active requests');
182+
181183
this.activeClients.set(client, {
182184
grpcEnabled: metadata.grpcEnabled,
183185
activeRequestCount: metadata.activeRequestCount - 1,
@@ -186,26 +188,38 @@ export class ClientPool<T extends object> {
186188
this.terminateDeferred.resolve();
187189
}
188190

189-
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-
);
198-
this.activeClients.delete(client);
199-
this.failedClients.delete(client);
200-
await this.clientDestructor(client);
201-
logger(
202-
'ClientPool.release',
203-
requestTag,
204-
'Garbage collected client [%s] (%s)',
205-
clientId,
206-
this.lazyLogStringForAllClientIds
207-
);
191+
const gcDetermination = this.shouldGarbageCollectClient(client);
192+
logger(
193+
'ClientPool.release',
194+
requestTag,
195+
'Releasing client [%s] (gc=%s)',
196+
clientId,
197+
gcDetermination
198+
);
199+
200+
if (!gcDetermination.shouldGarbageCollectClient) {
201+
return;
208202
}
203+
204+
logger(
205+
'ClientPool.release',
206+
requestTag,
207+
'Garbage collecting client [%s] (%s)',
208+
clientId,
209+
this.lazyLogStringForAllClientIds
210+
);
211+
212+
this.activeClients.delete(client);
213+
this.failedClients.delete(client);
214+
await this.clientDestructor(client);
215+
216+
logger(
217+
'ClientPool.release',
218+
requestTag,
219+
'Garbage collected client [%s] (%s)',
220+
clientId,
221+
this.lazyLogStringForAllClientIds
222+
);
209223
}
210224

211225
/**
@@ -214,23 +228,36 @@ export class ClientPool<T extends object> {
214228
* @private
215229
* @internal
216230
*/
217-
private shouldGarbageCollectClient(client: T): boolean {
231+
private shouldGarbageCollectClient(
232+
client: T
233+
): ShouldGarbageCollectClientResult {
218234
const clientMetadata = this.activeClients.get(client)!;
219235

220236
if (clientMetadata.activeRequestCount !== 0) {
221237
// Don't garbage collect clients that have active requests.
222-
return false;
238+
return new ClientHasActiveRequests({
239+
shouldGarbageCollectClient: false,
240+
clientActiveRequestCount: clientMetadata.activeRequestCount,
241+
});
223242
}
224243

225244
if (this.grpcEnabled !== clientMetadata.grpcEnabled) {
226245
// We are transitioning to GRPC. Garbage collect REST clients.
227-
return true;
246+
return new PoolIsTransitioningToGrpc({
247+
shouldGarbageCollectClient: true,
248+
clientActiveRequestCount: clientMetadata.activeRequestCount,
249+
poolGrpcEnabled: this.grpcEnabled,
250+
clientGrpcEnabled: clientMetadata.grpcEnabled,
251+
});
228252
}
229253

230254
// Idle clients that have received RST_STREAM errors are always garbage
231255
// collected.
232256
if (this.failedClients.has(client)) {
233-
return true;
257+
return new ClientIsFailed({
258+
shouldGarbageCollectClient: true,
259+
clientActiveRequestCount: clientMetadata.activeRequestCount,
260+
});
234261
}
235262

236263
// Otherwise, only garbage collect if we have too much idle capacity (e.g.
@@ -240,9 +267,17 @@ export class ClientPool<T extends object> {
240267
idleCapacityCount +=
241268
this.concurrentOperationLimit - metadata.activeRequestCount;
242269
}
243-
return (
244-
idleCapacityCount > this.maxIdleClients * this.concurrentOperationLimit
245-
);
270+
271+
const maxIdleCapacityCount =
272+
this.maxIdleClients * this.concurrentOperationLimit;
273+
return new IdleCapacity({
274+
shouldGarbageCollectClient: idleCapacityCount > maxIdleCapacityCount,
275+
clientActiveRequestCount: clientMetadata.activeRequestCount,
276+
idleCapacityCount: idleCapacityCount,
277+
maxIdleCapacityCount: maxIdleCapacityCount,
278+
maxIdleClients: this.maxIdleClients,
279+
concurrentOperationLimit: this.concurrentOperationLimit,
280+
});
246281
}
247282

248283
/**
@@ -386,3 +421,113 @@ class LazyLogStringForAllClientIds<T extends object> {
386421
.join(', ');
387422
}
388423
}
424+
425+
/**
426+
* Minimum data to be included in the objects returned from
427+
* ClientPool.shouldGarbageCollectClient().
428+
*/
429+
abstract class BaseShouldGarbageCollectClientResult {
430+
abstract readonly name: string;
431+
abstract readonly shouldGarbageCollectClient: boolean;
432+
abstract readonly clientActiveRequestCount: number;
433+
434+
/**
435+
* Return a terse, one-line string representation. This makes it easy to
436+
* grep through log output to find the logged values.
437+
*/
438+
toString(): string {
439+
const propertyStrings: string[] = [];
440+
for (const propertyName of Object.getOwnPropertyNames(this)) {
441+
const propertyValue = this[propertyName as keyof typeof this];
442+
propertyStrings.push(`${propertyName}=${propertyValue}`);
443+
}
444+
return '{' + propertyStrings.join(', ') + '}';
445+
}
446+
}
447+
448+
class ClientHasActiveRequests extends BaseShouldGarbageCollectClientResult {
449+
override readonly name = 'ClientHasActiveRequests' as const;
450+
override readonly shouldGarbageCollectClient: false;
451+
override readonly clientActiveRequestCount: number;
452+
453+
constructor(args: {
454+
shouldGarbageCollectClient: false;
455+
clientActiveRequestCount: number;
456+
}) {
457+
super();
458+
this.shouldGarbageCollectClient = args.shouldGarbageCollectClient;
459+
this.clientActiveRequestCount = args.clientActiveRequestCount;
460+
}
461+
}
462+
463+
class PoolIsTransitioningToGrpc extends BaseShouldGarbageCollectClientResult {
464+
override readonly name = 'PoolIsTransitioningToGrpc' as const;
465+
override readonly shouldGarbageCollectClient: true;
466+
override readonly clientActiveRequestCount: 0;
467+
readonly poolGrpcEnabled: boolean;
468+
readonly clientGrpcEnabled: boolean;
469+
470+
constructor(args: {
471+
shouldGarbageCollectClient: true;
472+
clientActiveRequestCount: 0;
473+
poolGrpcEnabled: boolean;
474+
clientGrpcEnabled: boolean;
475+
}) {
476+
super();
477+
this.shouldGarbageCollectClient = args.shouldGarbageCollectClient;
478+
this.clientActiveRequestCount = args.clientActiveRequestCount;
479+
this.poolGrpcEnabled = args.poolGrpcEnabled;
480+
this.clientGrpcEnabled = args.clientGrpcEnabled;
481+
}
482+
}
483+
484+
class ClientIsFailed extends BaseShouldGarbageCollectClientResult {
485+
override readonly name = 'ClientIsFailed' as const;
486+
override readonly shouldGarbageCollectClient: true;
487+
override readonly clientActiveRequestCount: 0;
488+
489+
constructor(args: {
490+
shouldGarbageCollectClient: true;
491+
clientActiveRequestCount: 0;
492+
}) {
493+
super();
494+
this.shouldGarbageCollectClient = args.shouldGarbageCollectClient;
495+
this.clientActiveRequestCount = args.clientActiveRequestCount;
496+
}
497+
}
498+
499+
class IdleCapacity extends BaseShouldGarbageCollectClientResult {
500+
override readonly name = 'IdleCapacity' as const;
501+
override readonly shouldGarbageCollectClient: boolean;
502+
override readonly clientActiveRequestCount: 0;
503+
readonly idleCapacityCount: number;
504+
readonly maxIdleCapacityCount: number;
505+
readonly maxIdleClients: number;
506+
readonly concurrentOperationLimit: number;
507+
508+
constructor(args: {
509+
shouldGarbageCollectClient: boolean;
510+
clientActiveRequestCount: 0;
511+
idleCapacityCount: number;
512+
maxIdleCapacityCount: number;
513+
maxIdleClients: number;
514+
concurrentOperationLimit: number;
515+
}) {
516+
super();
517+
this.shouldGarbageCollectClient = args.shouldGarbageCollectClient;
518+
this.clientActiveRequestCount = args.clientActiveRequestCount;
519+
this.idleCapacityCount = args.idleCapacityCount;
520+
this.maxIdleCapacityCount = args.maxIdleCapacityCount;
521+
this.maxIdleClients = args.maxIdleClients;
522+
this.concurrentOperationLimit = args.concurrentOperationLimit;
523+
}
524+
}
525+
526+
/**
527+
* The set of return types from ClientPool.shouldGarbageCollectClient().
528+
*/
529+
type ShouldGarbageCollectClientResult =
530+
| ClientHasActiveRequests
531+
| PoolIsTransitioningToGrpc
532+
| ClientIsFailed
533+
| IdleCapacity;

0 commit comments

Comments
 (0)