BUG/MINOR: mux_quic: do not exceed stream.max-concurrent on backend side
Some checks are pending
Contrib / admin/halog/ (push) Waiting to run
Contrib / dev/flags/ (push) Waiting to run
Contrib / dev/haring/ (push) Waiting to run
Contrib / dev/hpack/ (push) Waiting to run
Contrib / dev/poll/ (push) Waiting to run
VTest / Generate Build Matrix (push) Waiting to run
VTest / (push) Blocked by required conditions
Windows / Windows, gcc, all features (push) Waiting to run

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.
This commit is contained in:
Amaury Denoyelle 2026-05-18 10:53:37 +02:00
parent b7c607e207
commit 47a61eb86d
4 changed files with 27 additions and 6 deletions

View 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"

View 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);

View 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;
}

View 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,