@Test public void immediateDeadlineExceeded() { ClientCall<String, Integer> call = channel.newCall(method, CallOptions.DEFAULT.withDeadlineNanoTime(System.nanoTime())); call.start(mockCallListener, new Metadata.Headers()); verify(mockCallListener, timeout(1000)) .onClose(same(Status.DEADLINE_EXCEEDED), any(Metadata.Trailers.class)); }
@Test public void testNoDeadlockOnShutdown() { // Force creation of transport ClientCall<String, Integer> call = channel.newCall(method, CallOptions.DEFAULT); call.start(mockCallListener, new Metadata.Headers()); call.cancel(); verify(mockTransport).start(transportListenerCaptor.capture()); final ClientTransport.Listener transportListener = transportListenerCaptor.getValue(); final Object lock = new Object(); final CyclicBarrier barrier = new CyclicBarrier(2); new Thread() { @Override public void run() { synchronized (lock) { try { barrier.await(); } catch (Exception ex) { throw new AssertionError(ex); } // To deadlock, a lock would be needed for this call to proceed. transportListener.transportShutdown(Status.CANCELLED); } } }.start(); doAnswer( new Answer<Void>() { @Override public Void answer(InvocationOnMock invocation) { // To deadlock, a lock would need to be held while this method is in progress. try { barrier.await(); } catch (Exception ex) { throw new AssertionError(ex); } // If deadlock is possible with this setup, this sychronization completes the loop // because // the transportShutdown needs a lock that Channel is holding while calling this // method. synchronized (lock) { } return null; } }) .when(mockTransport) .shutdown(); channel.shutdown(); transportListener.transportTerminated(); }
@Test public void transportFailsOnStart() { Status goldenStatus = Status.INTERNAL.withDescription("wanted it to fail"); // Have transport throw exception on start ClientCall<String, Integer> call = channel.newCall(method, CallOptions.DEFAULT); ClientTransport mockTransport = mock(ClientTransport.class); when(mockTransportFactory.newClientTransport()).thenReturn(mockTransport); doThrow(goldenStatus.asRuntimeException()) .when(mockTransport) .start(any(ClientTransport.Listener.class)); call.start(mockCallListener, new Metadata.Headers()); verify(mockTransportFactory).newClientTransport(); verify(mockTransport).start(any(ClientTransport.Listener.class)); ArgumentCaptor<Status> statusCaptor = ArgumentCaptor.forClass(Status.class); verify(mockCallListener, timeout(1000)) .onClose(statusCaptor.capture(), any(Metadata.Trailers.class)); assertSame(goldenStatus, statusCaptor.getValue()); // Have transport shutdown immediately during start call = channel.newCall(method, CallOptions.DEFAULT); ClientTransport mockTransport2 = mock(ClientTransport.class); ClientStream mockStream2 = mock(ClientStream.class); Metadata.Headers headers2 = new Metadata.Headers(); when(mockTransportFactory.newClientTransport()).thenReturn(mockTransport2); doAnswer( new Answer<Void>() { @Override public Void answer(InvocationOnMock invocation) { ClientTransport.Listener listener = (ClientTransport.Listener) invocation.getArguments()[0]; listener.transportShutdown(Status.INTERNAL); listener.transportTerminated(); return null; } }) .when(mockTransport2) .start(any(ClientTransport.Listener.class)); when(mockTransport2.newStream(same(method), same(headers2), any(ClientStreamListener.class))) .thenReturn(mockStream2); call.start(mockCallListener2, headers2); verify(mockTransportFactory, times(2)).newClientTransport(); verify(mockTransport2).start(any(ClientTransport.Listener.class)); verify(mockTransport2).newStream(same(method), same(headers2), streamListenerCaptor.capture()); Metadata.Trailers trailers2 = new Metadata.Trailers(); streamListenerCaptor.getValue().closed(Status.CANCELLED, trailers2); verify(mockCallListener2, timeout(1000)).onClose(Status.CANCELLED, trailers2); // Make sure the Channel can still handle new calls call = channel.newCall(method, CallOptions.DEFAULT); ClientTransport mockTransport3 = mock(ClientTransport.class); ClientStream mockStream3 = mock(ClientStream.class); Metadata.Headers headers3 = new Metadata.Headers(); when(mockTransportFactory.newClientTransport()).thenReturn(mockTransport3); when(mockTransport3.newStream(same(method), same(headers3), any(ClientStreamListener.class))) .thenReturn(mockStream3); call.start(mockCallListener3, headers3); verify(mockTransportFactory, times(3)).newClientTransport(); verify(mockTransport3).start(transportListenerCaptor.capture()); verify(mockTransport3).newStream(same(method), same(headers3), streamListenerCaptor.capture()); Metadata.Trailers trailers3 = new Metadata.Trailers(); streamListenerCaptor.getValue().closed(Status.CANCELLED, trailers3); verify(mockCallListener3, timeout(1000)).onClose(Status.CANCELLED, trailers3); // Make sure shutdown still works channel.shutdown(); assertTrue(channel.isShutdown()); assertFalse(channel.isTerminated()); verify(mockTransport3).shutdown(); transportListenerCaptor.getValue().transportShutdown(Status.CANCELLED); assertFalse(channel.isTerminated()); transportListenerCaptor.getValue().transportTerminated(); assertTrue(channel.isTerminated()); verifyNoMoreInteractions(mockTransportFactory); verifyNoMoreInteractions(mockTransport); verifyNoMoreInteractions(mockTransport2); verifyNoMoreInteractions(mockTransport3); verifyNoMoreInteractions(mockStream2); verifyNoMoreInteractions(mockStream3); }
@Test public void twoCallsAndGracefulShutdown() { verifyNoMoreInteractions(mockTransportFactory); ClientCall<String, Integer> call = channel.newCall(method, CallOptions.DEFAULT); verifyNoMoreInteractions(mockTransportFactory); // Create transport and call ClientTransport mockTransport = mock(ClientTransport.class); ClientStream mockStream = mock(ClientStream.class); Metadata.Headers headers = new Metadata.Headers(); when(mockTransportFactory.newClientTransport()).thenReturn(mockTransport); when(mockTransport.newStream(same(method), same(headers), any(ClientStreamListener.class))) .thenReturn(mockStream); call.start(mockCallListener, headers); verify(mockTransportFactory).newClientTransport(); verify(mockTransport).start(transportListenerCaptor.capture()); ClientTransport.Listener transportListener = transportListenerCaptor.getValue(); verify(mockTransport).newStream(same(method), same(headers), streamListenerCaptor.capture()); ClientStreamListener streamListener = streamListenerCaptor.getValue(); // Second call ClientCall<String, Integer> call2 = channel.newCall(method, CallOptions.DEFAULT); ClientStream mockStream2 = mock(ClientStream.class); Metadata.Headers headers2 = new Metadata.Headers(); when(mockTransport.newStream(same(method), same(headers2), any(ClientStreamListener.class))) .thenReturn(mockStream2); call2.start(mockCallListener2, headers2); verify(mockTransport).newStream(same(method), same(headers2), streamListenerCaptor.capture()); ClientStreamListener streamListener2 = streamListenerCaptor.getValue(); Metadata.Trailers trailers = new Metadata.Trailers(); streamListener2.closed(Status.CANCELLED, trailers); verify(mockCallListener2, timeout(1000)).onClose(Status.CANCELLED, trailers); // Shutdown channel.shutdown(); assertTrue(channel.isShutdown()); assertFalse(channel.isTerminated()); verify(mockTransport).shutdown(); // Further calls should fail without going to the transport ClientCall<String, Integer> call3 = channel.newCall(method, CallOptions.DEFAULT); call3.start(mockCallListener3, new Metadata.Headers()); ArgumentCaptor<Status> statusCaptor = ArgumentCaptor.forClass(Status.class); verify(mockCallListener3, timeout(1000)) .onClose(statusCaptor.capture(), any(Metadata.Trailers.class)); assertSame(Status.Code.UNAVAILABLE, statusCaptor.getValue().getCode()); // Finish shutdown transportListener.transportShutdown(Status.CANCELLED); assertFalse(channel.isTerminated()); streamListener.closed(Status.CANCELLED, trailers); verify(mockCallListener, timeout(1000)).onClose(Status.CANCELLED, trailers); assertFalse(channel.isTerminated()); transportListener.transportTerminated(); assertTrue(channel.isTerminated()); verifyNoMoreInteractions(mockTransportFactory); verifyNoMoreInteractions(mockTransport); verifyNoMoreInteractions(mockStream); }