]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MINOR: mux-quic: limit emitted MSD frames count per qcs
authorAmaury Denoyelle <adenoyelle@haproxy.com>
Mon, 28 Apr 2025 13:36:44 +0000 (15:36 +0200)
committerAmaury Denoyelle <adenoyelle@haproxy.com>
Wed, 30 Apr 2025 14:08:47 +0000 (16:08 +0200)
The previous commit has implemented a new calcul method for
MAX_STREAM_DATA frame emission. Now, a frame may be emitted as soon as a
buffer was consumed by a QCS instance.

This will probably increase the number of MAX_STREAM_DATA frame
emission. It may even cause a series of frame emitted for the same
stream with increasing values under high load, which is completely
unnecessary.

To improve this, limit the number of MAX_STREAM_DATA frames built to one
per QCS instance. This is implemented by storing a reference to this
frame in QCS structure via a new member <tx.msd_frm>.

Note that to properly reset QCS msd_frm member, emission of flow-control
frames have been changed. Now, each frame is emitted individually. On
one side, it is better as it prevent to emit frames related to different
streams in a single datagram, which is not desirable in case of packet
loss. However, this can also increase sendto() syscall invocation.

include/haproxy/mux_quic-t.h
src/mux_quic.c

index 46f671329e84f56c20998510fc4e24d0cbdb7e5a..1c7c19a32a01e74514eb5a47af6f819d3a8d1f16 100644 (file)
@@ -160,6 +160,7 @@ struct qcs {
        } rx;
        struct {
                struct quic_fctl fc; /* stream flow control applied on sending */
+               struct quic_frame *msd_frm; /* MAX_STREAM_DATA frame prepared */
        } tx;
 
        struct eb64_node by_id;
index 329477413f8a22071772aa359c7971571fd5cdaf..0bc882e9c21028a600e1ce82140adc3d000972ff 100644 (file)
@@ -162,6 +162,8 @@ static struct qcs *qcs_new(struct qcc *qcc, uint64_t id, enum qcs_type type)
                qfctl_init(&qcs->tx.fc, 0);
        }
 
+       qcs->tx.msd_frm = NULL;
+
        qcs->rx.bufs = EB_ROOT_UNIQUE;
        qcs->rx.app_buf = BUF_NULL;
        qcs->rx.offset = qcs->rx.offset_max = 0;
@@ -1237,20 +1239,25 @@ static void qcs_consume(struct qcs *qcs, uint64_t bytes, struct qc_stream_rxbuf
        }
 
        if (inc) {
-               TRACE_DATA("increase stream credit via MAX_STREAM_DATA", QMUX_EV_QCS_RECV, qcc->conn, qcs);
-               frm = qc_frm_alloc(QUIC_FT_MAX_STREAM_DATA);
+               frm = qcs->tx.msd_frm;
                if (!frm) {
-                       qcc_set_error(qcc, QC_ERR_INTERNAL_ERROR, 0);
-                       return;
+                       frm = qc_frm_alloc(QUIC_FT_MAX_STREAM_DATA);
+                       if (!frm) {
+                               qcc_set_error(qcc, QC_ERR_INTERNAL_ERROR, 0);
+                               return;
+                       }
+
+                       frm->max_stream_data.id = qcs->id;
+                       LIST_APPEND(&qcc->lfctl.frms, &frm->list);
+                       qcs->tx.msd_frm = frm;
                }
 
+               TRACE_DATA("increase stream credit via MAX_STREAM_DATA", QMUX_EV_QCS_RECV, qcc->conn, qcs);
                qcs->rx.msd += inc;
                qcs->rx.msd_base += inc;
 
-               frm->max_stream_data.id = qcs->id;
                frm->max_stream_data.max_stream_data = qcs->rx.msd;
 
-               LIST_APPEND(&qcc->lfctl.frms, &frm->list);
                tasklet_wakeup(qcc->wait_event.tasklet);
        }
 
@@ -2585,6 +2592,56 @@ static int qcs_send(struct qcs *qcs, struct list *frms, uint64_t window_conn)
        return -1;
 }
 
+/* Emit prepared flow-control related frames from <qcc> list.
+ *
+ * Returns 0 on success else non zero.
+ */
+static int qcc_emit_fctl(struct qcc *qcc)
+{
+       struct list single_frm = LIST_HEAD_INIT(single_frm);
+       struct quic_frame *frm;
+       struct eb64_node *node;
+       struct qcs *qcs;
+       int64_t id;
+
+       TRACE_ENTER(QMUX_EV_QCC_END, qcc->conn);
+
+       while (!LIST_ISEMPTY(&qcc->lfctl.frms)) {
+               /* Each frame is sent individually in a single element list. */
+               frm = LIST_ELEM(qcc->lfctl.frms.n, struct quic_frame *, list);
+               id = frm->type == QUIC_FT_MAX_STREAM_DATA ?
+                    frm->max_stream_data.id : -1;
+
+               LIST_DELETE(&frm->list);
+               LIST_APPEND(&single_frm, &frm->list);
+
+               if (qcc_send_frames(qcc, &single_frm, 0)) {
+                       /* replace frame that cannot be sent in qcc list. */
+                       LIST_DELETE(&frm->list);
+                       LIST_INSERT(&qcc->lfctl.frms, &frm->list);
+                       goto err;
+               }
+
+               BUG_ON_HOT(!LIST_ISEMPTY(&single_frm));
+
+               /* If frame is MAX_STREAM_DATA, reset corresponding QCS msd_frm pointer. */
+               if (id >= 0) {
+                       node = eb64_lookup(&qcc->streams_by_id, frm->max_stream_data.id);
+                       if (node) {
+                               qcs = eb64_entry(node, struct qcs, by_id);
+                               qcs->tx.msd_frm = NULL;
+                       }
+               }
+       }
+
+       TRACE_LEAVE(QMUX_EV_QCC_SEND, qcc->conn);
+       return 0;
+
+ err:
+       TRACE_DEVEL("leaving on error", QMUX_EV_QCC_SEND, qcc->conn);
+       return 1;
+}
+
 /* Send RESET_STREAM/STOP_SENDING for streams in <qcc> send_list if requested.
  * Each frame is encoded and emitted separately for now. If a frame cannot be
  * sent, send_list looping is interrupted.
@@ -2830,7 +2887,7 @@ static int qcc_io_send(struct qcc *qcc)
        }
 
        if (!LIST_ISEMPTY(&qcc->lfctl.frms)) {
-               if (qcc_send_frames(qcc, &qcc->lfctl.frms, 0)) {
+               if (qcc_emit_fctl(qcc)) {
                        TRACE_DEVEL("flow-control frames rejected by transport, aborting send", QMUX_EV_QCC_SEND, qcc->conn);
                        goto out;
                }