/** {@inheritDoc} */
  public void write(S session, WriteRequest writeRequest) {
    WriteRequestQueue writeRequestQueue = session.getWriteRequestQueue();

    writeRequestQueue.offer(session, writeRequest);

    if (!session.isWriteSuspended()) {
      this.flush(session);
    }
  }
  /** Write all the pending messages */
  private void flush(long currentTime) {
    if (flushingSessions.isEmpty()) {
      return;
    }

    do {
      S session = flushingSessions.poll(); // the same one with firstSession

      if (session == null) {
        // Just in case ... It should not happen.
        break;
      }

      // Reset the Schedule for flush flag for this session,
      // as we are flushing it now
      session.unscheduledForFlush();

      SessionState state = getState(session);

      switch (state) {
        case OPENED:
          try {
            boolean flushedAll = flushNow(session, currentTime);

            if (flushedAll
                && !session.getWriteRequestQueue().isEmpty(session)
                && !session.isScheduledForFlush()) {
              scheduleFlush(session);
            }
          } catch (Exception e) {
            scheduleRemove(session);
            IoFilterChain filterChain = session.getFilterChain();
            filterChain.fireExceptionCaught(e);
          }

          break;

        case CLOSING:
          // Skip if the channel is already closed.
          break;

        case OPENING:
          // Retry later if session is not yet fully initialized.
          // (In case that Session.write() is called before addSession()
          // is processed)
          scheduleFlush(session);
          return;

        default:
          throw new IllegalStateException(String.valueOf(state));
      }

    } while (!flushingSessions.isEmpty());
  }
  /** {@inheritDoc} */
  public void updateTrafficControl(S session) {
    //
    try {
      setInterestedInRead(session, !session.isReadSuspended());
    } catch (Exception e) {
      IoFilterChain filterChain = session.getFilterChain();
      filterChain.fireExceptionCaught(e);
    }

    try {
      setInterestedInWrite(
          session, !session.getWriteRequestQueue().isEmpty(session) && !session.isWriteSuspended());
    } catch (Exception e) {
      IoFilterChain filterChain = session.getFilterChain();
      filterChain.fireExceptionCaught(e);
    }
  }
  private void clearWriteRequestQueue(S session) {
    WriteRequestQueue writeRequestQueue = session.getWriteRequestQueue();
    WriteRequest req;

    List<WriteRequest> failedRequests = new ArrayList<WriteRequest>();

    if ((req = writeRequestQueue.poll(session)) != null) {
      Object message = req.getMessage();

      if (message instanceof IoBuffer) {
        IoBuffer buf = (IoBuffer) message;

        // The first unwritten empty buffer must be
        // forwarded to the filter chain.
        if (buf.hasRemaining()) {
          buf.reset();
          failedRequests.add(req);
        } else {
          IoFilterChain filterChain = session.getFilterChain();
          filterChain.fireMessageSent(req);
        }
      } else {
        failedRequests.add(req);
      }

      // Discard others.
      while ((req = writeRequestQueue.poll(session)) != null) {
        failedRequests.add(req);
      }
    }

    // Create an exception and notify.
    if (!failedRequests.isEmpty()) {
      WriteToClosedSessionException cause = new WriteToClosedSessionException(failedRequests);

      for (WriteRequest r : failedRequests) {
        session.decreaseScheduledBytesAndMessages(r);
        r.getFuture().setException(cause);
      }

      IoFilterChain filterChain = session.getFilterChain();
      filterChain.fireExceptionCaught(cause);
    }
  }
  private boolean flushNow(S session, long currentTime) {
    if (!session.isConnected()) {
      scheduleRemove(session);
      return false;
    }

    final boolean hasFragmentation = session.getTransportMetadata().hasFragmentation();

    final WriteRequestQueue writeRequestQueue = session.getWriteRequestQueue();

    // Set limitation for the number of written bytes for read-write
    // fairness. I used maxReadBufferSize * 3 / 2, which yields best
    // performance in my experience while not breaking fairness much.
    final int maxWrittenBytes =
        session.getConfig().getMaxReadBufferSize()
            + (session.getConfig().getMaxReadBufferSize() >>> 1);
    int writtenBytes = 0;
    WriteRequest req = null;

    try {
      // Clear OP_WRITE
      setInterestedInWrite(session, false);

      do {
        // Check for pending writes.
        req = session.getCurrentWriteRequest();

        if (req == null) {
          req = writeRequestQueue.poll(session);

          if (req == null) {
            break;
          }

          session.setCurrentWriteRequest(req);
        }

        int localWrittenBytes = 0;
        Object message = req.getMessage();

        if (message instanceof IoBuffer) {
          localWrittenBytes =
              writeBuffer(
                  session, req, hasFragmentation, maxWrittenBytes - writtenBytes, currentTime);

          if ((localWrittenBytes > 0) && ((IoBuffer) message).hasRemaining()) {
            // the buffer isn't empty, we re-interest it in writing
            writtenBytes += localWrittenBytes;
            setInterestedInWrite(session, true);
            return false;
          }
        } else if (message instanceof FileRegion) {
          localWrittenBytes =
              writeFile(
                  session, req, hasFragmentation, maxWrittenBytes - writtenBytes, currentTime);

          // Fix for Java bug on Linux
          // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=5103988
          // If there's still data to be written in the FileRegion,
          // return 0 indicating that we need
          // to pause until writing may resume.
          if ((localWrittenBytes > 0) && (((FileRegion) message).getRemainingBytes() > 0)) {
            writtenBytes += localWrittenBytes;
            setInterestedInWrite(session, true);
            return false;
          }
        } else {
          throw new IllegalStateException(
              "Don't know how to handle message of type '"
                  + message.getClass().getName()
                  + "'.  Are you missing a protocol encoder?");
        }

        if (localWrittenBytes == 0) {
          // Kernel buffer is full.
          setInterestedInWrite(session, true);
          return false;
        }

        writtenBytes += localWrittenBytes;

        if (writtenBytes >= maxWrittenBytes) {
          // Wrote too much
          scheduleFlush(session);
          return false;
        }
      } while (writtenBytes < maxWrittenBytes);
    } catch (Exception e) {
      if (req != null) {
        req.getFuture().setException(e);
      }

      IoFilterChain filterChain = session.getFilterChain();
      filterChain.fireExceptionCaught(e);
      return false;
    }

    return true;
  }