Connection Idle

An idle network connection means that the current network connection is idle, i.e., there is no ongoing data transfer or communication activity. When one of our connections is no longer sending requests or receiving responses, that connection begins to be idle.

The longer the idle time of a network connection, the less active the connection is. At this point, different actions can be taken depending on different scenarios, such as:

  • Close the connection

  • Send heartbeat, long connection scenario requires sending heartbeat regularly to keep connection status or probe live

Netty connection idle handler

Netty provides by default io.netty.handler.timeout.IdleStateHandler to manage connection idle events, which detects connection idle time and triggers an IdleStateEvent event when the connection has no read or write operations for a specified period of time:

IdleStateHandler provides three main parameters, set to 0 to disable:

  1. readerIdleTimeSeconds: read idle time, that is, when the server specified time no data read, will trigger a read idle event.

  2. writerIdleTimeSeconds: write idle time, that is, when the server specifies the time no data is sent (or write action, the parameters are not the same at different stages), a write idle event will be triggered.

  3. allIdleTimeSeconds: read/write idle time. When the client connection has no read/write operation within the specified time, an IdleStateEvent's All event will be triggered.

public IdleStateHandler(  
  int readerIdleTimeSeconds,  
  int writerIdleTimeSeconds,  
  int allIdleTimeSeconds  
  this(readerIdleTimeSeconds, writerIdleTimeSeconds, allIdleTimeSeconds,    
  TimeUnit.SECONDS);
}

Read Idle

Netty's ReadTimeoutHandler is a handler for read idle processing, which is used to handle read timeout events. This class will trigger a read timeout event when the client or server does not receive data within a specified time.

In Netty, IdleStateHandler is used to manage read timeouts and write timeouts. ReadTimeoutHandler inherits from IdleStateHandler and implements a custom read timeout function on top of it. When a read timeout occurs, ReadTimeoutHandler calls its channelRead method, which implements the specific read timeout logic.

ReadTimeoutHandler throws a read timeout exception when the idle time is reached and closes the connection:

protected void readTimedOut(ChannelHandlerContext ctx) 
throws Exception {        
  if (!closed) {            
    ctx.fireExceptionCaught(ReadTimeoutException.INSTANCE);            
    ctx.close();            
    closed = true;        
  }    
}

I generally use the ReadTimeoutHandler on the server side, when a client's connection is idle for too long to close the client's connection and release resources.

In general IdleStateHandler is used as the first Handler in the ChannelPipeline to handle the heartbeat detection of the connection. The IdleStateEvent event is triggered when the connection times out and then handed over to the next handler to handle the event, so is the ReadTimeoutHandler, for example as follows:

@Override    
public void initChannel(SocketChannel ch) throws Exception {
  ChannelPipeline pipeline = ch.pipeline();        
  pipeline.addLast(new ReadTimeoutHandler(READ_TIMEOUT_SECONDS));        
  pipeline.addLast(new ServerHandler());    
}

Write idle

Writing idle I generally use on the client side, for example to reach the idle time to close the connection with the server or to send a heartbeat to the server to maintain a long connection.

The write idle then closes example is as follows:

public class ClientIdleHandler extends IdleStateHandler {    
  private static final long IDLE_TIMEOUT = 30000;    
  public ClientIdleHandler() {        
    super(true, 0, IDLE_TIMEOUT, 0, TimeUnit.MILLISECONDS);    
  }    
  @Override    
  protected void channelIdle(ChannelHandlerContext ctx, IdleStateEvent evt) throws Exception {        
      if (evt == IdleStateEvent.FIRST_WRITER_IDLE_STATE_EVENT) {            
      // Connection idle, close connection            
      ctx.close();        
    }    
  }
}

Or you can leave it off and send a heartbeat packet, but note that in this case, the idle time for the client is less than that of the server.

We can also add a timed task to send heartbeat packets: 

// Rewrite the handlerAdded method to add a timed task    
@Override    
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {        
  ctx.executor().scheduleAtFixedRate(() -> {            
    // Determine if a connection is active           
    if (ctx.channel().isActive()) {                 
    // Sending heartbeat packets             
      ctx.writeAndFlush(new Heartbeat());             
    }        
  }, 0, idleTimeSeconds, TimeUnit.SECONDS);    
}

Read/Write Idle

The above read idle time and write idle time are both reached before triggering, and both take the maximum value of:

long nextDelay = allIdleTimeNanos;
if (!reading) {    
nextDelay -= ticksInNanos() - Math.max(lastReadTime, lastWriteTime);
}

Writing Intent

public IdleStateHandler(boolean observeOutput,  long readerIdleTime, long writerIdleTime, long allIdleTime,  TimeUnit unit) 

There is one more parameter when evaluating write idle: observeOutput.

By default, the value is false, and data sent is judged as write idle, while set to true is when write action occurs, such as writing to the cache but not yet sent is non-idle.

It is judged by observing the current data change and progress of the cache. As follows, compare with the last time:

if (buf != null) {  
  int messageHashCode = System.identityHashCode(buf.current());  
  long pendingWriteBytes = buf.totalPendingWriteBytes();  
  if (messageHashCode != lastMessageHashCode || pendingWriteBytes != lastPendingWriteBytes) {      
    lastMessageHashCode = messageHashCode;      
    lastPendingWriteBytes = pendingWriteBytes;      
    if (!first) {        
      return true;      
    }  
  }  
  long flushProgress = buf.currentProgress();    
  if (flushProgress != lastFlushProgress) {      
    lastFlushProgress = flushProgress;      
    if (!first) {        
      return true;      
    }    
  }   
 }
}

Misconceptions

Netty provides ReadTimeoutHandler for reading idle processing, is not provided also WriteTimeoutHandler for writing idle.

Explain clearly: there is no write idle WriteTimeoutHandler, but there does exist WriteTimeoutHandler, the handler indicates the write data action timeout handler, but not the connection write idle handler, when the data is not written within the specified time, a write timeout exception will be thrown, you can look at the following Source code:

public void run() { // Was not written yet so issue a write timeout // The promise itself will be failed with a ClosedChannelException once the close() was issued // See https://github.com/netty/netty/issues/2159 if (!promise.isDone()) { try { writeTimedOut(ctx); } catch (Throwable t) { ctx.fireExceptionCaught(t); }   } removeWriteTimeoutTask(this); } protected void writeTimedOut(ChannelHandlerContext ctx) throws Exception { if (!closed) { ctx.fireExceptionCaught(WriteTimeoutException.INSTANCE); ctx.close(); closed = true;   } }