BUG/MEDIUM: mux_quic: complete stream shutdown for read channel

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
This commit is contained in:
Amaury Denoyelle 2026-06-17 18:04:56 +02:00
parent cc56803799
commit bf76275fff
4 changed files with 35 additions and 1 deletions

View 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 */

View file

@ -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);

View 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;
}
}

View 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);
}