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

Reply via email to