@@ -264,6 +264,113 @@ public void twoCallsAndGracefulShutdown() {
264264 verifyNoMoreInteractions (mockStream );
265265 }
266266
267+ @ Test
268+ public void callAndShutdownNow () {
269+ FakeNameResolverFactory nameResolverFactory = new FakeNameResolverFactory (true );
270+ ManagedChannel channel = createChannel (nameResolverFactory , NO_INTERCEPTOR );
271+ verifyNoMoreInteractions (mockTransportFactory );
272+ ClientCall <String , Integer > call = channel .newCall (method , CallOptions .DEFAULT );
273+ verifyNoMoreInteractions (mockTransportFactory );
274+
275+ // Create transport and call
276+ ClientStream mockStream = mock (ClientStream .class );
277+ Metadata headers = new Metadata ();
278+ when (mockTransportFactory .newClientTransport (
279+ any (SocketAddress .class ), any (String .class ), any (String .class )))
280+ .thenReturn (mockTransport );
281+ when (mockTransport .newStream (same (method ), same (headers ))).thenReturn (mockStream );
282+ call .start (mockCallListener , headers );
283+ verify (mockTransportFactory , timeout (1000 ))
284+ .newClientTransport (same (socketAddress ), eq (authority ), any (String .class ));
285+ verify (mockTransport , timeout (1000 )).start (transportListenerCaptor .capture ());
286+ ManagedClientTransport .Listener transportListener = transportListenerCaptor .getValue ();
287+ transportListener .transportReady ();
288+ verify (mockTransport , timeout (1000 )).newStream (same (method ), same (headers ));
289+ verify (mockStream , timeout (1000 )).start (streamListenerCaptor .capture ());
290+ verify (mockStream ).setCompressor (isA (Compressor .class ));
291+ // Depends on how quick the real transport is created, ClientCallImpl may start on mockStream
292+ // directly, or on a DelayedStream which later starts mockStream. In the former case,
293+ // setMessageCompression() is not called. In the latter case, it is (in
294+ // DelayedStream.startStream()).
295+ verify (mockStream , atMost (1 )).setMessageCompression (anyBoolean ());
296+ ClientStreamListener streamListener = streamListenerCaptor .getValue ();
297+
298+ // ShutdownNow
299+ channel .shutdownNow ();
300+ assertTrue (channel .isShutdown ());
301+ assertFalse (channel .isTerminated ());
302+ // ShutdownNow may or may not invoke shutdown. Ideally it wouldn't, but it doesn't matter much
303+ // either way.
304+ verify (mockTransport , atMost (1 )).shutdown ();
305+ verify (mockTransport ).shutdownNow (any (Status .class ));
306+ assertEquals (1 , nameResolverFactory .resolvers .size ());
307+ assertTrue (nameResolverFactory .resolvers .get (0 ).shutdown );
308+ assertEquals (1 , loadBalancerFactory .balancers .size ());
309+ verify (loadBalancerFactory .balancers .get (0 )).shutdown ();
310+
311+ // Further calls should fail without going to the transport
312+ ClientCall <String , Integer > call3 = channel .newCall (method , CallOptions .DEFAULT );
313+ call3 .start (mockCallListener3 , new Metadata ());
314+ verify (mockCallListener3 , timeout (1000 ))
315+ .onClose (statusCaptor .capture (), any (Metadata .class ));
316+ assertSame (Status .Code .UNAVAILABLE , statusCaptor .getValue ().getCode ());
317+
318+ // Finish shutdown
319+ transportListener .transportShutdown (Status .CANCELLED );
320+ assertFalse (channel .isTerminated ());
321+ Metadata trailers = new Metadata ();
322+ streamListener .closed (Status .CANCELLED , trailers );
323+ verify (mockCallListener , timeout (1000 )).onClose (Status .CANCELLED , trailers );
324+ assertFalse (channel .isTerminated ());
325+
326+ transportListener .transportTerminated ();
327+ assertTrue (channel .isTerminated ());
328+
329+ verify (mockTransportFactory ).close ();
330+ verifyNoMoreInteractions (mockTransportFactory );
331+ verify (mockTransport , atLeast (0 )).getLogId ();
332+ verifyNoMoreInteractions (mockTransport );
333+ verifyNoMoreInteractions (mockStream );
334+ }
335+
336+ /** Make sure shutdownNow() after shutdown() has an effect. */
337+ @ Test
338+ public void callAndShutdownAndShutdownNow () {
339+ ManagedChannel channel = createChannel (new FakeNameResolverFactory (true ), NO_INTERCEPTOR );
340+ ClientCall <String , Integer > call = channel .newCall (method , CallOptions .DEFAULT );
341+
342+ // Create transport and call
343+ ClientStream mockStream = mock (ClientStream .class );
344+ Metadata headers = new Metadata ();
345+ when (mockTransport .newStream (same (method ), same (headers ))).thenReturn (mockStream );
346+ call .start (mockCallListener , headers );
347+ verify (mockTransport , timeout (1000 )).start (transportListenerCaptor .capture ());
348+ ManagedClientTransport .Listener transportListener = transportListenerCaptor .getValue ();
349+ transportListener .transportReady ();
350+ verify (mockStream , timeout (1000 )).start (streamListenerCaptor .capture ());
351+ ClientStreamListener streamListener = streamListenerCaptor .getValue ();
352+
353+ // ShutdownNow
354+ channel .shutdown ();
355+ channel .shutdownNow ();
356+ // ShutdownNow may or may not invoke shutdown. Ideally it wouldn't, but it doesn't matter much
357+ // either way.
358+ verify (mockTransport , atMost (2 )).shutdown ();
359+ verify (mockTransport ).shutdownNow (any (Status .class ));
360+
361+ // Finish shutdown
362+ transportListener .transportShutdown (Status .CANCELLED );
363+ assertFalse (channel .isTerminated ());
364+ Metadata trailers = new Metadata ();
365+ streamListener .closed (Status .CANCELLED , trailers );
366+ verify (mockCallListener , timeout (1000 )).onClose (Status .CANCELLED , trailers );
367+ assertFalse (channel .isTerminated ());
368+
369+ transportListener .transportTerminated ();
370+ assertTrue (channel .isTerminated ());
371+ }
372+
373+
267374 @ Test
268375 public void interceptor () throws Exception {
269376 final AtomicLong atomic = new AtomicLong ();
0 commit comments