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