]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MEDIUM: listener/mux-h2: implement idle-ping on frontend side
authorAmaury Denoyelle <adenoyelle@haproxy.com>
Tue, 8 Apr 2025 14:08:17 +0000 (16:08 +0200)
committerAmaury Denoyelle <adenoyelle@haproxy.com>
Thu, 17 Apr 2025 12:49:36 +0000 (14:49 +0200)
This commit is the counterpart of the previous one, adapted on the
frontend side. "idle-ping" is added as keyword to bind lines, to be able
to refresh client timeout of idle frontend connections.

H2 MUX behavior remains similar as the previous patch. The only
significant change is in h2c_update_timeout(), as idle-ping is now taken
into account also for frontend connection. The calculated value is
compared with http-request/http-keep-alive timeout value. The shorter
delay is then used as expired date. As hr/ka timeout are based on
idle_start, this allows to run them in parallel with an idle-ping timer.

doc/configuration.txt
include/haproxy/connection.h
include/haproxy/listener-t.h
src/listener.c
src/mux_h2.c

index 7bcf22cc0bf7c061693df7ad6b96e1a95f96a22c..b1f471c6a74baea8313ff3891f1a60b8715b7274 100644 (file)
@@ -17621,6 +17621,18 @@ id <id>
   must be strictly positive and unique within the listener/frontend. This
   option can only be used when defining only a single socket.
 
+idle-ping <delay>
+  May be used in the following contexts: tcp, http, log
+
+  Define an interval for periodic liveliness on idle frontend connections. If
+  the peer is unable to respond before the next scheduled test, the connection
+  is closed. Else, the client timeout is refreshed and the connection is kept.
+  Note that http-request/http-keep-alive timers run in parallel and are not
+  refreshed by idle-ping.
+
+  This feature relies on specific underlying protocol support. For now, only H2
+  mux implements it. Idle-ping is simply ignored by other protocols.
+
 interface <interface>
   Restricts the socket to a specific interface. When specified, only packets
   received from that particular interface are processed by the socket. This is
index 981c9480382a5fcbe6923f0af2cefacd29db2988..2e79a129f88e045f647d9c5ac7af8c49d5e599ad 100644 (file)
@@ -716,8 +716,8 @@ static inline int conn_idle_ping(const struct connection *conn)
                return srv ? srv->idle_ping : TICK_ETERNITY;
        }
        else {
-               /* TODO */
-               return TICK_ETERNITY;
+               struct session *sess = conn->owner;
+               return sess->listener->bind_conf->idle_ping;
        }
 }
 
index becc83b01d5df6a9891b78a62005c4f272d94a0e..7518aa6ec121053a60e1f0405be5f980a1dab5f6 100644 (file)
@@ -193,6 +193,7 @@ struct bind_conf {
        unsigned int analysers;    /* bitmap of required protocol analysers */
        int maxseg;                /* for TCP, advertised MSS */
        int tcp_ut;                /* for TCP, user timeout */
+       int idle_ping;             /* MUX idle-ping interval in ms */
        int maxaccept;             /* if set, max number of connections accepted at once (-1 when disabled) */
        unsigned int backlog;      /* if set, listen backlog */
        int maxconn;               /* maximum connections allowed on this listener */
index 95a5fef7036c44a44f94f936b0ed13718fe9b94e..c161c885b5614c6f5960706c880708b2812a4bf6 100644 (file)
@@ -2216,6 +2216,44 @@ static int bind_parse_id(char **args, int cur_arg, struct proxy *px, struct bind
        return 0;
 }
 
+/* Parse the "idle-ping" bind keyword */
+static int bind_parse_idle_ping(char **args, int cur_arg,
+                                struct proxy *px, struct bind_conf *conf,
+                                char **err)
+{
+       const char *res;
+       unsigned int value;
+
+       if (!*(args[cur_arg+1])) {
+               memprintf(err, "'%s' expects an argument.", args[cur_arg]);
+               goto error;
+       }
+
+       res = parse_time_err(args[cur_arg+1], &value, TIME_UNIT_MS);
+       if (res == PARSE_TIME_OVER) {
+               memprintf(err, "timer overflow in argument <%s> to <%s> on bind line, maximum value is 2147483647 ms (~24.8 days).",
+                         args[cur_arg+1], args[cur_arg]);
+               goto error;
+       }
+       else if (res == PARSE_TIME_UNDER) {
+               memprintf(err, "timer underflow in argument <%s> to <%s> on bind line, minimum non-null value is 1 ms.",
+                         args[cur_arg+1], args[cur_arg]);
+               goto error;
+       }
+       else if (res) {
+               memprintf(err, "unexpected character '%c' in '%s' argument on bind line.",
+                         *res, args[cur_arg]);
+               goto error;
+       }
+
+       conf->idle_ping = value;
+
+       return 0;
+
+  error:
+       return ERR_ALERT | ERR_FATAL;
+}
+
 /* Complete a bind_conf by parsing the args after the address. <args> is the
  * arguments array, <cur_arg> is the first one to be considered. <section> is
  * the section name to report in error messages, and <file> and <linenum> are
@@ -2584,6 +2622,7 @@ static struct bind_kw_list bind_kws = { "ALL", { }, {
        { "backlog",      bind_parse_backlog,      1, 0 }, /* set backlog of listening socket */
        { "guid-prefix",  bind_parse_guid_prefix,  1, 1 }, /* set guid of listening socket */
        { "id",           bind_parse_id,           1, 1 }, /* set id of listening socket */
+       { "idle-ping",    bind_parse_idle_ping,    1, 1 }, /* activate idle ping if mux support it */
        { "maxconn",      bind_parse_maxconn,      1, 0 }, /* set maxconn of listening socket */
        { "name",         bind_parse_name,         1, 1 }, /* set name of listening socket */
        { "nbconn",       bind_parse_nbconn,       1, 1 }, /* set number of connection on active preconnect */
index 7f99ffd5458a0bd6714839aa888b490f0cd17caf..86e39e5e2373b8d437f15b223a9fd4d5448036b9 100644 (file)
@@ -863,6 +863,7 @@ static void h2c_update_timeout(struct h2c *h2c)
                else {
                        int dft = TICK_ETERNITY;
                        int exp = TICK_ETERNITY;
+                       int ping = TICK_ETERNITY;
 
                        /* idle connection : no stream, no output data */
                        if (h2c->flags & (H2_CF_GOAWAY_SENT|H2_CF_GOAWAY_FAILED)) {
@@ -904,13 +905,19 @@ static void h2c_update_timeout(struct h2c *h2c)
 
                                        is_idle_conn = 1;
                                }
-                               else if (!(h2c->proxy->flags & (PR_FL_DISABLED|PR_FL_STOPPED))) {
+                               else {
                                        /* Only idle-ping is relevant for backend idle conn. */
-                                       exp = tick_add_ifset(now_ms, conn_idle_ping(h2c->conn));
-                                       if (tick_isset(exp) && !(h2c->flags & H2_CF_IDL_PING_SENT)) {
-                                               /* If PING timer selected, set flag to trigger its emission rather than conn deletion on next timeout. */
-                                               h2c->flags |= H2_CF_IDL_PING;
-                                       }
+                                       exp = TICK_ETERNITY;
+                               }
+
+                               if (!(h2c->proxy->flags & (PR_FL_DISABLED|PR_FL_STOPPED)))
+                                       ping = tick_add_ifset(now_ms, conn_idle_ping(h2c->conn));
+
+                               exp = tick_first(exp, ping);
+                               /* If PING timer selected, set flag to trigger its emission rather than conn deletion on next timeout. */
+                               if (tick_isset(exp) && exp == ping && ping != dft &&
+                                   !(h2c->flags & H2_CF_IDL_PING_SENT)) {
+                                       h2c->flags |= H2_CF_IDL_PING;
                                }
                        }