[MEDIUM] add the SN_CURR_SESS flag to the session to track open sessions

It is quite hard to track when the current session has already been counted
or discounted from the server's total number of established sessions. For
this reason, we introduce a new session flag, SN_CURR_SESS, which indicates
if the current session is one of those reported by the server or not. It
simplifies session accounting and makes it far more robust. It also makes
it possible to perform a last-minute cleanup during session_free().

Right now, with this fix and a few more buffer transitions fixes, no session
were found to remain after a test.
diff --git a/include/types/session.h b/include/types/session.h
index 8b87be2..1177924 100644
--- a/include/types/session.h
+++ b/include/types/session.h
@@ -47,7 +47,7 @@
 #define SN_BE_ASSIGNED	0x00000008	/* a backend was assigned. Conns are accounted. */
 #define SN_CONN_CLOSED	0x00000010	/* "Connection: close" was present or added */
 #define SN_MONITOR	0x00000020	/* this session comes from a monitoring system */
-/* unused:              0x00000040 */
+#define SN_CURR_SESS	0x00000040	/* a connection is currently being counted on the server */
 #define SN_FRT_ADDR_SET	0x00000080	/* set if the frontend address has been filled */
 #define SN_REDISP	0x00000100	/* set if this session was redispatched from one server to another */
 #define SN_CONN_TAR	0x00000200	/* set if this session is turning around before reconnecting */
diff --git a/src/backend.c b/src/backend.c
index de0537a..0d6fef1 100644
--- a/src/backend.c
+++ b/src/backend.c
@@ -1820,6 +1820,7 @@
 
 	s->req->cons->state = SI_ST_CON;
 	if (s->srv) {
+		s->flags |= SN_CURR_SESS;
 		s->srv->cur_sess++;
 		if (s->srv->cur_sess > s->srv->cur_sess_max)
 			s->srv->cur_sess_max = s->srv->cur_sess;
diff --git a/src/proto_http.c b/src/proto_http.c
index 6f2dab3..fe59841 100644
--- a/src/proto_http.c
+++ b/src/proto_http.c
@@ -963,8 +963,8 @@
 	 * upper by setting flags into the buffers. Note that the side towards
 	 * the client cannot have connect (hence retryable) errors.
 	 */
-	if (unlikely(s->si[0].state == SI_ST_EST)) {
-		if (s->si[0].flags & SI_FL_ERR) {
+	if (s->si[0].state == SI_ST_EST) {
+		if (unlikely(s->si[0].flags & SI_FL_ERR)) {
 			s->si[0].state = SI_ST_CLO;
 			fd_delete(s->si[0].fd);
 			stream_int_report_error(&s->si[0]);
@@ -972,10 +972,13 @@
 	}
 
 	if (s->si[1].state == SI_ST_EST) {
-		if (s->si[1].flags & SI_FL_ERR) {
+		if (unlikely(s->si[1].flags & SI_FL_ERR)) {
 			s->si[1].state = SI_ST_CLO;
 			fd_delete(s->si[1].fd);
 			stream_int_report_error(&s->si[1]);
+			s->be->failed_resp++;
+			if (s->srv)
+				s->srv->failed_resp++;
 		}
 	}
 	else if (s->si[1].state != SI_ST_INI && s->si[1].state != SI_ST_CLO) {
@@ -1044,35 +1047,33 @@
 		s->req->cons->shutw(s->req->cons);
 	}
 
+	if (unlikely((s->req->flags & (BF_SHUTW|BF_SHUTR|BF_SHUTR_NOW)) == BF_SHUTW)) {
+		/* write closed on server side, let's forward it to the client */
+		buffer_shutr_now(s->req);
+		s->req->prod->shutr(s->req->prod);
+	}
+
 	if (unlikely((s->rep->flags & (BF_SHUTW|BF_EMPTY|BF_HIJACK|BF_WRITE_ENA|BF_SHUTR)) == (BF_EMPTY|BF_WRITE_ENA|BF_SHUTR)) ||
 	    unlikely((s->rep->flags & (BF_SHUTW|BF_SHUTW_NOW)) == BF_SHUTW_NOW)) {
 		buffer_shutw(s->rep);
 		s->rep->cons->shutw(s->rep->cons);
 	}
 
+	if (unlikely((s->rep->flags & (BF_SHUTW|BF_SHUTR|BF_SHUTR_NOW)) == BF_SHUTW)) {
+		/* write closed on client side, let's forward it to the server */
+		buffer_shutr_now(s->rep);
+		s->rep->prod->shutr(s->rep->prod);
+	}
+
 	/* 3: When a server-side connection is released, we have to
 	 * count it and check for pending connections on this server.
-	 * FIXME: the test below is not accurate. An audit is needed
-	 * to find all uncaught transitions. We need a way to ensure
-	 * that shutdowns called right after connect() after TAR will
-	 * correctly be caught for instance. In fact we need a way to
-	 * track when the connection is assigned to the server.
 	 */
-	if (unlikely(s->req->cons->state == SI_ST_CLO &&
-		     (s->req->cons->prev_state == SI_ST_EST || s->req->cons->prev_state == SI_ST_CON))) {
-		/* Count server-side errors (but not timeouts). */
-		if (s->req->flags & BF_WRITE_ERROR) {
-			s->be->failed_resp++;
-			if (s->srv)
-				s->srv->failed_resp++;
-		}
-
-		if (s->srv) {
-			s->srv->cur_sess--;
-			sess_change_server(s, NULL);
-			if (may_dequeue_tasks(s->srv, s->be))
-				process_srv_queue(s->srv);
-		}
+	if (unlikely(s->req->cons->state == SI_ST_CLO && s->srv && (s->flags & SN_CURR_SESS))) {
+		s->flags &= ~SN_CURR_SESS;
+		s->srv->cur_sess--;
+		sess_change_server(s, NULL);
+		if (may_dequeue_tasks(s->srv, s->be))
+			process_srv_queue(s->srv);
 	}
 
 	/* Dirty trick: force one first pass everywhere */
@@ -1153,7 +1154,8 @@
 					 * count it and check for pending connections on this server.
 					 */
 					if (s->req->cons->state == SI_ST_CLO) {
-						if (s->srv) {
+						if (s->srv && (s->flags & SN_CURR_SESS)) {
+							s->flags &= ~SN_CURR_SESS;
 							s->srv->cur_sess--;
 							sess_change_server(s, NULL);
 							if (may_dequeue_tasks(s->srv, s->be))
@@ -3734,7 +3736,8 @@
 		si->state = SI_ST_CER;
 		fd_delete(si->fd);
 
-		if (s->srv) {
+		if (s->srv && (s->flags & SN_CURR_SESS)) {
+			s->flags &= ~SN_CURR_SESS;
 			s->srv->cur_sess--;
 			sess_change_server(s, NULL);
 			si->err_loc = s->srv;
@@ -3759,7 +3762,8 @@
 		/* give up */
 		req->wex = TICK_ETERNITY;
 		fd_delete(si->fd);
-		if (s->srv) {
+		if (s->srv && (s->flags & SN_CURR_SESS)) {
+			s->flags &= ~SN_CURR_SESS;
 			s->srv->cur_sess--;
 			sess_change_server(s, NULL);
 		}
diff --git a/src/session.c b/src/session.c
index ceef0b7..fb7de4a 100644
--- a/src/session.c
+++ b/src/session.c
@@ -37,8 +37,13 @@
 
 	if (s->pend_pos)
 		pendconn_free(s->pend_pos);
-	if (s->srv)  /* there may be requests left pending in queue */
+	if (s->srv) { /* there may be requests left pending in queue */
+		if (s->flags & SN_CURR_SESS) {
+			s->flags &= ~SN_CURR_SESS;
+			s->srv->cur_sess--;
+		}
 		process_srv_queue(s->srv);
+	}
 	if (unlikely(s->srv_conn)) {
 		/* the session still has a reserved slot on a server, but
 		 * it should normally be only the same as the one above,