/** * Initiates an orderly shutdown in which preexisting calls continue but new calls are immediately * cancelled. */ @Override public ManagedChannelImpl shutdown() { ArrayList<TransportSet> transportsCopy = new ArrayList<TransportSet>(); synchronized (lock) { if (shutdown) { return this; } shutdown = true; // After shutdown there are no new calls, so no new cancellation tasks are needed scheduledExecutor = SharedResourceHolder.release(TIMER_SERVICE, scheduledExecutor); if (transports.isEmpty()) { terminated = true; lock.notifyAll(); onChannelTerminated(); } else { transportsCopy.addAll(transports.values()); } } loadBalancer.shutdown(); nameResolver.shutdown(); for (TransportSet ts : transportsCopy) { ts.shutdown(); } return this; }
@Test public void obtainTransportAfterShutdown() throws Exception { SocketAddress addr = mock(SocketAddress.class); createTransportSet(addr); transportSet.shutdown(); assertEquals(ConnectivityState.SHUTDOWN, transportSet.getState(false)); ClientTransport pick = transportSet.obtainActiveTransport(); assertNotNull(pick); verify(mockTransportFactory, times(0)).newClientTransport(addr, AUTHORITY, USER_AGENT); assertEquals(ConnectivityState.SHUTDOWN, transportSet.getState(false)); }
@Test public void requireConnectionThroughGetState() throws Exception { SocketAddress addr = mock(SocketAddress.class); createTransportSet(addr); assertEquals(ConnectivityState.IDLE, transportSet.getState(false)); assertEquals(ConnectivityState.CONNECTING, transportSet.getState(true)); assertEquals(ConnectivityState.CONNECTING, transportSet.getState(true)); transportSet .obtainActiveTransport() .newStream(method, new Metadata(), waitForReadyCallOptions, statsTraceCtx); assertEquals(ConnectivityState.CONNECTING, transportSet.getState(true)); // Fail it transports.peek().listener.transportShutdown(Status.UNAVAILABLE); // requireConnection == true doesn't skip the back-off assertEquals(ConnectivityState.TRANSIENT_FAILURE, transportSet.getState(true)); transports.poll().listener.transportTerminated(); fakeClock.forwardMillis(9); assertEquals(ConnectivityState.TRANSIENT_FAILURE, transportSet.getState(true)); fakeClock.forwardMillis(1); // Only when back-off is over, do we try to connect again assertEquals(ConnectivityState.CONNECTING, transportSet.getState(false)); // Let it through and fail, thus a go-away transports.peek().listener.transportReady(); transports.peek().listener.transportShutdown(Status.UNAVAILABLE); assertEquals(ConnectivityState.IDLE, transportSet.getState(false)); transports.poll().listener.transportTerminated(); // Request for connecting again assertEquals(ConnectivityState.CONNECTING, transportSet.getState(true)); // And fail it again transports.peek().listener.transportShutdown(Status.UNAVAILABLE); assertEquals(ConnectivityState.TRANSIENT_FAILURE, transportSet.getState(true)); // Shut it down transportSet.shutdown(); assertEquals(ConnectivityState.SHUTDOWN, transportSet.getState(true)); assertEquals(ConnectivityState.SHUTDOWN, transportSet.getState(false)); assertFalse(transportSet.isTerminated()); // Terminate it transports.poll().listener.transportTerminated(); assertTrue(transportSet.isTerminated()); assertEquals(ConnectivityState.SHUTDOWN, transportSet.getState(true)); assertEquals(ConnectivityState.SHUTDOWN, transportSet.getState(false)); fakeExecutor.runDueTasks(); // What tasks are scheduled is not important to this test. }
@Test public void shutdownBeforeTransportReady() throws Exception { SocketAddress addr = mock(SocketAddress.class); createTransportSet(addr); ClientTransport pick = transportSet.obtainActiveTransport(); assertEquals(ConnectivityState.CONNECTING, transportSet.getState(false)); MockClientTransportInfo transportInfo = transports.poll(); assertNotSame(transportInfo.transport, pick); // Shutdown the TransportSet before the pending transport is ready transportSet.shutdown(); assertEquals(ConnectivityState.SHUTDOWN, transportSet.getState(false)); // The transport should've been shut down even though it's not the active transport yet. verify(transportInfo.transport).shutdown(); transportInfo.listener.transportShutdown(Status.UNAVAILABLE); transportInfo.listener.transportTerminated(); assertEquals(ConnectivityState.SHUTDOWN, transportSet.getState(false)); }
@Test public void shutdownBeforeTransportCreatedWithoutPendingStream() throws Exception { SocketAddress addr = mock(SocketAddress.class); createTransportSet(addr); // First transport is created immediately ClientTransport pick = transportSet.obtainActiveTransport(); verify(mockTransportFactory).newClientTransport(addr, AUTHORITY, USER_AGENT); assertNotNull(pick); // Fail this one MockClientTransportInfo transportInfo = transports.poll(); transportInfo.listener.transportShutdown(Status.UNAVAILABLE); transportInfo.listener.transportTerminated(); // Second transport will wait for back-off pick = transportSet.obtainActiveTransport(); assertTrue(pick instanceof DelayedClientTransport); // Shut down TransportSet before the transport is created. Futher call to // obtainActiveTransport() gets failing transports transportSet.shutdown(); assertEquals(ConnectivityState.SHUTDOWN, transportSet.getState(false)); pick = transportSet.obtainActiveTransport(); assertEquals(ConnectivityState.SHUTDOWN, transportSet.getState(false)); assertNotNull(pick); assertTrue(pick instanceof FailingClientTransport); // TransportSet terminated promptly. verify(mockTransportSetCallback).onTerminated(transportSet); assertEquals(ConnectivityState.SHUTDOWN, transportSet.getState(false)); // No more transports will be created. fakeClock.forwardMillis(10000); assertEquals(ConnectivityState.SHUTDOWN, transportSet.getState(false)); verifyNoMoreInteractions(mockTransportFactory); assertEquals(0, transports.size()); }
@Test public void shutdownBeforeTransportCreatedWithPendingStream() throws Exception { SocketAddress addr = mock(SocketAddress.class); createTransportSet(addr); // First transport is created immediately ClientTransport pick = transportSet.obtainActiveTransport(); assertEquals(ConnectivityState.CONNECTING, transportSet.getState(false)); verify(mockTransportFactory).newClientTransport(addr, AUTHORITY, USER_AGENT); assertNotNull(pick); // Fail this one MockClientTransportInfo transportInfo = transports.poll(); transportInfo.listener.transportShutdown(Status.UNAVAILABLE); assertEquals(ConnectivityState.TRANSIENT_FAILURE, transportSet.getState(false)); transportInfo.listener.transportTerminated(); // Second transport will wait for back-off pick = transportSet.obtainActiveTransport(); assertTrue(pick instanceof DelayedClientTransport); // Start a stream, which will be pending in the delayed transport ClientStream pendingStream = pick.newStream(method, headers, waitForReadyCallOptions, statsTraceCtx); pendingStream.start(mockStreamListener); // Shut down TransportSet before the transport is created. Further call to // obtainActiveTransport() gets failing transports assertEquals(ConnectivityState.TRANSIENT_FAILURE, transportSet.getState(false)); transportSet.shutdown(); assertEquals(ConnectivityState.SHUTDOWN, transportSet.getState(false)); pick = transportSet.obtainActiveTransport(); assertNotNull(pick); assertTrue(pick instanceof FailingClientTransport); verify(mockTransportFactory).newClientTransport(addr, AUTHORITY, USER_AGENT); // Reconnect will eventually happen, even though TransportSet has been shut down fakeClock.forwardMillis(10); assertEquals(ConnectivityState.SHUTDOWN, transportSet.getState(false)); verify(mockTransportFactory, times(2)).newClientTransport(addr, AUTHORITY, USER_AGENT); // The pending stream will be started on this newly started transport after it's ready. // The transport is shut down by TransportSet right after the stream is created. transportInfo = transports.poll(); verify(transportInfo.transport, times(0)).shutdown(); assertEquals(0, fakeExecutor.numPendingTasks()); transportInfo.listener.transportReady(); assertEquals(ConnectivityState.SHUTDOWN, transportSet.getState(false)); verify(transportInfo.transport, times(0)) .newStream(any(MethodDescriptor.class), any(Metadata.class)); assertEquals(1, fakeExecutor.runDueTasks()); verify(transportInfo.transport) .newStream( same(method), same(headers), same(waitForReadyCallOptions), any(StatsTraceContext.class)); verify(transportInfo.transport).shutdown(); transportInfo.listener.transportShutdown(Status.UNAVAILABLE); assertEquals(ConnectivityState.SHUTDOWN, transportSet.getState(false)); verify(mockTransportSetCallback, never()).onTerminated(any(TransportSet.class)); // Terminating the transport will let TransportSet to be terminated. transportInfo.listener.transportTerminated(); assertEquals(ConnectivityState.SHUTDOWN, transportSet.getState(false)); verify(mockTransportSetCallback).onTerminated(transportSet); // No more transports will be created. fakeClock.forwardMillis(10000); assertEquals(ConnectivityState.SHUTDOWN, transportSet.getState(false)); verifyNoMoreInteractions(mockTransportFactory); assertEquals(0, transports.size()); }