Fix #239: IdleStateHandler starts two timers

1) Do not rely on ChannelPipeline.isAttached() to ensure initialize() is
called once.
2) Fix a race condition where initialize() can schedule timeouts after
destroy() is called.
This commit is contained in:
Trustin Lee 2012-03-22 15:48:08 +09:00
parent 32ff810b64
commit f8253e031d

View File

@ -224,15 +224,7 @@ public class IdleStateHandler extends SimpleChannelUpstreamHandler
@Override @Override
public void beforeAdd(ChannelHandlerContext ctx) throws Exception { 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); initialize(ctx);
} else {
// channelOpen event has not been fired yet.
// this.channelOpen() will be invoked and initialization will occur there.
}
} }
@Override @Override
@ -250,16 +242,6 @@ public class IdleStateHandler extends SimpleChannelUpstreamHandler
// NOOP // 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 @Override
public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e) public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e)
throws Exception { throws Exception {
@ -286,8 +268,13 @@ public class IdleStateHandler extends SimpleChannelUpstreamHandler
} }
private void initialize(ChannelHandlerContext ctx) { private void initialize(ChannelHandlerContext ctx) {
State state = new State(); State state = state(ctx);
ctx.setAttachment(state);
// Avoid the case where destroy() is called before scheduling timeouts.
// See: https://github.com/netty/netty/issues/143
if (state.destroyed) {
return;
}
state.lastReadTime = state.lastWriteTime = System.currentTimeMillis(); state.lastReadTime = state.lastWriteTime = System.currentTimeMillis();
if (readerIdleTimeMillis > 0) { if (readerIdleTimeMillis > 0) {
@ -308,12 +295,13 @@ public class IdleStateHandler extends SimpleChannelUpstreamHandler
} }
private void destroy(ChannelHandlerContext ctx) { private void destroy(ChannelHandlerContext ctx) {
State state = (State) ctx.getAttachment(); State state;
// Check if the state was set before, it may not if the destroy method was called before the
// channelOpen(...) method. synchronized (ctx) {
// state = state(ctx);
// See #143 state.destroyed = true;
if (state != null) { }
if (state.readerIdleTimeout != null) { if (state.readerIdleTimeout != null) {
state.readerIdleTimeout.cancel(); state.readerIdleTimeout.cancel();
state.readerIdleTimeout = null; state.readerIdleTimeout = null;
@ -327,6 +315,19 @@ public class IdleStateHandler extends SimpleChannelUpstreamHandler
state.allIdleTimeout = null; state.allIdleTimeout = null;
} }
} }
private State state(ChannelHandlerContext ctx) {
State state;
synchronized (ctx) {
// FIXME: It could have been better if there is setAttachmentIfAbsent().
state = (State) ctx.getAttachment();
if (state != null) {
return state;
}
state = new State();
ctx.setAttachment(state);
}
return state;
} }
protected void channelIdle( protected void channelIdle(
@ -453,5 +454,7 @@ public class IdleStateHandler extends SimpleChannelUpstreamHandler
volatile long lastWriteTime; volatile long lastWriteTime;
volatile Timeout allIdleTimeout; volatile Timeout allIdleTimeout;
volatile boolean destroyed;
} }
} }