/**
   * check to see if we have lost connection with the remote node and if we have attempts a
   * reconnect.
   *
   * @param key the key relating to the heartbeat that we are checking
   * @param approxTimeOutTime the approximate time in milliseconds
   * @throws ConnectException
   */
  private void heartbeatCheckHasReceived(
      @NotNull final SelectionKey key, final long approxTimeOutTime) throws ConnectException {

    final Attached attached = (Attached) key.attachment();

    // we wont attempt to reconnect the server socket
    if (attached.isServer || !attached.isHandShakingComplete()) return;

    final SocketChannel channel = (SocketChannel) key.channel();

    if (approxTimeOutTime
        > attached.entryReader.lastHeartBeatReceived + attached.remoteHeartbeatInterval) {
      if (LOG.isDebugEnabled())
        LOG.debug(
            "lost connection, attempting to reconnect. "
                + "missed heartbeat from identifier="
                + attached.remoteIdentifier);
      try {
        channel.socket().close();
        channel.close();
        activeKeys.clear(attached.remoteIdentifier);
        closeables.remove(channel);
      } catch (IOException e) {
        LOG.debug("", e);
      }

      attached.connector.connectLater();
    }
  }
    /**
     * reads from the socket and writes them to the buffer
     *
     * @param socketChannel the socketChannel to read from
     * @return the number of bytes read
     * @throws IOException
     */
    private int readSocketToBuffer(@NotNull final SocketChannel socketChannel) throws IOException {

      compactBuffer();
      final int len = socketChannel.read(in);
      out.limit(in.position());
      return len;
    }
    /**
     * writes the contents of the buffer to the socket
     *
     * @param socketChannel the socket to publish the buffer to
     * @param approxTime an approximation of the current time in millis
     * @throws IOException
     */
    private int writeBufferToSocket(
        @NotNull final SocketChannel socketChannel, final long approxTime) throws IOException {

      if (in.position() == 0) return 0;

      // if we still have some unwritten writer from last time
      lastSentTime = approxTime;
      out.limit((int) in.position());

      final int len = socketChannel.write(out);

      if (LOG.isDebugEnabled()) LOG.debug("bytes-written=" + len);

      if (out.remaining() == 0) {
        out.clear();
        in.clear();
      } else {
        out.compact();
        in.position(out.position());
        in.limit(in.capacity());
        out.clear();
      }

      return len;
    }
    /** blocks until connected */
    @Override
    SelectableChannel doConnect() throws IOException, InterruptedException {
      boolean success = false;
      final SocketChannel socketChannel = SocketChannel.open();
      try {
        socketChannel.configureBlocking(false);
        socketChannel.socket().setReuseAddress(true);
        socketChannel.socket().setSoLinger(false, 0);
        socketChannel.socket().setSoTimeout(0);
        socketChannel.socket().setTcpNoDelay(true);

        try {
          socketChannel.connect(details.address());
        } catch (UnresolvedAddressException e) {
          this.connectLater();
        }

        // Under experiment, the concoction was found to be more successful if we
        // paused before registering the OP_CONNECT
        Thread.sleep(10);

        // the registration has be be run on the same thread as the selector
        addPendingRegistration(
            new Runnable() {
              @Override
              public void run() {

                final Attached attached = new Attached();
                attached.connector = ClientConnector.this;

                try {
                  socketChannel.register(selector, OP_CONNECT, attached);
                } catch (ClosedChannelException e) {
                  if (socketChannel.isOpen()) LOG.error("", e);
                }
              }
            });

        selector.wakeup();
        success = true;
        return socketChannel;

      } finally {
        if (!success) {
          try {
            try {
              socketChannel.socket().close();
            } catch (Exception e) {
              LOG.error("", e);
            }
            socketChannel.close();
          } catch (IOException e) {
            LOG.error("", e);
          }
        }
      }
    }
  /** called when the selector receives a OP_ACCEPT message */
  private void onAccept(@NotNull final SelectionKey key) throws IOException {

    final ServerSocketChannel server = (ServerSocketChannel) key.channel();
    final SocketChannel channel = server.accept();
    channel.configureBlocking(false);
    channel.socket().setReuseAddress(true);
    channel.socket().setTcpNoDelay(true);
    channel.socket().setSoTimeout(0);
    channel.socket().setSoLinger(false, 0);

    final Attached attached = new Attached();
    channel.register(selector, OP_WRITE | OP_READ, attached);

    throttle(channel);

    attached.entryReader = new TcpSocketChannelEntryReader();
    attached.entryWriter = new TcpSocketChannelEntryWriter();

    attached.isServer = true;
    attached.entryWriter.identifierToBuffer(localIdentifier);
  }
    /**
     * removes back in the OP_WRITE from the selector, otherwise it'll spin loop. The OP_WRITE will
     * get added back in as soon as we have data to write
     *
     * @param socketChannel the socketChannel we wish to stop writing to
     * @param attached data associated with the socketChannels key
     */
    public synchronized void disableWrite(
        @NotNull final SocketChannel socketChannel, @NotNull final Attached attached) {
      try {
        SelectionKey key = socketChannel.keyFor(selector);
        if (key != null) {
          if (attached.isHandShakingComplete() && selector.isOpen()) {
            if (LOG.isDebugEnabled())
              LOG.debug(
                  "Disabling OP_WRITE to remoteIdentifier="
                      + attached.remoteIdentifier
                      + ", localIdentifier="
                      + localIdentifier);
            key.interestOps(key.interestOps() & ~OP_WRITE);
          }
        }

      } catch (Exception e) {
        LOG.error("", e);
      }
    }
  /** called when the selector receives a OP_CONNECT message */
  private void onConnect(@NotNull final SelectionKey key) throws IOException, InterruptedException {

    final SocketChannel channel = (SocketChannel) key.channel();
    final Attached attached = (Attached) key.attachment();

    try {
      if (!channel.finishConnect()) {
        return;
      }
    } catch (SocketException e) {
      quietClose(key, e);
      attached.connector.connect();
      throw e;
    }

    attached.connector.setSuccessfullyConnected();

    if (LOG.isDebugEnabled())
      LOG.debug(
          "successfully connected to {}, local-id={}",
          channel.socket().getInetAddress(),
          localIdentifier);

    channel.configureBlocking(false);
    channel.socket().setTcpNoDelay(true);
    channel.socket().setSoTimeout(0);
    channel.socket().setSoLinger(false, 0);

    attached.entryReader = new TcpSocketChannelEntryReader();
    attached.entryWriter = new TcpSocketChannelEntryWriter();

    key.interestOps(OP_WRITE | OP_READ);

    throttle(channel);

    // register it with the selector and store the ModificationIterator for this key
    attached.entryWriter.identifierToBuffer(localIdentifier);
  }