29
29
*/
30
30
package com .google .api .gax .grpc ;
31
31
32
+ import static com .google .api .gax .grpc .testing .FakeServiceGrpc .METHOD_RECOGNIZE ;
32
33
import static com .google .api .gax .grpc .testing .FakeServiceGrpc .METHOD_SERVER_STREAMING_RECOGNIZE ;
33
34
import static com .google .common .truth .Truth .assertThat ;
34
35
36
+ import com .google .api .core .ApiFuture ;
35
37
import com .google .api .gax .grpc .testing .FakeChannelFactory ;
36
38
import com .google .api .gax .grpc .testing .FakeMethodDescriptor ;
37
- import com .google .api .gax .grpc .testing .FakeServiceGrpc ;
38
39
import com .google .api .gax .rpc .ClientContext ;
39
40
import com .google .api .gax .rpc .ResponseObserver ;
40
41
import com .google .api .gax .rpc .ServerStreamingCallSettings ;
41
42
import com .google .api .gax .rpc .ServerStreamingCallable ;
42
43
import com .google .api .gax .rpc .StreamController ;
44
+ import com .google .api .gax .rpc .UnaryCallSettings ;
45
+ import com .google .api .gax .rpc .UnaryCallable ;
43
46
import com .google .common .base .Preconditions ;
44
47
import com .google .common .collect .ImmutableList ;
45
48
import com .google .common .collect .Lists ;
63
66
import java .util .concurrent .ScheduledFuture ;
64
67
import java .util .concurrent .TimeUnit ;
65
68
import java .util .concurrent .atomic .AtomicInteger ;
69
+ import java .util .logging .Handler ;
70
+ import java .util .logging .LogRecord ;
71
+ import java .util .stream .Collectors ;
66
72
import org .junit .After ;
67
73
import org .junit .Assert ;
68
74
import org .junit .Test ;
@@ -117,7 +123,7 @@ public void testRoundRobin() throws IOException {
117
123
118
124
private void verifyTargetChannel (
119
125
ChannelPool pool , List <ManagedChannel > channels , ManagedChannel targetChannel ) {
120
- MethodDescriptor <Color , Money > methodDescriptor = FakeServiceGrpc . METHOD_RECOGNIZE ;
126
+ MethodDescriptor <Color , Money > methodDescriptor = METHOD_RECOGNIZE ;
121
127
CallOptions callOptions = CallOptions .DEFAULT ;
122
128
@ SuppressWarnings ("unchecked" )
123
129
ClientCall <Color , Money > expectedClientCall = Mockito .mock (ClientCall .class );
@@ -143,7 +149,7 @@ public void ensureEvenDistribution() throws InterruptedException, IOException {
143
149
final ManagedChannel [] channels = new ManagedChannel [numChannels ];
144
150
final AtomicInteger [] counts = new AtomicInteger [numChannels ];
145
151
146
- final MethodDescriptor <Color , Money > methodDescriptor = FakeServiceGrpc . METHOD_RECOGNIZE ;
152
+ final MethodDescriptor <Color , Money > methodDescriptor = METHOD_RECOGNIZE ;
147
153
final CallOptions callOptions = CallOptions .DEFAULT ;
148
154
@ SuppressWarnings ("unchecked" )
149
155
final ClientCall <Color , Money > clientCall = Mockito .mock (ClientCall .class );
@@ -472,23 +478,21 @@ public void channelCountShouldNotChangeWhenOutstandingRpcsAreWithinLimits() thro
472
478
// Start the minimum number of
473
479
for (int i = 0 ; i < 2 ; i ++) {
474
480
ClientCalls .futureUnaryCall (
475
- pool .newCall (FakeServiceGrpc .METHOD_RECOGNIZE , CallOptions .DEFAULT ),
476
- Color .getDefaultInstance ());
481
+ pool .newCall (METHOD_RECOGNIZE , CallOptions .DEFAULT ), Color .getDefaultInstance ());
477
482
}
478
483
pool .resize ();
479
484
assertThat (pool .entries .get ()).hasSize (2 );
480
485
481
486
// Add enough RPCs to be just at the brink of expansion
482
487
for (int i = startedCalls .size (); i < 4 ; i ++) {
483
488
ClientCalls .futureUnaryCall (
484
- pool .newCall (FakeServiceGrpc .METHOD_RECOGNIZE , CallOptions .DEFAULT ),
485
- Color .getDefaultInstance ());
489
+ pool .newCall (METHOD_RECOGNIZE , CallOptions .DEFAULT ), Color .getDefaultInstance ());
486
490
}
487
491
pool .resize ();
488
492
assertThat (pool .entries .get ()).hasSize (2 );
489
493
490
494
// Add another RPC to push expansion
491
- pool .newCall (FakeServiceGrpc . METHOD_RECOGNIZE , CallOptions .DEFAULT );
495
+ pool .newCall (METHOD_RECOGNIZE , CallOptions .DEFAULT );
492
496
pool .resize ();
493
497
assertThat (pool .entries .get ()).hasSize (4 ); // += ChannelPool::MAX_RESIZE_DELTA
494
498
assertThat (startedCalls ).hasSize (5 );
@@ -593,8 +597,7 @@ public void removedActiveChannelsAreShutdown() throws Exception {
593
597
// Start 2 RPCs
594
598
for (int i = 0 ; i < 2 ; i ++) {
595
599
ClientCalls .futureUnaryCall (
596
- pool .newCall (FakeServiceGrpc .METHOD_RECOGNIZE , CallOptions .DEFAULT ),
597
- Color .getDefaultInstance ());
600
+ pool .newCall (METHOD_RECOGNIZE , CallOptions .DEFAULT ), Color .getDefaultInstance ());
598
601
}
599
602
// Complete the first one
600
603
@ SuppressWarnings ("unchecked" )
@@ -663,4 +666,74 @@ public void onComplete() {}
663
666
assertThat (e .getCause ()).isInstanceOf (CancellationException .class );
664
667
assertThat (e .getMessage ()).isEqualTo ("Call is already cancelled" );
665
668
}
669
+
670
+ @ Test
671
+ public void testDoubleRelease () throws Exception {
672
+ FakeLogHandler logHandler = new FakeLogHandler ();
673
+ ChannelPool .LOG .addHandler (logHandler );
674
+
675
+ try {
676
+ // Create a fake channel pool thats backed by mock channels that simply record invocations
677
+ ClientCall mockClientCall = Mockito .mock (ClientCall .class );
678
+ ManagedChannel fakeChannel = Mockito .mock (ManagedChannel .class );
679
+ Mockito .when (fakeChannel .newCall (Mockito .any (), Mockito .any ())).thenReturn (mockClientCall );
680
+ ChannelPoolSettings channelPoolSettings = ChannelPoolSettings .staticallySized (1 );
681
+ ChannelFactory factory = new FakeChannelFactory (ImmutableList .of (fakeChannel ));
682
+
683
+ pool = ChannelPool .create (channelPoolSettings , factory );
684
+
685
+ // Construct a fake callable to use the channel pool
686
+ ClientContext context =
687
+ ClientContext .newBuilder ()
688
+ .setTransportChannel (GrpcTransportChannel .create (pool ))
689
+ .setDefaultCallContext (GrpcCallContext .of (pool , CallOptions .DEFAULT ))
690
+ .build ();
691
+
692
+ UnaryCallSettings <Color , Money > settings =
693
+ UnaryCallSettings .<Color , Money >newUnaryCallSettingsBuilder ().build ();
694
+ UnaryCallable <Color , Money > callable =
695
+ GrpcCallableFactory .createUnaryCallable (
696
+ GrpcCallSettings .create (METHOD_RECOGNIZE ), settings , context );
697
+
698
+ // Start the RPC
699
+ ApiFuture <Money > rpcFuture =
700
+ callable .futureCall (Color .getDefaultInstance (), context .getDefaultCallContext ());
701
+
702
+ // Get the server side listener and intentionally close it twice
703
+ ArgumentCaptor <ClientCall .Listener <?>> clientCallListenerCaptor =
704
+ ArgumentCaptor .forClass (ClientCall .Listener .class );
705
+ Mockito .verify (mockClientCall ).start (clientCallListenerCaptor .capture (), Mockito .any ());
706
+ clientCallListenerCaptor .getValue ().onClose (Status .INTERNAL , new Metadata ());
707
+ clientCallListenerCaptor .getValue ().onClose (Status .UNKNOWN , new Metadata ());
708
+
709
+ // Ensure that the channel pool properly logged the double call and kept the refCount correct
710
+ assertThat (logHandler .getAllMessages ())
711
+ .contains (
712
+ "Call is being closed more than once. Please make sure that onClose() is not being manually called." );
713
+ assertThat (pool .entries .get ()).hasSize (1 );
714
+ ChannelPool .Entry entry = pool .entries .get ().get (0 );
715
+ assertThat (entry .outstandingRpcs .get ()).isEqualTo (0 );
716
+ } finally {
717
+ ChannelPool .LOG .removeHandler (logHandler );
718
+ }
719
+ }
720
+
721
+ private static class FakeLogHandler extends Handler {
722
+ List <LogRecord > records = new ArrayList <>();
723
+
724
+ @ Override
725
+ public void publish (LogRecord record ) {
726
+ records .add (record );
727
+ }
728
+
729
+ @ Override
730
+ public void flush () {}
731
+
732
+ @ Override
733
+ public void close () throws SecurityException {}
734
+
735
+ List <String > getAllMessages () {
736
+ return records .stream ().map (LogRecord ::getMessage ).collect (Collectors .toList ());
737
+ }
738
+ }
666
739
}
0 commit comments