/** Reads map entries from a socket, this could be a client or server socket */
  private class TcpSocketChannelEntryReader {
    private final ByteBuffer in;
    private final ByteBufferBytes out;

    // we use Integer.MIN_VALUE as N/A
    private int sizeOfNextEntry = Integer.MIN_VALUE;
    public long lastHeartBeatReceived = System.currentTimeMillis();

    private TcpSocketChannelEntryReader() {
      in = ByteBuffer.allocateDirect(packetSize + maxEntrySizeBytes);
      out = new ByteBufferBytes(in);
      out.limit(0);
      in.clear();
    }

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

    /**
     * reads entries from the buffer till empty
     *
     * @throws InterruptedException
     */
    private void entriesFromBuffer() throws InterruptedException, IOException {

      for (; ; ) {

        out.limit(in.position());

        // its set to MIN_VALUE when it should be read again
        if (sizeOfNextEntry == Integer.MIN_VALUE) {
          if (out.remaining() < SIZE_OF_SHORT) {
            return;
          }

          int value = out.readUnsignedShort();

          // this is the heartbeat
          if (value == 0) continue;

          sizeOfNextEntry = value;
        }

        if (out.remaining() < sizeOfNextEntry) {
          return;
        }

        final long nextEntryPos = out.position() + sizeOfNextEntry;
        final long limit = out.limit();
        out.limit(nextEntryPos);
        externalizable.readExternalEntry(out);

        out.limit(limit);
        // skip onto the next entry
        out.position(nextEntryPos);

        // to allow the sizeOfNextEntry to be read the next time around
        sizeOfNextEntry = Integer.MIN_VALUE;
      }
    }

    /** compacts the buffer and updates the {@code in} and {@code out} accordingly */
    private void compactBuffer() {

      // the maxEntrySizeBytes used here may not be the maximum size of the entry in its serialized
      // form
      // however, its only use as an indication that the buffer is becoming full and should be
      // compacted
      // the buffer can be compacted at any time
      if (in.position() == 0 || in.remaining() > maxEntrySizeBytes) return;

      in.limit(in.position());
      in.position((int) out.position());

      in.compact();
      out.position(0);
    }

    /** @return the identifier or -1 if unsuccessful */
    byte identifierFromBuffer() {
      return (out.remaining() >= 1) ? out.readByte() : Byte.MIN_VALUE;
    }

    /** @return the timestamp or -1 if unsuccessful */
    long remoteBootstrapTimestamp() {
      return (out.remaining() >= 8) ? out.readLong() : Long.MIN_VALUE;
    }

    public long remoteHeartbeatIntervalFromBuffer() {
      return (out.remaining() >= 8) ? out.readLong() : Long.MIN_VALUE;
    }
  }
  @Override
  void process() throws IOException {
    try {
      final Details serverDetails = new Details(serverInetSocketAddress, localIdentifier);
      connectorBySocket.put(serverInetSocketAddress, new ServerConnector(serverDetails));

      for (InetSocketAddress client : endpoints) {
        final Details clientDetails = new Details(client, localIdentifier);
        connectorBySocket.put(client, new ClientConnector(clientDetails));
      }

      for (AbstractConnector connector : connectorBySocket.values()) {
        connector.connect();
      }

      while (selector.isOpen()) {

        registerPendingRegistrations();

        final int nSelectedKeys = selector.select(selectorTimeout);

        // its less resource intensive to set this less frequently and use an approximation
        final long approxTime = System.currentTimeMillis();

        checkThrottleInterval();

        // check that we have sent and received heartbeats
        heartBeatMonitor(approxTime);

        // set the OP_WRITE when data is ready to send
        opWriteUpdater.applyUpdates();

        if (nSelectedKeys == 0) continue; // go back and check pendingRegistrations

        final Set<SelectionKey> selectionKeys = selector.selectedKeys();
        for (final SelectionKey key : selectionKeys) {
          try {

            if (!key.isValid()) continue;

            if (key.isAcceptable()) onAccept(key);

            if (key.isConnectable()) onConnect(key);

            if (key.isReadable()) onRead(key, approxTime);

            if (key.isWritable()) onWrite(key, approxTime);

          } catch (CancelledKeyException e) {
            quietClose(key, e);
          } catch (ClosedSelectorException e) {
            quietClose(key, e);
          } catch (IOException e) {
            quietClose(key, e);
          } catch (InterruptedException e) {
            quietClose(key, e);
          } catch (Exception e) {
            LOG.info("", e);
            closeEarlyAndQuietly(key.channel());
          }
        }
        selectionKeys.clear();
      }
    } catch (CancelledKeyException e) {
      if (LOG.isDebugEnabled()) LOG.debug("", e);
    } catch (ClosedSelectorException e) {
      if (LOG.isDebugEnabled()) LOG.debug("", e);
    } catch (ClosedChannelException e) {
      if (LOG.isDebugEnabled()) LOG.debug("", e);
    } catch (ConnectException e) {
      if (LOG.isDebugEnabled()) LOG.debug("", e);
    } catch (Exception e) {
      LOG.error("", e);
    } finally {
      if (selector != null)
        try {
          selector.close();
        } catch (IOException e) {
          if (LOG.isDebugEnabled()) LOG.debug("", e);
        }
      close();
    }
  }