Reduce calls to System.nanoTime() and object creation in IdleStateHandler. Related to [#3808]

Motivation:

Calling System.nanoTime() for each channelRead(...) is very expensive. See [#3808] for more detailed description.
Also we always do extra work for each write and read even if read or write idle states should not be handled.

Modifications:

- Move System.nanoTime() call to channelReadComplete(...).
- Reuse ChannelFutureListener for writes
- Only add ChannelFutureListener to writes if write and all idle states should be handled.
- Only call System.nanoTime() for reads if idle state events for read and all states should be handled.

Result:

Less overhead when using the IdleStateHandler.
This commit is contained in:
Norman Maurer 2015-05-21 22:21:22 +02:00
parent 6fce3b79c3
commit bac2e3a6d2

View File

@ -97,6 +97,15 @@ import java.util.concurrent.TimeUnit;
public class IdleStateHandler extends ChannelDuplexHandler { public class IdleStateHandler extends ChannelDuplexHandler {
private static final long MIN_TIMEOUT_NANOS = TimeUnit.MILLISECONDS.toNanos(1); private static final long MIN_TIMEOUT_NANOS = TimeUnit.MILLISECONDS.toNanos(1);
// Not create a new ChannelFutureListener per write operation to reduce GC pressure.
private final ChannelFutureListener writeListener = new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
lastWriteTime = System.nanoTime();
firstWriterIdleEvent = firstAllIdleEvent = true;
}
};
private final long readerIdleTimeNanos; private final long readerIdleTimeNanos;
private final long writerIdleTimeNanos; private final long writerIdleTimeNanos;
private final long allIdleTimeNanos; private final long allIdleTimeNanos;
@ -113,6 +122,7 @@ public class IdleStateHandler extends ChannelDuplexHandler {
private boolean firstAllIdleEvent = true; private boolean firstAllIdleEvent = true;
private volatile int state; // 0 - none, 1 - initialized, 2 - destroyed private volatile int state; // 0 - none, 1 - initialized, 2 - destroyed
private volatile boolean reading;
/** /**
* Creates a new instance firing {@link IdleStateEvent}s. * Creates a new instance firing {@link IdleStateEvent}s.
@ -249,23 +259,28 @@ public class IdleStateHandler extends ChannelDuplexHandler {
@Override @Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
lastReadTime = System.nanoTime(); if (readerIdleTimeNanos > 0 || allIdleTimeNanos > 0) {
reading = true;
firstReaderIdleEvent = firstAllIdleEvent = true; firstReaderIdleEvent = firstAllIdleEvent = true;
}
ctx.fireChannelRead(msg); ctx.fireChannelRead(msg);
} }
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
if (readerIdleTimeNanos > 0 || allIdleTimeNanos > 0) {
lastReadTime = System.nanoTime();
reading = false;
}
ctx.fireChannelReadComplete();
}
@Override @Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
// Allow writing with void promise if handler is only configured for read timeout events. // Allow writing with void promise if handler is only configured for read timeout events.
if (writerIdleTimeNanos > 0 || allIdleTimeNanos > 0) { if (writerIdleTimeNanos > 0 || allIdleTimeNanos > 0) {
ChannelPromise unvoid = promise.unvoid(); ChannelPromise unvoid = promise.unvoid();
unvoid.addListener(new ChannelFutureListener() { unvoid.addListener(writeListener);
@Override
public void operationComplete(ChannelFuture future) throws Exception {
lastWriteTime = System.nanoTime();
firstWriterIdleEvent = firstAllIdleEvent = true;
}
});
ctx.write(msg, unvoid); ctx.write(msg, unvoid);
} else { } else {
ctx.write(msg, promise); ctx.write(msg, promise);
@ -342,9 +357,11 @@ public class IdleStateHandler extends ChannelDuplexHandler {
return; return;
} }
long currentTime = System.nanoTime(); long nextDelay = readerIdleTimeNanos;
long lastReadTime = IdleStateHandler.this.lastReadTime; if (!reading) {
long nextDelay = readerIdleTimeNanos - (currentTime - lastReadTime); nextDelay -= System.nanoTime() - lastReadTime;
}
if (nextDelay <= 0) { if (nextDelay <= 0) {
// Reader is idle - set a new timeout and notify the callback. // Reader is idle - set a new timeout and notify the callback.
readerIdleTimeout = readerIdleTimeout =
@ -382,9 +399,8 @@ public class IdleStateHandler extends ChannelDuplexHandler {
return; return;
} }
long currentTime = System.nanoTime();
long lastWriteTime = IdleStateHandler.this.lastWriteTime; long lastWriteTime = IdleStateHandler.this.lastWriteTime;
long nextDelay = writerIdleTimeNanos - (currentTime - lastWriteTime); long nextDelay = writerIdleTimeNanos - (System.nanoTime() - lastWriteTime);
if (nextDelay <= 0) { if (nextDelay <= 0) {
// Writer is idle - set a new timeout and notify the callback. // Writer is idle - set a new timeout and notify the callback.
writerIdleTimeout = ctx.executor().schedule( writerIdleTimeout = ctx.executor().schedule(
@ -422,9 +438,10 @@ public class IdleStateHandler extends ChannelDuplexHandler {
return; return;
} }
long currentTime = System.nanoTime(); long nextDelay = allIdleTimeNanos;
long lastIoTime = Math.max(lastReadTime, lastWriteTime); if (!reading) {
long nextDelay = allIdleTimeNanos - (currentTime - lastIoTime); nextDelay -= System.nanoTime() - Math.max(lastReadTime, lastWriteTime);
}
if (nextDelay <= 0) { if (nextDelay <= 0) {
// Both reader and writer are idle - set a new timeout and // Both reader and writer are idle - set a new timeout and
// notify the callback. // notify the callback.