[MEDIUM] add support for "maxqueue" to limit server queue overload

This patch adds the "maxqueue" parameter to the server. This allows new
sessions to be immediately rebalanced when the server's queue is filled.
It's useful when session stickiness is just a performance boost (even a
huge one) but not a requirement.

This should only be used if session affinity isn't a hard functional
requirement but provides performance boost by keeping server-local
caches hot and compact).

Absence of 'maxqueue' option means unlimited queue. When queue gets filled
up to 'maxqueue' client session is moved from server-local queue to a global
one.
diff --git a/doc/haproxy-en.txt b/doc/haproxy-en.txt
index 09d311e..3e8ce10 100644
--- a/doc/haproxy-en.txt
+++ b/doc/haproxy-en.txt
@@ -1364,6 +1364,21 @@
 will receive only 10 simultaneous sessions when the proxy will be under 1000
 sessions.
 
+It is possible to limit server queue length in order to rebalance excess
+sessions between less busy application servers IF session affinity isn't
+hard functional requirement (for example it just gives huge performance boost
+by keeping server-local caches hot and compact). 'maxqueue' option sets a
+queue limit on a server, as in example below:
+
+... (just the same as in example above)
+        server pentium3-800 192.168.1.1:80 cookie s1 weight  8 minconn 10 maxconn 100 check maxqueue 50
+        server opteron-2.0G 192.168.1.2:80 cookie s2 weight 20 minconn 30 maxconn 300 check maxqueue 200
+        server opteron-2.4G 192.168.1.3:80 cookie s3 weight 24 minconn 30 maxconn 300 check
+
+Absence of 'maxqueue' option means unlimited queue. When queue gets filled
+up to 'maxqueue' client session is moved from server-local queue to a global
+one.
+
 Notes :
 -------
   - The requests will not stay indefinitely in the queue, they follow the
diff --git a/doc/haproxy-fr.txt b/doc/haproxy-fr.txt
index d29259f..7974546 100644
--- a/doc/haproxy-fr.txt
+++ b/doc/haproxy-fr.txt
@@ -1391,6 +1391,23 @@
 recevra seulement 10 connexions simultanées tant que le proxy sera sous les 1000
 sessions.
 
+Il est possible de limiter la taille de la file d'attente dans le but de
+redistribuer les connexions destinées à un serveur en particulier qui sont trop
+loin pour avoir une chance d'être servies en un temps raisonnable. Ceci n'est
+acceptable que dans le cas où l'affinité entre le client et le serveur n'est
+pas obligatoire, mais motivée uniquement par des raisons de performances, par
+exemple, par l'utilisation d'un cache local au serveur. L'option 'maxqueue'
+permet de préciser la limite par serveur, tel que dans l'exemple ci-dessous :
+
+... (même exemple que précédemment)
+        server pentium3-800 192.168.1.1:80 cookie s1 weight  8 minconn 10 maxconn 100 check maxqueue 50
+        server opteron-2.0G 192.168.1.2:80 cookie s2 weight 20 minconn 30 maxconn 300 check maxqueue 200
+        server opteron-2.4G 192.168.1.3:80 cookie s3 weight 24 minconn 30 maxconn 300 check
+
+En l'absence du paramètre 'maxqueue', la file d'un serveur n'a pas de limite
+définie. Dans le cas contraire, lorsque la file atteint la limite fixée par
+'maxqueue', les clients sont déplacés vers la file globale.
+
 Notes :
 -------
   - la requête ne restera pas indéfiniment en file d'attente, elle est
diff --git a/include/types/server.h b/include/types/server.h
index 62179f3..6e3a913 100644
--- a/include/types/server.h
+++ b/include/types/server.h
@@ -64,6 +64,7 @@
 	int cur_sess, cur_sess_max;		/* number of currently active sessions (including syn_sent) */
 	unsigned maxconn, minconn;		/* max # of active sessions (0 = unlimited), min# for dynamic limit. */
 	int nbpend, nbpend_max;			/* number of pending connections */
+	int maxqueue;				/* maximum number of pending connections allowed */
 	struct list pendconns;			/* pending connections */
 	struct task *queue_mgt;			/* the task associated to the queue processing */
 
diff --git a/src/backend.c b/src/backend.c
index 596d567..faf5db8 100644
--- a/src/backend.c
+++ b/src/backend.c
@@ -294,19 +294,25 @@
 		return SRV_STATUS_INTERNAL;
 
 	if (s->flags & SN_ASSIGNED) {
-		/* a server does not need to be assigned, perhaps because we're in
-		 * direct mode, or in dispatch or transparent modes where the server
-		 * is not needed.
-		 */
-		if (s->srv &&
-		    s->srv->maxconn && s->srv->cur_sess >= srv_dynamic_maxconn(s->srv)) {
-			p = pendconn_add(s);
-			if (p)
-				return SRV_STATUS_QUEUED;
-			else
-				return SRV_STATUS_FULL;
+		if (s->srv && s->srv->maxqueue > 0 && s->srv->nbpend >= s->srv->maxqueue) {
+			s->flags &= ~(SN_DIRECT | SN_ASSIGNED | SN_ADDR_SET);
+			s->srv = NULL;
+			http_flush_cookie_flags(&s->txn);
+		} else {
+			/* a server does not need to be assigned, perhaps because we're in
+			 * direct mode, or in dispatch or transparent modes where the server
+			 * is not needed.
+			 */
+			if (s->srv &&
+			    s->srv->maxconn && s->srv->cur_sess >= srv_dynamic_maxconn(s->srv)) {
+				p = pendconn_add(s);
+				if (p)
+					return SRV_STATUS_QUEUED;
+				else
+					return SRV_STATUS_FULL;
+			}
+			return SRV_STATUS_OK;
 		}
-		return SRV_STATUS_OK;
 	}
 
 	/* a server needs to be assigned */
diff --git a/src/cfgparse.c b/src/cfgparse.c
index 7b73e0b..89c8c19 100644
--- a/src/cfgparse.c
+++ b/src/cfgparse.c
@@ -1413,6 +1413,7 @@
 		newsrv->fall = DEF_FALLTIME;
 		newsrv->health = newsrv->rise; /* up, but will fall down at first failure */
 		newsrv->uweight = 1;
+		newsrv->maxqueue = 0;
 
 		cur_arg = 3;
 		while (*args[cur_arg]) {
@@ -1465,6 +1466,10 @@
 				newsrv->maxconn = atol(args[cur_arg + 1]);
 				cur_arg += 2;
 			}
+			else if (!strcmp(args[cur_arg], "maxqueue")) {
+				newsrv->maxqueue = atol(args[cur_arg + 1]);
+				cur_arg += 2;
+			}
 			else if (!strcmp(args[cur_arg], "check")) {
 				global.maxsock++;
 				do_check = 1;
@@ -1521,7 +1526,7 @@
 			}
 #endif
 			else {
-				Alert("parsing [%s:%d] : server %s only supports options 'backup', 'cookie', 'check', 'inter', 'rise', 'fall', 'addr', 'port', 'source', 'minconn', 'maxconn' and 'weight'.\n",
+				Alert("parsing [%s:%d] : server %s only supports options 'backup', 'cookie', 'check', 'inter', 'rise', 'fall', 'addr', 'port', 'source', 'minconn', 'maxconn', 'maxqueue', and 'weight'.\n",
 				      file, linenum, newsrv->id);
 				return -1;
 			}
diff --git a/src/dumpstats.c b/src/dumpstats.c
index 7ec4b56..4d34c47 100644
--- a/src/dumpstats.c
+++ b/src/dumpstats.c
@@ -372,7 +372,7 @@
 			     "ereq,econ,eresp,"
 			     "wretr,wredis,"
 			     "status,weight,act,bck,"
-			     "chkfail,chkdown,lastchg,downtime,"
+			     "chkfail,chkdown,lastchg,downtime,qlimit,"
 			     "\n");
 		}
 		if (buffer_write_chunk(rep, &msg) != 0)
@@ -582,20 +582,20 @@
 		if (flags & STAT_FMT_HTML) {
 			/* print a new table */
 			chunk_printf(&msg, sizeof(trash),
-				     "<table cols=\"23\" class=\"tbl\" width=\"100%%\">\n"
+				     "<table cols=\"24\" class=\"tbl\" width=\"100%%\">\n"
 				     "<tr align=\"center\" class=\"titre\">"
 				     "<th colspan=2 class=\"pxname\">%s</th>"
 				     "<th colspan=21 class=\"empty\"></th>"
 				     "</tr>\n"
 				     "<tr align=\"center\" class=\"titre\">"
 				     "<th rowspan=2></th>"
-				     "<th colspan=2>Queue</th><th colspan=4>Sessions</th>"
+				     "<th colspan=3>Queue</th><th colspan=4>Sessions</th>"
 				     "<th colspan=2>Bytes</th><th colspan=2>Denied</th>"
 				     "<th colspan=3>Errors</th><th colspan=2>Warnings</th>"
 				     "<th colspan=7>Server</th>"
 				     "</tr>\n"
 				     "<tr align=\"center\" class=\"titre\">"
-				     "<th>Cur</th><th>Max</th><th>Cur</th><th>Max</th>"
+				     "<th>Cur</th><th>Max</th><th>Limit</th><th>Cur</th><th>Max</th>"
 				     "<th>Limit</th><th>Cumul</th><th>In</th><th>Out</th>"
 				     "<th>Req</th><th>Resp</th><th>Req</th><th>Conn</th>"
 				     "<th>Resp</th><th>Retr</th><th>Redis</th>"
@@ -617,7 +617,7 @@
 			if (flags & STAT_FMT_HTML) {
 				chunk_printf(&msg, sizeof(trash),
 				     /* name, queue */
-				     "<tr align=center class=\"frontend\"><td>Frontend</td><td colspan=2></td>"
+				     "<tr align=center class=\"frontend\"><td>Frontend</td><td colspan=3></td>"
 				     /* sessions : current, max, limit, cumul */
 				     "<td align=right>%d</td><td align=right>%d</td>"
 				     "<td align=right>%d</td><td align=right>%d</td>"
@@ -657,7 +657,7 @@
 				     /* server status : reflect frontend status */
 				     "%s,"
 				     /* rest of server: nothing */
-				     ",,,,,,,"
+				     ",,,,,,,,"
 				     "\n",
 				     px->id,
 				     px->feconn, px->feconn_max, px->maxconn, px->cum_feconn,
@@ -709,8 +709,8 @@
 				chunk_printf(&msg, sizeof(trash),
 				     /* name */
 				     "<tr align=\"center\" class=\"%s%d\"><td>%s</td>"
-				     /* queue : current, max */
-				     "<td align=right>%d</td><td align=right>%d</td>"
+				     /* queue : current, max, limit */
+				     "<td align=right>%d</td><td align=right>%d</td><td align=right>%s</td>"
 				     /* sessions : current, max, limit, cumul */
 				     "<td align=right>%d</td><td align=right>%d</td>"
 				     "<td align=right>%s</td><td align=right>%d</td>"
@@ -725,8 +725,8 @@
 				     "",
 				     (sv->state & SRV_BACKUP) ? "backup" : "active",
 				     sv_state, sv->id,
-				     sv->nbpend, sv->nbpend_max,
-				     sv->cur_sess, sv->cur_sess_max, sv->maxconn ? ultoa(sv->maxconn) : "-", sv->cum_sess,
+				     sv->nbpend, sv->nbpend_max, LIM2A0(sv->maxqueue, "-"),
+				     sv->cur_sess, sv->cur_sess_max, LIM2A1(sv->maxconn, "-"), sv->cum_sess,
 				     sv->bytes_in, sv->bytes_out,
 				     sv->failed_secu,
 				     sv->failed_conns, sv->failed_resp,
@@ -809,12 +809,17 @@
 				/* check failures: unique, fatal; last change, total downtime */
 				if (sv->state & SRV_CHECKED)
 					chunk_printf(&msg, sizeof(trash),
-					     "%d,%d,%d,%d,\n",
+					     "%d,%d,%d,%d,",
 					     sv->failed_checks, sv->down_trans,
 					     now.tv_sec - sv->last_change, srv_downtime(sv));
 				else
 					chunk_printf(&msg, sizeof(trash),
-					     ",,,,\n");
+					     ",,,,");
+
+				/* queue limit and EOL */
+				chunk_printf(&msg, sizeof(trash),
+				     "%s,\n",
+				     LIM2A0(sv->maxqueue, ""));
 			}
 			if (buffer_write_chunk(rep, &msg) != 0)
 				return 0;
@@ -845,7 +850,7 @@
 				     /* name */
 				     "<tr align=center class=\"backend\"><td>Backend</td>"
 				     /* queue : current, max */
-				     "<td align=right>%d</td><td align=right>%d</td>"
+				     "<td align=right>%d</td><td align=right>%d</td><td></td>"
 				     /* sessions : current, max, limit, cumul. */
 				     "<td align=right>%d</td><td align=right>%d</td><td align=right>%d</td><td align=right>%d</td>"
 				     /* bytes : in, out */
@@ -903,7 +908,7 @@
 				     "%d,%d,%d,"
 				     /* rest of backend: nothing, down transformations,
 				      * last change, total downtime. */
-				     ",%d,%d,%d,"
+				     ",%d,%d,%d,,"
 				     "\n",
 				     px->id,
 				     px->nbpend /* or px->totpend ? */, px->nbpend_max,