@@ -96,8 +96,7 @@ class ChannelPool extends ManagedChannel {
9696
9797 // Tracks the number of consecutive resize cycles where a resize actually occurred (either expand
9898 // or shrink). Used to detect repeated resizing activity and log a warning.
99- // Note: This field is only accessed safely within resizeSafely() and does not need to be atomic.
100- private int consecutiveResizes = 0 ;
99+ private final AtomicInteger consecutiveResizes = new AtomicInteger (0 );
101100
102101 static ChannelPool create (
103102 ChannelPoolSettings settings ,
@@ -304,61 +303,82 @@ void resize() {
304303 localEntries .stream ().mapToInt (Entry ::getAndResetMaxOutstanding ).sum ();
305304
306305 // Number of channels if each channel operated at max capacity
307- int minChannels =
306+ int calculatedResizeMinChannels =
308307 (int ) Math .ceil (actualOutstandingRpcs / (double ) settings .getMaxRpcsPerChannel ());
309308 // Limit the threshold to absolute range
310- if (minChannels < settings .getMinChannelCount ()) {
311- minChannels = settings .getMinChannelCount ();
309+ if (calculatedResizeMinChannels < settings .getMinChannelCount ()) {
310+ calculatedResizeMinChannels = settings .getMinChannelCount ();
311+ }
312+ // Limit in case the calculated min channel count exceeds the configured max channel count
313+ if (calculatedResizeMinChannels > settings .getMaxChannelCount ()) {
314+ calculatedResizeMinChannels = settings .getMaxChannelCount ();
312315 }
313316
314317 // Number of channels if each channel operated at minimum capacity
315318 // Note: getMinRpcsPerChannel() can return 0, but division by 0 shouldn't cause a problem.
316- int maxChannels =
319+ int calculatedResizeMaxChannels =
317320 (int ) Math .ceil (actualOutstandingRpcs / (double ) settings .getMinRpcsPerChannel ());
318321 // Limit the threshold to absolute range
319- if (maxChannels > settings .getMaxChannelCount ()) {
320- maxChannels = settings .getMaxChannelCount ();
322+ if (calculatedResizeMaxChannels > settings .getMaxChannelCount ()) {
323+ calculatedResizeMaxChannels = settings .getMaxChannelCount ();
321324 }
322- if (maxChannels < minChannels ) {
323- maxChannels = minChannels ;
325+ // Limit in case the calculated max channel count falls below the configured min channel count
326+ if (calculatedResizeMaxChannels < settings .getMinChannelCount ()) {
327+ calculatedResizeMaxChannels = settings .getMinChannelCount ();
324328 }
325329
326- // If the pool were to be resized, try to aim for the middle of the bound, but limit rate of
327- // change.
328- int tentativeTarget = (maxChannels + minChannels ) / 2 ;
330+ // If the pool were to be resized, try to aim for the middle of the bound. The tentativeTarget
331+ // is guaranteed to be between configured min and max channel bounds
332+ int tentativeTarget =
333+ calculatedResizeMinChannels
334+ + (calculatedResizeMaxChannels - calculatedResizeMinChannels ) / 2 ;
329335 int currentSize = localEntries .size ();
336+
337+ // Calculate the desired change in pool size.
330338 int delta = tentativeTarget - currentSize ;
339+
340+ // Dampen the rate of change if the desired delta exceeds the maximum allowed step size.
341+ // Ensure that the step size is capped by the max channel count.
342+ // Note: resize delta value is not enforced to be smaller than max channel count in
343+ // ChannelPoolSettings as DEFAULT_RESIZE_DELTA is 2 and max channel pool count can be 1
331344 int dampenedTarget = tentativeTarget ;
332- if (Math .abs (delta ) > settings .getMaxResizeDelta ()) {
333- dampenedTarget = currentSize + (int ) Math .copySign (settings .getMaxResizeDelta (), delta );
345+ int effectiveMaxResizeDelta =
346+ Math .min (settings .getMaxResizeDelta (), settings .getMaxChannelCount ());
347+
348+ // Rate-limit the change to not exceed the effectiveMaxResizeDelta
349+ if (Math .abs (delta ) > effectiveMaxResizeDelta ) {
350+ // Maintaining the correct direction (positive or negative) to handle expand/shrink
351+ int step = delta > 0 ? effectiveMaxResizeDelta : -effectiveMaxResizeDelta ;
352+ dampenedTarget = currentSize + step ;
334353 }
335354
336355 // Only count as "resized" if the thresholds are crossed and Gax attempts to scale. Checking
337356 // that `dampenedTarget != currentSize` would cause false positives when the pool is within
338357 // bounds but not at the target (target aims for the middle of the bounds)
339- boolean resized = (currentSize < minChannels || currentSize > maxChannels );
358+ boolean resized =
359+ (currentSize < calculatedResizeMinChannels || currentSize > calculatedResizeMaxChannels );
340360 if (resized ) {
341- consecutiveResizes ++ ;
361+ consecutiveResizes . incrementAndGet () ;
342362 } else {
343- consecutiveResizes = 0 ;
363+ consecutiveResizes . set ( 0 ) ;
344364 }
345365
346366 // Log warning only once when the consecutive threshold is reached to avoid spamming logs. Log
347367 // message will repeat if the number of consecutive resizes resets (e.g. stabilizes for a bit).
348368 // However, aim to log once to ensure that this does not incur log spam.
349- if (consecutiveResizes == CONSECUTIVE_RESIZE_THRESHOLD ) {
369+ if (consecutiveResizes . get () == CONSECUTIVE_RESIZE_THRESHOLD ) {
350370 LOG .warning (CHANNEL_POOL_CONSECUTIVE_RESIZING_WARNING );
351371 }
352372
353373 // Only resize the pool when thresholds are crossed
354- if (localEntries .size () < minChannels ) {
374+ if (localEntries .size () < calculatedResizeMinChannels ) {
355375 LOG .fine (
356376 String .format (
357377 "Detected throughput peak of %d, expanding channel pool size: %d -> %d." ,
358378 actualOutstandingRpcs , currentSize , dampenedTarget ));
359379
360380 expand (dampenedTarget );
361- } else if (localEntries .size () > maxChannels ) {
381+ } else if (localEntries .size () > calculatedResizeMaxChannels ) {
362382 LOG .fine (
363383 String .format (
364384 "Detected throughput drop to %d, shrinking channel pool size: %d -> %d." ,
0 commit comments