netty5/src/main/java/io/netty/handler/timeout/IdleStateHandler.java

452 lines
16 KiB
Java
Raw Normal View History

/*
2009-08-28 09:15:49 +02:00
* Copyright 2009 Red Hat, Inc.
*
2009-08-28 09:15:49 +02:00
* Red Hat licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
2009-08-28 09:15:49 +02:00
* http://www.apache.org/licenses/LICENSE-2.0
*
2009-08-28 09:15:49 +02:00
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
2011-12-09 04:38:59 +01:00
package io.netty.handler.timeout;
2011-12-09 04:38:59 +01:00
import static io.netty.channel.Channels.*;
import java.util.concurrent.TimeUnit;
2011-12-09 04:38:59 +01:00
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandler.Sharable;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.ChannelPipelineFactory;
import io.netty.channel.ChannelStateEvent;
import io.netty.channel.Channels;
import io.netty.channel.LifeCycleAwareChannelHandler;
import io.netty.channel.MessageEvent;
import io.netty.channel.SimpleChannelUpstreamHandler;
import io.netty.channel.WriteCompletionEvent;
import io.netty.util.ExternalResourceReleasable;
import io.netty.util.HashedWheelTimer;
import io.netty.util.Timeout;
import io.netty.util.Timer;
import io.netty.util.TimerTask;
/**
2009-06-19 13:17:38 +02:00
* Triggers an {@link IdleStateEvent} when a {@link Channel} has not performed
* read, write, or both operation for a while.
*
* <h3>Supported idle states</h3>
2009-06-19 18:45:30 +02:00
* <table border="1">
2009-06-19 13:17:38 +02:00
* <tr>
* <th>Property</th><th>Meaning</th>
* </tr>
* <tr>
* <td>{@code readerIdleTime}</td>
* <td>an {@link IdleStateEvent} whose state is {@link IdleState#READER_IDLE}
* will be triggered when no read was performed for the specified period of
* time. Specify {@code 0} to disable.</td>
* </tr>
* <tr>
* <td>{@code writerIdleTime}</td>
* <td>an {@link IdleStateEvent} whose state is {@link IdleState#WRITER_IDLE}
* will be triggered when no write was performed for the specified period of
* time. Specify {@code 0} to disable.</td>
* </tr>
* <tr>
* <td>{@code allIdleTime}</td>
* <td>an {@link IdleStateEvent} whose state is {@link IdleState#ALL_IDLE}
* will be triggered when neither read nor write was performed for the
* specified period of time. Specify {@code 0} to disable.</td>
* </tr>
* </table>
*
* <pre>
2010-03-30 10:02:32 +02:00
* // An example that sends a ping message when there is no outbound traffic
* // for 30 seconds. The connection is closed when there is no inbound traffic
* // for 60 seconds.
*
2010-02-02 03:00:04 +01:00
* public class MyPipelineFactory implements {@link ChannelPipelineFactory} {
*
2010-02-02 03:00:04 +01:00
* private final {@link Timer} timer;
* private final {@link ChannelHandler} idleStateHandler;
2010-02-02 03:00:04 +01:00
*
* public MyPipelineFactory({@link Timer} timer) {
* this.timer = timer;
* this.idleStateHandler = <b>new {@link IdleStateHandler}(timer, 60, 30, 0), // timer must be shared.</b>
* }
*
2010-02-02 03:00:04 +01:00
* public {@link ChannelPipeline} getPipeline() {
* return {@link Channels}.pipeline(
* idleStateHandler,
* new MyHandler());
* }
* }
*
2010-03-30 10:02:32 +02:00
* // Handler should handle the {@link IdleStateEvent} triggered by {@link IdleStateHandler}.
2010-02-02 03:00:04 +01:00
* public class MyHandler extends {@link IdleStateAwareChannelHandler} {
*
* {@code @Override}
* public void channelIdle({@link ChannelHandlerContext} ctx, {@link IdleStateEvent} e) {
2010-03-30 10:02:32 +02:00
* if (e.getState() == {@link IdleState}.READER_IDLE) {
* e.getChannel().close();
* } else if (e.getState() == {@link IdleState}.WRITER_IDLE) {
* e.getChannel().write(new PingMessage());
* }
* }
* }
*
2010-02-02 03:00:04 +01:00
* {@link ServerBootstrap} bootstrap = ...;
* {@link Timer} timer = new {@link HashedWheelTimer}();
* ...
* bootstrap.setPipelineFactory(new MyPipelineFactory(timer));
* ...
* </pre>
*
2011-02-01 04:28:40 +01:00
* The {@link Timer} which was specified when the {@link IdleStateHandler} is
2010-02-08 07:11:25 +01:00
* created should be stopped manually by calling {@link #releaseExternalResources()}
* or {@link Timer#stop()} when your application shuts down.
2009-06-19 13:33:38 +02:00
* @see ReadTimeoutHandler
* @see WriteTimeoutHandler
2009-07-20 06:50:03 +02:00
*
* @apiviz.landmark
2011-12-09 04:38:59 +01:00
* @apiviz.uses io.netty.util.HashedWheelTimer
* @apiviz.has io.netty.handler.timeout.IdleStateEvent oneway - - triggers
*/
@Sharable
public class IdleStateHandler extends SimpleChannelUpstreamHandler
implements LifeCycleAwareChannelHandler,
ExternalResourceReleasable {
final Timer timer;
final long readerIdleTimeMillis;
final long writerIdleTimeMillis;
final long allIdleTimeMillis;
2009-06-19 13:17:38 +02:00
/**
* Creates a new instance.
*
* @param timer
2009-06-19 13:40:28 +02:00
* the {@link Timer} that is used to trigger the scheduled event.
* The recommended {@link Timer} implementation is {@link HashedWheelTimer}.
2009-06-19 13:17:38 +02:00
* @param readerIdleTimeSeconds
* an {@link IdleStateEvent} whose state is {@link IdleState#READER_IDLE}
* will be triggered when no read was performed for the specified
* period of time. Specify {@code 0} to disable.
* @param writerIdleTimeSeconds
* an {@link IdleStateEvent} whose state is {@link IdleState#WRITER_IDLE}
* will be triggered when no write was performed for the specified
* period of time. Specify {@code 0} to disable.
* @param allIdleTimeSeconds
* an {@link IdleStateEvent} whose state is {@link IdleState#ALL_IDLE}
* will be triggered when neither read nor write was performed for
* the specified period of time. Specify {@code 0} to disable.
*/
public IdleStateHandler(
Timer timer,
int readerIdleTimeSeconds,
int writerIdleTimeSeconds,
int allIdleTimeSeconds) {
this(timer,
readerIdleTimeSeconds, writerIdleTimeSeconds, allIdleTimeSeconds,
TimeUnit.SECONDS);
}
2009-06-19 13:17:38 +02:00
/**
* Creates a new instance.
*
* @param timer
2009-06-19 13:40:28 +02:00
* the {@link Timer} that is used to trigger the scheduled event.
* The recommended {@link Timer} implementation is {@link HashedWheelTimer}.
2009-06-19 13:17:38 +02:00
* @param readerIdleTime
* an {@link IdleStateEvent} whose state is {@link IdleState#READER_IDLE}
* will be triggered when no read was performed for the specified
* period of time. Specify {@code 0} to disable.
* @param writerIdleTime
* an {@link IdleStateEvent} whose state is {@link IdleState#WRITER_IDLE}
* will be triggered when no write was performed for the specified
* period of time. Specify {@code 0} to disable.
* @param allIdleTime
* an {@link IdleStateEvent} whose state is {@link IdleState#ALL_IDLE}
* will be triggered when neither read nor write was performed for
* the specified period of time. Specify {@code 0} to disable.
* @param unit
* the {@link TimeUnit} of {@code readerIdleTime},
* {@code writeIdleTime}, and {@code allIdleTime}
*/
public IdleStateHandler(
Timer timer,
long readerIdleTime, long writerIdleTime, long allIdleTime,
TimeUnit unit) {
if (timer == null) {
throw new NullPointerException("timer");
}
if (unit == null) {
throw new NullPointerException("unit");
}
this.timer = timer;
if (readerIdleTime <= 0) {
readerIdleTimeMillis = 0;
} else {
readerIdleTimeMillis = Math.max(unit.toMillis(readerIdleTime), 1);
}
if (writerIdleTime <= 0) {
writerIdleTimeMillis = 0;
} else {
writerIdleTimeMillis = Math.max(unit.toMillis(writerIdleTime), 1);
}
if (allIdleTime <= 0) {
allIdleTimeMillis = 0;
} else {
allIdleTimeMillis = Math.max(unit.toMillis(allIdleTime), 1);
}
}
2009-06-19 13:17:38 +02:00
/**
* Stops the {@link Timer} which was specified in the constructor of this
* handler. You should not call this method if the {@link Timer} is in use
* by other objects.
*/
@Override
public void releaseExternalResources() {
timer.stop();
}
@Override
public void beforeAdd(ChannelHandlerContext ctx) throws Exception {
if (ctx.getPipeline().isAttached()) {
// channelOpen event has been fired already, which means
// this.channelOpen() will not be invoked.
// We have to initialize here instead.
initialize(ctx);
2009-12-17 11:21:34 +01:00
} else {
// channelOpen event has not been fired yet.
// this.channelOpen() will be invoked and initialization will occur there.
}
}
@Override
public void afterAdd(ChannelHandlerContext ctx) throws Exception {
// NOOP
}
@Override
public void beforeRemove(ChannelHandlerContext ctx) throws Exception {
destroy(ctx);
}
@Override
public void afterRemove(ChannelHandlerContext ctx) throws Exception {
// NOOP
}
@Override
public void channelOpen(ChannelHandlerContext ctx, ChannelStateEvent e)
throws Exception {
// This method will be invoked only if this handler was added
// before channelOpen event is fired. If a user adds this handler
// after the channelOpen event, initialize() will be called by beforeAdd().
initialize(ctx);
ctx.sendUpstream(e);
}
@Override
public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e)
throws Exception {
destroy(ctx);
ctx.sendUpstream(e);
}
@Override
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e)
throws Exception {
State state = (State) ctx.getAttachment();
state.lastReadTime = System.currentTimeMillis();
ctx.sendUpstream(e);
}
@Override
public void writeComplete(ChannelHandlerContext ctx, WriteCompletionEvent e)
throws Exception {
if (e.getWrittenAmount() > 0) {
State state = (State) ctx.getAttachment();
state.lastWriteTime = System.currentTimeMillis();
}
ctx.sendUpstream(e);
}
private void initialize(ChannelHandlerContext ctx) {
State state = new State();
ctx.setAttachment(state);
state.lastReadTime = state.lastWriteTime = System.currentTimeMillis();
if (readerIdleTimeMillis > 0) {
state.readerIdleTimeout = timer.newTimeout(
new ReaderIdleTimeoutTask(ctx),
readerIdleTimeMillis, TimeUnit.MILLISECONDS);
}
if (writerIdleTimeMillis > 0) {
state.writerIdleTimeout = timer.newTimeout(
new WriterIdleTimeoutTask(ctx),
writerIdleTimeMillis, TimeUnit.MILLISECONDS);
}
if (allIdleTimeMillis > 0) {
state.allIdleTimeout = timer.newTimeout(
new AllIdleTimeoutTask(ctx),
allIdleTimeMillis, TimeUnit.MILLISECONDS);
}
}
private void destroy(ChannelHandlerContext ctx) {
State state = (State) ctx.getAttachment();
if (state.readerIdleTimeout != null) {
state.readerIdleTimeout.cancel();
state.readerIdleTimeout = null;
}
if (state.writerIdleTimeout != null) {
state.writerIdleTimeout.cancel();
state.writerIdleTimeout = null;
}
if (state.allIdleTimeout != null) {
state.allIdleTimeout.cancel();
state.allIdleTimeout = null;
}
}
protected void channelIdle(
ChannelHandlerContext ctx, IdleState state, long lastActivityTimeMillis) throws Exception {
ctx.sendUpstream(new DefaultIdleStateEvent(ctx.getChannel(), state, lastActivityTimeMillis));
}
private final class ReaderIdleTimeoutTask implements TimerTask {
private final ChannelHandlerContext ctx;
ReaderIdleTimeoutTask(ChannelHandlerContext ctx) {
this.ctx = ctx;
}
@Override
public void run(Timeout timeout) throws Exception {
if (timeout.isCancelled() || !ctx.getChannel().isOpen()) {
return;
}
State state = (State) ctx.getAttachment();
long currentTime = System.currentTimeMillis();
long lastReadTime = state.lastReadTime;
long nextDelay = readerIdleTimeMillis - (currentTime - lastReadTime);
if (nextDelay <= 0) {
// Reader is idle - set a new timeout and notify the callback.
state.readerIdleTimeout =
timer.newTimeout(this, readerIdleTimeMillis, TimeUnit.MILLISECONDS);
try {
channelIdle(ctx, IdleState.READER_IDLE, lastReadTime);
} catch (Throwable t) {
fireExceptionCaught(ctx, t);
}
} else {
// Read occurred before the timeout - set a new timeout with shorter delay.
state.readerIdleTimeout =
timer.newTimeout(this, nextDelay, TimeUnit.MILLISECONDS);
}
}
}
private final class WriterIdleTimeoutTask implements TimerTask {
private final ChannelHandlerContext ctx;
WriterIdleTimeoutTask(ChannelHandlerContext ctx) {
this.ctx = ctx;
}
@Override
public void run(Timeout timeout) throws Exception {
if (timeout.isCancelled() || !ctx.getChannel().isOpen()) {
return;
}
State state = (State) ctx.getAttachment();
long currentTime = System.currentTimeMillis();
long lastWriteTime = state.lastWriteTime;
long nextDelay = writerIdleTimeMillis - (currentTime - lastWriteTime);
if (nextDelay <= 0) {
// Writer is idle - set a new timeout and notify the callback.
state.writerIdleTimeout =
timer.newTimeout(this, writerIdleTimeMillis, TimeUnit.MILLISECONDS);
try {
channelIdle(ctx, IdleState.WRITER_IDLE, lastWriteTime);
} catch (Throwable t) {
fireExceptionCaught(ctx, t);
}
} else {
// Write occurred before the timeout - set a new timeout with shorter delay.
state.writerIdleTimeout =
timer.newTimeout(this, nextDelay, TimeUnit.MILLISECONDS);
}
}
}
private final class AllIdleTimeoutTask implements TimerTask {
private final ChannelHandlerContext ctx;
AllIdleTimeoutTask(ChannelHandlerContext ctx) {
this.ctx = ctx;
}
@Override
public void run(Timeout timeout) throws Exception {
if (timeout.isCancelled() || !ctx.getChannel().isOpen()) {
return;
}
State state = (State) ctx.getAttachment();
long currentTime = System.currentTimeMillis();
long lastIoTime = Math.max(state.lastReadTime, state.lastWriteTime);
long nextDelay = allIdleTimeMillis - (currentTime - lastIoTime);
if (nextDelay <= 0) {
// Both reader and writer are idle - set a new timeout and
// notify the callback.
state.allIdleTimeout =
timer.newTimeout(this, allIdleTimeMillis, TimeUnit.MILLISECONDS);
try {
channelIdle(ctx, IdleState.ALL_IDLE, lastIoTime);
} catch (Throwable t) {
fireExceptionCaught(ctx, t);
}
} else {
// Either read or write occurred before the timeout - set a new
// timeout with shorter delay.
state.allIdleTimeout =
timer.newTimeout(this, nextDelay, TimeUnit.MILLISECONDS);
}
}
}
private static final class State {
State() {
}
volatile Timeout readerIdleTimeout;
volatile long lastReadTime;
volatile Timeout writerIdleTimeout;
volatile long lastWriteTime;
volatile Timeout allIdleTimeout;
}
}