]> git.kaiwu.me - haproxy.git/commitdiff
BUG/MEDIUM: mux_quic: complete stream shutdown for read channel
authorAmaury Denoyelle <adenoyelle@haproxy.com>
Wed, 17 Jun 2026 16:04:56 +0000 (18:04 +0200)
committerAmaury Denoyelle <adenoyelle@haproxy.com>
Mon, 6 Jul 2026 08:19:12 +0000 (10:19 +0200)
Prior to this patch, shut stream callback only handles write channel
closure. In case of an early closure, a RESET_STREAM would be emitted.

On the frontend side in most cases this is sufficient as read channel is
already closed, as HTTP/3 GET requests has been fully received. However,
this may not be the case for POST requests. Also, on the backend side,
haproxy acts a client. In this case, a stream early closure will
typically happen before receiving the full response. Nothing will be
emitted (RESET_STREAM is unnecessary as write channel is already
closed), thus the server peer will continue to emit.

To fix this situation, the current patch implement read channel closure
on shut if SE_SHR_RESET is set. Callback lclose from app_ops is called
with a new dedicated mode for read channel closure, which will result in
a STOP_SENDING frame generated by H3 and hq transcoders. This will
instruct the peer to stop emission.

This should be backported up to 3.3. Note that this depends on the
following patch :
  dde3ee06c30f20091443bdafdda0e0294f7ac26b
  MINOR: mux_quic: use separate error code for STOP_SENDING

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

index 25bab15d97cffd859073c1efa6c76afb11d2801b..7ec46738656e9f7317e80575565798b48f43a869 100644 (file)
@@ -220,6 +220,7 @@ enum qcc_app_ops_lclose_mode {
        QCC_APP_OPS_LCLO_MODE_NORMAL,
        QCC_APP_OPS_LCLO_MODE_ABORT,
        QCC_APP_OPS_LCLO_MODE_KILL_CONN,
+       QCC_APP_OPS_LCLO_MODE_READ,
 };
 
 /* QUIC application layer operations */
index 32d8f33dac5064fe2d5db03993fde1180ba66278..d11005eb011266a140a9d3b13b449ccaaaf33d45 100644 (file)
--- a/src/h3.c
+++ b/src/h3.c
@@ -3282,6 +3282,21 @@ static void h3_lclose(struct qcs *qcs, enum qcc_app_ops_lclose_mode mode)
 {
        TRACE_ENTER(H3_EV_H3S_END, qcs->qcc->conn, qcs);
 
+       /* RFC 9114 4.1.1. Request Cancellation and Rejection
+        *
+        * Servers MUST NOT use the H3_REQUEST_REJECTED error code for requests
+        * that were partially or fully processed. When a server abandons a
+        * response after partial processing, it SHOULD abort its response
+        * stream with the error code H3_REQUEST_CANCELLED.
+        * Client SHOULD use the error code H3_REQUEST_CANCELLED to cancel requests.
+        */
+
+       /* Following the above recommendations, H3_REQUEST_REJECTED is never
+        * used in practice in haproxy as a server on shut as upper stream
+        * layer is instantiated immediately with HEADERS content attached. The
+        * only exception is for stream closure after GOAWAY emission.
+        */
+
        switch (mode) {
        case QCC_APP_OPS_LCLO_MODE_NORMAL:
                /* Close stream with FIN. This can only be performed if at
@@ -3312,6 +3327,11 @@ static void h3_lclose(struct qcs *qcs, enum qcc_app_ops_lclose_mode mode)
                                      muxc_tevt_type_graceful_shut);
                }
                break;
+
+       case QCC_APP_OPS_LCLO_MODE_READ:
+               TRACE_STATE("request stream read channel closure", H3_EV_H3S_END, qcs->qcc->conn, qcs);
+               qcc_abort_stream_read(qcs, H3_ERR_REQUEST_CANCELLED);
+               break;
        }
 
        TRACE_LEAVE(H3_EV_H3S_END, qcs->qcc->conn, qcs);
index 50c6791d8547f26aa4c47954be4e5ed47d1abb2f..4d2b4a80f042c393339a1b5e9927efa582cf6fff 100644 (file)
@@ -424,6 +424,10 @@ static void hq_interop_lclose(struct qcs *qcs, enum qcc_app_ops_lclose_mode mode
                if (!(qcs->qcc->flags & (QC_CF_ERR_CONN|QC_CF_ERRL)))
                        qcc_set_error(qcs->qcc, 0, 0, muxc_tevt_type_graceful_shut);
                break;
+
+       case QCC_APP_OPS_LCLO_MODE_READ:
+               qcc_abort_stream_read(qcs, 0);
+               break;
        }
 }
 
index 6960c599443e1f426502e518934df83dc5e4ae47..dcf21a726bed8ecdcdde9a620dc31d048d5abf75 100644 (file)
@@ -4711,8 +4711,12 @@ static void qcm_strm_shut(struct stconn *sc, unsigned int mode, struct se_abort_
 
        TRACE_ENTER(QMUX_EV_STRM_SHUT, qcc->conn, qcs);
 
+       /* Not implemented. */
+       BUG_ON(mode & SE_SHR_DRAIN);
+
        /* Early closure reported if QC_SF_FIN_STREAM not yet set. */
-       if (!qcs_is_close_local(qcs) &&
+       if ((mode & (SE_SHW_SILENT|SE_SHW_NORMAL)) &&
+           !qcs_is_close_local(qcs) &&
            !(qcs->flags & (QC_SF_FIN_STREAM|QC_SF_TO_RESET))) {
 
                if (qcs->flags & QC_SF_UNKNOWN_PL_LENGTH)
@@ -4726,6 +4730,11 @@ static void qcm_strm_shut(struct stconn *sc, unsigned int mode, struct se_abort_
                tasklet_wakeup(qcc->wait_event.tasklet);
        }
 
+       if ((mode & SE_SHR_RESET) &&
+           !qcs_is_close_remote(qcs) && !(qcs->flags & (QC_SF_READ_ABORTED))) {
+               qcc->app_ops->lclose(qcs, QCC_APP_OPS_LCLO_MODE_READ);
+       }
+
  out:
        TRACE_LEAVE(QMUX_EV_STRM_SHUT, qcc->conn, qcs);
 }