diff --git a/doc/configuration.txt b/doc/configuration.txt index 6d1621202..fea7a1903 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -5358,6 +5358,10 @@ max-keep-alive-queue See also : "option http-server-close", "option prefer-last-server", server "maxconn" and cookie persistence. +max-session-srv-conns + Set the maximum number of outgoing connections we can keep idling for a given + client session. The default is 5 (it precisely equals MAX_SRV_LIST which is + defined at build time). maxconn Fix the maximum number of concurrent connections on a frontend diff --git a/include/proto/connection.h b/include/proto/connection.h index 7c8c1649c..6be311c45 100644 --- a/include/proto/connection.h +++ b/include/proto/connection.h @@ -664,7 +664,11 @@ static inline void conn_force_unsubscribe(struct connection *conn) static inline void conn_free(struct connection *conn) { /* Remove ourself from the session's connections list, if any. */ - LIST_DEL(&conn->session_list); + if (!LIST_ISEMPTY(&conn->session_list)) { + struct session *sess = conn->owner; + sess->resp_conns--; + LIST_DEL(&conn->session_list); + } /* If we temporarily stored the connection as the stream_interface's * end point, remove it. */ diff --git a/include/proto/server.h b/include/proto/server.h index 75cba471c..b3a9b877f 100644 --- a/include/proto/server.h +++ b/include/proto/server.h @@ -233,6 +233,27 @@ static inline enum srv_initaddr srv_get_next_initaddr(unsigned int *list) return ret; } +static inline int srv_add_to_idle_list(struct server *srv, struct connection *conn) +{ + if (srv && srv->pool_purge_delay > 0 && (srv->max_idle_conns == -1 || + srv->max_idle_conns > srv->curr_idle_conns) && + !(conn->flags & CO_FL_PRIVATE) && + ((srv->proxy->options & PR_O_REUSE_MASK) != PR_O_REUSE_NEVR) && + conn->mux->avail_streams(conn) == conn->mux->max_streams(conn)) { + LIST_DEL(&conn->list); + LIST_ADDQ(&srv->idle_orphan_conns[tid], &conn->list); + srv->curr_idle_conns++; + + 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->pool_purge_delay)); + return 1; + } + return 0; +} + #endif /* _PROTO_SERVER_H */ /* diff --git a/include/proto/session.h b/include/proto/session.h index bc5498a44..8c99e7d3c 100644 --- a/include/proto/session.h +++ b/include/proto/session.h @@ -30,7 +30,9 @@ #include #include +#include #include +#include extern struct pool_head *pool_head_session; struct session *session_new(struct proxy *fe, struct listener *li, enum obj_type *origin); @@ -76,6 +78,7 @@ static inline void session_add_conn(struct session *sess, struct connection *con 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; @@ -99,6 +102,7 @@ static inline void session_add_conn(struct session *sess, struct connection *con } /* 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); @@ -111,6 +115,24 @@ static inline void session_add_conn(struct session *sess, struct connection *con LIST_ADDQ(&sess->srv_list[avail].list, &conn->session_list); } +/* 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 */ +static inline int session_check_idle_conn(struct session *sess, struct connection *conn) +{ + 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); + conn->owner = NULL; + sess->resp_conns--; + if (!srv_add_to_idle_list(objt_server(conn->target), conn)) { + /* The server doesn't want it, let's kill the connection right away */ + conn->mux->destroy(conn); + return -1; + } + return 1; + } + return 0; +} #endif /* _PROTO_SESSION_H */ diff --git a/include/types/proxy.h b/include/types/proxy.h index b0877a26f..14b6046c6 100644 --- a/include/types/proxy.h +++ b/include/types/proxy.h @@ -278,6 +278,7 @@ struct proxy { int options; /* PR_O_REDISP, PR_O_TRANSP, ... */ int options2; /* PR_O2_* */ + int max_out_conns; /* Max number of idling connections we keep for a session */ struct in_addr mon_net, mon_mask; /* don't forward connections from this net (network order) FIXME: should support IPv6 */ unsigned int ck_opts; /* PR_CK_* (cookie options) */ unsigned int fe_req_ana, be_req_ana; /* bitmap of common request protocol analysers for the frontend and backend */ diff --git a/include/types/session.h b/include/types/session.h index 334a07157..33ce4fc32 100644 --- a/include/types/session.h +++ b/include/types/session.h @@ -54,6 +54,7 @@ struct session { struct vars vars; /* list of variables for the session scope. */ 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 */ }; diff --git a/src/backend.c b/src/backend.c index c79302d42..210cc3100 100644 --- a/src/backend.c +++ b/src/backend.c @@ -1254,8 +1254,9 @@ int connect_server(struct stream *s) (srv_conn->mux->avail_streams(srv_conn) == 1)) { LIST_DEL(&old_conn->session_list); LIST_INIT(&old_conn->session_list); - session_add_conn(sess, old_conn, s->target); old_conn->owner = sess; + session_add_conn(sess, old_conn, s->target); + session_check_idle_conn(sess, old_conn); } } @@ -1283,6 +1284,7 @@ int connect_server(struct stream *s) if (srv_conn && old_conn != srv_conn) { srv_conn->owner = s->sess; LIST_DEL(&srv_conn->session_list); + LIST_INIT(&srv_conn->session_list); session_add_conn(s->sess, srv_conn, s->target); } diff --git a/src/cfgparse-listen.c b/src/cfgparse-listen.c index aa082be75..aa2d8608f 100644 --- a/src/cfgparse-listen.c +++ b/src/cfgparse-listen.c @@ -421,6 +421,7 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int kwm) curproxy->fe_sps_lim = defproxy.fe_sps_lim; curproxy->to_log = defproxy.to_log & ~LW_COOKIE & ~LW_REQHDR & ~ LW_RSPHDR; + curproxy->max_out_conns = defproxy.max_out_conns; } if (curproxy->cap & PR_CAP_BE) { @@ -1327,6 +1328,17 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int kwm) else curproxy->server_state_file_name = strdup(args[1]); } + else if (!strcmp(args[0], "max-session-srv-conns")) { + if (warnifnotcap(curproxy, PR_CAP_FE, file, linenum, args[0], NULL)) + err_code |= ERR_WARN; + if (*(args[1]) == 0) { + ha_alert("parsine [%s:%d] : '%s' expects a number. Got no argument\n", + file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + curproxy->max_out_conns = atoi(args[1]); + } else if (!strcmp(args[0], "capture")) { if (warnifnotcap(curproxy, PR_CAP_FE, file, linenum, args[0], NULL)) err_code |= ERR_WARN; diff --git a/src/cfgparse.c b/src/cfgparse.c index 977d4bd46..7c316df05 100644 --- a/src/cfgparse.c +++ b/src/cfgparse.c @@ -455,6 +455,7 @@ void init_default_instance() defproxy.redispatch_after = 0; defproxy.lbprm.chash.balance_factor = 0; defproxy.options = PR_O_REUSE_SAFE; + defproxy.max_out_conns = MAX_SRV_LIST; defproxy.defsrv.check.inter = DEF_CHKINTR; defproxy.defsrv.check.fastinter = 0; diff --git a/src/mux_h1.c b/src/mux_h1.c index d116cbd0f..45d24c183 100644 --- a/src/mux_h1.c +++ b/src/mux_h1.c @@ -1947,6 +1947,19 @@ static void h1_detach(struct conn_stream *cs) h1c->conn->owner = sess; session_add_conn(sess, h1c->conn, h1c->conn->target); } + if (h1c->conn->owner == sess) { + int ret = session_check_idle_conn(sess, h1c->conn); + if (ret == -1) + /* The connection got destroyed, let's leave */ + return; + else if (ret == 1) { + /* The connection was added to the server list, + * wake the task so we can subscribe to events + */ + tasklet_wakeup(h1c->wait_event.task); + return; + } + } /* we're in keep-alive with an idle connection, monitor it if not already done */ if (LIST_ISEMPTY(&h1c->conn->list)) { struct server *srv = objt_server(h1c->conn->target); diff --git a/src/mux_h2.c b/src/mux_h2.c index 3509db5a6..1cd76fdfb 100644 --- a/src/mux_h2.c +++ b/src/mux_h2.c @@ -2850,6 +2850,11 @@ static void h2_detach(struct conn_stream *cs) h2c->conn->owner = sess; session_add_conn(sess, h2c->conn, h2c->conn->target); } + if (eb_is_empty(&h2c->streams_by_id)) { + if (session_check_idle_conn(h2c->conn->owner, h2c->conn) != 0) + /* At this point either the connection is destroyed, or it's been added to the server idle list, just stop */ + return; + } /* Never ever allow to reuse a connection from a non-reuse backend */ if ((h2c->proxy->options & PR_O_REUSE_MASK) == PR_O_REUSE_NEVR) h2c->conn->flags |= CO_FL_PRIVATE; diff --git a/src/proto_http.c b/src/proto_http.c index 2b9c0f51e..100a7baf3 100644 --- a/src/proto_http.c +++ b/src/proto_http.c @@ -3489,9 +3489,19 @@ void http_end_txn_clean_session(struct stream *s) srv_conn = NULL; else if (!srv_conn->owner) { srv_conn->owner = s->sess; + /* Add it unconditionally to the session list, it'll be removed + * later if needed by session_check_idle_conn(), once we'll + * have released the endpoint and know if it no longer has + * attached streams, and so an idling connection + */ session_add_conn(s->sess, srv_conn, s->target); } si_release_endpoint(&s->si[1]); + if (srv_conn && srv_conn->owner == s->sess) { + if (session_check_idle_conn(s->sess, srv_conn) != 0) + srv_conn = NULL; + } + s->si[1].state = s->si[1].prev_state = SI_ST_INI; s->si[1].err_type = SI_ET_NONE; diff --git a/src/session.c b/src/session.c index 502f00ea5..211181c3a 100644 --- a/src/session.c +++ b/src/session.c @@ -64,6 +64,7 @@ struct session *session_new(struct proxy *fe, struct listener *li, enum obj_type sess->srv_list[i].target = NULL; LIST_INIT(&sess->srv_list[i].list); } + sess->resp_conns = 0; } return sess; } @@ -86,30 +87,11 @@ void session_free(struct session *sess) 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; - if (srv && srv->pool_purge_delay > 0 && - (srv->max_idle_conns == -1 || - srv->max_idle_conns > srv->curr_idle_conns) && - !(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); - srv->curr_idle_conns++; - - 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->pool_purge_delay)); - } else + if (!srv_add_to_idle_list(objt_server(conn->target), conn)) conn->mux->destroy(conn); } else { /* We have a connection, but not yet an associated mux.