BUG/MAJOR: sessions: Use an unlimited number of servers for the conn list.

When a session adds a connection to its connection list, we used to remove
connections for an another server if there were not enough room for our
server. This can't work, because those lists are now the list of connections
we're responsible for, not just the idle connections.
To fix this, allow for an unlimited number of servers, instead of using
an array, we're now using a linked list.
diff --git a/include/proto/connection.h b/include/proto/connection.h
index f674658..28e7580 100644
--- a/include/proto/connection.h
+++ b/include/proto/connection.h
@@ -29,6 +29,7 @@
 #include <types/listener.h>
 #include <proto/fd.h>
 #include <proto/obj_type.h>
+#include <proto/session.h>
 #include <proto/task.h>
 
 extern struct pool_head *pool_head_connection;
@@ -676,7 +677,7 @@
 	if (!LIST_ISEMPTY(&conn->session_list)) {
 		struct session *sess = conn->owner;
 		sess->resp_conns--;
-		LIST_DEL(&conn->session_list);
+		session_unown_conn(sess, conn);
 	}
 
 	/* By convention we always place a NULL where the ctx points to if the
diff --git a/include/proto/session.h b/include/proto/session.h
index 8c99e7d..c34866d 100644
--- a/include/proto/session.h
+++ b/include/proto/session.h
@@ -35,6 +35,8 @@
 #include <proto/server.h>
 
 extern struct pool_head *pool_head_session;
+extern struct pool_head *pool_head_sess_srv_list;
+
 struct session *session_new(struct proxy *fe, struct listener *li, enum obj_type *origin);
 void session_free(struct session *sess);
 int session_accept_fd(struct listener *l, int cfd, struct sockaddr_storage *addr);
@@ -73,46 +75,46 @@
 	}
 }
 
-static inline void session_add_conn(struct session *sess, struct connection *conn, void *target)
+/* Remove the connection from the session list, and destroy the srv_list if it's now empty */
+static inline void session_unown_conn(struct session *sess, struct connection *conn)
 {
-	int avail = -1;
-	int i;
-
-	sess->resp_conns++;
-	for (i = 0; i < MAX_SRV_LIST; i++) {
-		if (sess->srv_list[i].target == target) {
-			avail = i;
+	struct sess_srv_list *srv_list = NULL;
+	LIST_DEL(&conn->session_list);
+	LIST_INIT(&conn->session_list);
+	list_for_each_entry(srv_list, &sess->srv_list, srv_list) {
+		if (srv_list->target == conn->target) {
+			if (LIST_ISEMPTY(&srv_list->conn_list)) {
+				LIST_DEL(&srv_list->srv_list);
+				pool_free(pool_head_sess_srv_list, srv_list);
+			}
 			break;
 		}
-		if (LIST_ISEMPTY(&sess->srv_list[i].list) && avail == -1)
-			avail = i;
 	}
-	if (avail == -1) {
-		struct connection *conn, *conn_back;
-		int count = 0;
-		/* We have no slot free, let's free the one with the fewer connections */
-		for (i = 0; i < MAX_SRV_LIST; i++) {
-			int count_list = 0;
-			list_for_each_entry(conn, &sess->srv_list[i].list, session_list)
-			    count_list++;
-			if (count == 0 || count_list < count) {
-				count = count_list;
-				avail = i;
-			}
-		}
-		/* Now unown all the connections */
-		list_for_each_entry_safe(conn, conn_back, &sess->srv_list[avail].list, session_list) {
-			sess->resp_conns--;
-			conn->owner = NULL;
-			LIST_DEL(&conn->session_list);
-			LIST_INIT(&conn->session_list);
-			if (conn->mux)
-				conn->mux->destroy(conn);
-		}
+}
+
+static inline int session_add_conn(struct session *sess, struct connection *conn, void *target)
+{
+	struct sess_srv_list *srv_list = NULL;
+	int found = 0;
 
+	list_for_each_entry(srv_list, &sess->srv_list, srv_list) {
+		if (srv_list->target == target) {
+			found = 1;
+			break;
+		}
+	}
+	if (!found) {
+		/* The session has no connection for the server, create a new entry */
+		srv_list = pool_alloc(pool_head_sess_srv_list);
+		if (!srv_list)
+			return 0;
+		srv_list->target = target;
+		LIST_INIT(&srv_list->conn_list);
+		LIST_ADDQ(&sess->srv_list, &srv_list->srv_list);
 	}
-	sess->srv_list[avail].target = target;
-	LIST_ADDQ(&sess->srv_list[avail].list, &conn->session_list);
+	sess->resp_conns++;
+	LIST_ADDQ(&srv_list->conn_list, &conn->session_list);
+	return 1;
 }
 
 /* Returns 0 if the session can keep the idle conn, -1 if it was destroyed, or 1 if it was added to the server list */
@@ -120,8 +122,7 @@
 {
 	if (sess->resp_conns > sess->fe->max_out_conns) {
 		/* We can't keep the connection, let's try to add it to the server idle list */
-		LIST_DEL(&conn->session_list);
-		LIST_INIT(&conn->session_list);
+		session_unown_conn(sess, conn);
 		conn->owner = NULL;
 		sess->resp_conns--;
 		if (!srv_add_to_idle_list(objt_server(conn->target), conn)) {
diff --git a/include/types/session.h b/include/types/session.h
index 33ce4fc..1675f8f 100644
--- a/include/types/session.h
+++ b/include/types/session.h
@@ -39,7 +39,8 @@
 
 struct sess_srv_list {
 	void *target;
-	struct list list;
+	struct list conn_list; /* Head of the connections list */
+	struct list srv_list; /* Next element of the server list */
 };
 
 #define MAX_SRV_LIST	5
@@ -55,7 +56,7 @@
 	struct task *task;              /* handshake timeout processing */
 	long t_handshake;               /* handshake duration, -1 = not completed */
 	int resp_conns;                 /* Number of connections we're currently responsible for */
-	struct sess_srv_list srv_list[MAX_SRV_LIST]; /* List of servers and the connections the session is currently responsible for */
+	struct list srv_list;           /* List of servers and the connections the session is currently responsible for */
 };
 
 #endif /* _TYPES_SESSION_H */