@@ -523,6 +523,53 @@ void channelCountShouldNotChangeWhenOutstandingRpcsAreWithinLimits() throws Exce
523523 assertThat (pool .entries .get ()).hasSize (2 );
524524 }
525525
526+ @ Test
527+ void customResizeDeltaIsRespected () throws Exception {
528+ ScheduledExecutorService executor = Mockito .mock (ScheduledExecutorService .class );
529+ FixedExecutorProvider provider = FixedExecutorProvider .create (executor );
530+
531+ List <ManagedChannel > channels = new ArrayList <>();
532+ List <ClientCall <Object , Object >> startedCalls = new ArrayList <>();
533+
534+ ChannelFactory channelFactory =
535+ () -> {
536+ ManagedChannel channel = Mockito .mock (ManagedChannel .class );
537+ Mockito .when (channel .newCall (Mockito .any (), Mockito .any ()))
538+ .thenAnswer (
539+ invocation -> {
540+ @ SuppressWarnings ("unchecked" )
541+ ClientCall <Object , Object > clientCall = Mockito .mock (ClientCall .class );
542+ startedCalls .add (clientCall );
543+ return clientCall ;
544+ });
545+
546+ channels .add (channel );
547+ return channel ;
548+ };
549+
550+ pool =
551+ new ChannelPool (
552+ ChannelPoolSettings .builder ()
553+ .setInitialChannelCount (2 )
554+ .setMinRpcsPerChannel (1 )
555+ .setMaxRpcsPerChannel (2 )
556+ .setMaxResizeDelta (5 )
557+ .build (),
558+ channelFactory ,
559+ provider );
560+ assertThat (pool .entries .get ()).hasSize (2 );
561+
562+ // Add 20 RPCs to push expansion
563+ for (int i = 0 ; i < 20 ; i ++) {
564+ ClientCalls .futureUnaryCall (
565+ pool .newCall (METHOD_RECOGNIZE , CallOptions .DEFAULT ), Color .getDefaultInstance ());
566+ }
567+ pool .resize ();
568+ // delta is 15 - 2 = 13. Capped at maxResizeDelta = 5.
569+ // Expected size = 2 + 5 = 7.
570+ assertThat (pool .entries .get ()).hasSize (7 );
571+ }
572+
526573 @ Test
527574 void removedIdleChannelsAreShutdown () throws Exception {
528575 ScheduledExecutorService executor = Mockito .mock (ScheduledExecutorService .class );
@@ -679,6 +726,125 @@ public void onComplete() {}
679726 assertThat (e .getMessage ()).isEqualTo ("Call is already cancelled" );
680727 }
681728
729+ @ Test
730+ void repeatedResizingLogsWarningOnExpand () throws Exception {
731+ ScheduledExecutorService executor = Mockito .mock (ScheduledExecutorService .class );
732+ FixedExecutorProvider provider = FixedExecutorProvider .create (executor );
733+
734+ List <ManagedChannel > channels = new ArrayList <>();
735+ List <ClientCall <Object , Object >> startedCalls = new ArrayList <>();
736+
737+ ChannelFactory channelFactory =
738+ () -> {
739+ ManagedChannel channel = Mockito .mock (ManagedChannel .class );
740+ Mockito .when (channel .newCall (Mockito .any (), Mockito .any ()))
741+ .thenAnswer (
742+ invocation -> {
743+ @ SuppressWarnings ("unchecked" )
744+ ClientCall <Object , Object > clientCall = Mockito .mock (ClientCall .class );
745+ startedCalls .add (clientCall );
746+ return clientCall ;
747+ });
748+
749+ channels .add (channel );
750+ return channel ;
751+ };
752+
753+ pool =
754+ new ChannelPool (
755+ ChannelPoolSettings .builder ()
756+ .setInitialChannelCount (1 )
757+ .setMinRpcsPerChannel (1 )
758+ .setMaxRpcsPerChannel (2 )
759+ .setMaxResizeDelta (1 )
760+ .setMinChannelCount (1 )
761+ .setMaxChannelCount (10 )
762+ .build (),
763+ channelFactory ,
764+ provider );
765+ assertThat (pool .entries .get ()).hasSize (1 );
766+
767+ FakeLogHandler logHandler = new FakeLogHandler ();
768+ ChannelPool .LOG .addHandler (logHandler );
769+
770+ try {
771+ // Add 20 RPCs to push expansion
772+ for (int i = 0 ; i < 20 ; i ++) {
773+ ClientCalls .futureUnaryCall (
774+ pool .newCall (METHOD_RECOGNIZE , CallOptions .DEFAULT ), Color .getDefaultInstance ());
775+ }
776+
777+ // Resize 4 times, should not log warning yet
778+ for (int i = 0 ; i < 4 ; i ++) {
779+ pool .resize ();
780+ }
781+ assertThat (logHandler .getAllMessages ()).isEmpty ();
782+
783+ // 5th resize, should log warning
784+ pool .resize ();
785+ assertThat (logHandler .getAllMessages ()).hasSize (1 );
786+ assertThat (logHandler .getAllMessages ())
787+ .contains (
788+ "Channel pool is repeatedly resizing. Consider adjusting `initialChannelCount` or `maxResizeDelta` to a more reasonable value. "
789+ + "See https://docs.cloud.google.com/java/docs/troubleshooting to enable logging and set `com.google.api.gax.grpc.ChannelPool.level=FINEST` to log the channel pool resize behavior." );
790+
791+ // 6th resize, should not log again
792+ pool .resize ();
793+ assertThat (logHandler .getAllMessages ()).hasSize (1 );
794+ } finally {
795+ ChannelPool .LOG .removeHandler (logHandler );
796+ }
797+ }
798+
799+ @ Test
800+ void repeatedResizingLogsWarningOnShrink () throws Exception {
801+ ScheduledExecutorService executor = Mockito .mock (ScheduledExecutorService .class );
802+ FixedExecutorProvider provider = FixedExecutorProvider .create (executor );
803+
804+ List <ManagedChannel > channels = new ArrayList <>();
805+ ChannelFactory channelFactory =
806+ () -> {
807+ ManagedChannel channel = Mockito .mock (ManagedChannel .class );
808+ channels .add (channel );
809+ return channel ;
810+ };
811+
812+ pool =
813+ new ChannelPool (
814+ ChannelPoolSettings .builder ()
815+ .setInitialChannelCount (10 )
816+ .setMinRpcsPerChannel (1 )
817+ .setMaxRpcsPerChannel (2 )
818+ .setMaxResizeDelta (1 )
819+ .setMinChannelCount (1 )
820+ .setMaxChannelCount (10 )
821+ .build (),
822+ channelFactory ,
823+ provider );
824+ assertThat (pool .entries .get ()).hasSize (10 );
825+
826+ FakeLogHandler logHandler = new FakeLogHandler ();
827+ ChannelPool .LOG .addHandler (logHandler );
828+
829+ try {
830+ // 0 RPCs, should shrink every cycle
831+ // Resize 4 times, should not log warning yet
832+ for (int i = 0 ; i < 4 ; i ++) {
833+ pool .resize ();
834+ }
835+ assertThat (logHandler .getAllMessages ()).isEmpty ();
836+
837+ // 5th resize, should log warning
838+ pool .resize ();
839+ assertThat (logHandler .getAllMessages ())
840+ .contains (
841+ "Channel pool is repeatedly resizing. Consider adjusting `initialChannelCount` or `maxResizeDelta` to a more reasonable value. "
842+ + "See https://docs.cloud.google.com/java/docs/troubleshooting to enable logging and set `com.google.api.gax.grpc.ChannelPool.level=FINEST` to log the channel pool resize behavior." );
843+ } finally {
844+ ChannelPool .LOG .removeHandler (logHandler );
845+ }
846+ }
847+
682848 @ Test
683849 void testDoubleRelease () throws Exception {
684850 FakeLogHandler logHandler = new FakeLogHandler ();
0 commit comments