@Override
  public void write(final ChannelHandlerContext ctx, final Object msg, final ChannelPromise promise)
      throws Exception {
    long curtime = System.currentTimeMillis();
    long size = calculateSize(msg);

    if (size > -1 && trafficCounter != null) {
      trafficCounter.bytesWriteFlowControl(size);
      if (writeLimit == 0) {
        ctx.write(msg, promise);
        return;
      }
      // compute the number of ms to wait before continue with the
      // channel
      long wait =
          getTimeToWait(
              writeLimit, trafficCounter.currentWrittenBytes(), trafficCounter.lastTime(), curtime);
      if (wait >= MINIMAL_WAIT) {
        ctx.executor()
            .schedule(
                new Runnable() {
                  @Override
                  public void run() {
                    ctx.write(msg, promise);
                  }
                },
                wait,
                TimeUnit.MILLISECONDS);
        return;
      }
    }
    ctx.write(msg, promise);
  }
  @Override
  public void channelRead(final ChannelHandlerContext ctx, final Object msg) throws Exception {
    long size = calculateSize(msg);
    long curtime = System.currentTimeMillis();

    if (trafficCounter != null) {
      trafficCounter.bytesRecvFlowControl(size);
      if (readLimit == 0) {
        // no action
        ctx.fireChannelRead(msg);

        return;
      }

      // compute the number of ms to wait before reopening the channel
      long wait =
          getTimeToWait(
              readLimit, trafficCounter.currentReadBytes(), trafficCounter.lastTime(), curtime);
      if (wait >= MINIMAL_WAIT) { // At least 10ms seems a minimal
        // time in order to
        // try to limit the traffic
        if (!isSuspended(ctx)) {
          ctx.attr(READ_SUSPENDED).set(true);

          // Create a Runnable to reactive the read if needed. If one was create before it will just
          // be
          // reused to limit object creation
          Attribute<Runnable> attr = ctx.attr(REOPEN_TASK);
          Runnable reopenTask = attr.get();
          if (reopenTask == null) {
            reopenTask = new ReopenReadTimerTask(ctx);
            attr.set(reopenTask);
          }
          ctx.executor().schedule(reopenTask, wait, TimeUnit.MILLISECONDS);
        } else {
          // Create a Runnable to update the next handler in the chain. If one was create before it
          // will
          // just be reused to limit object creation
          Runnable bufferUpdateTask =
              new Runnable() {
                @Override
                public void run() {
                  ctx.fireChannelRead(msg);
                }
              };
          ctx.executor().schedule(bufferUpdateTask, wait, TimeUnit.MILLISECONDS);
          return;
        }
      }
    }
    ctx.fireChannelRead(msg);
  }
 /**
  * Change the underlying limitations.
  *
  * @param newWriteLimit The new write limit (in bytes)
  * @param newReadLimit The new read limit (in bytes)
  */
 public void configure(long newWriteLimit, long newReadLimit) {
   writeLimit = newWriteLimit;
   readLimit = newReadLimit;
   if (trafficCounter != null) {
     trafficCounter.resetAccounting(System.currentTimeMillis() + 1);
   }
 }
 @Override
 public String toString() {
   return "TrafficShaping with Write Limit: "
       + writeLimit
       + " Read Limit: "
       + readLimit
       + " and Counter: "
       + (trafficCounter != null ? trafficCounter.toString() : "none");
 }
 /**
  * Change the check interval.
  *
  * @param newCheckInterval The new check interval (in milliseconds)
  */
 public void configure(long newCheckInterval) {
   checkInterval = newCheckInterval;
   if (trafficCounter != null) {
     trafficCounter.configure(checkInterval);
   }
 }