]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MEDIUM: sessions: Don't keep an infinite number of idling connections.
authorOlivier Houchard <ohouchard@haproxy.com>
Fri, 14 Dec 2018 18:27:06 +0000 (19:27 +0100)
committerWilly Tarreau <w@1wt.eu>
Sat, 15 Dec 2018 22:50:10 +0000 (23:50 +0100)
In session, don't keep an infinite number of connection that can idle.
Add a new frontend parameter, "max-session-srv-conns" to set a max number,
with a default value of 5.

13 files changed:
doc/configuration.txt
include/proto/connection.h
include/proto/server.h
include/proto/session.h
include/types/proxy.h
include/types/session.h
src/backend.c
src/cfgparse-listen.c
src/cfgparse.c
src/mux_h1.c
src/mux_h2.c
src/proto_http.c
src/session.c

index 6d1621202972630efe71fa8af93583f322dbfd7a..fea7a190344ca612a455d2d0ec3eaa82c25b34d9 100644 (file)
@@ -5358,6 +5358,10 @@ max-keep-alive-queue <value>
   See also : "option http-server-close", "option prefer-last-server", server
              "maxconn" and cookie persistence.
 
+max-session-srv-conns <nb>
+  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 <conns>
   Fix the maximum number of concurrent connections on a frontend
index 7c8c1649ce83b32472f460421310e29cbed4c0c9..6be311c459c177498b7d489cc5d597c5e7261b08 100644 (file)
@@ -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.
         */
index 75cba471cbf59d88116930d7746d40d24ce6789c..b3a9b877f935377abc0a05c69d8425a0463f3fef 100644 (file)
@@ -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 */
 
 /*
index bc5498a44cbf81f68045dd8a3f59f0fd37a6fefc..8c99e7d3c9a292d8d45e02c92e6af1ed25127ee3 100644 (file)
@@ -30,7 +30,9 @@
 #include <types/global.h>
 #include <types/session.h>
 
+#include <proto/obj_type.h>
 #include <proto/stick_table.h>
+#include <proto/server.h>
 
 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 */
 
index b0877a26f51d6c549e8509fae0eb1e1a1a38f12f..14b6046c698d4372bd88b6f7c2119c4afd85bd09 100644 (file)
@@ -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 */
index 334a071578ee9e31ce5b77b1665649ad94a846eb..33ce4fc3211f99b3b4257c1486ec38abc7c1462d 100644 (file)
@@ -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 */
 };
 
index c79302d422b71ca36d1a1581e2c0dcacead15ad2..210cc310024540c5bfaca86dfdae61e4c123f994 100644 (file)
@@ -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);
        }
 
index aa082be75e84c12b402a955f1031e5ea9f68844f..aa2d8608f4181f7673deef27a4dba9c2cf7a6fdb 100644 (file)
@@ -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;
index 977d4bd46660067d68ca1d310fa223bbb2a7550b..7c316df05c17483a4031ab0f24e5465646063ca1 100644 (file)
@@ -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;
index d116cbd0fdbca092e69516c5a82c27ec51ca8ff5..45d24c1836b9eee88641c1d731cb83eb9db6ba87 100644 (file)
@@ -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);
index 3509db5a6eaa0568ee9ae8a5c855aeeec4ff8fae..1cd76fdfbe09c8208a5ae026048995c8e8636600 100644 (file)
@@ -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;
index 2b9c0f51e6086e8401da3df810b3895bff1cf344..100a7baf3fe0db43ab08c3b9f273278e41a32690 100644 (file)
@@ -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;
index 502f00ea5e6b2952e8c075c89281711e9c4adca4..211181c3a555c99ee6e943db6c1ece6c50439439 100644 (file)
@@ -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.