]> git.kaiwu.me - haproxy.git/commitdiff
BUG/MEDIUM: mux-h2: always return already allocated buffers in h2_get_buf()
authorWilly Tarreau <w@1wt.eu>
Tue, 6 Oct 2026 20:26:44 +0000 (22:26 +0200)
committerWilly Tarreau <w@1wt.eu>
Mon, 9 Nov 2026 19:48:35 +0000 (20:48 +0100)
h2_get_buf() is used to get the demux buffer, the mux buffer ring's tail
and the streams' rxbufs. It refrains from calling b_alloc() when the
connection is already in the buffer wait queue, but it does so even when
the requested buffer is already allocated, in which case it returns NULL.
The consequence is that as soon as an H2 connection waits for any buffer
(e.g. a stream's rxbuf), all other users consider that their already
allocated buffer is missing: h2_recv() stops reading and sets
H2_CF_DEM_DALLOC although the demux buffer is allocated, frame emitters
set H2_CF_MUX_MALLOC although the mbuf has room, etc. The connection then
stops both receiving and sending until the buffer callback is called,
and these spurious flags confuse h2_buf_available() which only serves one
of them (see next fixes).

This can be observed by limiting the number of buffers with
tune.buffers.limit, holding them with incomplete HTTP/1 requests, then
sending an H2 request: once the stream's rxbuf allocation fails, the
connection reports a demux buffer allocation failure on the next recv
attempt (seen with debug traces).

Let's always return an already allocated buffer. This has been there
since the merge of the buffer allocators in 1.9 with commit 44e973f50
("MEDIUM: h2: use a single buffer allocator"), so this fix may be
backported to all supported versions.

src/mux_h2.c

index 95984aa48952de902c012fc302507da7ed61ec7d..0c354c091a07f17a2e8c6ce9fa63de5eb8b28966 100644 (file)
@@ -1301,6 +1301,12 @@ static inline struct buffer *h2_get_buf(struct h2c *h2c, struct buffer *bptr)
 {
        struct buffer *buf = NULL;
 
+       /* an already allocated buffer must always be returned, even while
+        * waiting for another one to be allocated.
+        */
+       if (b_size(bptr))
+               return bptr;
+
        if (likely(!LIST_INLIST(&h2c->buf_wait.list)) &&
            unlikely((buf = b_alloc(bptr, DB_MUX_RX)) == NULL)) {
                b_queue(DB_MUX_RX, &h2c->buf_wait, h2c, h2_buf_available);