/**
  * 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());
  }