MEDIUM: servers: Add a way to keep idle connections alive.

Add a new keyword for servers, "idle-timeout". If set, unused connections are
kept alive until the timeout happens, and will be picked for reuse if no
other connection is available.
diff --git a/src/backend.c b/src/backend.c
index 404c5b3..e62a3b8 100644
--- a/src/backend.c
+++ b/src/backend.c
@@ -1118,6 +1118,7 @@
 	struct conn_stream *srv_cs = NULL;
 	struct server *srv;
 	int reuse = 0;
+	int reuse_orphan = 0;
 	int err;
 	int i;
 
@@ -1189,6 +1190,13 @@
 		else if (srv->idle_conns && !LIST_ISEMPTY(&srv->idle_conns[tid]) &&
 			 (s->be->options & PR_O_REUSE_MASK) == PR_O_REUSE_ALWS) {
 			srv_conn = LIST_ELEM(srv->idle_conns[tid].n, struct connection *, list);
+		} else if (srv->idle_orphan_conns && !LIST_ISEMPTY(&srv->idle_orphan_conns[tid]) &&
+		    (((s->be->options & PR_O_REUSE_MASK) == PR_O_REUSE_ALWS) ||
+		    (((s->be->options & PR_O_REUSE_MASK) != PR_O_REUSE_NEVR) &&
+		     s->txn && (s->txn->flags & TX_NOT_FIRST)))) {
+			srv_conn = LIST_ELEM(srv->idle_orphan_conns[tid].n,
+			    struct connection *, list);
+			reuse_orphan = 1;
 		}
 
 		/* If we've picked a connection from the pool, we now have to
@@ -1216,6 +1224,15 @@
 				reuse = 0;
 		}
 	}
+	/* If we're really reusing the connection, remove it from the orphan
+	 * list and add it back to the idle list.
+	 */
+	if (reuse && reuse_orphan) {
+		LIST_DEL(&srv_conn->list);
+		LIST_ADDQ(&srv->idle_conns[tid], &srv_conn->list);
+		if (LIST_ISEMPTY(&srv->idle_orphan_conns[tid]))
+			task_unlink_wq(srv->idle_task[tid]);
+	}
 
 	/* We're about to use another connection, let the mux know we're
 	 * done with this one
diff --git a/src/haproxy.c b/src/haproxy.c
index 6567b43..08ad7e5 100644
--- a/src/haproxy.c
+++ b/src/haproxy.c
@@ -2418,6 +2418,14 @@
 			free(s->idle_conns);
 			free(s->priv_conns);
 			free(s->safe_conns);
+			free(s->idle_orphan_conns);
+			if (s->idle_task) {
+				int i;
+
+				for (i = 0; i < global.nbthread; i++)
+					task_free(s->idle_task[i]);
+				free(s->idle_task);
+			}
 
 			if (s->use_ssl || s->check.use_ssl) {
 				if (xprt_get(XPRT_SSL) && xprt_get(XPRT_SSL)->destroy_srv)
diff --git a/src/server.c b/src/server.c
index 53b9177..bae9a3b 100644
--- a/src/server.c
+++ b/src/server.c
@@ -50,6 +50,7 @@
 static void srv_update_state(struct server *srv, int version, char **params);
 static int srv_apply_lastaddr(struct server *srv, int *err_code);
 static int srv_set_fqdn(struct server *srv, const char *fqdn, int dns_locked);
+static struct task *cleanup_idle_connections(struct task *task, void *ctx, unsigned short state);
 
 /* List head of all known server keywords */
 static struct srv_kw_list srv_keywords = {
@@ -357,6 +358,28 @@
 	return 0;
 }
 
+static int srv_parse_idle_timeout(char **args, int *cur_arg, struct proxy *curproxy, struct server *newsrv, char **err)
+{
+	const char *res;
+	char *arg;
+	unsigned int time;
+
+	arg = args[*cur_arg + 1];
+	if (!*arg) {
+		memprintf(err, "'%s' expects <value> as argument.\n", args[*cur_arg]);
+		return ERR_ALERT | ERR_FATAL;
+	}
+	res = parse_time_err(arg, &time, TIME_UNIT_MS);
+	if (res) {
+		memprintf(err, "unexpected character '%c' in argument to <%s>.\n",
+		    *res, args[*cur_arg]);
+		return ERR_ALERT | ERR_FATAL;
+	}
+	newsrv->idle_timeout = time;
+
+	return 0;
+}
+
 /* parse the "id" server keyword */
 static int srv_parse_id(char **args, int *cur_arg, struct proxy *curproxy, struct server *newsrv, char **err)
 {
@@ -1197,6 +1220,7 @@
 	{ "disabled",            srv_parse_disabled,            0,  1 }, /* Start the server in 'disabled' state */
 	{ "enabled",             srv_parse_enabled,             0,  1 }, /* Start the server in 'enabled' state */
 	{ "id",                  srv_parse_id,                  1,  0 }, /* set id# of server */
+	{ "idle-timeout",        srv_parse_idle_timeout,        1,  1 }, /* Set the time before we destroy orphan idle connections, defaults to 0 */
 	{ "namespace",           srv_parse_namespace,           1,  1 }, /* Namespace the server socket belongs to (if supported) */
 	{ "no-agent-check",      srv_parse_no_agent_check,      0,  1 }, /* Do not enable any auxiliary agent check */
 	{ "no-backup",           srv_parse_no_backup,           0,  1 }, /* Flag as non-backup server */
@@ -1640,6 +1664,7 @@
 	srv->tcp_ut = src->tcp_ut;
 #endif
 	srv->mux_proto = src->mux_proto;
+	srv->idle_timeout = src->idle_timeout;
 
 	if (srv_tmpl)
 		srv->srvrq = src->srvrq;
@@ -1889,7 +1914,28 @@
 		px->srv_act++;
 	srv_lb_commit_status(srv);
 
+	if (!srv->tmpl_info.prefix && srv->idle_timeout != 0) {
+			int i;
+
+			srv->idle_orphan_conns = calloc(global.nbthread, sizeof(*srv->idle_orphan_conns));
+			if (!srv->idle_orphan_conns)
+				goto err;
+			srv->idle_task = calloc(global.nbthread, sizeof(*srv->idle_task));
+			if (!srv->idle_task)
+				goto err;
+			for (i = 0; i < global.nbthread; i++) {
+				LIST_INIT(&srv->idle_orphan_conns[i]);
+				srv->idle_task[i] = task_new(1 << i);
+				if (!srv->idle_task[i])
+					goto err;
+				srv->idle_task[i]->process = cleanup_idle_connections;
+				srv->idle_task[i]->context = srv;
+			}
+		}
+
 	return 0;
+err:
+	return ERR_ALERT | ERR_FATAL;
 }
 
 /*
@@ -1973,6 +2019,24 @@
 		/* Linked backwards first. This will be restablished after parsing. */
 		newsrv->next = px->srv;
 		px->srv = newsrv;
+		if (newsrv->idle_timeout != 0) {
+			int i;
+
+			newsrv->idle_orphan_conns = calloc(global.nbthread, sizeof(*newsrv->idle_orphan_conns));
+			if (!newsrv->idle_orphan_conns)
+				goto err;
+			newsrv->idle_task = calloc(global.nbthread, sizeof(*newsrv->idle_task));
+			if (!newsrv->idle_task)
+				goto err;
+			for (i = 0; i < global.nbthread; i++) {
+				LIST_INIT(&newsrv->idle_orphan_conns[i]);
+				newsrv->idle_task[i] = task_new(1 << i);
+				if (!newsrv->idle_task[i])
+					goto err;
+				newsrv->idle_task[i]->process = cleanup_idle_connections;
+				newsrv->idle_task[i]->context = newsrv;
+			}
+		}
 	}
 	srv_set_id_from_prefix(srv, srv->tmpl_info.prefix, srv->tmpl_info.nb_low);
 
@@ -5230,6 +5294,23 @@
 	*s->adm_st_chg_cause = 0;
 }
 
+static struct task *cleanup_idle_connections(struct task *task, void *context, unsigned short state)
+{
+	struct server *srv = context;
+	struct connection *conn, *conn_back;
+	unsigned int next_wakeup = 0;
+
+	list_for_each_entry_safe(conn, conn_back, &srv->idle_orphan_conns[tid], list) {
+		if (conn->idle_time + srv->idle_timeout > now_ms) {
+			next_wakeup = conn->idle_time + srv->idle_timeout;
+			break;
+		}
+		conn->mux->destroy(conn);
+	}
+	if (next_wakeup > 0)
+		task_schedule(task, next_wakeup);
+	return task;
+}
 /*
  * Local variables:
  *  c-indent-level: 8
diff --git a/src/session.c b/src/session.c
index 8080c94..9c52efc 100644
--- a/src/session.c
+++ b/src/session.c
@@ -86,10 +86,28 @@
 		list_for_each_entry_safe(conn, conn_back, &sess->srv_list[i].list, session_list) {
 			count++;
 			if (conn->mux) {
+				struct server *srv;
+
 				LIST_DEL(&conn->session_list);
 				LIST_INIT(&conn->session_list);
+				srv = objt_server(conn->target);
 				conn->owner = NULL;
-				conn->mux->destroy(conn);
+				if (srv && srv->idle_timeout > 0 &&
+				    !(conn->flags & CO_FL_PRIVATE) &&
+				    conn->mux->avail_streams(conn) ==
+				    conn->mux->max_streams(conn)) {
+					LIST_DEL(&conn->list);
+
+					LIST_ADDQ(&srv->idle_orphan_conns[tid],
+					    &conn->list);
+
+					conn->idle_time = now_ms;
+					if (!(task_in_wq(srv->idle_task[tid])) &&
+					    !(task_in_rq(srv->idle_task[tid])))
+						task_schedule(srv->idle_task[tid],
+						    tick_add(now_ms, srv->idle_timeout));
+				} else
+					conn->mux->destroy(conn);
 			} else {
 				/* We have a connection, but not yet an associated mux.
 				 * So destroy it now.