From: Willy Tarreau Date: Tue, 6 Oct 2026 20:26:44 +0000 (+0200) Subject: BUG/MEDIUM: mux-h2: always return already allocated buffers in h2_get_buf() X-Git-Url: https://git.kaiwu.me/$%7BISSUES%7D/static/gitweb.js?a=commitdiff_plain;h=064430bb0dea43e2aa7a7288bfeb6b23d904b90d;p=haproxy.git BUG/MEDIUM: mux-h2: always return already allocated buffers in h2_get_buf() 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. --- diff --git a/src/mux_h2.c b/src/mux_h2.c index 95984aa48..0c354c091 100644 --- a/src/mux_h2.c +++ b/src/mux_h2.c @@ -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);