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.
This commit is contained in:
Olivier Houchard 2018-12-02 14:11:41 +01:00 committed by Willy Tarreau
parent 8defe4b51a
commit 0c18a6fe34
7 changed files with 135 additions and 2 deletions

View File

@ -11676,6 +11676,12 @@ id <value>
the proxy. An unused ID will automatically be assigned if unset. The first the proxy. An unused ID will automatically be assigned if unset. The first
assigned value will be 1. This ID is currently only returned in statistics. assigned value will be 1. This ID is currently only returned in statistics.
idle-timeout <delay>
Set the time to keep a connection alive before destroying it. By default
connections are destroyed as soon as they are unused, if idle-timeout is
non-zero, then connection are kept alive for up to <delay> before being
destroyed, and can be reused if no other connection is available.
init-addr {last | libc | none | <ip>},[...]* init-addr {last | libc | none | <ip>},[...]*
Indicate in what order the server's address should be resolved upon startup Indicate in what order the server's address should be resolved upon startup
if it uses an FQDN. Attempts are made to resolve the address by applying in if it uses an FQDN. Attempts are made to resolve the address by applying in

View File

@ -444,7 +444,7 @@ struct connection {
struct sockaddr_storage from; /* client address, or address to spoof when connecting to the server */ struct sockaddr_storage from; /* client address, or address to spoof when connecting to the server */
struct sockaddr_storage to; /* address reached by the client, or address to connect to */ struct sockaddr_storage to; /* address reached by the client, or address to connect to */
} addr; /* addresses of the remote side, client for producer and server for consumer */ } addr; /* addresses of the remote side, client for producer and server for consumer */
struct timeval idle_tv; /* Time the connection was added to the idle list */ unsigned int idle_time; /* Time the connection was added to the idle list */
}; };
/* PROTO token registration */ /* PROTO token registration */

View File

@ -221,6 +221,9 @@ struct server {
struct list *priv_conns; /* private idle connections attached to stream interfaces */ struct list *priv_conns; /* private idle connections attached to stream interfaces */
struct list *idle_conns; /* sharable idle connections attached or not to a stream interface */ struct list *idle_conns; /* sharable idle connections attached or not to a stream interface */
struct list *safe_conns; /* safe idle connections attached to stream interfaces, shared */ struct list *safe_conns; /* safe idle connections attached to stream interfaces, shared */
struct list *idle_orphan_conns; /* Orphan connections idling */
unsigned int idle_timeout; /* Time to keep an idling orphan connection alive */
struct task **idle_task; /* task responsible for cleaning idle orphan connections */
struct task *warmup; /* the task dedicated to the warmup when slowstart is set */ struct task *warmup; /* the task dedicated to the warmup when slowstart is set */
struct conn_src conn_src; /* connection source settings */ struct conn_src conn_src; /* connection source settings */

View File

@ -1118,6 +1118,7 @@ int connect_server(struct stream *s)
struct conn_stream *srv_cs = NULL; struct conn_stream *srv_cs = NULL;
struct server *srv; struct server *srv;
int reuse = 0; int reuse = 0;
int reuse_orphan = 0;
int err; int err;
int i; int i;
@ -1189,6 +1190,13 @@ int connect_server(struct stream *s)
else if (srv->idle_conns && !LIST_ISEMPTY(&srv->idle_conns[tid]) && else if (srv->idle_conns && !LIST_ISEMPTY(&srv->idle_conns[tid]) &&
(s->be->options & PR_O_REUSE_MASK) == PR_O_REUSE_ALWS) { (s->be->options & PR_O_REUSE_MASK) == PR_O_REUSE_ALWS) {
srv_conn = LIST_ELEM(srv->idle_conns[tid].n, struct connection *, list); 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 /* If we've picked a connection from the pool, we now have to
@ -1216,6 +1224,15 @@ int connect_server(struct stream *s)
reuse = 0; 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 /* We're about to use another connection, let the mux know we're
* done with this one * done with this one

View File

@ -2418,6 +2418,14 @@ void deinit(void)
free(s->idle_conns); free(s->idle_conns);
free(s->priv_conns); free(s->priv_conns);
free(s->safe_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 (s->use_ssl || s->check.use_ssl) {
if (xprt_get(XPRT_SSL) && xprt_get(XPRT_SSL)->destroy_srv) if (xprt_get(XPRT_SSL) && xprt_get(XPRT_SSL)->destroy_srv)

View File

@ -50,6 +50,7 @@ static void srv_update_status(struct server *s);
static void srv_update_state(struct server *srv, int version, char **params); 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_apply_lastaddr(struct server *srv, int *err_code);
static int srv_set_fqdn(struct server *srv, const char *fqdn, int dns_locked); 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 */ /* List head of all known server keywords */
static struct srv_kw_list srv_keywords = { static struct srv_kw_list srv_keywords = {
@ -357,6 +358,28 @@ static int srv_parse_enabled(char **args, int *cur_arg,
return 0; 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 */ /* parse the "id" server keyword */
static int srv_parse_id(char **args, int *cur_arg, struct proxy *curproxy, struct server *newsrv, char **err) static int srv_parse_id(char **args, int *cur_arg, struct proxy *curproxy, struct server *newsrv, char **err)
{ {
@ -1197,6 +1220,7 @@ static struct srv_kw_list srv_kws = { "ALL", { }, {
{ "disabled", srv_parse_disabled, 0, 1 }, /* Start the server in 'disabled' state */ { "disabled", srv_parse_disabled, 0, 1 }, /* Start the server in 'disabled' state */
{ "enabled", srv_parse_enabled, 0, 1 }, /* Start the server in 'enabled' state */ { "enabled", srv_parse_enabled, 0, 1 }, /* Start the server in 'enabled' state */
{ "id", srv_parse_id, 1, 0 }, /* set id# of server */ { "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) */ { "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-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 */ { "no-backup", srv_parse_no_backup, 0, 1 }, /* Flag as non-backup server */
@ -1640,6 +1664,7 @@ static void srv_settings_cpy(struct server *srv, struct server *src, int srv_tmp
srv->tcp_ut = src->tcp_ut; srv->tcp_ut = src->tcp_ut;
#endif #endif
srv->mux_proto = src->mux_proto; srv->mux_proto = src->mux_proto;
srv->idle_timeout = src->idle_timeout;
if (srv_tmpl) if (srv_tmpl)
srv->srvrq = src->srvrq; srv->srvrq = src->srvrq;
@ -1889,7 +1914,28 @@ static int server_finalize_init(const char *file, int linenum, char **args, int
px->srv_act++; px->srv_act++;
srv_lb_commit_status(srv); 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; return 0;
err:
return ERR_ALERT | ERR_FATAL;
} }
/* /*
@ -1973,6 +2019,24 @@ static int server_template_init(struct server *srv, struct proxy *px)
/* Linked backwards first. This will be restablished after parsing. */ /* Linked backwards first. This will be restablished after parsing. */
newsrv->next = px->srv; newsrv->next = px->srv;
px->srv = newsrv; 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); srv_set_id_from_prefix(srv, srv->tmpl_info.prefix, srv->tmpl_info.nb_low);
@ -5230,6 +5294,23 @@ static void srv_update_status(struct server *s)
*s->adm_st_chg_cause = 0; *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: * Local variables:
* c-indent-level: 8 * c-indent-level: 8

View File

@ -86,9 +86,27 @@ void session_free(struct session *sess)
list_for_each_entry_safe(conn, conn_back, &sess->srv_list[i].list, session_list) { list_for_each_entry_safe(conn, conn_back, &sess->srv_list[i].list, session_list) {
count++; count++;
if (conn->mux) { if (conn->mux) {
struct server *srv;
LIST_DEL(&conn->session_list); LIST_DEL(&conn->session_list);
LIST_INIT(&conn->session_list); LIST_INIT(&conn->session_list);
srv = objt_server(conn->target);
conn->owner = NULL; conn->owner = NULL;
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); conn->mux->destroy(conn);
} else { } else {
/* We have a connection, but not yet an associated mux. /* We have a connection, but not yet an associated mux.