Make sure ChannelHandler.handlerRemoved() is always invoked

- Fixes #1366: No elegant way to free non-in/outbound buffers held by a handler
- handlerRemoved() is now also invoked when a channel is deregistered, as well as when a handler is removed from a pipeline.
- A little bit of clean-up for readability
- Fix a bug in forwardBufferContentAndRemove() where the handler buffers are not freed (mainly because we were relying on channel.isRegistered() to determine if the handler has been removed from inside the handler.
- ChunkedWriteHandler.handlerRemoved() is unnecessary anymore because ChannelPipeline now always forwards the content of the buffer.
This commit is contained in:
Trustin Lee 2013-05-16 19:32:39 +09:00
parent 670d3f53a8
commit dc13b68632
3 changed files with 214 additions and 174 deletions

View File

@ -19,7 +19,6 @@ import io.netty.buffer.MessageBuf;
import io.netty.buffer.Unpooled; import io.netty.buffer.Unpooled;
import io.netty.channel.Channel; import io.netty.channel.Channel;
import io.netty.channel.ChannelDuplexHandler; import io.netty.channel.ChannelDuplexHandler;
import io.netty.channel.ChannelException;
import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandler;
@ -99,14 +98,6 @@ public class ChunkedWriteHandler
this.ctx = ctx; this.ctx = ctx;
} }
// This method should not need any synchronization as the ChunkedWriteHandler will not receive any new events
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
// Fail all promised that are queued. This is needed because otherwise we would never notify the
// ChannelFuture and the registered FutureListener. See #304
discard(ctx, new ChannelException(ChunkedWriteHandler.class.getSimpleName() + " removed from pipeline."));
}
private boolean isWritable() { private boolean isWritable() {
return pendingWrites.get() < maxPendingWrites; return pendingWrites.get() < maxPendingWrites;
} }

View File

@ -60,8 +60,8 @@ final class DefaultChannelHandlerContext extends DefaultAttributeMap implements
private final ByteBuf inByteBuf; private final ByteBuf inByteBuf;
private MessageBuf<Object> outMsgBuf; private MessageBuf<Object> outMsgBuf;
private ByteBuf outByteBuf; private ByteBuf outByteBuf;
private short callDepth;
private int flags; private short flags;
// When the two handlers run in a different thread and they are next to each other, // When the two handlers run in a different thread and they are next to each other,
// each other's buffers can be accessed at the same time resulting in a race condition. // each other's buffers can be accessed at the same time resulting in a race condition.
@ -200,9 +200,11 @@ final class DefaultChannelHandlerContext extends DefaultAttributeMap implements
void forwardBufferContentAndRemove( void forwardBufferContentAndRemove(
final DefaultChannelHandlerContext forwardPrev, final DefaultChannelHandlerContext forwardNext) { final DefaultChannelHandlerContext forwardPrev, final DefaultChannelHandlerContext forwardNext) {
try { try {
boolean flush = false; boolean flush = false;
boolean inboundBufferUpdated = false; boolean inboundBufferUpdated = false;
if (!isOutboundFreed()) {
if (hasOutboundByteBuffer() && outboundByteBuffer().isReadable()) { if (hasOutboundByteBuffer() && outboundByteBuffer().isReadable()) {
ByteBuf forwardPrevBuf; ByteBuf forwardPrevBuf;
if (forwardPrev.hasOutboundByteBuffer()) { if (forwardPrev.hasOutboundByteBuffer()) {
@ -224,6 +226,9 @@ final class DefaultChannelHandlerContext extends DefaultAttributeMap implements
flush = true; flush = true;
} }
} }
}
if (!isInboundFreed()) {
if (hasInboundByteBuffer() && inboundByteBuffer().isReadable()) { if (hasInboundByteBuffer() && inboundByteBuffer().isReadable()) {
ByteBuf forwardNextBuf; ByteBuf forwardNextBuf;
if (forwardNext.hasInboundByteBuffer()) { if (forwardNext.hasInboundByteBuffer()) {
@ -245,6 +250,8 @@ final class DefaultChannelHandlerContext extends DefaultAttributeMap implements
inboundBufferUpdated = true; inboundBufferUpdated = true;
} }
} }
}
if (flush) { if (flush) {
EventExecutor executor = executor(); EventExecutor executor = executor();
Thread currentThread = Thread.currentThread(); Thread currentThread = Thread.currentThread();
@ -260,6 +267,7 @@ final class DefaultChannelHandlerContext extends DefaultAttributeMap implements
}); });
} }
} }
if (inboundBufferUpdated) { if (inboundBufferUpdated) {
EventExecutor executor = executor(); EventExecutor executor = executor();
if (executor.inEventLoop()) { if (executor.inEventLoop()) {
@ -275,11 +283,7 @@ final class DefaultChannelHandlerContext extends DefaultAttributeMap implements
} }
} finally { } finally {
flags |= FLAG_REMOVED; flags |= FLAG_REMOVED;
freeAllIfRemoved();
// Free all buffers before completing removal.
if (!channel.isRegistered()) {
freeHandlerBuffersAfterRemoval();
}
} }
} }
@ -430,34 +434,105 @@ final class DefaultChannelHandlerContext extends DefaultAttributeMap implements
return nextBufferHadEnoughRoom; return nextBufferHadEnoughRoom;
} }
private void freeHandlerBuffersAfterRemoval() { private boolean isInboundFreed() {
int flags = this.flags; return (flags & FLAG_FREED_INBOUND) != 0;
}
private boolean isOutboundFreed() {
return (flags & FLAG_FREED_OUTBOUND) != 0;
}
private void freeAllIfRemoved() {
if (callDepth != 0) {
// Free only when the current context's handler is not being called.
return;
}
final int flags = this.flags;
if ((flags & FLAG_REMOVED) != 0 && (flags & FLAG_FREED) == 0) { // Removed, but not freed yet if ((flags & FLAG_REMOVED) != 0 && (flags & FLAG_FREED) == 0) { // Removed, but not freed yet
try { try {
freeBuffer(inByteBuf); safeFree(inByteBuf);
freeBuffer(inMsgBuf); safeFree(inMsgBuf);
freeBuffer(outByteBuf); safeFree(outByteBuf);
freeBuffer(outMsgBuf); safeFree(outMsgBuf);
} finally { } finally {
flags |= FLAG_FREED | FLAG_FREED_INBOUND | FLAG_FREED_OUTBOUND; this.flags = (short) (flags | FLAG_FREED | FLAG_FREED_INBOUND | FLAG_FREED_OUTBOUND);
freeNextInboundBridgeFeeder(); freeNextInboundBridgeFeeder();
freeNextOutboundBridgeFeeder(); freeNextOutboundBridgeFeeder();
} }
} }
} }
private void freeBuffer(Buf buf) { void freeInbound() {
if (buf != null) { EventExecutor executor = executor();
try { if (executor.inEventLoop()) {
buf.release(); freeInbound0();
} catch (Exception e) { } else {
notifyHandlerException(e); executor.execute(new Runnable() {
@Override
public void run() {
freeInbound0();
} }
});
} }
} }
private boolean isInboundFreed() { private void freeInbound0() {
return (flags & FLAG_FREED_INBOUND) != 0; try {
safeFree(inByteBuf);
safeFree(inMsgBuf);
} finally {
flags |= FLAG_FREED_INBOUND;
freeNextInboundBridgeFeeder();
}
if (next != null) {
DefaultChannelHandlerContext nextCtx = findContextInbound();
nextCtx.freeInbound();
} else {
// Freed all inbound buffers. Remove all handlers from the pipeline one by one from tail (exclusive)
// to head (inclusive) to trigger handlerRemoved(). If the removed handler has an outbound buffer, free it,
// too. Note that the tail handler is excluded because it's neither an outbound buffer and it doesn't
// do anything in handlerRemoved().
pipeline.tail.prev.freeOutboundAndRemove();
}
}
/** Invocation initiated by {@link #freeInbound0()} after freeing all inbound buffers. */
private void freeOutboundAndRemove() {
EventExecutor executor = executor();
if (executor.inEventLoop()) {
freeOutboundAndRemove0();
} else {
executor.execute(new Runnable() {
@Override
public void run() {
freeOutboundAndRemove0();
}
});
}
}
private void freeOutboundAndRemove0() {
if (handler instanceof ChannelOperationHandler) {
// Outbound handler - free the buffers / bridge feeders
try {
safeFree(outByteBuf);
safeFree(outMsgBuf);
} finally {
// We also OR FLAG_FREED because at this point we are sure both inbound and outbound were freed.
flags |= FLAG_FREED | FLAG_FREED_OUTBOUND;
freeNextOutboundBridgeFeeder();
}
}
DefaultChannelHandlerContext prev = this.prev;
if (prev != null) {
synchronized (pipeline) {
pipeline.remove0(this, false);
}
prev.freeOutboundAndRemove();
}
} }
private void freeNextInboundBridgeFeeder() { private void freeNextInboundBridgeFeeder() {
@ -479,10 +554,6 @@ final class DefaultChannelHandlerContext extends DefaultAttributeMap implements
} }
} }
private boolean isOutboundFreed() {
return (flags & FLAG_FREED_OUTBOUND) != 0;
}
private void freeNextOutboundBridgeFeeder() { private void freeNextOutboundBridgeFeeder() {
// Release the bridge feeder // Release the bridge feeder
NextBridgeFeeder feeder = nextOutBridgeFeeder; NextBridgeFeeder feeder = nextOutBridgeFeeder;
@ -500,6 +571,16 @@ final class DefaultChannelHandlerContext extends DefaultAttributeMap implements
} }
} }
private static void safeFree(Buf buf) {
if (buf != null) {
try {
buf.release();
} catch (Exception e) {
logger.warn("Failed to release a handler buffer.", e);
}
}
}
@Override @Override
public Channel channel() { public Channel channel() {
return channel; return channel;
@ -710,12 +791,14 @@ final class DefaultChannelHandlerContext extends DefaultAttributeMap implements
} }
private void invokeChannelRegistered() { private void invokeChannelRegistered() {
callDepth ++;
try { try {
((ChannelStateHandler) handler()).channelRegistered(this); ((ChannelStateHandler) handler()).channelRegistered(this);
} catch (Throwable t) { } catch (Throwable t) {
notifyHandlerException(t); notifyHandlerException(t);
} finally { } finally {
freeHandlerBuffersAfterRemoval(); callDepth --;
freeAllIfRemoved();
} }
} }
@ -737,10 +820,13 @@ final class DefaultChannelHandlerContext extends DefaultAttributeMap implements
} }
private void invokeChannelUnregistered() { private void invokeChannelUnregistered() {
callDepth ++;
try { try {
((ChannelStateHandler) handler()).channelUnregistered(this); ((ChannelStateHandler) handler()).channelUnregistered(this);
} catch (Throwable t) { } catch (Throwable t) {
notifyHandlerException(t); notifyHandlerException(t);
} finally {
callDepth --;
} }
} }
@ -762,12 +848,14 @@ final class DefaultChannelHandlerContext extends DefaultAttributeMap implements
} }
private void invokeChannelActive() { private void invokeChannelActive() {
callDepth ++;
try { try {
((ChannelStateHandler) handler()).channelActive(this); ((ChannelStateHandler) handler()).channelActive(this);
} catch (Throwable t) { } catch (Throwable t) {
notifyHandlerException(t); notifyHandlerException(t);
} finally { } finally {
freeHandlerBuffersAfterRemoval(); callDepth --;
freeAllIfRemoved();
} }
} }
@ -789,12 +877,14 @@ final class DefaultChannelHandlerContext extends DefaultAttributeMap implements
} }
private void invokeChannelInactive() { private void invokeChannelInactive() {
callDepth ++;
try { try {
((ChannelStateHandler) handler()).channelInactive(this); ((ChannelStateHandler) handler()).channelInactive(this);
} catch (Throwable t) { } catch (Throwable t) {
notifyHandlerException(t); notifyHandlerException(t);
} finally { } finally {
freeHandlerBuffersAfterRemoval(); callDepth --;
freeAllIfRemoved();
} }
} }
@ -831,6 +921,7 @@ final class DefaultChannelHandlerContext extends DefaultAttributeMap implements
private void invokeExceptionCaught0(Throwable cause) { private void invokeExceptionCaught0(Throwable cause) {
ChannelHandler handler = handler(); ChannelHandler handler = handler();
callDepth ++;
try { try {
handler.exceptionCaught(this, cause); handler.exceptionCaught(this, cause);
} catch (Throwable t) { } catch (Throwable t) {
@ -840,7 +931,8 @@ final class DefaultChannelHandlerContext extends DefaultAttributeMap implements
"exceptionCaught() method while handling the following exception:", cause); "exceptionCaught() method while handling the following exception:", cause);
} }
} finally { } finally {
freeHandlerBuffersAfterRemoval(); callDepth --;
freeAllIfRemoved();
} }
} }
@ -868,12 +960,14 @@ final class DefaultChannelHandlerContext extends DefaultAttributeMap implements
private void invokeUserEventTriggered(Object event) { private void invokeUserEventTriggered(Object event) {
ChannelStateHandler handler = (ChannelStateHandler) handler(); ChannelStateHandler handler = (ChannelStateHandler) handler();
callDepth ++;
try { try {
handler.userEventTriggered(this, event); handler.userEventTriggered(this, event);
} catch (Throwable t) { } catch (Throwable t) {
notifyHandlerException(t); notifyHandlerException(t);
} finally { } finally {
freeHandlerBuffersAfterRemoval(); callDepth --;
freeAllIfRemoved();
} }
} }
@ -931,6 +1025,7 @@ final class DefaultChannelHandlerContext extends DefaultAttributeMap implements
ChannelStateHandler handler = (ChannelStateHandler) handler(); ChannelStateHandler handler = (ChannelStateHandler) handler();
if (handler instanceof ChannelInboundHandler) { if (handler instanceof ChannelInboundHandler) {
for (;;) { for (;;) {
callDepth ++;
try { try {
boolean flushedAll = flushInboundBridge(); boolean flushedAll = flushInboundBridge();
handler.inboundBufferUpdated(this); handler.inboundBufferUpdated(this);
@ -941,6 +1036,7 @@ final class DefaultChannelHandlerContext extends DefaultAttributeMap implements
notifyHandlerException(t); notifyHandlerException(t);
break; break;
} finally { } finally {
callDepth --;
if (handler instanceof ChannelInboundByteHandler && !isInboundFreed()) { if (handler instanceof ChannelInboundByteHandler && !isInboundFreed()) {
try { try {
((ChannelInboundByteHandler) handler).discardInboundReadBytes(this); ((ChannelInboundByteHandler) handler).discardInboundReadBytes(this);
@ -948,14 +1044,21 @@ final class DefaultChannelHandlerContext extends DefaultAttributeMap implements
notifyHandlerException(t); notifyHandlerException(t);
} }
} }
freeHandlerBuffersAfterRemoval(); freeAllIfRemoved();
}
if (isInboundFreed()) {
break;
} }
} }
} else { } else {
callDepth ++;
try { try {
handler.inboundBufferUpdated(this); handler.inboundBufferUpdated(this);
} catch (Throwable t) { } catch (Throwable t) {
notifyHandlerException(t); notifyHandlerException(t);
} finally {
callDepth --;
} }
} }
} }
@ -982,12 +1085,14 @@ final class DefaultChannelHandlerContext extends DefaultAttributeMap implements
} }
private void invokeChannelReadSuspended() { private void invokeChannelReadSuspended() {
callDepth ++;
try { try {
((ChannelStateHandler) handler()).channelReadSuspended(this); ((ChannelStateHandler) handler()).channelReadSuspended(this);
} catch (Throwable t) { } catch (Throwable t) {
notifyHandlerException(t); notifyHandlerException(t);
} finally { } finally {
freeHandlerBuffersAfterRemoval(); callDepth --;
freeAllIfRemoved();
} }
} }
@ -1056,12 +1161,14 @@ final class DefaultChannelHandlerContext extends DefaultAttributeMap implements
} }
private void invokeBind0(SocketAddress localAddress, ChannelPromise promise) { private void invokeBind0(SocketAddress localAddress, ChannelPromise promise) {
callDepth ++;
try { try {
((ChannelOperationHandler) handler()).bind(this, localAddress, promise); ((ChannelOperationHandler) handler()).bind(this, localAddress, promise);
} catch (Throwable t) { } catch (Throwable t) {
notifyHandlerException(t); notifyHandlerException(t);
} finally { } finally {
freeHandlerBuffersAfterRemoval(); callDepth --;
freeAllIfRemoved();
} }
} }
@ -1097,12 +1204,14 @@ final class DefaultChannelHandlerContext extends DefaultAttributeMap implements
} }
private void invokeConnect0(SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) { private void invokeConnect0(SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) {
callDepth ++;
try { try {
((ChannelOperationHandler) handler()).connect(this, remoteAddress, localAddress, promise); ((ChannelOperationHandler) handler()).connect(this, remoteAddress, localAddress, promise);
} catch (Throwable t) { } catch (Throwable t) {
notifyHandlerException(t); notifyHandlerException(t);
} finally { } finally {
freeHandlerBuffersAfterRemoval(); callDepth --;
freeAllIfRemoved();
} }
} }
@ -1136,12 +1245,14 @@ final class DefaultChannelHandlerContext extends DefaultAttributeMap implements
} }
private void invokeDisconnect0(ChannelPromise promise) { private void invokeDisconnect0(ChannelPromise promise) {
callDepth ++;
try { try {
((ChannelOperationHandler) handler()).disconnect(this, promise); ((ChannelOperationHandler) handler()).disconnect(this, promise);
} catch (Throwable t) { } catch (Throwable t) {
notifyHandlerException(t); notifyHandlerException(t);
} finally { } finally {
freeHandlerBuffersAfterRemoval(); callDepth --;
freeAllIfRemoved();
} }
} }
@ -1168,12 +1279,14 @@ final class DefaultChannelHandlerContext extends DefaultAttributeMap implements
} }
private void invokeClose0(ChannelPromise promise) { private void invokeClose0(ChannelPromise promise) {
callDepth ++;
try { try {
((ChannelOperationHandler) handler()).close(this, promise); ((ChannelOperationHandler) handler()).close(this, promise);
} catch (Throwable t) { } catch (Throwable t) {
notifyHandlerException(t); notifyHandlerException(t);
} finally { } finally {
freeHandlerBuffersAfterRemoval(); callDepth --;
freeAllIfRemoved();
} }
} }
@ -1200,12 +1313,14 @@ final class DefaultChannelHandlerContext extends DefaultAttributeMap implements
} }
private void invokeDeregister0(ChannelPromise promise) { private void invokeDeregister0(ChannelPromise promise) {
callDepth ++;
try { try {
((ChannelOperationHandler) handler()).deregister(this, promise); ((ChannelOperationHandler) handler()).deregister(this, promise);
} catch (Throwable t) { } catch (Throwable t) {
notifyHandlerException(t); notifyHandlerException(t);
} finally { } finally {
freeHandlerBuffersAfterRemoval(); callDepth --;
freeAllIfRemoved();
} }
} }
@ -1233,12 +1348,14 @@ final class DefaultChannelHandlerContext extends DefaultAttributeMap implements
} }
private void invokeRead0() { private void invokeRead0() {
callDepth ++;
try { try {
((ChannelOperationHandler) handler()).read(this); ((ChannelOperationHandler) handler()).read(this);
} catch (Throwable t) { } catch (Throwable t) {
notifyHandlerException(t); notifyHandlerException(t);
} finally { } finally {
freeHandlerBuffersAfterRemoval(); callDepth --;
freeAllIfRemoved();
} }
} }
@ -1308,11 +1425,13 @@ final class DefaultChannelHandlerContext extends DefaultAttributeMap implements
flushOutboundBridge(); flushOutboundBridge();
} }
callDepth ++;
try { try {
handler.flush(this, promise); handler.flush(this, promise);
} catch (Throwable t) { } catch (Throwable t) {
notifyHandlerException(t); notifyHandlerException(t);
} finally { } finally {
callDepth --;
if (handler instanceof ChannelOutboundByteHandler && !isOutboundFreed()) { if (handler instanceof ChannelOutboundByteHandler && !isOutboundFreed()) {
try { try {
((ChannelOutboundByteHandler) handler).discardOutboundReadBytes(this); ((ChannelOutboundByteHandler) handler).discardOutboundReadBytes(this);
@ -1320,7 +1439,7 @@ final class DefaultChannelHandlerContext extends DefaultAttributeMap implements
notifyHandlerException(t); notifyHandlerException(t);
} }
} }
freeHandlerBuffersAfterRemoval(); freeAllIfRemoved();
} }
} }
@ -1360,12 +1479,14 @@ final class DefaultChannelHandlerContext extends DefaultAttributeMap implements
flushOutboundBridge(); flushOutboundBridge();
} }
callDepth ++;
try { try {
handler.sendFile(this, region, promise); handler.sendFile(this, region, promise);
} catch (Throwable t) { } catch (Throwable t) {
notifyHandlerException(t); notifyHandlerException(t);
} finally { } finally {
freeHandlerBuffersAfterRemoval(); callDepth --;
freeAllIfRemoved();
} }
} }
@ -1453,80 +1574,6 @@ final class DefaultChannelHandlerContext extends DefaultAttributeMap implements
invokeFlush0(promise); invokeFlush0(promise);
} }
void freeInbound() {
EventExecutor executor = executor();
if (executor.inEventLoop()) {
freeInbound0();
} else {
executor.execute(new Runnable() {
@Override
public void run() {
freeInbound0();
}
});
}
}
private void freeInbound0() {
try {
freeBuffer(inByteBuf);
freeBuffer(inMsgBuf);
} finally {
flags |= FLAG_FREED_INBOUND;
freeNextInboundBridgeFeeder();
}
if (next != null) {
DefaultChannelHandlerContext nextCtx = findContextInbound();
nextCtx.freeInbound();
} else {
// Freed all inbound buffers. Free all outbound buffers in a reverse order.
findContextOutbound().freeOutbound();
}
}
/** Invocation initiated by {@link #freeInbound0()} after freeing all inbound buffers. */
private void freeOutbound() {
EventExecutor executor = executor();
if (next == null) {
if (executor.inEventLoop()) {
freeOutbound0();
} else {
executor.execute(new Runnable() {
@Override
public void run() {
freeOutbound0();
}
});
}
} else {
if (executor.inEventLoop()) {
freeOutbound0();
} else {
executor.execute(new Runnable() {
@Override
public void run() {
freeOutbound0();
}
});
}
}
}
private void freeOutbound0() {
try {
freeBuffer(outByteBuf);
freeBuffer(outMsgBuf);
} finally {
flags |= FLAG_FREED_OUTBOUND;
freeNextOutboundBridgeFeeder();
}
if (prev != null) {
findContextOutbound().freeOutbound();
}
}
private void notifyHandlerException(Throwable cause) { private void notifyHandlerException(Throwable cause) {
if (inExceptionCaught(cause)) { if (inExceptionCaught(cause)) {
if (logger.isWarnEnabled()) { if (logger.isWarnEnabled()) {

View File

@ -331,14 +331,14 @@ final class DefaultChannelPipeline implements ChannelPipeline {
synchronized (this) { synchronized (this) {
if (!ctx.channel().isRegistered() || ctx.executor().inEventLoop()) { if (!ctx.channel().isRegistered() || ctx.executor().inEventLoop()) {
remove0(ctx); remove0(ctx, true);
return ctx; return ctx;
} else { } else {
future = ctx.executor().submit(new Runnable() { future = ctx.executor().submit(new Runnable() {
@Override @Override
public void run() { public void run() {
synchronized (DefaultChannelPipeline.this) { synchronized (DefaultChannelPipeline.this) {
remove0(ctx); remove0(ctx, true);
} }
} }
}); });
@ -354,14 +354,14 @@ final class DefaultChannelPipeline implements ChannelPipeline {
return context; return context;
} }
private void remove0(DefaultChannelHandlerContext ctx) { void remove0(DefaultChannelHandlerContext ctx, boolean forward) {
DefaultChannelHandlerContext prev = ctx.prev; DefaultChannelHandlerContext prev = ctx.prev;
DefaultChannelHandlerContext next = ctx.next; DefaultChannelHandlerContext next = ctx.next;
prev.next = next; prev.next = next;
next.prev = prev; next.prev = prev;
name2ctx.remove(ctx.name()); name2ctx.remove(ctx.name());
callHandlerRemoved(ctx, prev, next); callHandlerRemoved(ctx, prev, next, forward);
} }
@Override @Override
@ -462,7 +462,7 @@ final class DefaultChannelPipeline implements ChannelPipeline {
// because callHandlerRemoved() will trigger inboundBufferUpdated() or flush() on newHandler and those // because callHandlerRemoved() will trigger inboundBufferUpdated() or flush() on newHandler and those
// event handlers must be called after handlerAdded(). // event handlers must be called after handlerAdded().
callHandlerAdded(newCtx); callHandlerAdded(newCtx);
callHandlerRemoved(oldCtx, newCtx, newCtx); callHandlerRemoved(oldCtx, newCtx, newCtx, true);
} }
private static void checkMultiplicity(ChannelHandlerContext ctx) { private static void checkMultiplicity(ChannelHandlerContext ctx) {
@ -519,32 +519,34 @@ final class DefaultChannelPipeline implements ChannelPipeline {
private void callHandlerRemoved( private void callHandlerRemoved(
final DefaultChannelHandlerContext ctx, final DefaultChannelHandlerContext ctxPrev, final DefaultChannelHandlerContext ctx, final DefaultChannelHandlerContext ctxPrev,
final DefaultChannelHandlerContext ctxNext) { final DefaultChannelHandlerContext ctxNext, final boolean forward) {
if (ctx.channel().isRegistered() && !ctx.executor().inEventLoop()) { if (ctx.channel().isRegistered() && !ctx.executor().inEventLoop()) {
ctx.executor().execute(new Runnable() { ctx.executor().execute(new Runnable() {
@Override @Override
public void run() { public void run() {
callHandlerRemoved0(ctx, ctxPrev, ctxNext); callHandlerRemoved0(ctx, ctxPrev, ctxNext, forward);
} }
}); });
return; return;
} }
callHandlerRemoved0(ctx, ctxPrev, ctxNext); callHandlerRemoved0(ctx, ctxPrev, ctxNext, forward);
} }
private void callHandlerRemoved0( private void callHandlerRemoved0(
final DefaultChannelHandlerContext ctx, DefaultChannelHandlerContext ctxPrev, final DefaultChannelHandlerContext ctx, DefaultChannelHandlerContext ctxPrev,
DefaultChannelHandlerContext ctxNext) { DefaultChannelHandlerContext ctxNext, boolean forward) {
final ChannelHandler handler = ctx.handler(); final ChannelHandler handler = ctx.handler();
// Finish removal by forwarding buffer content and freeing the buffers. // Finish removal by forwarding buffer content and freeing the buffers.
if (forward) {
try { try {
ctx.forwardBufferContentAndRemove(ctxPrev, ctxNext); ctx.forwardBufferContentAndRemove(ctxPrev, ctxNext);
} catch (Throwable t) { } catch (Throwable t) {
fireExceptionCaught(new ChannelPipelineException( fireExceptionCaught(new ChannelPipelineException(
"failed to forward buffer content of " + ctx.handler().getClass().getName(), t)); "failed to forward buffer content of " + ctx.handler().getClass().getName(), t));
} }
}
// Notify the complete removal. // Notify the complete removal.
try { try {