@Override
    public void run(Timeout timeout) throws Exception {
      if (timeout.isCancelled()) {
        return;
      }

      if (!ctx.getChannel().isOpen()) {
        return;
      }

      State state = (State) ctx.getAttachment();
      long currentTime = System.currentTimeMillis();
      long nextDelay = timeoutMillis - (currentTime - state.lastReadTime);
      if (nextDelay <= 0) {
        // Read timed out - set a new timeout and notify the callback.
        state.timeout = timer.newTimeout(this, timeoutMillis, TimeUnit.MILLISECONDS);
        try {
          // FIXME This should be called from an I/O thread.
          //       To be fixed in Netty 4.
          readTimedOut(ctx);
        } catch (Throwable t) {
          fireExceptionCaught(ctx, t);
        }
      } else {
        // Read occurred before the timeout - set a new timeout with shorter delay.
        state.timeout = timer.newTimeout(this, nextDelay, TimeUnit.MILLISECONDS);
      }
    }
 private void destroy(ChannelHandlerContext ctx) {
   State state = (State) ctx.getAttachment();
   if (state.timeout != null) {
     state.timeout.cancel();
     state.timeout = null;
   }
 }
 private void initialize(ChannelHandlerContext ctx) {
   State state = new State();
   ctx.setAttachment(state);
   if (timeoutMillis > 0) {
     state.timeout =
         timer.newTimeout(new ReadTimeoutTask(ctx), timeoutMillis, TimeUnit.MILLISECONDS);
   }
 }