MEDIUM: mux-quic: implement be connection reuse

Implement support for QUIC connection reuse on the backend side. The
main change is done during detach stream operation. If a connection is
idle, it is inserted in the server list. Else, it is stored in the
server avail tree if there is room for more streams.

For non idle connection, qmux_avail_streams() is reused to detect that
stream flow-control limit is not yet reached. If this is the case, the
connection is not inserted in the avail tree, so it cannot be reuse,
even if flow-control is unblocked later by the peer. This latter point
could be improved in the future.

Note that support for QUIC private connections is still missing. Reuse
code will evolved to fully support this case.
This commit is contained in:
Amaury Denoyelle 2025-07-22 11:36:34 +02:00
parent 3bf37596ba
commit 3217835b1d

View File

@ -3133,6 +3133,15 @@ static int qmux_avail_streams(struct connection *conn)
return qcc_fctl_avail_streams(qcc, 1);
}
/* Returns the number of streams currently attached into <conn> connection.
* Used to determine if a connection can be considered as idle or not.
*/
static int qmux_used_streams(struct connection *conn)
{
struct qcc *qcc = conn->ctx;
return qcc->nb_sc;
}
/* Release all streams which have their transfer operation achieved. */
static void qcc_purge_streams(struct qcc *qcc)
{
@ -3734,8 +3743,9 @@ static void qmux_strm_detach(struct sedesc *sd)
{
struct qcs *qcs = sd->se;
struct qcc *qcc = qcs->qcc;
struct connection *conn = qcc->conn;
TRACE_ENTER(QMUX_EV_STRM_END, qcc->conn, qcs);
TRACE_ENTER(QMUX_EV_STRM_END, conn, qcs);
/* TODO this BUG_ON_HOT() is not correct as the stconn layer may detach
* from the stream even if it is not closed remotely at the QUIC layer.
@ -3750,7 +3760,7 @@ static void qmux_strm_detach(struct sedesc *sd)
if (!qcs_is_close_local(qcs) &&
!(qcc->flags & (QC_CF_ERR_CONN|QC_CF_ERRL))) {
TRACE_STATE("remaining data, detaching qcs", QMUX_EV_STRM_END, qcc->conn, qcs);
TRACE_STATE("remaining data, detaching qcs", QMUX_EV_STRM_END, conn, qcs);
qcs->flags |= QC_SF_DETACH;
qcc_refresh_timeout(qcc);
@ -3760,16 +3770,38 @@ static void qmux_strm_detach(struct sedesc *sd)
qcs_destroy(qcs);
/* Backend connection can be reused unless it is already on error/closed. */
if (qcc->flags & QC_CF_IS_BACK && !qcc_is_dead(qcc)) {
if (!(conn->flags & CO_FL_PRIVATE)) {
if (!qcc->nb_sc) {
TRACE_DEVEL("prepare for idle connection reuse", QMUX_EV_STRM_END, conn);
if (!srv_add_to_idle_list(objt_server(conn->target), conn, 1)) {
/* Idle conn insert failure, gracefully close the connection. */
TRACE_DEVEL("idle connection cannot be kept on the server", QMUX_EV_STRM_END, conn);
qcc_shutdown(qcc);
}
goto end;
}
else if (!conn->hash_node->node.node.leaf_p &&
qmux_avail_streams(conn) &&
objt_server(conn->target)) {
TRACE_DEVEL("mark connection as available for reuse", QMUX_EV_STRM_END, conn);
srv_add_to_avail_list(__objt_server(conn->target), conn);
}
}
}
if (qcc_is_dead(qcc)) {
TRACE_STATE("killing dead connection", QMUX_EV_STRM_END, qcc->conn);
TRACE_STATE("killing dead connection", QMUX_EV_STRM_END, conn);
goto release;
}
else {
TRACE_DEVEL("refreshing connection's timeout", QMUX_EV_STRM_END, qcc->conn);
TRACE_DEVEL("refreshing connection's timeout", QMUX_EV_STRM_END, conn);
qcc_refresh_timeout(qcc);
}
TRACE_LEAVE(QMUX_EV_STRM_END, qcc->conn);
end:
TRACE_LEAVE(QMUX_EV_STRM_END, conn);
return;
release:
@ -4253,6 +4285,8 @@ static const struct mux_ops qmux_ops = {
.unsubscribe = qmux_strm_unsubscribe,
.wake = qmux_wake,
.avail_streams = qmux_avail_streams,
.used_streams = qmux_used_streams,
.takeover = NULL, /* QUIC takeover support not implemented yet */
.attach = qmux_strm_attach,
.shut = qmux_strm_shut,
.ctl = qmux_ctl,