MEDIUM: mux-h1: Handle H1C states via its state field instead of H1C_F_ST_*

The H1 connection state is now handled in a dedicated state. H1C_F_ST_*
flags are removed. All states are now exclusives. It is easier to know the
H1 connection states. It is alive, or usable, if it is not CLOSING or
CLOSED. It is CLOSING if it should be closed ASAP but a stream is still
attached and/or the output buffer is not empty. CLOSED is used when the H1
connection is ready to be closed. Other states are quite easy to understand.

There is no special changes in the H1 connection behavior. Except in
h1_send(). When a CLOSING connection is CLOSED, the function now reports an
activity. In addition, when an embryonic H1 stream is aborted, it is
destroyed. This way, the H1 connection can be switched to CLOSED state.
diff --git a/include/haproxy/mux_h1-t.h b/include/haproxy/mux_h1-t.h
index abe5cb8..4b9b8ab 100644
--- a/include/haproxy/mux_h1-t.h
+++ b/include/haproxy/mux_h1-t.h
@@ -38,16 +38,9 @@
 #define H1C_F_IN_FULL        0x00000020 /* mux is blocked on input buffer full */
 #define H1C_F_IN_SALLOC      0x00000040 /* mux is blocked on lack of stream's request buffer */
 
-/* Flags indicating the connection state */
-#define H1C_F_ST_EMBRYONIC   0x00000100 /* Set when a H1 stream with no stream connector is attached to the connection */
-#define H1C_F_ST_ATTACHED    0x00000200 /* Set when a H1 stream with a stream connector is attached to the connection (may be not READY) */
-#define H1C_F_ST_IDLE        0x00000400 /* connection is idle and may be reused
-					 * (exclusive to all H1C_F_ST flags and never set when an h1s is attached) */
-#define H1C_F_ERROR          0x00000800 /* connection must be closed ASAP because an error occurred (stream connector may still be attached) */
-#define H1C_F_ST_SHUTDOWN    0x00001000 /* connection must be shut down ASAP flushing output first (stream connector may still be attached) */
-#define H1C_F_ST_READY       0x00002000 /* Set in ATTACHED state with a READY stream connector. A stream connector is not ready when
-					 * a TCP>H1 upgrade is in progress Thus this flag is only set if ATTACHED is also set */
-#define H1C_F_ST_ALIVE       (H1C_F_ST_IDLE|H1C_F_ST_EMBRYONIC|H1C_F_ST_ATTACHED)
+/* 0x00000100 - 0x00000400 unused */
+#define H1C_F_ERROR       0x00000800 /* connection must be closed ASAP because an error occurred (stream connector may still be attached) */
+/* 0x00001000 - 0x00002000 unused */
 #define H1C_F_SILENT_SHUT    0x00004000 /* if H1C is closed closed, silent (or dirty) shutdown must be performed */
 /* 0x00008000 unused */
 
@@ -74,11 +67,9 @@
 	/* flags */
 	_(H1C_F_OUT_ALLOC, _(H1C_F_OUT_FULL,
 	_(H1C_F_IN_ALLOC, _(H1C_F_IN_FULL, _(H1C_F_IN_SALLOC,
-	_(H1C_F_ST_EMBRYONIC, _(H1C_F_ST_ATTACHED, _(H1C_F_ST_IDLE,
-	_(H1C_F_ERROR, _(H1C_F_ST_SHUTDOWN, _(H1C_F_ST_READY,
-	_(H1C_F_SILENT_SHUT, _(H1C_F_WANT_SPLICE, _(H1C_F_ERR_PENDING,
+	_(H1C_F_ERROR, _(H1C_F_SILENT_SHUT, _(H1C_F_WANT_SPLICE, _(H1C_F_ERR_PENDING,
 	_(H1C_F_WAIT_NEXT_REQ, _(H1C_F_UPG_H2C, _(H1C_F_CO_MSG_MORE,
-	_(H1C_F_CO_STREAMER, _(H1C_F_IS_BACK)))))))))))))))))));
+	_(H1C_F_CO_STREAMER, _(H1C_F_IS_BACK))))))))))))));
 	/* epilogue */
 	_(~0U);
 	return buf;
diff --git a/src/mux_h1.c b/src/mux_h1.c
index 6fd4836..ebda92c 100644
--- a/src/mux_h1.c
+++ b/src/mux_h1.c
@@ -538,15 +538,15 @@
 }
 
 /* returns the number of streams in use on a connection to figure if it's idle
- * or not. We rely on H1C_F_ST_IDLE to know if the connection is in-use or
- * not. This flag is only set when no H1S is attached and when the previous
- * stream, if any, was fully terminated without any error and in K/A mode.
+ * or not. We rely on H1C state to know if the connection is in-use or not. It
+ * is IDLE only when no H1 stream is attached and when the previous stream, if
+ * any, was fully terminated without any error and in K/A mode.
  */
 static int h1_used_streams(struct connection *conn)
 {
 	struct h1c *h1c = conn->ctx;
 
-	return ((h1c->flags & H1C_F_ST_IDLE) ? 0 : 1);
+	return ((h1c->state == H1_CS_IDLE) ? 0 : 1);
 }
 
 /* returns the number of streams still available on a connection */
@@ -561,7 +561,7 @@
 	int is_idle_conn = 0;
 
 	if (h1c->task) {
-		if (!(h1c->flags & H1C_F_ST_ALIVE) || (h1c->flags & H1C_F_ST_SHUTDOWN)) {
+		if (!h1_is_alive(h1c)) {
 			/* half-closed or dead connections : switch to clientfin/serverfin
 			 * timeouts so that we don't hang too long on clients that have
 			 * gone away (especially in tunnel mode).
@@ -571,12 +571,12 @@
 			is_idle_conn = 1;
 		}
 		else if (b_data(&h1c->obuf)) {
-			/* connection with pending outgoing data, need a timeout (server or client). */
+			/* alive connection with pending outgoing data, need a timeout (server or client). */
 			h1c->task->expire = tick_add(now_ms, h1c->timeout);
 			TRACE_DEVEL("refreshing connection's timeout (pending outgoing data)", H1_EV_H1C_SEND|H1_EV_H1C_RECV, h1c->conn);
 		}
-		else if (!(h1c->flags & (H1C_F_IS_BACK|H1C_F_ST_READY))) {
-			/* front connections waiting for a fully usable stream need a timeout. */
+		else if (!(h1c->flags & H1C_F_IS_BACK) && (h1c->state != H1_CS_RUNNING)) {
+			/* alive front connections waiting for a fully usable stream need a timeout. */
 			h1c->task->expire = tick_add(now_ms, h1c->timeout);
 			TRACE_DEVEL("refreshing connection's timeout (alive front h1c but not ready)", H1_EV_H1C_SEND|H1_EV_H1C_RECV, h1c->conn);
 			/* A frontend connection not yet ready could be treated the same way as an idle
@@ -634,8 +634,7 @@
 		h1c->idle_exp = TICK_ETERNITY;
 		return;
 	}
-
-	if (h1c->flags & H1C_F_ST_IDLE) {
+	if (h1c->state == H1_CS_IDLE) {
 		if (!tick_isset(h1c->idle_exp)) {
 			if ((h1c->flags & H1C_F_WAIT_NEXT_REQ) &&   /* Not the first request */
 			    !b_data(&h1c->ibuf) &&                 /*  No input data */
@@ -649,15 +648,15 @@
 			}
 		}
 	}
-	else if ((h1c->flags & H1C_F_ST_ALIVE) && !(h1c->flags & H1C_F_ST_READY)) {
+	else if (h1c->state < H1_CS_RUNNING) {
 		if (!tick_isset(h1c->idle_exp)) {
 			h1c->idle_exp = tick_add_ifset(now_ms, h1c->px->timeout.httpreq);
 			TRACE_DEVEL("set idle expiration (http-request timeout)", H1_EV_H1C_RECV, h1c->conn);
 		}
 	}
-	else { // ST_ATTACHED or SHUTDOWN
+	else {
 		h1c->idle_exp = TICK_ETERNITY;
-		TRACE_DEVEL("unset idle expiration (attached || shutdown)", H1_EV_H1C_RECV, h1c->conn);
+		TRACE_DEVEL("unset idle expiration (running or closing)", H1_EV_H1C_RECV, h1c->conn);
 	}
 }
 /*****************************************************************/
@@ -696,7 +695,7 @@
 		goto err;
 	}
 
-	h1c->flags = (h1c->flags & ~H1C_F_ST_EMBRYONIC) | H1C_F_ST_ATTACHED | H1C_F_ST_READY;
+	h1c->state = H1_CS_RUNNING;
 	TRACE_LEAVE(H1_EV_STRM_NEW, h1c->conn, h1s);
 	return h1s_sc(h1s);
 
@@ -714,7 +713,7 @@
 		goto err;
 	}
 
-	h1s->h1c->flags |= H1C_F_ST_READY;
+	h1s->h1c->state = H1_CS_RUNNING;
 	TRACE_LEAVE(H1_EV_STRM_NEW, h1s->h1c->conn, h1s);
 	return h1s_sc(h1s);
 
@@ -754,7 +753,7 @@
 
 	if (h1c->flags & H1C_F_WAIT_NEXT_REQ)
 		h1s->flags |= H1S_F_NOT_FIRST;
-	h1c->flags = (h1c->flags & ~(H1C_F_ST_IDLE|H1C_F_WAIT_NEXT_REQ)) | H1C_F_ST_EMBRYONIC;
+	h1s->h1c->state = H1_CS_EMBRYONIC;
 
 	TRACE_LEAVE(H1_EV_H1S_NEW, h1c->conn, h1s);
 	return h1s;
@@ -824,7 +823,7 @@
 	h1s->sd = sc->sedesc;
 	h1s->sess = sess;
 
-	h1c->flags = (h1c->flags & ~H1C_F_ST_EMBRYONIC) | H1C_F_ST_ATTACHED | H1C_F_ST_READY;
+	h1c->state = H1_CS_RUNNING;
 
 	if (h1c->px->options2 & PR_O2_RSPBUG_OK)
 		h1s->res.err_pos = -1;
@@ -855,7 +854,6 @@
 		h1_release_buf(h1c, &h1s->rxbuf);
 
 		h1c->flags &= ~(H1C_F_WANT_SPLICE|
-				H1C_F_ST_EMBRYONIC|H1C_F_ST_ATTACHED|H1C_F_ST_READY|
 				H1C_F_OUT_FULL|H1C_F_OUT_ALLOC|H1C_F_IN_SALLOC|
 				H1C_F_CO_MSG_MORE|H1C_F_CO_STREAMER);
 		if (h1s->flags & H1S_F_ERROR_MASK) {
@@ -863,16 +861,18 @@
 			TRACE_ERROR("h1s on error, set error on h1c", H1_EV_H1S_END|H1_EV_H1C_ERR, h1c->conn, h1s);
 		}
 
-		if (!(h1c->flags & (H1C_F_ERROR|H1C_F_ST_SHUTDOWN)) &&                       /* No error/shutdown on h1c */
+		if (!(h1c->flags & H1C_F_ERROR) &&                                           /* No error */
+		    h1_is_alive(h1c) &&                                                      /* still alive */
 		    !(h1c->conn->flags & (CO_FL_ERROR|CO_FL_SOCK_RD_SH|CO_FL_SOCK_WR_SH)) && /* No error/shutdown on conn */
 		    (h1s->flags & H1S_F_WANT_KAL) &&                                         /* K/A possible */
 		    h1s->req.state == H1_MSG_DONE && h1s->res.state == H1_MSG_DONE) {        /* req/res in DONE state */
-			h1c->flags |= (H1C_F_ST_IDLE|H1C_F_WAIT_NEXT_REQ);
+			h1c->state = H1_CS_IDLE;
+			h1c->flags |= H1C_F_WAIT_NEXT_REQ;
 			TRACE_STATE("set idle mode on h1c, waiting for the next request", H1_EV_H1C_ERR, h1c->conn, h1s);
 		}
 		else {
-			TRACE_STATE("set shutdown on h1c", H1_EV_H1S_END, h1c->conn, h1s);
-			h1c->flags |= H1C_F_ST_SHUTDOWN;
+			h1_close(h1c);
+			TRACE_STATE("close h1c", H1_EV_H1S_END, h1c->conn, h1s);
 		}
 
 		HA_ATOMIC_DEC(&h1c->px_counters->open_streams);
@@ -908,7 +908,7 @@
 	h1c->px   = proxy;
 
 	h1c->state = H1_CS_IDLE;
-	h1c->flags = H1C_F_ST_IDLE;
+	h1c->flags = H1C_F_NONE;
 	h1c->errcode = 0;
 	h1c->ibuf  = *input;
 	h1c->obuf  = BUF_NULL;
@@ -970,7 +970,7 @@
 			goto fail;
 
 		/* Attach the SC but Not ready yet */
-		h1c->flags = (h1c->flags & ~H1C_F_ST_EMBRYONIC) | H1C_F_ST_ATTACHED;
+		h1c->state = H1_CS_UPGRADING;
 		TRACE_DEVEL("Inherit the SC from TCP connection to perform an upgrade",
 			    H1_EV_H1C_NEW|H1_EV_STRM_NEW, h1c->conn, h1c->h1s);
 	}
@@ -1378,7 +1378,7 @@
 	struct proxy *other_end;
 	union error_snapshot_ctx ctx;
 
-	if ((h1c->flags & H1C_F_ST_ATTACHED) && sc_strm(h1s_sc(h1s))) {
+	if (h1c->state == H1_CS_UPGRADING || h1c->state == H1_CS_RUNNING) {
 		if (sess == NULL)
 			sess = __sc_strm(h1s_sc(h1s))->sess;
 		if (!(h1m->flags & H1_MF_RESP))
@@ -1836,7 +1836,7 @@
 	if (!b_data(&h1c->ibuf))
 		h1_release_buf(h1c, &h1c->ibuf);
 
-	if (!(h1c->flags & H1C_F_ST_READY)) {
+	if (h1c->state < H1_CS_RUNNING) {
 		/* The H1 connection is not ready. Most of time, there is no SC
 		 * attached, except for TCP>H1 upgrade, from a TCP frontend. In both
 		 * cases, it is only possible on the client side.
@@ -1849,7 +1849,7 @@
 			goto end;
 		}
 
-		if (!(h1c->flags & H1C_F_ST_ATTACHED)) {
+		if (h1c->state == H1_CS_EMBRYONIC) {
 			TRACE_DEVEL("request headers fully parsed, create and attach the SC", H1_EV_RX_DATA, h1c->conn, h1s);
 			BUG_ON(h1s_sc(h1s));
 			if (!h1s_new_sc(h1s, buf)) {
@@ -2636,7 +2636,14 @@
 			ret = 1;
 		}
 	}
+
+	if (h1c->h1s) {
+		TRACE_DEVEL("Abort embryonic H1S", H1_EV_H1C_ERR, h1c->conn, h1c->h1s);
+		h1s_destroy(h1c->h1s);
+	}
+
 	h1c->flags &= ~H1C_F_ERR_PENDING;
+	h1c->state = H1_CS_CLOSING;
   out:
 	TRACE_LEAVE(H1_EV_H1C_ERR, h1c->conn);
 	return ret;
@@ -2673,8 +2680,10 @@
 	struct session *sess = h1c->conn->owner;
 	int ret = 1;
 
-	if (!b_data(&h1c->ibuf) && ((h1c->flags & H1C_F_WAIT_NEXT_REQ) || (sess->fe->options & PR_O_IGNORE_PRB)))
+	if (!b_data(&h1c->ibuf) && ((h1c->flags & H1C_F_WAIT_NEXT_REQ) || (sess->fe->options & PR_O_IGNORE_PRB))) {
+		h1c->state = H1_CS_CLOSING;
 		goto end;
+	}
 
 	session_inc_http_req_ctr(sess);
 	session_inc_http_err_ctr(sess);
@@ -2703,8 +2712,10 @@
 	struct session *sess = h1c->conn->owner;
 	int ret = 1;
 
-	if (!b_data(&h1c->ibuf) && ((h1c->flags & H1C_F_WAIT_NEXT_REQ) || (sess->fe->options & PR_O_IGNORE_PRB)))
+	if (!b_data(&h1c->ibuf) && ((h1c->flags & H1C_F_WAIT_NEXT_REQ) || (sess->fe->options & PR_O_IGNORE_PRB))) {
+		h1c->state = H1_CS_CLOSING;
 		goto end;
+	}
 
 	session_inc_http_req_ctr(sess);
 	proxy_inc_fe_req_ctr(sess->listener, sess->fe);
@@ -2730,8 +2741,10 @@
 	struct session *sess = h1c->conn->owner;
 	int ret = 1;
 
-	if (!b_data(&h1c->ibuf) && ((h1c->flags & H1C_F_WAIT_NEXT_REQ) || (sess->fe->options & PR_O_IGNORE_PRB)))
+	if (!b_data(&h1c->ibuf) && ((h1c->flags & H1C_F_WAIT_NEXT_REQ) || (sess->fe->options & PR_O_IGNORE_PRB))) {
+		h1c->state = H1_CS_CLOSING;
 		goto end;
+	}
 
 	session_inc_http_req_ctr(sess);
 	proxy_inc_fe_req_ctr(sess->listener, sess->fe);
@@ -2878,7 +2891,7 @@
 	if (!b_data(&h1c->obuf)) {
 		TRACE_DEVEL("leaving with everything sent", H1_EV_H1C_SEND, h1c->conn);
 		h1_release_buf(h1c, &h1c->obuf);
-		if (h1c->flags & H1C_F_ST_SHUTDOWN) {
+		if (h1c->state == H1_CS_CLOSING) {
 			TRACE_STATE("process pending shutdown for writes", H1_EV_H1C_SEND, h1c->conn);
 			h1_shutw_conn(conn);
 		}
@@ -2889,7 +2902,7 @@
 	}
 
 	TRACE_LEAVE(H1_EV_H1C_SEND, h1c->conn);
-	return sent;
+	return sent || (h1c->state == H1_CS_CLOSED);
 }
 
 /* callback called on any event by the connection handler.
@@ -2899,14 +2912,14 @@
 static int h1_process(struct h1c * h1c)
 {
 	struct connection *conn = h1c->conn;
-	struct h1s *h1s = h1c->h1s;
 
 	TRACE_ENTER(H1_EV_H1C_WAKE, conn);
 
 	/* Try to parse now the first block of a request, creating the H1 stream if necessary */
 	if (b_data(&h1c->ibuf) &&                                                /* Input data to be processed */
-	    (h1c->flags & H1C_F_ST_ALIVE) && !(h1c->flags & H1C_F_ST_READY) &&   /* ST_IDLE/ST_EMBRYONIC or ST_ATTACH but not ST_READY */
-	    !(h1c->flags & (H1C_F_IN_SALLOC|H1C_F_ERROR))) {                     /* No allocation failure on the stream rxbuf and no ERROR on the H1C */
+	    (h1c->state < H1_CS_RUNNING) &&                                      /* IDLE, EMBRYONIC or UPGRADING */
+	    !(h1c->flags & (H1C_F_IN_SALLOC|H1C_F_ERROR))) {                  /* No allocation failure on the stream rxbuf and no ERROR on the H1C */
+		struct h1s *h1s = h1c->h1s;
 		struct buffer *buf;
 		size_t count;
 
@@ -2921,11 +2934,11 @@
 			/* Try to match H2 preface before parsing the request headers. */
 			if (b_isteq(&h1c->ibuf, 0, b_data(&h1c->ibuf), ist(H2_CONN_PREFACE)) > 0) {
 				h1c->flags |= H1C_F_UPG_H2C;
-				if (h1c->flags & H1C_F_ST_ATTACHED) {
+				if (h1c->state == H1_CS_UPGRADING) {
 					/* Force the REOS here to be sure to release the SC.
 					   Here ATTACHED implies !READY, and h1s defined
 					*/
-					BUG_ON(!h1s ||  (h1c->flags & H1C_F_ST_READY));
+					BUG_ON(!h1s);
 					h1s->flags |= H1S_F_REOS;
 				}
 				TRACE_STATE("release h1c to perform H2 upgrade ", H1_EV_RX_DATA|H1_EV_H1C_WAKE);
@@ -2938,7 +2951,7 @@
 			h1s = h1c_frt_stream_new(h1c, NULL, h1c->conn->owner);
 			if (!h1s) {
 				b_reset(&h1c->ibuf);
-				h1c->flags = (h1c->flags & ~(H1C_F_ST_IDLE|H1C_F_WAIT_NEXT_REQ)) | H1C_F_ERROR;
+				h1c->flags |= H1C_F_ERROR;
 				goto no_parsing;
 			}
 		}
@@ -2962,17 +2975,17 @@
 	  no_parsing:
 		if (h1c->flags & H1C_F_ERROR) {
 			h1_handle_internal_err(h1c);
-			h1c->flags &= ~(H1C_F_ST_IDLE|H1C_F_WAIT_NEXT_REQ);
+			h1c->flags &= ~H1C_F_WAIT_NEXT_REQ;
 			TRACE_ERROR("internal error detected", H1_EV_H1C_WAKE|H1_EV_H1C_ERR);
 		}
 		else if (h1s->flags & H1S_F_NOT_IMPL_ERROR) {
 			h1_handle_not_impl_err(h1c);
-			h1c->flags = (h1c->flags & ~(H1C_F_ST_IDLE|H1C_F_WAIT_NEXT_REQ)) | H1C_F_ERROR;
+			h1c->flags = (h1c->flags & ~H1C_F_WAIT_NEXT_REQ) | H1C_F_ERROR;
 			TRACE_ERROR("not-implemented error detected", H1_EV_H1C_WAKE|H1_EV_H1C_ERR);
 		}
 		else if (h1s->flags & H1S_F_PARSING_ERROR || se_fl_test(h1s->sd, SE_FL_ERROR)) {
 			h1_handle_parsing_error(h1c);
-			h1c->flags = (h1c->flags & ~(H1C_F_ST_IDLE|H1C_F_WAIT_NEXT_REQ)) | H1C_F_ERROR;
+			h1c->flags = (h1c->flags & ~H1C_F_WAIT_NEXT_REQ) | H1C_F_ERROR;
 			TRACE_ERROR("parsing error detected", H1_EV_H1C_WAKE|H1_EV_H1C_ERR);
 		}
 	}
@@ -2986,34 +2999,41 @@
 	if ((conn->flags & CO_FL_ERROR) ||
 	    conn_xprt_read0_pending(conn) ||
 	    (h1c->flags & H1C_F_ERROR) ||
-	    ((h1c->flags & H1C_F_SILENT_SHUT) && !b_data(&h1c->obuf))) {
-		if (!(h1c->flags & H1C_F_ST_READY)) {
+	    (h1c->state >= H1_CS_CLOSING && (h1c->flags & H1C_F_SILENT_SHUT) && !b_data(&h1c->obuf))) {
+		if (h1c->state != H1_CS_RUNNING) {
 			/* No stream connector or not ready */
-			/* shutdown for reads and error on the frontend connection: Send an error */
-			if (!(h1c->flags & (H1C_F_IS_BACK|H1C_F_ERROR|H1C_F_ST_SHUTDOWN))) {
+			if (h1c->state < H1_CS_RUNNING && !(h1c->flags & (H1C_F_IS_BACK|H1C_F_ERROR))) {
+				/* shutdown for reads and error on the frontend connection: Send an error */
 				if (h1_handle_parsing_error(h1c))
 					h1_send(h1c);
-				h1c->flags = (h1c->flags & ~(H1C_F_ST_IDLE|H1C_F_WAIT_NEXT_REQ)) | H1C_F_ERROR;
+				h1c->flags = (h1c->flags & ~H1C_F_WAIT_NEXT_REQ) | H1C_F_ERROR;
 			}
-
-			/* Handle pending error, if any (only possible on frontend connection) */
-			if (h1c->flags & H1C_F_ERR_PENDING) {
+			else if (h1c->flags & H1C_F_ERR_PENDING) {
+				/* Handle pending error, if any (only possible on frontend connection) */
 				BUG_ON(h1c->flags & H1C_F_IS_BACK);
 				if (h1_send_error(h1c))
 					h1_send(h1c);
 			}
+			else {
+				h1_close(h1c);
+				TRACE_STATE("close h1c", H1_EV_H1S_END, h1c->conn);
+			}
 
 			/* If there is some pending outgoing data or error, just wait */
-			if (b_data(&h1c->obuf) || (h1c->flags & H1C_F_ERR_PENDING))
+			if (h1c->state == H1_CS_CLOSING || (h1c->flags & H1C_F_ERR_PENDING))
 				goto end;
 
 			/* Otherwise we can release the H1 connection */
 			goto release;
 		}
 		else {
+			struct h1s *h1s = h1c->h1s;
+
 			/* Here there is still a H1 stream with a stream connector.
 			 * Report the connection state at the stream level
 			 */
+			BUG_ON(!h1s);
+
 			if (conn_xprt_read0_pending(conn)) {
 				h1s->flags |= H1S_F_REOS;
 				TRACE_STATE("read0 on connection", H1_EV_H1C_RECV, conn, h1s);
@@ -3059,9 +3079,9 @@
 		}
 	}
 
-	if ((h1c->flags & H1C_F_WANT_SPLICE) && !h1s_data_pending(h1s)) {
+	if (h1c->state == H1_CS_RUNNING && (h1c->flags & H1C_F_WANT_SPLICE) && !h1s_data_pending(h1c->h1s)) {
 		TRACE_DEVEL("xprt rcv_buf blocked (want_splice), notify h1s for recv", H1_EV_H1C_RECV, h1c->conn);
-		h1_wake_stream_for_recv(h1s);
+		h1_wake_stream_for_recv(h1c->h1s);
 	}
 
   end:
@@ -3070,10 +3090,12 @@
 	return 0;
 
   release:
-	if (h1c->flags & H1C_F_ST_ATTACHED) {
-		/* Don't release the H1 connection right now, we must destroy the
-		 * attached SC first. Here, the H1C must not be READY */
-		BUG_ON(!h1s || h1c->flags & H1C_F_ST_READY);
+	if (h1c->state == H1_CS_UPGRADING) {
+		struct h1s *h1s = h1c->h1s;
+
+		/* Don't release the H1 connection right now, we must destroy
+		 * the attached SC first */
+		BUG_ON(!h1s);
 
 		if (conn_xprt_read0_pending(conn) || (h1s->flags & H1S_F_REOS))
 			se_fl_set(h1s->sd, SE_FL_EOS);
@@ -3169,7 +3191,7 @@
 	if (ret == 0) {
 		struct h1s *h1s = h1c->h1s;
 
-		if (h1c->flags & H1C_F_ST_ATTACHED)
+		if (h1c->state == H1_CS_UPGRADING || h1c->state == H1_CS_RUNNING)
 			h1_alert(h1s);
 	}
 	return ret;
@@ -3207,7 +3229,7 @@
 		/* If a stream connector is still attached and ready to the mux, wait for the
 		 * stream's timeout
 		 */
-		if (h1c->flags & H1C_F_ST_READY) {
+		if (h1c->state == H1_CS_RUNNING) {
 			HA_SPIN_UNLOCK(IDLE_CONNS_LOCK, &idle_conns[tid].idle_conns_lock);
 			t->expire = TICK_ETERNITY;
 			TRACE_DEVEL("leaving (SC still attached)", H1_EV_H1C_WAKE, h1c->conn, h1c->h1s);
@@ -3215,8 +3237,8 @@
 		}
 
 		/* Try to send an error to the client */
-		if (!(h1c->flags & (H1C_F_IS_BACK|H1C_F_ERROR|H1C_F_ERR_PENDING|H1C_F_ST_SHUTDOWN))) {
-			h1c->flags = (h1c->flags & ~H1C_F_ST_IDLE) | H1C_F_ERROR;
+		if (h1c->state != H1_CS_CLOSING && !(h1c->flags & (H1C_F_IS_BACK|H1C_F_ERROR|H1C_F_ERR_PENDING))) {
+			h1c->flags |= H1C_F_ERROR;
 			TRACE_DEVEL("timeout error detected", H1_EV_H1C_WAKE|H1_EV_H1C_ERR, h1c->conn, h1c->h1s);
 			if (h1_handle_req_tout(h1c))
 				h1_send(h1c);
@@ -3227,9 +3249,9 @@
 			}
 		}
 
-		if (h1c->flags & H1C_F_ST_ATTACHED) {
+		if (h1c->state == H1_CS_UPGRADING) {
 			/* Don't release the H1 connection right now, we must destroy the
-			 * attached SC first. Here, the H1C must not be READY */
+			 * attached SC first. */
 			se_fl_set(h1c->h1s->sd, SE_FL_EOS | SE_FL_ERROR);
 			h1_alert(h1c->h1s);
 			h1_refresh_timeout(h1c);
@@ -3352,7 +3374,7 @@
 	is_not_first = h1s->flags & H1S_F_NOT_FIRST;
 	h1s_destroy(h1s);
 
-	if ((h1c->flags & (H1C_F_IS_BACK|H1C_F_ST_IDLE)) == (H1C_F_IS_BACK|H1C_F_ST_IDLE)) {
+	if (h1c->state == H1_CS_IDLE && (h1c->flags & H1C_F_IS_BACK)) {
 		/* this connection may be killed at any moment, we want it to
 		 * die "cleanly" (i.e. only an RST).
 		 */
@@ -3366,7 +3388,7 @@
 		 */
 		if (b_data(&h1c->ibuf)) {
 			h1_release_buf(h1c, &h1c->ibuf);
-			h1c->flags = (h1c->flags & ~H1C_F_ST_IDLE) | H1C_F_ST_SHUTDOWN;
+			h1c->state = H1_CS_CLOSING;
 			TRACE_DEVEL("remaining data on detach, kill connection", H1_EV_STRM_END|H1_EV_H1C_END);
 			goto release;
 		}
@@ -3413,14 +3435,15 @@
   release:
 	/* We don't want to close right now unless the connection is in error or shut down for writes */
 	if ((h1c->flags & H1C_F_ERROR) ||
+	    (h1c->state == H1_CS_CLOSED) ||
 	    (h1c->conn->flags & (CO_FL_ERROR|CO_FL_SOCK_WR_SH)) ||
-	    ((h1c->flags & H1C_F_ST_SHUTDOWN) && !b_data(&h1c->obuf)) ||
+	    (h1c->state == H1_CS_CLOSING && !b_data(&h1c->obuf)) ||
 	    !h1c->conn->owner) {
 		TRACE_DEVEL("killing dead connection", H1_EV_STRM_END, h1c->conn);
 		h1_release(h1c);
 	}
 	else {
-		if (h1c->flags & H1C_F_ST_IDLE) {
+		if (h1c->state == H1_CS_IDLE) {
 			/* If we have a new request, process it immediately or
 			 * subscribe for reads waiting for new data
 			 */
@@ -3473,9 +3496,8 @@
 		goto do_shutw;
 	}
 
-	if (!(h1c->flags & (H1C_F_ST_READY|H1C_F_ERROR))) {
-		/* Here attached is implicit because there is SC */
-		TRACE_STATE("keep connection alive (ALIVE but not READY nor ERROR)", H1_EV_STRM_SHUT, h1c->conn, h1s);
+	if (h1c->state == H1_CS_UPGRADING && !(h1c->flags & H1C_F_ERROR)) {
+		TRACE_STATE("keep connection alive (UPGRADING)", H1_EV_STRM_SHUT, h1c->conn, h1s);
 		goto end;
 	}
 	if (((h1s->flags & H1S_F_WANT_KAL) && h1s->req.state == H1_MSG_DONE && h1s->res.state == H1_MSG_DONE)) {
@@ -3484,7 +3506,7 @@
 	}
 
   do_shutw:
-	h1c->flags |= H1C_F_ST_SHUTDOWN;
+	h1c->state = H1_CS_CLOSING;
 	if (mode != CO_SHW_NORMAL)
 		h1c->flags |= H1C_F_SILENT_SHUT;
 
@@ -3498,10 +3520,11 @@
 {
 	struct h1c *h1c = conn->ctx;
 
+	TRACE_ENTER(H1_EV_H1C_END, conn);
+	h1_close(h1c);
 	if (conn->flags & CO_FL_SOCK_WR_SH)
 		return;
 
-	TRACE_ENTER(H1_EV_H1C_END, conn);
 	conn_xprt_shutw(conn);
 	conn_sock_shutw(conn, (h1c && !(h1c->flags & H1C_F_SILENT_SHUT)));
 	TRACE_LEAVE(H1_EV_H1C_END, conn);
@@ -3598,8 +3621,8 @@
 
 	TRACE_ENTER(H1_EV_STRM_RECV, h1c->conn, h1s, 0, (size_t[]){count});
 
-	/* Do nothing for now if not READY */
-	if (!(h1c->flags & H1C_F_ST_READY)) {
+	/* Do nothing for now if not RUNNING (implies UPGRADING) */
+	if (h1c->state < H1_CS_RUNNING) {
 		TRACE_DEVEL("h1c not ready yet", H1_EV_H1C_RECV|H1_EV_H1C_BLK, h1c->conn);
 		goto end;
 	}