@Override
  public void close() {
    // Due to certain code paths, close may be called in a recursive
    // fashion. Rather than trying to handle all of the cases, simply keep
    // track of whether we've been called before and only perform the logic
    // once.
    if (!isClosed.compareAndSet(false, true)) return;

    completeClientRequest();
    closeInternal();
  }
  public synchronized void addClientRequest(
      ClientRequest<?> clientRequest, long timeoutMs, long elapsedNs) {
    if (logger.isTraceEnabled()) logger.trace("Associating client with " + socketChannel.socket());

    this.clientRequest = clientRequest;
    computeExpirationTime(timeoutMs, elapsedNs);
    outputStream.getBuffer().clear();

    boolean wasSuccessful = clientRequest.formatRequest(outputStream);
    outputStream.getBuffer().flip();

    if (wasSuccessful) {
      SelectionKey selectionKey = socketChannel.keyFor(selector);

      if (selectionKey != null) {
        selectionKey.interestOps(SelectionKey.OP_WRITE);

        // This wakeup is required because it's invoked by the calling
        // code in a different thread than the SelectorManager.
        selector.wakeup();
      } else {
        if (logger.isDebugEnabled())
          logger.debug(
              "Client associated with "
                  + socketChannel.socket()
                  + " was not registered with Selector "
                  + selector
                  + ", assuming initial protocol negotiation");
      }
    } else {
      if (logger.isEnabledFor(Level.WARN))
        logger.warn(
            "Client associated with "
                + socketChannel.socket()
                + " did not successfully buffer output for request");

      completeClientRequest();
    }
  }