Hi, I'd like to submit a patch for a bug we found in the H1 mux that adds ~200ms of
artificial latency to every HEAD response served over an HTTP/1.1 frontend under many conditions. We discovered this while debugging elevated latencies in a production proxy chain (S3 client -> HAProxy -> upstream origin) where HEAD requests were consistently 2-3x slower than GET requests despite being functionally simpler. The root cause turned out to be MSG_MORE being incorrectly asserted on the sendmsg() call for bodyless responses, causing the Linux kernel to cork the TCP segment for ~200ms waiting for body data that never arrives. The details and fix are in the patch below. Happy to answer any questions or rework the patch as needed. Thanks, Cody --- >From f0880f7b27ef222a90e48c9ce52da079049e6c6c Mon Sep 17 00:00:00 2001 From: Cody Ohlsen <[email protected]> Date: Tue, 24 Mar 2026 20:35:59 -0700 Subject: [PATCH] BUG/MEDIUM: mux-h1: clear MSG_MORE on bodyless responses to avoid ~200ms cork delay When HAProxy sends an HTTP/1.1 HEAD response, h1_snd_buf() incorrectly sets H1C_F_CO_MSG_MORE because (count - ret) > 0 includes HTX body DATA blocks that h1_make_data() will later skip via H1S_F_BODYLESS_RESP. This causes sendmsg() to be called with MSG_MORE, telling the kernel to cork the TCP segment for ~200ms waiting for body data that never arrives. The upper layer (sc_conn_send in stconn.c) correctly avoids setting CO_SFL_MSG_MORE for HEAD responses since htx_expect_more() returns 0 when HTX_FL_EOM is set. However, h1_snd_buf() re-asserts H1C_F_CO_MSG_MORE based on its internal count-ret loop counter, overriding the upper layer's correct decision. The H2 mux (h2_send()) does not have this problem -- it only sets CO_SFL_MSG_MORE when the mux transmit buffer is literally full (H2_CF_MUX_MFULL), which never happens for a small HEAD response. The fix clears H1C_F_CO_MSG_MORE when H1S_F_BODYLESS_RESP is set, consistent with the pattern in h1_done_ff() (commit 85da7116a9) and the H1S_F_BODYLESS_RESP guard in h1_make_data() (commit 226082d13a). Verified empirically: - H1 frontend HEAD: ~300ms; H2 frontend HEAD: ~100ms (same backend) - H1 trace shows 205ms gap between sendmsg() and h1_io_cb = cork timer - BPF tracing confirms delay is in kernel TCP stack, not userspace - option http-no-delay (TCP_NODELAY) has no effect, as expected since MSG_MORE and Nagle's algorithm are independent mechanisms This should be backported to all stable branches. --- src/mux_h1.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/mux_h1.c b/src/mux_h1.c index 47cdfff..4f7db85 100644 --- a/src/mux_h1.c +++ b/src/mux_h1.c @@ -4903,6 +4903,8 @@ static size_t h1_snd_buf(struct stconn *sc, struct buffer *buf, size_t count, in if ((count - ret) > 0) h1c->flags |= H1C_F_CO_MSG_MORE; + if (h1s->flags & H1S_F_BODYLESS_RESP) + h1c->flags &= ~H1C_F_CO_MSG_MORE; total += ret; count -= ret; -- 2.52.0

