/**
   * Handles incoming control commands on channel zero.
   *
   * @see ChannelN#processAsync
   */
  @SuppressWarnings("unused")
  public boolean processControlCommand(Command c) throws IOException {
    // Similar trick to ChannelN.processAsync used here, except
    // we're interested in whole-connection quiescing.

    // See the detailed comments in ChannelN.processAsync.

    Method method = c.getMethod();

    if (isOpen()) {
      if (method instanceof AMQP.Connection.Close) {
        handleConnectionClose(c);
        return true;
      } else if (method instanceof AMQP.Connection.Blocked) {
        AMQP.Connection.Blocked blocked = (AMQP.Connection.Blocked) method;
        try {
          for (BlockedListener l : this.blockedListeners) {
            l.handleBlocked(blocked.getReason());
          }
        } catch (Throwable ex) {
          getExceptionHandler().handleBlockedListenerException(this, ex);
        }
        return true;
      } else if (method instanceof AMQP.Connection.Unblocked) {
        try {
          for (BlockedListener l : this.blockedListeners) {
            l.handleUnblocked();
          }
        } catch (Throwable ex) {
          getExceptionHandler().handleBlockedListenerException(this, ex);
        }
        return true;
      } else {
        return false;
      }
    } else {
      if (method instanceof AMQP.Connection.Close) {
        // Already shutting down, so just send back a CloseOk.
        try {
          _channel0.quiescingTransmit(new AMQP.Connection.CloseOk.Builder().build());
        } catch (IOException ignored) {
        } // ignore
        return true;
      } else if (method instanceof AMQP.Connection.CloseOk) {
        // It's our final "RPC". Time to shut down.
        _running = false;
        // If Close was sent from within the MainLoop we
        // will not have a continuation to return to, so
        // we treat this as processed in that case.
        return !_channel0.isOutstandingRpc();
      } else { // Ignore all others.
        return true;
      }
    }
  }
  /**
   * Protected API - Close this connection with the given code, message, source and timeout value
   * for all the close operations to complete. Specifies if any encountered exceptions should be
   * ignored.
   */
  public void close(
      int closeCode,
      String closeMessage,
      boolean initiatedByApplication,
      Throwable cause,
      int timeout,
      boolean abort)
      throws IOException {
    boolean sync = !(Thread.currentThread() == mainLoopThread);

    try {
      AMQP.Connection.Close reason =
          new AMQP.Connection.Close.Builder().replyCode(closeCode).replyText(closeMessage).build();

      final ShutdownSignalException sse =
          startShutdown(reason, initiatedByApplication, cause, true);
      if (sync) {
        BlockingRpcContinuation<AMQCommand> k =
            new BlockingRpcContinuation<AMQCommand>() {
              @Override
              public AMQCommand transformReply(AMQCommand command) {
                AMQConnection.this.finishShutdown(sse);
                return command;
              }
            };

        _channel0.quiescingRpc(reason, k);
        k.getReply(timeout);
      } else {
        _channel0.quiescingTransmit(reason);
      }
    } catch (TimeoutException tte) {
      if (!abort) {
        ShutdownSignalException sse = new ShutdownSignalException(true, true, null, this);
        sse.initCause(cause);
        throw sse;
      }
    } catch (ShutdownSignalException sse) {
      if (!abort) throw sse;
    } catch (IOException ioe) {
      if (!abort) throw ioe;
    } finally {
      if (sync) _frameHandler.close();
    }
  }
  private ShutdownSignalException startShutdown(
      Method reason, boolean initiatedByApplication, Throwable cause, boolean notifyRpc) {
    ShutdownSignalException sse =
        new ShutdownSignalException(true, initiatedByApplication, reason, this);
    sse.initCause(cause);
    if (!setShutdownCauseIfOpen(sse)) {
      if (initiatedByApplication) throw new AlreadyClosedException(getCloseReason(), cause);
    }

    // stop any heartbeating
    _heartbeatSender.shutdown();

    _channel0.processShutdownSignal(sse, !initiatedByApplication, notifyRpc);

    return sse;
  }
  @SuppressWarnings("unused")
  public void handleConnectionClose(Command closeCommand) {
    ShutdownSignalException sse =
        shutdown(closeCommand.getMethod(), false, null, _inConnectionNegotiation);
    try {
      _channel0.quiescingTransmit(new AMQP.Connection.CloseOk.Builder().build());
    } catch (IOException ignored) {
    } // ignore
    _brokerInitiatedShutdown = true;
    SocketCloseWait scw = new SocketCloseWait(sse);

    // if shutdown executor is configured, use it. Otherwise
    // execut socket close monitor the old fashioned way.
    // see rabbitmq/rabbitmq-java-client#91
    if (shutdownExecutor != null) {
      shutdownExecutor.execute(scw);
    } else {
      final String name =
          "RabbitMQ connection shutdown monitor " + getHostAddress() + ":" + getPort();
      Thread waiter = Environment.newThread(threadFactory, scw, name);
      waiter.start();
    }
  }
  /**
   * Start up the connection, including the MainLoop thread. Sends the protocol version negotiation
   * header, and runs through Connection.Start/.StartOk, Connection.Tune/.TuneOk, and then calls
   * Connection.Open and waits for the OpenOk. Sets heart-beat and frame max values after tuning has
   * taken place.
   *
   * @throws IOException if an error is encountered either before, or during, protocol negotiation;
   *     sub-classes {@link ProtocolVersionMismatchException} and {@link
   *     PossibleAuthenticationFailureException} will be thrown in the corresponding circumstances.
   *     {@link AuthenticationFailureException} will be thrown if the broker closes the connection
   *     with ACCESS_REFUSED. If an exception is thrown, connection resources allocated can all be
   *     garbage collected when the connection object is no longer referenced.
   */
  public void start() throws IOException, TimeoutException {
    initializeConsumerWorkService();
    initializeHeartbeatSender();
    this._running = true;
    // Make sure that the first thing we do is to send the header,
    // which should cause any socket errors to show up for us, rather
    // than risking them pop out in the MainLoop
    AMQChannel.SimpleBlockingRpcContinuation connStartBlocker =
        new AMQChannel.SimpleBlockingRpcContinuation();
    // We enqueue an RPC continuation here without sending an RPC
    // request, since the protocol specifies that after sending
    // the version negotiation header, the client (connection
    // initiator) is to wait for a connection.start method to
    // arrive.
    _channel0.enqueueRpc(connStartBlocker);
    try {
      // The following two lines are akin to AMQChannel's
      // transmit() method for this pseudo-RPC.
      _frameHandler.setTimeout(handshakeTimeout);
      _frameHandler.sendHeader();
    } catch (IOException ioe) {
      _frameHandler.close();
      throw ioe;
    }

    // start the main loop going
    MainLoop loop = new MainLoop();
    final String name = "AMQP Connection " + getHostAddress() + ":" + getPort();
    mainLoopThread = Environment.newThread(threadFactory, loop, name);
    mainLoopThread.start();
    // after this point clear-up of MainLoop is triggered by closing the frameHandler.

    AMQP.Connection.Start connStart;
    AMQP.Connection.Tune connTune = null;
    try {
      connStart =
          (AMQP.Connection.Start) connStartBlocker.getReply(handshakeTimeout / 2).getMethod();

      _serverProperties = Collections.unmodifiableMap(connStart.getServerProperties());

      Version serverVersion = new Version(connStart.getVersionMajor(), connStart.getVersionMinor());

      if (!Version.checkVersion(clientVersion, serverVersion)) {
        throw new ProtocolVersionMismatchException(clientVersion, serverVersion);
      }

      String[] mechanisms = connStart.getMechanisms().toString().split(" ");
      SaslMechanism sm = this.saslConfig.getSaslMechanism(mechanisms);
      if (sm == null) {
        throw new IOException(
            "No compatible authentication mechanism found - "
                + "server offered ["
                + connStart.getMechanisms()
                + "]");
      }

      LongString challenge = null;
      LongString response = sm.handleChallenge(null, this.username, this.password);

      do {
        Method method =
            (challenge == null)
                ? new AMQP.Connection.StartOk.Builder()
                    .clientProperties(_clientProperties)
                    .mechanism(sm.getName())
                    .response(response)
                    .build()
                : new AMQP.Connection.SecureOk.Builder().response(response).build();

        try {
          Method serverResponse = _channel0.rpc(method, handshakeTimeout / 2).getMethod();
          if (serverResponse instanceof AMQP.Connection.Tune) {
            connTune = (AMQP.Connection.Tune) serverResponse;
          } else {
            challenge = ((AMQP.Connection.Secure) serverResponse).getChallenge();
            response = sm.handleChallenge(challenge, this.username, this.password);
          }
        } catch (ShutdownSignalException e) {
          Method shutdownMethod = e.getReason();
          if (shutdownMethod instanceof AMQP.Connection.Close) {
            AMQP.Connection.Close shutdownClose = (AMQP.Connection.Close) shutdownMethod;
            if (shutdownClose.getReplyCode() == AMQP.ACCESS_REFUSED) {
              throw new AuthenticationFailureException(shutdownClose.getReplyText());
            }
          }
          throw new PossibleAuthenticationFailureException(e);
        }
      } while (connTune == null);
    } catch (TimeoutException te) {
      _frameHandler.close();
      throw te;
    } catch (ShutdownSignalException sse) {
      _frameHandler.close();
      throw AMQChannel.wrap(sse);
    } catch (IOException ioe) {
      _frameHandler.close();
      throw ioe;
    }

    try {
      int channelMax = negotiateChannelMax(this.requestedChannelMax, connTune.getChannelMax());
      _channelManager = instantiateChannelManager(channelMax, threadFactory);

      int frameMax = negotiatedMaxValue(this.requestedFrameMax, connTune.getFrameMax());
      this._frameMax = frameMax;

      int heartbeat = negotiatedMaxValue(this.requestedHeartbeat, connTune.getHeartbeat());

      setHeartbeat(heartbeat);

      _channel0.transmit(
          new AMQP.Connection.TuneOk.Builder()
              .channelMax(channelMax)
              .frameMax(frameMax)
              .heartbeat(heartbeat)
              .build());
      _channel0.exnWrappingRpc(
          new AMQP.Connection.Open.Builder().virtualHost(_virtualHost).build());
    } catch (IOException ioe) {
      _heartbeatSender.shutdown();
      _frameHandler.close();
      throw ioe;
    } catch (ShutdownSignalException sse) {
      _heartbeatSender.shutdown();
      _frameHandler.close();
      throw AMQChannel.wrap(sse);
    }

    // We can now respond to errors having finished tailoring the connection
    this._inConnectionNegotiation = false;
  }