From: Amaury Denoyelle Date: Mon, 18 May 2026 08:53:37 +0000 (+0200) Subject: BUG/MINOR: mux_quic: do not exceed stream.max-concurrent on backend side X-Git-Tag: v3.4-dev13~9 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=47a61eb86d4008bd1f9b722fa726eb63c079a244;p=thirdparty%2Fhaproxy.git BUG/MINOR: mux_quic: do not exceed stream.max-concurrent on backend side 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. --- diff --git a/doc/configuration.txt b/doc/configuration.txt index 52c5bdfdf..8d2e7605f 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -5321,17 +5321,26 @@ tune.quic.frontend.stream-data-ratio <0..100, in percent> (deprecated) tune.quic.be.stream.max-concurrent tune.quic.fe.stream.max-concurrent - 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" diff --git a/include/haproxy/mux_quic.h b/include/haproxy/mux_quic.h index 5293c204b..ae4a58846 100644 --- a/include/haproxy/mux_quic.h +++ b/include/haproxy/mux_quic.h @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -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); diff --git a/src/mux_quic.c b/src/mux_quic.c index b5f1375b4..b36799a6b 100644 --- a/src/mux_quic.c +++ b/src/mux_quic.c @@ -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; } diff --git a/src/quic_tp.c b/src/quic_tp.c index e85f8b58d..75243b44d 100644 --- a/src/quic_tp.c +++ b/src/quic_tp.c @@ -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,