From 3217835b1df6a4cf9e56c310b0a8e2dd7d5981bc Mon Sep 17 00:00:00 2001 From: Amaury Denoyelle Date: Tue, 22 Jul 2025 11:36:34 +0200 Subject: [PATCH] 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. --- src/mux_quic.c | 44 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 39 insertions(+), 5 deletions(-) diff --git a/src/mux_quic.c b/src/mux_quic.c index a65b564b9..cad164442 100644 --- a/src/mux_quic.c +++ b/src/mux_quic.c @@ -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 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, -- 2.47.2