MEDIUM: mux-quic: defer stream shut if remaining tx data

Defer the shutting of a qcs if there is still data in its tx buffers. In
this case, the conn_stream is closed but the qcs is kept with a new flag
QC_SF_DETACH.

On ACK reception, the xprt wake up the shut_tl tasklet if the stream is
flagged with QC_SF_DETACH. This tasklet is responsible to free the qcs
and possibly the qcc when all bidirectional streams are removed.
diff --git a/include/haproxy/mux_quic-t.h b/include/haproxy/mux_quic-t.h
index 2dff828..a958f45 100644
--- a/include/haproxy/mux_quic-t.h
+++ b/include/haproxy/mux_quic-t.h
@@ -202,6 +202,7 @@
 #define QC_SF_KILL_CONN         0x00020000  // kill the whole connection with this stream
 
 #define QC_SF_FIN_STREAM        0x00040000  // FIN bit must be set for last frame of the stream
+#define QC_SF_DETACH            0x00080000
 
 /* QUIC stream descriptor, describing the stream as it appears in the QUIC_CONN, and as
  * it is being processed in the internal HTTP representation (HTX).
diff --git a/src/mux_quic.c b/src/mux_quic.c
index 578a59e..df6d147 100644
--- a/src/mux_quic.c
+++ b/src/mux_quic.c
@@ -1564,9 +1564,16 @@
 	struct qcc *qcc = qcs->qcc;
 
 	TRACE_ENTER(QC_EV_STRM_END, qcs ? qcs->qcc->conn : NULL, qcs);
+	if (b_data(&qcs->tx.buf) || b_data(&qcs->tx.xprt_buf)) {
+		qcs->flags |= QC_SF_DETACH;
+		goto out;
+	}
+
 	qcs_destroy(qcs);
 	if (qcc_is_dead(qcc))
 		qc_release(qcc);
+
+ out:
 	TRACE_LEAVE(QC_EV_STRM_END, qcs ? qcs->qcc->conn : NULL);
 }
 
@@ -1603,7 +1610,8 @@
 
 	TRACE_ENTER(QC_EV_STRM_SHUT, qcc->conn, qcs);
 
-	if (qcs->flags & QC_SF_NOTIFIED) {
+	if (qcs->flags & QC_SF_NOTIFIED ||
+	    (b_data(&qcs->tx.buf) || b_data(&qcs->tx.xprt_buf))) {
 		/* some data processing remains to be done first */
 		goto end;
 	}
diff --git a/src/xprt_quic.c b/src/xprt_quic.c
index 455c83b..130652f 100644
--- a/src/xprt_quic.c
+++ b/src/xprt_quic.c
@@ -1210,6 +1210,9 @@
 			eb64_insert(&qcs->tx.acked_frms, &strm->offset);
 		}
 		stream_acked |= qcs_try_to_consume(qcs);
+
+		if (qcs->flags & QC_SF_DETACH)
+			tasklet_wakeup(qcs->shut_tl);
 	}
 	break;
 	default: