Hi Christopher,

Thanks for the review and the suggested adaptation. I will certainly
resubmit the patch based on your proposal. I verified it would work in the
situation we were describing, and it definitely makes sense and seems like
an improvement. Appreciate your help.

However - I want to share additional context about our trigger scenario,
since it may affect the severity assessment. Our setup is H1.1 frontend ->
H2 backend (reverse proxy to an S3-style storage system). HEAD requests are
used for metadata checks (content-length, object existence). I _think_ our
H2 backend responds to HEAD with:

  1. HEADERS frame (status + content-length, NO END_STREAM)
  2. DATA frame (0 bytes payload, END_STREAM)

I suspect that when these frames arrive in separate TCP segments
(timing-dependent, but happens in practice on all requests), the H2 demux
processes HEADERS first, notifies the stream (deferred via
tasklet_wakeup_after), and process_stream forwards headers to the H1
frontend without EOM. At that point htx_expect_more() returns true,
CO_SFL_MSG_MORE propagates to h1_snd_buf(), and the kernel corks for ~200ms
waiting for body data that will never come.

The fix (as well as your suggested fix) addresses this path. But I suspect
no body payload is needed in the channel, just the absence of EOM when
headers are first forwarded.

For H1 backends, h1_postparse_res_hdrs() correctly sets HTX_FL_EOM during
header parsing for HEAD responses, eliminating the timing dependency
entirely. I _think_ this explains why reproduction requires set-method GET
with H1 backends - the bug is latent with H1 (unless you add http-request
set-method GET), but I think often (or perhaps always) active with H2
backend.

I am wondering - in your test, did you happen to have an H2 backend? or an
H1.1 backend. Thanks.

Regardless, I verified that the adapted patch solves our issue - I really
appreciate your help and feedback - thanks again! I will re-submit the
updated patch as suggested.

- Cody

On Fri, Mar 27, 2026 at 1:25 AM Christopher Faulet <[email protected]>
wrote:

> >
> Le 26/03/2026 à 4:00 PM, Cody Ohlsen a écrit :
> > 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.
> >
>
> Hi Cody,
>
> Thanks for the detailed analysis. The fix should be adapted a bit because
> we
> cannot systematically remove H1C_F_CO_MSG_MORE flag when
> H1S_F_BODYLESS_RESP is
> set. This last flag is also set on the server side, to be able to drop
> payload
> for responses to HEAD requests. In attachment, my proposal to fix the
> issue.
> You've done all the work. Do you want to adapt your patch ?
>
> In addition, I proposed to flag it as a minor bug because it only concerns
> bodyless responses with a payload, which should not happen.
>
> On my side, I'm only able to reproduce the issue by changing the HEAD
> method
> with an "http-request set-method GET" rule. Otherwise, the payload is
> ignored on
> server side. So I'm curious. On your side, how are you able to trigger the
> issue ?
>
> Regards,
> --
> Christopher Faulet
>

Reply via email to