]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MEDIUM: mux-quic: implement be connection reuse
authorAmaury Denoyelle <adenoyelle@haproxy.com>
Tue, 22 Jul 2025 09:36:34 +0000 (11:36 +0200)
committerAmaury Denoyelle <adenoyelle@haproxy.com>
Wed, 23 Jul 2025 13:45:09 +0000 (15:45 +0200)
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

index a65b564b9fd38f0a49394a0958f3ab44f28bf265..cad1644421d4fbf8684f066f2e5f997e2cd2ae83 100644 (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,