[#4635] Stop decoding if decoder was removed

Motivation:

We need to check if this handler was removed before continuing with decoding.
If it was removed, it is not safe to continue to operate on the buffer.

Modifications:

Check if decoder was removed after fire messages through the pipeline.

Result:

No illegal buffer access when decoder was removed.
This commit is contained in:
Norman Maurer 2015-12-29 22:06:28 +01:00
parent 9e1e4f79c7
commit 4cdbe39284
2 changed files with 72 additions and 24 deletions

View File

@ -353,6 +353,15 @@ public abstract class ByteToMessageDecoder extends ChannelInboundHandlerAdapter
if (outSize > 0) {
fireChannelRead(ctx, out, outSize);
out.clear();
// Check if this handler was removed before continuing with decoding.
// If it was removed, it is not safe to continue to operate on the buffer.
//
// See:
// - https://github.com/netty/netty/issues/4635
if (ctx.isRemoved()) {
break;
}
outSize = 0;
}

View File

@ -21,13 +21,14 @@ import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.embedded.EmbeddedChannel;
import io.netty.util.ReferenceCountUtil;
import org.junit.Assert;
import org.junit.Test;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingDeque;
import static org.junit.Assert.*;
public class ByteToMessageDecoderTest {
@Test
@ -37,7 +38,7 @@ public class ByteToMessageDecoderTest {
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
Assert.assertFalse(removed);
assertFalse(removed);
in.readByte();
ctx.pipeline().remove(this);
removed = true;
@ -47,20 +48,20 @@ public class ByteToMessageDecoderTest {
ByteBuf buf = Unpooled.wrappedBuffer(new byte[] {'a', 'b', 'c'});
channel.writeInbound(buf.copy());
ByteBuf b = channel.readInbound();
Assert.assertEquals(b, buf.skipBytes(1));
assertEquals(b, buf.skipBytes(1));
b.release();
buf.release();
}
@Test
public void testRemoveItselfWriteBuffer() {
final ByteBuf buf = Unpooled.buffer().writeBytes(new byte[]{'a', 'b', 'c'});
final ByteBuf buf = Unpooled.buffer().writeBytes(new byte[] {'a', 'b', 'c'});
EmbeddedChannel channel = new EmbeddedChannel(new ByteToMessageDecoder() {
private boolean removed;
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
Assert.assertFalse(removed);
assertFalse(removed);
in.readByte();
ctx.pipeline().remove(this);
@ -71,8 +72,10 @@ public class ByteToMessageDecoderTest {
});
channel.writeInbound(buf.copy());
ByteBuf expected = Unpooled.wrappedBuffer(new byte[] {'b', 'c'});
ByteBuf b = channel.readInbound();
Assert.assertEquals(b, Unpooled.wrappedBuffer(new byte[] { 'b', 'c'}));
assertEquals(expected, b);
expected.release();
buf.release();
b.release();
}
@ -83,20 +86,20 @@ public class ByteToMessageDecoderTest {
*/
@Test
public void testInternalBufferClearReadAll() {
final ByteBuf buf = ReferenceCountUtil.releaseLater(Unpooled.buffer().writeBytes(new byte[]{'a'}));
final ByteBuf buf = ReferenceCountUtil.releaseLater(Unpooled.buffer().writeBytes(new byte[] {'a'}));
EmbeddedChannel channel = new EmbeddedChannel(new ByteToMessageDecoder() {
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
ByteBuf byteBuf = internalBuffer();
Assert.assertEquals(1, byteBuf.refCnt());
assertEquals(1, byteBuf.refCnt());
in.readByte();
// Removal from pipeline should clear internal buffer
ctx.pipeline().remove(this);
Assert.assertEquals(0, byteBuf.refCnt());
assertEquals(0, byteBuf.refCnt());
}
});
Assert.assertFalse(channel.writeInbound(buf));
Assert.assertFalse(channel.finish());
assertFalse(channel.writeInbound(buf));
assertFalse(channel.finish());
}
/**
@ -105,28 +108,32 @@ public class ByteToMessageDecoderTest {
*/
@Test
public void testInternalBufferClearReadPartly() {
final ByteBuf buf = ReferenceCountUtil.releaseLater(Unpooled.buffer().writeBytes(new byte[]{'a', 'b'}));
final ByteBuf buf = ReferenceCountUtil.releaseLater(Unpooled.buffer().writeBytes(new byte[] {'a', 'b'}));
EmbeddedChannel channel = new EmbeddedChannel(new ByteToMessageDecoder() {
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
ByteBuf byteBuf = internalBuffer();
Assert.assertEquals(1, byteBuf.refCnt());
assertEquals(1, byteBuf.refCnt());
in.readByte();
// Removal from pipeline should clear internal buffer
ctx.pipeline().remove(this);
Assert.assertEquals(0, byteBuf.refCnt());
assertEquals(0, byteBuf.refCnt());
}
});
Assert.assertTrue(channel.writeInbound(buf));
Assert.assertTrue(channel.finish());
Assert.assertEquals(channel.readInbound(), Unpooled.wrappedBuffer(new byte[] {'b'}));
Assert.assertNull(channel.readInbound());
assertTrue(channel.writeInbound(buf));
assertTrue(channel.finish());
ByteBuf expected = Unpooled.wrappedBuffer(new byte[] {'b'});
ByteBuf b = channel.readInbound();
assertEquals(expected, b);
assertNull(channel.readInbound());
expected.release();
b.release();
}
@Test
public void testFireChannelReadCompleteOnInactive() throws InterruptedException {
final BlockingQueue<Integer> queue = new LinkedBlockingDeque<Integer>();
final ByteBuf buf = ReferenceCountUtil.releaseLater(Unpooled.buffer().writeBytes(new byte[]{'a', 'b'}));
final ByteBuf buf = ReferenceCountUtil.releaseLater(Unpooled.buffer().writeBytes(new byte[] {'a', 'b'}));
EmbeddedChannel channel = new EmbeddedChannel(new ByteToMessageDecoder() {
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
@ -153,11 +160,43 @@ public class ByteToMessageDecoderTest {
}
}
});
Assert.assertFalse(channel.writeInbound(buf));
assertFalse(channel.writeInbound(buf));
channel.finish();
Assert.assertEquals(1, (int) queue.take());
Assert.assertEquals(2, (int) queue.take());
Assert.assertEquals(3, (int) queue.take());
Assert.assertTrue(queue.isEmpty());
assertEquals(1, (int) queue.take());
assertEquals(2, (int) queue.take());
assertEquals(3, (int) queue.take());
assertTrue(queue.isEmpty());
}
// See https://github.com/netty/netty/issues/4635
@Test
public void testRemoveWhileInCallDecode() {
final Object upgradeMessage = new Object();
final ByteToMessageDecoder decoder = new ByteToMessageDecoder() {
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
assertEquals('a', in.readByte());
out.add(upgradeMessage);
}
};
EmbeddedChannel channel = new EmbeddedChannel(decoder, new ChannelInboundHandlerAdapter() {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (msg == upgradeMessage) {
ctx.pipeline().remove(decoder);
return;
}
ctx.fireChannelRead(msg);
}
});
ByteBuf buf = Unpooled.wrappedBuffer(new byte[] { 'a', 'b', 'c' });
assertTrue(channel.writeInbound(buf.copy()));
ByteBuf b = channel.readInbound();
assertEquals(b, buf.skipBytes(1));
assertFalse(channel.finish());
buf.release();
b.release();
}
}