OK, I managed to reproduce it and to find the cause. It was a side effect
of an optimization of the chunked-encoding mode which was globally applied
instead of being applied only to chunked-encoded data. With chunks, we always
have to see the last empty chunk so we can decide to enable TCP delayed ACKs
or not on a chunk-by-chunk basis. In content-length mode, we cannot (and must
not).

I'll release 1.4.20 shortly with this fix as well as another one. In the mean
time you can apply the attached patch (which works for both 1.4 and 1.5).

Thanks Randy for the details you shared, there were helpful to reproduce the
issue !

Willy

>From 869fc1edc2acfa8ff88de2f4e638ad48dca5d246 Mon Sep 17 00:00:00 2001
From: Willy Tarreau <[email protected]>
Date: Mon, 5 Mar 2012 08:29:20 +0100
Subject: BUG: http: disable TCP delayed ACKs when forwarding content-length data

Commits 5c6209 and 072930 were aimed at avoiding undesirable PUSH flags
when forwarding chunked data, but had the undesired effect of causing
data advertised by content-length to be affected by the delayed ACK too.
This can happen when the data to be forwarded are small enough to fit into
a single send() call, otherwise the BF_EXPECT_MORE flag would be removed.

Content-length data don't need the BF_EXPECT_MORE flag since the low-level
forwarder already knows it can safely rely on bf->to_forward to set the
appropriate TCP flags.

Note that the issue is only observed in requests at the moment, though the
later introduction of server-side keep-alive could trigger the issue on the
response path too.

Special thanks to Randy Shults for reporting this issue with a lot of
details helping to reproduce it.

The fix must be backported to 1.4.
---
 src/proto_http.c |   16 ++++++++++++----
 1 files changed, 12 insertions(+), 4 deletions(-)

diff --git a/src/proto_http.c b/src/proto_http.c
index f36dc3c..c71b839 100644
--- a/src/proto_http.c
+++ b/src/proto_http.c
@@ -4684,9 +4684,13 @@ int http_request_forward_body(struct session *s, struct 
buffer *req, int an_bit)
        /* We know that more data are expected, but we couldn't send more that
         * what we did. So we always set the BF_EXPECT_MORE flag so that the
         * system knows it must not set a PUSH on this first part. Interactive
-        * modes are already handled by the stream sock layer.
+        * modes are already handled by the stream sock layer. We must not do
+        * this in content-length mode because it could present the MSG_MORE
+        * flag with the last block of forwarded data, which would cause an
+        * additional delay to be observed by the receiver.
         */
-       req->flags |= BF_EXPECT_MORE;
+       if (txn->flags & TX_REQ_TE_CHNK)
+               req->flags |= BF_EXPECT_MORE;
 
        http_silent_debug(__LINE__, s);
        return 0;
@@ -5731,9 +5735,13 @@ int http_response_forward_body(struct session *s, struct 
buffer *res, int an_bit
        /* We know that more data are expected, but we couldn't send more that
         * what we did. So we always set the BF_EXPECT_MORE flag so that the
         * system knows it must not set a PUSH on this first part. Interactive
-        * modes are already handled by the stream sock layer.
+        * modes are already handled by the stream sock layer. We must not do
+        * this in content-length mode because it could present the MSG_MORE
+        * flag with the last block of forwarded data, which would cause an
+        * additional delay to be observed by the receiver.
         */
-       res->flags |= BF_EXPECT_MORE;
+       if (txn->flags & TX_RES_TE_CHNK)
+               res->flags |= BF_EXPECT_MORE;
 
        /* the session handler will take care of timeouts and errors */
        http_silent_debug(__LINE__, s);
-- 
1.7.2.1.45.g54fbc

Reply via email to