[#4386] ByteToMessage.decodeLast(...) should not call decode(...) if buffer is empty.

Motivation:

If the input buffer is empty we should not have decodeLast(...) call decode(...) as the user may not expect this.

Modifications:

- Not call decode(...) in decodeLast(...) if the input buffer is empty.
- Add testcases.

Result:

decodeLast(...) will not call decode(...) if input buffer is empty.
This commit is contained in:
Norman Maurer 2016-02-25 16:20:41 +01:00
parent c4fbc0642d
commit 9aac6dac2e
5 changed files with 93 additions and 11 deletions

View File

@ -398,7 +398,7 @@ public abstract class HttpObjectDecoder extends ByteToMessageDecoder {
@Override
protected void decodeLast(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
decode(ctx, in, out);
super.decodeLast(ctx, in, out);
// Handle the last unfinished message.
if (message != null) {

View File

@ -150,7 +150,11 @@ public abstract class ByteToMessageCodec<I> extends ChannelDuplexHandler {
* @see ByteToMessageDecoder#decodeLast(ChannelHandlerContext, ByteBuf, List)
*/
protected void decodeLast(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
decode(ctx, in, out);
if (in.isReadable()) {
// Only call decode() if there is something left in the buffer to decode.
// See https://github.com/netty/netty/issues/4386
decode(ctx, in, out);
}
}
private final class Encoder extends MessageToByteEncoder<I> {

View File

@ -439,7 +439,11 @@ public abstract class ByteToMessageDecoder extends ChannelInboundHandlerAdapter
* override this for some special cleanup operation.
*/
protected void decodeLast(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
decode(ctx, in, out);
if (in.isReadable()) {
// Only call decode() if there is something left in the buffer to decode.
// See https://github.com/netty/netty/issues/4386
decode(ctx, in, out);
}
}
static ByteBuf expandCumulation(ByteBufAllocator alloc, ByteBuf cumulation, int readable) {

View File

@ -21,6 +21,7 @@ import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.embedded.EmbeddedChannel;
import io.netty.util.ReferenceCountUtil;
import io.netty.util.internal.ThreadLocalRandom;
import org.junit.Test;
import java.util.List;
@ -137,10 +138,15 @@ public class ByteToMessageDecoderTest {
EmbeddedChannel channel = new EmbeddedChannel(new ByteToMessageDecoder() {
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
in.skipBytes(in.readableBytes());
if (!ctx.channel().isActive()) {
out.add("data");
}
int readable = in.readableBytes();
assertTrue(readable > 0);
in.skipBytes(readable);
}
@Override
protected void decodeLast(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
assertFalse(in.isReadable());
out.add("data");
}
}, new ChannelInboundHandlerAdapter() {
@Override
@ -199,4 +205,66 @@ public class ByteToMessageDecoderTest {
buf.release();
b.release();
}
@Test
public void testDecodeLastEmptyBuffer() {
EmbeddedChannel channel = new EmbeddedChannel(new ByteToMessageDecoder() {
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
int readable = in.readableBytes();
assertTrue(readable > 0);
out.add(in.readBytes(readable));
}
});
byte[] bytes = new byte[1024];
ThreadLocalRandom.current().nextBytes(bytes);
assertTrue(channel.writeInbound(Unpooled.wrappedBuffer(bytes)));
assertBuffer(Unpooled.wrappedBuffer(bytes), (ByteBuf) channel.readInbound());
assertNull(channel.readInbound());
assertFalse(channel.finish());
assertNull(channel.readInbound());
}
@Test
public void testDecodeLastNonEmptyBuffer() {
EmbeddedChannel channel = new EmbeddedChannel(new ByteToMessageDecoder() {
private boolean decodeLast;
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
int readable = in.readableBytes();
assertTrue(readable > 0);
if (!decodeLast && readable == 1) {
return;
}
out.add(in.readBytes(decodeLast ? readable : readable - 1));
}
@Override
protected void decodeLast(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
assertFalse(decodeLast);
decodeLast = true;
super.decodeLast(ctx, in, out);
}
});
byte[] bytes = new byte[1024];
ThreadLocalRandom.current().nextBytes(bytes);
assertTrue(channel.writeInbound(Unpooled.wrappedBuffer(bytes)));
assertBuffer(Unpooled.wrappedBuffer(bytes, 0, bytes.length - 1), (ByteBuf) channel.readInbound());
assertNull(channel.readInbound());
assertTrue(channel.finish());
assertBuffer(Unpooled.wrappedBuffer(bytes, bytes.length - 1, 1), (ByteBuf) channel.readInbound());
assertNull(channel.readInbound());
}
private static void assertBuffer(ByteBuf expected, ByteBuf buffer) {
try {
assertEquals(expected, buffer);
} finally {
buffer.release();
expected.release();
}
}
}

View File

@ -188,10 +188,16 @@ public class ReplayingDecoderTest {
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
in.skipBytes(in.readableBytes());
if (!ctx.channel().isActive()) {
out.add("data");
}
int readable = in.readableBytes();
assertTrue(readable > 0);
in.skipBytes(readable);
out.add("data");
}
@Override
protected void decodeLast(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
assertFalse(in.isReadable());
out.add("data");
}
}, new ChannelInboundHandlerAdapter() {
@Override