]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MEDIUM: h3: properly manage tx buffers for large data
authorAmaury Denoyelle <adenoyelle@haproxy.com>
Wed, 6 Oct 2021 12:53:13 +0000 (14:53 +0200)
committerAmaury Denoyelle <adenoyelle@haproxy.com>
Wed, 13 Oct 2021 14:38:56 +0000 (16:38 +0200)
Properly handle tx buffers management in h3 data sending. If there is
not enough contiguous space, the buffer is first realigned. If this is
not enough, the stream is flagged with QC_SF_BLK_MROOM waiting for the
buffer to be emptied.

If a frame on a stream is successfully pushed for sending, the stream is
called if it was flagged with QC_SF_BLK_MROOM.

src/h3.c
src/mux_quic.c

index a94b24b646bf654638b0f37f0d7601f78606b1d4..602627f61f0f7430256bfe28af721646e7f27c90 100644 (file)
--- a/src/h3.c
+++ b/src/h3.c
@@ -534,8 +534,7 @@ static int h3_resp_data_send(struct qcs *qcs, struct buffer *buf, size_t count)
        struct buffer *res;
        size_t total = 0;
        struct htx *htx;
-       int bsize, fsize;
-       int frame_length_size;  /* size in bytes of frame length varint field */
+       int bsize, fsize, hsize;
        struct htx_blk *blk;
        enum htx_blk_type type;
 
@@ -557,19 +556,34 @@ static int h3_resp_data_send(struct qcs *qcs, struct buffer *buf, size_t count)
        if (fsize > count)
                fsize = count;
 
-       frame_length_size = quic_int_getsize(fsize);
+       /* h3 DATA headers : 1-byte frame type + varint frame length */
+       hsize = 1 + QUIC_VARINT_MAX_SIZE;
 
-       b_reset(&outbuf);
-       outbuf = b_make(b_tail(res), b_contig_space(res), 0, 0);
+       while (1) {
+               b_reset(&outbuf);
+               outbuf = b_make(b_tail(res), b_contig_space(res), 0, 0);
+               if (b_size(&outbuf) > hsize || !b_space_wraps(res))
+                       break;
+               b_slow_realign(res, trash.area, b_data(res));
+       }
 
-       if (1 + fsize + frame_length_size > b_room(&outbuf))
-               ABORT_NOW();
+       /* not enough room for headers and at least one data byte, block the
+        * stream
+        */
+       if (b_size(&outbuf) <= hsize) {
+               qcs->flags |= QC_SF_BLK_MROOM;
+               goto end;
+       }
 
-       b_putchr(&outbuf, 0x00); /* h3 frame type = DATA */
-       b_quic_enc_int(&outbuf, fsize);
+       if (b_size(&outbuf) < hsize + fsize)
+               fsize = b_size(&outbuf) - hsize;
+       BUG_ON(fsize <= 0);
+
+       b_putchr(&outbuf, 0x00);        /* h3 frame type = DATA */
+       b_quic_enc_int(&outbuf, fsize); /* h3 frame length */
 
-       total += fsize;
        b_putblk(&outbuf, htx_get_blk_ptr(htx, blk), fsize);
+       total += fsize;
        count -= fsize;
 
        if (fsize == bsize)
@@ -577,6 +591,7 @@ static int h3_resp_data_send(struct qcs *qcs, struct buffer *buf, size_t count)
        else
                htx_cut_data_blk(htx, blk, fsize);
 
+       /* commit the buffer */
        b_add(res, b_data(&outbuf));
        goto new_frame;
 
@@ -597,7 +612,7 @@ size_t h3_snd_buf(struct conn_stream *cs, struct buffer *buf, size_t count, int
 
        htx = htx_from_buf(buf);
 
-       while (count && !htx_is_empty(htx)) {
+       while (count && !htx_is_empty(htx) && !(qcs->flags & QC_SF_BLK_MROOM)) {
                idx = htx_get_head(htx);
                blk = htx_get_blk(htx, idx);
                btype = htx_get_blk_type(blk);
@@ -643,10 +658,13 @@ size_t h3_snd_buf(struct conn_stream *cs, struct buffer *buf, size_t count, int
 
        if ((htx->flags & HTX_FL_EOM) && htx_is_empty(htx))
                qcs->flags |= QC_SF_FIN_STREAM;
-       // TODO should I call the mux directly here ?
-       qc_snd_buf(cs, buf, total, flags);
 
  out:
+       if (total) {
+               if (!(qcs->qcc->wait_event.events & SUB_RETRY_SEND))
+                       tasklet_wakeup(qcs->qcc->wait_event.tasklet);
+       }
+
        return total;
 }
 
index 710aa0e63b4b6d4279da9b00de62da2edcc53a6d..55a4dc5577703703285e62f4aaf2ff277e84b83b 100644 (file)
@@ -1350,7 +1350,7 @@ static int qcs_push_frame(struct qcs *qcs, struct buffer *payload, int fin, uint
 static int qc_send(struct qcc *qcc)
 {
        struct eb64_node *node;
-       int ret, done;
+       int ret, done, xprt_wake = 0;
 
        TRACE_ENTER(QC_EV_QCC_SEND, qcc->conn);
        ret = done = 0;
@@ -1380,18 +1380,26 @@ static int qc_send(struct qcc *qcc)
                        if (ret < 0)
                                ABORT_NOW();
 
+                       if (ret > 0) {
+                               xprt_wake = 1;
+                               if (qcs->flags & QC_SF_BLK_MROOM) {
+                                       qcs->flags &= ~QC_SF_BLK_MROOM;
+                                       qcs_notify_send(qcs);
+                               }
+                       }
+
                        qcs->tx.offset += ret;
+
                        if (b_data(buf)) {
                                qcc->conn->xprt->subscribe(qcc->conn, qcc->conn->xprt_ctx,
                                                           SUB_RETRY_SEND, &qcc->wait_event);
-                               break;
                        }
                }
                node = eb64_next(node);
        }
-       if (ret > 0)
-               tasklet_wakeup(((struct ssl_sock_ctx *)(qcc->conn->xprt_ctx))->wait_event.tasklet);
 
+       if (xprt_wake)
+               tasklet_wakeup(((struct ssl_sock_ctx *)(qcc->conn->xprt_ctx))->wait_event.tasklet);
 
        TRACE_LEAVE(QC_EV_QCC_SEND, qcc->conn);
        return 0;
@@ -1834,25 +1842,6 @@ static size_t qc_rcv_buf(struct conn_stream *cs, struct buffer *buf, size_t coun
        return ret;
 }
 
-/* Called from the upper layer, to send data from buffer <buf> for no more than
- * <count> bytes. Returns the number of bytes effectively sent. Some status
- * flags may be updated on the conn_stream.
- */
-size_t qc_snd_buf(struct conn_stream *cs, struct buffer *buf, size_t count, int flags)
-{
-       struct qcs *qcs = cs->ctx;
-
-       TRACE_ENTER(QC_EV_QCS_SEND|QC_EV_STRM_SEND, qcs->qcc->conn, qcs);
-
-       if (count) {
-               if (!(qcs->qcc->wait_event.events & SUB_RETRY_SEND))
-                       tasklet_wakeup(qcs->qcc->wait_event.tasklet);
-       }
-
-       TRACE_LEAVE(QC_EV_QCS_SEND|QC_EV_STRM_SEND, qcs->qcc->conn, qcs);
-       return count;
-}
-
 /* Called from the upper layer, to send data from buffer <buf> for no more than
  * <count> bytes. Returns the number of bytes effectively sent. Some status
  * flags may be updated on the mux.