Skip to content

FlowControlHandler and HttpContentDecompressor do not play nicely together #15053

@DaveCTurner

Description

@DaveCTurner

The following test fails, which indicates behaviour that is surprising to me and I suspect might be a bug:

index d9f5cd5b8d..f8edf63a29 100644
--- a/codec-http/src/test/java/io/netty/handler/codec/http/HttpContentDecompressorTest.java
+++ b/codec-http/src/test/java/io/netty/handler/codec/http/HttpContentDecompressorTest.java
@@ -20,6 +20,7 @@ import io.netty.channel.ChannelHandlerContext;
 import io.netty.channel.ChannelInboundHandlerAdapter;
 import io.netty.channel.ChannelOutboundHandlerAdapter;
 import io.netty.channel.embedded.EmbeddedChannel;
+import io.netty.handler.flow.FlowControlHandler;
 import org.junit.jupiter.api.Test;

 import java.util.concurrent.atomic.AtomicInteger;
@@ -70,4 +71,33 @@ public class HttpContentDecompressorTest {
         assertEquals(2, readCalled.get());
         assertFalse(channel.finishAndReleaseAll());
     }
+
+    @Test
+    public void testFlowController() {
+        final AtomicInteger messagesEmitted = new AtomicInteger();
+        final EmbeddedChannel channel = new EmbeddedChannel(
+                new FlowControlHandler(),
+                new HttpContentDecompressor(),
+                new ChannelInboundHandlerAdapter() {
+                    @Override
+                    public void channelRead(ChannelHandlerContext ctx, Object msg) {
+                        messagesEmitted.incrementAndGet();
+                    }
+                });
+
+        channel.config().setAutoRead(false);
+
+        final HttpResponse response = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
+        response.headers().set(HttpHeaderNames.TRANSFER_ENCODING, HttpHeaderValues.CHUNKED);
+
+        assertFalse(channel.writeInbound(response));
+        assertFalse(channel.writeInbound(new DefaultHttpContent(Unpooled.buffer(1))));
+        assertFalse(channel.writeInbound(new DefaultHttpContent(Unpooled.buffer(1))));
+
+        assertEquals(1, messagesEmitted.get());
+        channel.read();
+        assertEquals(2, messagesEmitted.get());
+        channel.read();
+        assertEquals(3, messagesEmitted.get());
+    }
 }

Specifically, with autoread disabled and a FlowControlHandler in place I would expect the pipeline to emit exactly one message per read() but in fact today it emits all three messages at once:

org.opentest4j.AssertionFailedError: 
Expected :1
Actual   :3
<Click to see difference>

	at org.junit.jupiter.api.AssertionFailureBuilder.build(AssertionFailureBuilder.java:151)
	at org.junit.jupiter.api.AssertionFailureBuilder.buildAndThrow(AssertionFailureBuilder.java:132)
	at org.junit.jupiter.api.AssertEquals.failNotEqual(AssertEquals.java:197)
	at org.junit.jupiter.api.AssertEquals.assertEquals(AssertEquals.java:150)
	at org.junit.jupiter.api.AssertEquals.assertEquals(AssertEquals.java:145)
	at org.junit.jupiter.api.Assertions.assertEquals(Assertions.java:527)
	at io.netty.handler.codec.http.HttpContentDecompressorTest.testFlowController(HttpContentDecompressorTest.java:97)
...

This is happening because the FlowControlHandler fabricates its own READ COMPLETE messages when it empties its queue, but also passes through the READ COMPLETE message from upstream when its internal queue is empty, so the HttpContentDecompressor sees two READ COMPLETE events after processing the first READ. The first such event sets HttpContentDecoder#needRead to true and then the second of them triggers another ctx.read().

Unfortunately I'm not sure whether the problem is that FlowControlHandler emits two READ COMPLETE events in a row without any intervening READ, or if this is expected but HttpContentDecoder should be able to handle those events without triggering another ctx.read(), or indeed if this overall behaviour is expected and I'm just holding it wrong somehow.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions