]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
BUG/MINOR: mux_quic: do not exceed stream.max-concurrent on backend side
authorAmaury Denoyelle <adenoyelle@haproxy.com>
Mon, 18 May 2026 08:53:37 +0000 (10:53 +0200)
committerAmaury Denoyelle <adenoyelle@haproxy.com>
Wed, 20 May 2026 12:42:03 +0000 (14:42 +0200)
Fix usage of stream.max-concurrent QUIC setting on the backend side.
Contrary to frontend connections, this limit must be enforced by QUIC
MUX directly. This is necessary as the peer may allow a larger number of
concurrent streams via its flow control.

First, QUIC TP initial max bidi streams value is now set to 0. This is
fine as only the HTTP/3 client is expected to open bidirectional
streams.

The most important changes is performed in qcm_avail_streams(). The
value first depends on the peer flow control. Now, it is further reduced
if necessary to not exceed the configured BE stream.max-concurrent.

Note that this new behavior may further increases current limitation on
QUIC BE reuse when a QCS instance is kept while its upper stream layer
is detached. In this case there is a risk that the connection is not
reinserted in the correct server pool, as an idle or avail one.

This is a breaking change as BE stream.max-concurrent keyword setting
meaning is changed in effect. However, this does not necessitate extra
warnings as the previous usage was in effect useless. Furthermore, QUIC
on the backend side is still considered as experimental.

This can be backported up to 3.3.

doc/configuration.txt
include/haproxy/mux_quic.h
src/mux_quic.c
src/quic_tp.c

index 52c5bdfdfdd555befc1fe26e9d93c66ab77fb4d3..8d2e7605f911df065009e5ec8ecd6c3c27061406 100644 (file)
@@ -5321,17 +5321,26 @@ tune.quic.frontend.stream-data-ratio  <0..100, in percent> (deprecated)
 
 tune.quic.be.stream.max-concurrent <number>
 tune.quic.fe.stream.max-concurrent <number>
-  Sets the QUIC initial_max_streams_bidi transport parameter either on frontend
-  or backend side. This is the maximum number of bidirectional streams that the
-  remote peer will be authorized to open concurrently during the connection
-  lifetime. On frontend side, this limits the number of concurrent HTTP/3
-  client requests.
+  On frontend side, this is used as the value for the advertised
+  initial_max_streams_bidi transport parameter. This is enforced as the maximum
+  number of bidirectional streams that the remote peer will be authorized to
+  open concurrently during the connection lifetime. This effectively limits the
+  number of concurrent HTTP/3 client requests.
 
   The default value is 100. Note that if you reduces it, it can restrict the
   buffering capabilities of streams on receive, which would result in poor
   upload throughput. It can be corrected by increasing the QUIC stream rxbuf
   connection setting.
 
+  On backend side, this is enforced locally by haproxy to limit the number of
+  concurrent requests multiplexed over a single connection. This may be further
+  restricted by the peer flow control. It may be necessary to reduce the
+  default value of 100 to improve a site's responsiveness at the expense of a
+  higher number of opened backend connections. Similarly to the frontend side,
+  this setting also directly impacts the Rx buffering capability, this time
+  though limiting the HTTP download capacity. QUIC stream rxbuf setting can be
+  increased when dealing mostly with HTTP responses larger than "tune.bufsize".
+
   See also: "tune.quic.be.stream.rxbuf", "tune.quic.fe.stream.rxbuf",
             "tune.quic.be.stream.data-ratio", "tune.quic.fe.stream.data-ratio"
 
index 5293c204b165dc37788ea7c5b54233d3b3ab7bf9..ae4a58846036180195fb0c4544c233a78bafa8d2 100644 (file)
@@ -10,6 +10,7 @@
 #include <haproxy/connection.h>
 #include <haproxy/list.h>
 #include <haproxy/mux_quic-t.h>
+#include <haproxy/quic_tune.h>
 #include <haproxy/stconn.h>
 
 #include <haproxy/h3.h>
@@ -128,6 +129,9 @@ static inline void qcs_wait_http_req(struct qcs *qcs)
        BUG_ON_HOT(qcs->flags & QC_SF_HREQ_RECV);
        qcs->flags |= QC_SF_HREQ_RECV;
        ++qcc->nb_hreq;
+
+       /* On BE side avail_streams cb should prevent opening of too many concurrent streams. */
+       BUG_ON(conn_is_back(qcc->conn) && qcc->nb_hreq > quic_tune.be.stream_max_concurrent);
 }
 
 void qcc_show_quic(struct qcc *qcc);
index b5f1375b4c750540e8285ef6a0967ee97fd7918a..b36799a6bcf9881a8e22443441fa3a1bf5f39361 100644 (file)
@@ -3341,6 +3341,10 @@ static int qcm_avail_streams(struct connection *conn)
 
        ret = qcc_fctl_avail_streams(qcc, 1);
 
+       /* Enforce stream_max_concurrent limit even if peer allows more streams. */
+       if (ret > quic_tune.be.stream_max_concurrent - qcc->nb_hreq)
+               ret = quic_tune.be.stream_max_concurrent - qcc->nb_hreq;
+
        /* Now cap return value if reaching max-reuse server or maximum stream
         * ID. qcc_be_is_reusable() already detected if one of these has been
         * exceeded.
@@ -4199,6 +4203,10 @@ static void qcm_strm_detach(struct sedesc *sd)
                qcs->flags |= QC_SF_DETACH;
                qcc_refresh_timeout(qcc);
 
+               /* TODO on backend side if a QCS is detached, the connection may
+                * not be reinserted in the correct server pool (idle or avail).
+                */
+
                TRACE_LEAVE(QMUX_EV_STRM_END, qcc->conn, qcs);
                return;
        }
index e85f8b58d0e0049f68d44da98965981611e33a85..75243b44d50023840734f3d6e41a6aca43d03854 100644 (file)
@@ -71,7 +71,7 @@ void quic_transport_params_init(struct quic_transport_params *p, int server)
                                       quic_tune.be.max_idle_timeout;
 
        /* Set limit on number of concurrently opened streams. */
-       p->initial_max_streams_bidi = max_streams_bidi;
+       p->initial_max_streams_bidi = server ? max_streams_bidi : 0;
        p->initial_max_streams_uni  = max_streams_uni;
 
        /* Set connection flow-control data limit, either from configuration,