MINOR: http: implement the max-keep-alive-queue setting

Finn Arne Gangstad suggested that we should have the ability to break
keep-alive when the target server has reached its maxconn and that a
number of connections are present in the queue. After some discussion
around his proposed patch, the following solution was suggested : have
a per-proxy setting to fix a limit to the number of queued connections
on a server after which we break keep-alive. This ensures that even in
high latency networks where keep-alive is beneficial, we try to find a
different server.

This patch is partially based on his original proposal and implements
this configurable threshold.
diff --git a/doc/configuration.txt b/doc/configuration.txt
index a1f58fb..5a185c3 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -1275,6 +1275,7 @@
 id                                        -          X         X         X
 ignore-persist                            -          X         X         X
 log                                  (*)  X          X         X         X
+max-keep-alive-queue                      X          -         X         X
 maxconn                                   X          X         X         -
 mode                                      X          X         X         X
 monitor fail                              -          X         X         -
@@ -3379,6 +3380,34 @@
    See also : Custom Log Format (8.2.4)
 
 
+max-keep-alive-queue <value>
+  Set the maximum server queue size for maintaining keep-alive connections
+  May be used in sections:    defaults | frontend | listen | backend
+                                 yes   |     no   |   yes  |   yes
+
+  HTTP keep-alive tries to reuse the same server connection whenever possible,
+  but sometimes it can be counter-productive, for example if a server has a lot
+  of connections while other ones are idle. This is especially true for static
+  servers.
+
+  The purpose of this setting is to set a threshold on the number of queued
+  connections at which haproxy stops trying to reuse the same server and prefers
+  to find another one. The default value, -1, means there is no limit. A value
+  of zero means that keep-alive requests will never be queued. For very close
+  servers which can be reached with a low latency and which are not sensible to
+  breaking keep-alive, a low value is recommended (eg: local static server can
+  use a value of 10 or less). For remote servers suffering from a high latency,
+  higher values might be needed to cover for the latency and/or the cost of
+  picking a different server.
+
+  Note that this has no impact on responses which are maintained to the same
+  server consecutively to a 401 response. They will still go to the same server
+  even if they have to be queued.
+
+  See also : "option http-server-close", "option prefer-last-server", server
+             "maxconn" and cookie persistence.
+
+
 maxconn <conns>
   Fix the maximum number of concurrent connections on a frontend
   May be used in sections :   defaults | frontend | listen | backend
diff --git a/include/proto/queue.h b/include/proto/queue.h
index 7bf8137..fb73926 100644
--- a/include/proto/queue.h
+++ b/include/proto/queue.h
@@ -65,6 +65,11 @@
 	return LIST_ELEM(px->pendconns.n, struct pendconn *, list);
 }
 
+/* Returns 0 if all slots are full on a server, or 1 if there are slots available. */
+static inline int server_has_room(const struct server *s) {
+	return !s->maxconn || s->cur_sess < srv_dynamic_maxconn(s);
+}
+
 /* returns 0 if nothing has to be done for server <s> regarding queued connections,
  * and non-zero otherwise. If the server is down, we only check its own queue. Suited
  * for and if/else usage.
diff --git a/include/types/proxy.h b/include/types/proxy.h
index 28be984..4c0c660 100644
--- a/include/types/proxy.h
+++ b/include/types/proxy.h
@@ -289,6 +289,7 @@
 	struct list pendconns;			/* pending connections with no server assigned yet */
 	int nbpend;				/* number of pending connections with no server assigned yet */
 	int totpend;				/* total number of pending connections on this instance (for stats) */
+	int max_ka_queue;			/* 1+maximum requests in queue accepted for reusing a K-A conn (0=none) */
 	unsigned int feconn, beconn;		/* # of active frontend and backends sessions */
 	struct freq_ctr fe_req_per_sec;		/* HTTP requests per second on the frontend */
 	struct freq_ctr fe_conn_per_sec;	/* received connections per second on the frontend */
diff --git a/src/backend.c b/src/backend.c
index 4bb2ea3..bc63903 100644
--- a/src/backend.c
+++ b/src/backend.c
@@ -541,8 +541,12 @@
 
 	if (conn &&
 	    (conn->flags & CO_FL_CONNECTED) &&
-	    ((s->be->options & PR_O_PREF_LAST) || (s->txn.flags & TX_PREFER_LAST)) &&
 	    objt_server(conn->target) && __objt_server(conn->target)->proxy == s->be &&
+	    ((s->txn.flags & TX_PREFER_LAST) ||
+	     ((s->be->options & PR_O_PREF_LAST) &&
+	      (!s->be->max_ka_queue ||
+	       server_has_room(__objt_server(conn->target)) ||
+	       (__objt_server(conn->target)->nbpend + 1) < s->be->max_ka_queue))) &&
 	    srv_is_usable(__objt_server(conn->target)->state, __objt_server(conn->target)->eweight)) {
 		/* This session was relying on a server in a previous request
 		 * and the proxy has "option prefer-current-server" set, so
diff --git a/src/cfgparse.c b/src/cfgparse.c
index ec8f3ae..7a869e3 100644
--- a/src/cfgparse.c
+++ b/src/cfgparse.c
@@ -1930,6 +1930,7 @@
 		if (curproxy->cap & PR_CAP_BE) {
 			curproxy->fullconn = defproxy.fullconn;
 			curproxy->conn_retries = defproxy.conn_retries;
+			curproxy->max_ka_queue = defproxy.max_ka_queue;
 
 			if (defproxy.check_req) {
 				curproxy->check_req = calloc(1, defproxy.check_len);
diff --git a/src/proxy.c b/src/proxy.c
index 2f2eb49..fb1a3b4 100644
--- a/src/proxy.c
+++ b/src/proxy.c
@@ -275,6 +275,45 @@
 	return retval;
 }
 
+/* This function parses a "max-keep-alive-queue" statement in a proxy section.
+ * It returns -1 if there is any error, 1 for a warning, otherwise zero. If it
+ * does not return zero, it will write an error or warning message into a
+ * preallocated buffer returned at <err>. The function must be called with
+ * <args> pointing to the first command line word, with <proxy> pointing to
+ * the proxy being parsed, and <defpx> to the default proxy or NULL.
+ */
+static int proxy_parse_max_ka_queue(char **args, int section, struct proxy *proxy,
+                                    struct proxy *defpx, const char *file, int line,
+                                    char **err)
+{
+	int retval;
+	char *res;
+	unsigned int val;
+
+	retval = 0;
+
+	if (*args[1] == 0) {
+		memprintf(err, "'%s' expects expects an integer value (or -1 to disable)", args[0]);
+		return -1;
+	}
+
+	val = strtol(args[1], &res, 0);
+	if (*res) {
+		memprintf(err, "'%s' : unexpected character '%c' in integer value '%s'", args[0], *res, args[1]);
+		return -1;
+	}
+
+	if (!(proxy->cap & PR_CAP_BE)) {
+		memprintf(err, "%s will be ignored because %s '%s' has no backend capability",
+		          args[0], proxy_type_str(proxy), proxy->id);
+		retval = 1;
+	}
+
+	/* we store <val+1> so that a user-facing value of -1 is stored as zero (default) */
+	proxy->max_ka_queue = val + 1;
+	return retval;
+}
+
 /* This function inserts proxy <px> into the tree of known proxies. The proxy's
  * name is used as the storing key so it must already have been initialized.
  */
@@ -926,6 +965,7 @@
 	{ CFG_LISTEN, "contimeout", proxy_parse_timeout },
 	{ CFG_LISTEN, "srvtimeout", proxy_parse_timeout },
 	{ CFG_LISTEN, "rate-limit", proxy_parse_rate_limit },
+	{ CFG_LISTEN, "max-keep-alive-queue", proxy_parse_max_ka_queue },
 	{ 0, NULL, NULL },
 }};