/**
     * writes all the entries that have changed, to the buffer which will later be written to TCP/IP
     *
     * @param modificationIterator a record of which entries have modification
     * @param selectionKey
     */
    void entriesToBuffer(
        @NotNull final ModificationIterator modificationIterator,
        @NotNull final SelectionKey selectionKey)
        throws InterruptedException, IOException {

      final SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
      final Attached attached = (Attached) selectionKey.attachment();

      // this can occur when new SHM's are added to a cluster
      final boolean handShakingComplete = attached.isHandShakingComplete();

      for (; ; ) {

        final boolean wasDataRead = modificationIterator.nextEntry(entryCallback, 0);

        if (!wasDataRead) {

          // if we have no more data to write to the socket then we will
          // un-register OP_WRITE on the selector, until more data becomes available
          if (in.position() == 0 && handShakingComplete) disableWrite(socketChannel, attached);

          return;
        }

        // we've filled up the buffer lets give another channel a chance to send some data
        if (in.remaining() <= maxEntrySizeBytes) return;

        // if we have space in the buffer to write more data and we just wrote data into the
        // buffer then let try and write some more
      }
    }
  /**
   * 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();
    }
  }
  /**
   * checks that we receive heartbeats and send out heart beats.
   *
   * @param approxTime the approximate time in milliseconds
   */
  void heartBeatMonitor(long approxTime) {
    for (int i = activeKeys.nextSetBit(0); i >= 0; i = activeKeys.nextSetBit(i + 1)) {
      try {
        final SelectionKey key = selectionKeysStore[i];
        if (!key.isValid() || !key.channel().isOpen()) {
          activeKeys.clear(i);
          continue;
        }
        try {
          sendHeartbeatIfRequired(approxTime, key);
        } catch (Exception e) {
          if (LOG.isDebugEnabled()) LOG.debug("", e);
        }

        try {
          heartbeatCheckHasReceived(key, approxTime);
        } catch (Exception e) {
          if (LOG.isDebugEnabled()) LOG.debug("", e);
        }

      } catch (Exception e) {
        if (LOG.isDebugEnabled()) LOG.debug("", e);
      }
    }
  }
 public void applyUpdates() {
   if (wasChanged.getAndSet(false)) {
     for (int i = changeOfOpWriteRequired.nextSetBit(0);
         i >= 0;
         i = changeOfOpWriteRequired.nextSetBit(i + 1)) {
       changeOfOpWriteRequired.clear(i);
       final SelectionKey key = selectionKeys[i];
       try {
         key.interestOps(key.interestOps() | op);
       } catch (Exception e) {
         LOG.debug("", e);
       }
     }
   }
 }
  /** called when the selector receives a OP_WRITE message */
  private void onWrite(@NotNull final SelectionKey key, final long approxTime)
      throws InterruptedException, IOException {
    final SocketChannel socketChannel = (SocketChannel) key.channel();
    final Attached attached = (Attached) key.attachment();

    if (attached.remoteModificationIterator != null)
      attached.entryWriter.entriesToBuffer(attached.remoteModificationIterator, key);

    try {
      int bytesJustWritten = attached.entryWriter.writeBufferToSocket(socketChannel, approxTime);

      contemplateThrottleWrites(bytesJustWritten);

    } catch (IOException e) {
      quietClose(key, e);
      if (!attached.isServer) attached.connector.connectLater();
      throw e;
    }
  }
    /**
     * 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);
  }
  /** called when the selector receives a OP_READ message */
  private void onRead(final SelectionKey key, final long approxTime)
      throws IOException, InterruptedException {

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

    try {
      if (attached.entryReader.readSocketToBuffer(socketChannel) <= 0) return;

    } catch (IOException e) {
      if (!attached.isServer) attached.connector.connectLater();
      throw e;
    }

    if (LOG.isDebugEnabled()) LOG.debug("heartbeat or data received.");

    attached.entryReader.lastHeartBeatReceived = approxTime;

    if (attached.isHandShakingComplete()) {
      attached.entryReader.entriesFromBuffer();
    } else {
      doHandShaking(key);
    }
  }
  /**
   * check to see if its time to send a heartbeat, and send one if required
   *
   * @param approxTime the current time ( approximately )
   * @param key nio selection key
   */
  private void sendHeartbeatIfRequired(final long approxTime, @NotNull final SelectionKey key) {

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

    if (attachment.isHandShakingComplete()
        && attachment.entryWriter.lastSentTime + heartBeatInterval < approxTime) {

      attachment.entryWriter.lastSentTime = approxTime;
      attachment.entryWriter.writeHeartbeatToBuffer();

      enableOpWrite(key);

      if (LOG.isDebugEnabled()) LOG.debug("sending heartbeat");
    }
  }
  /** 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);
  }
  /**
   * used to exchange identifiers and timestamps and heartbeat intervals between the server and
   * client
   *
   * @param key the SelectionKey relating to the this cha
   * @throws java.io.IOException
   * @throws InterruptedException
   */
  private void doHandShaking(@NotNull final SelectionKey key)
      throws IOException, InterruptedException {
    final Attached attached = (Attached) key.attachment();
    final TcpSocketChannelEntryWriter writer = attached.entryWriter;
    final TcpSocketChannelEntryReader reader = attached.entryReader;

    if (attached.remoteIdentifier == Byte.MIN_VALUE) {

      final byte remoteIdentifier = reader.identifierFromBuffer();

      if (remoteIdentifier == Byte.MIN_VALUE) return;

      attached.remoteIdentifier = remoteIdentifier;

      // we use the as iterating the activeKeys via the bitset wont create and Objects
      // but if we use the selector.keys() this will.
      selectionKeysStore[remoteIdentifier] = key;
      activeKeys.set(remoteIdentifier);

      if (LOG.isDebugEnabled()) {
        LOG.debug(
            "server-connection id={}, remoteIdentifier={}", localIdentifier, remoteIdentifier);
      }

      if (remoteIdentifier == localIdentifier) {
        throw new IllegalStateException(
            "Where are connecting to a remote "
                + "map with the same "
                + "identifier as this map, identifier="
                + localIdentifier
                + ", "
                + "please change either this maps identifier or the remote one");
      }

      attached.remoteModificationIterator =
          replica.acquireModificationIterator(remoteIdentifier, attached);

      writer.writeRemoteBootstrapTimestamp(replica.lastModificationTime(remoteIdentifier));

      // tell the remote node, what are heartbeat interval is
      writer.writeRemoteHeartbeatInterval(heartBeatInterval);
    }

    if (attached.remoteBootstrapTimestamp == Long.MIN_VALUE) {
      attached.remoteBootstrapTimestamp = reader.remoteBootstrapTimestamp();
      if (attached.remoteBootstrapTimestamp == Long.MIN_VALUE) return;
    }

    if (!attached.hasRemoteHeartbeatInterval) {

      long value = reader.remoteHeartbeatIntervalFromBuffer();

      if (value == Long.MIN_VALUE) return;

      if (value < 0) {
        LOG.error("value=" + value);
      }

      // we add a 10% safety margin to the timeout time due to latency fluctuations on the network,
      // in other words we wont consider a connection to have
      // timed out, unless the heartbeat interval has exceeded 25% of the expected time.
      attached.remoteHeartbeatInterval = (long) (value * 1.25);

      // we have to make our selector poll interval at least as short as the minimum selector
      // timeout
      selectorTimeout = Math.min(selectorTimeout, value);

      if (selectorTimeout < 0) LOG.info("");

      attached.hasRemoteHeartbeatInterval = true;

      // now we're finished we can get on with reading the entries
      attached.setHandShakingComplete();
      attached.remoteModificationIterator.dirtyEntries(attached.remoteBootstrapTimestamp);
      reader.entriesFromBuffer();
    }
  }
 /**
  * closes and only logs the exception at debug
  *
  * @param key the SelectionKey
  * @param e the Exception that caused the issue
  */
 private void quietClose(@NotNull final SelectionKey key, @NotNull final Exception e) {
   if (LOG.isDebugEnabled()) LOG.debug("", e);
   closeEarlyAndQuietly(key.channel());
 }
 private void enableOpWrite(SelectionKey key) {
   int ops = key.interestOps();
   if ((ops & (OP_CONNECT | OP_ACCEPT)) == 0) key.interestOps(ops | OP_WRITE);
 }
  @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();
    }
  }