MINOR: mux-quic: adjust timeout to accelerate closing

Improve timeout handling on the MUX. When releasing a stream, first
check if the connection can be considered as dead and should be freed
immediatly. This allows to liberate resources faster when possible.

If the connection is still active, ensure there is no attached
conn-stream before scheduling the timeout. To do this, add a nb_cs field
in the qcc structure.
diff --git a/include/haproxy/mux_quic-t.h b/include/haproxy/mux_quic-t.h
index 61ff95a..edc6930 100644
--- a/include/haproxy/mux_quic-t.h
+++ b/include/haproxy/mux_quic-t.h
@@ -27,6 +27,7 @@
 
 struct qcc {
 	struct connection *conn;
+	uint64_t nb_cs; /* number of attached conn-streams */
 	uint32_t flags; /* QC_CF_* */
 
 	struct {
diff --git a/include/haproxy/mux_quic.h b/include/haproxy/mux_quic.h
index 715f7e9..c01d156 100644
--- a/include/haproxy/mux_quic.h
+++ b/include/haproxy/mux_quic.h
@@ -115,6 +115,8 @@
 	cs->ctx = qcs;
 	stream_new(qcs->qcc->conn->owner, cs, buf);
 
+	++qcs->qcc->nb_cs;
+
 	return cs;
 }
 
diff --git a/src/mux_quic.c b/src/mux_quic.c
index 8e91d7b..ad1a693 100644
--- a/src/mux_quic.c
+++ b/src/mux_quic.c
@@ -471,14 +471,7 @@
 /* Return true if the mux timeout should be armed. */
 static inline int qcc_may_expire(struct qcc *qcc)
 {
-
-	/* Consider that the timeout must be set if no bidirectional streams
-	 * are opened.
-	 */
-	if (!qcc->strms[QCS_CLT_BIDI].nb_streams)
-		return 1;
-
-	return 0;
+	return !qcc->nb_cs;
 }
 
 /* release function. This one should be called to free all resources allocated
@@ -502,6 +495,11 @@
 		if (qcc->app_ops && qcc->app_ops->release)
 			qcc->app_ops->release(qcc->ctx);
 
+		if (qcc->task) {
+			task_destroy(qcc->task);
+			qcc->task = NULL;
+		}
+
 		if (qcc->wait_event.tasklet)
 			tasklet_free(qcc->wait_event.tasklet);
 
@@ -894,9 +892,14 @@
 	qc_send(qcc);
 
 	if (qc_release_detached_streams(qcc)) {
-		/* Schedule the mux timeout if no bidirectional streams left. */
-		if (qcc_may_expire(qcc)) {
-			qcc->task->expire = tick_add(now_ms, qcc->timeout);
+		if (qcc_is_dead(qcc)) {
+			qc_release(qcc);
+		}
+		else {
+			if (qcc_may_expire(qcc))
+				qcc->task->expire = tick_add(now_ms, qcc->timeout);
+			else
+				qcc->task->expire = TICK_ETERNITY;
 			task_queue(qcc->task);
 		}
 	}
@@ -955,6 +958,7 @@
 
 	qcc->conn = conn;
 	conn->ctx = qcc;
+	qcc->nb_cs = 0;
 	qcc->flags = 0;
 
 	qcc->app_ops = NULL;
@@ -1052,6 +1056,8 @@
 	 * managment between xprt and mux is reorganized.
 	 */
 
+	--qcc->nb_cs;
+
 	if (b_data(&qcs->tx.buf) || qcs->tx.offset > qcs->tx.sent_offset) {
 		TRACE_DEVEL("leaving with remaining data, detaching qcs", QMUX_EV_STRM_END, qcc->conn, qcs);
 		qcs->flags |= QC_SF_DETACH;
@@ -1060,9 +1066,14 @@
 
 	qcs_destroy(qcs);
 
-	/* Schedule the mux timeout if no bidirectional streams left. */
-	if (qcc_may_expire(qcc)) {
-		qcc->task->expire = tick_add(now_ms, qcc->timeout);
+	if (qcc_is_dead(qcc)) {
+		qc_release(qcc);
+	}
+	else {
+		if (qcc_may_expire(qcc))
+			qcc->task->expire = tick_add(now_ms, qcc->timeout);
+		else
+			qcc->task->expire = TICK_ETERNITY;
 		task_queue(qcc->task);
 	}