]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MEDIUM: server/mux-h2: implement idle-ping on backend side
authorAmaury Denoyelle <adenoyelle@haproxy.com>
Tue, 8 Apr 2025 09:09:06 +0000 (11:09 +0200)
committerAmaury Denoyelle <adenoyelle@haproxy.com>
Thu, 17 Apr 2025 12:49:36 +0000 (14:49 +0200)
This commit implements support for idle-ping on the backend side. First,
a new server keyword "idle-ping" is defined in configuration parsing. It
is used to set the corresponding new server member.

The second part of this commit implements idle-ping support on H2 MUX. A
new inlined function conn_idle_ping() is defined to access connection
idle-ping value. Two new connection flags are defined H2_CF_IDL_PING and
H2_CF_IDL_PING_SENT. The first one is set for idle connections via
h2c_update_timeout().

On h2_timeout_task() handler, if first flag is set, instead of releasing
the connection as before, the second flag is set and tasklet is
scheduled. As both flags are now set, h2_process_mux() will proceed to
PING emission. The timer has also been rearmed to the idle-ping value.
If a PING ACK is received before next timeout, connection timer is
refreshed. Else, the connection is released, as with timer expiration.

Also of importance, special care is needed when a backend connection is
going to idle. In this case, idle-ping timer must be rearmed. Thus a new
invokation of h2c_update_timeout() is performed on h2_detach().

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

index 15f9446872b295ecbb7d5cbdcf5f255bba397e74..7bcf22cc0bf7c061693df7ad6b96e1a95f96a22c 100644 (file)
@@ -18639,6 +18639,18 @@ id <value>
   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.
 
+idle-ping <delay>
+  May be used in the following contexts: tcp, http, log
+
+  Define an interval for periodic liveliness on idle backend connections. If
+  the peer is unable to respond before the next scheduled test, the connection
+  is closed. This keyword refers to the backend side, so it is useful to check
+  that idle connections are still usable. Note that this won't prevent the
+  connection from being destroyed on idle pool purge.
+
+  This feature relies on specific underlying protocol support. For now, only H2
+  mux implements it. Idle-ping is simply ignored by other protocols.
+
 init-addr {last | libc | none | <ip>},[...]*
   May be used in the following contexts: tcp, http, log
 
index 2963ec076c655a0fabc15a6a4d5651eb4d519f86..981c9480382a5fcbe6923f0af2cefacd29db2988 100644 (file)
@@ -708,6 +708,19 @@ static inline void conn_set_reverse(struct connection *conn, enum obj_type *targ
        conn->reverse.target = target;
 }
 
+/* Returns idle-ping value for <conn> depending on its proxy side. */
+static inline int conn_idle_ping(const struct connection *conn)
+{
+       if (conn_is_back(conn)) {
+               struct server *srv = objt_server(conn->target);
+               return srv ? srv->idle_ping : TICK_ETERNITY;
+       }
+       else {
+               /* TODO */
+               return TICK_ETERNITY;
+       }
+}
+
 /* Returns the listener instance for connection used for active reverse. */
 static inline struct listener *conn_active_reverse_listener(const struct connection *conn)
 {
index 31bf6f08a817c4ed5af59465a131c05fcb9248ae..a16085987d82a8a174c8ce430efc6b4deb18796d 100644 (file)
@@ -70,6 +70,9 @@
 #define H2_CF_ERROR             0x01000000  //A read error was detected (handled has an abort)
 #define H2_CF_WAIT_INLIST       0x02000000  // there is at least one stream blocked by another stream in send_list/fctl_list
 
+#define H2_CF_IDL_PING          0x04000000  // timer task scheduled for a PING emission
+#define H2_CF_IDL_PING_SENT     0x08000000  // PING emitted, or will be on next tasklet run, waiting for ACK
+
 /* This function is used to report flags in debugging tools. Please reflect
  * below any single-bit flag addition above in the same order via the
  * __APPEND_FLAG macro. The new end of the buffer is returned.
index 716ac79fb11d5c4c9fcc37c3687b5fef7eae784e..dffa2c625583f6adada27fa8c21a5c7ee2ebba55 100644 (file)
@@ -340,6 +340,7 @@ struct server {
        short onmarkeddown;                     /* what to do when marked down: one of HANA_ONMARKEDDOWN_* */
        short onmarkedup;                       /* what to do when marked up: one of HANA_ONMARKEDUP_* */
        int slowstart;                          /* slowstart time in seconds (ms in the conf) */
+       int idle_ping;                          /* MUX idle-ping interval in ms */
 
        char *id;                               /* just for identification */
        uint32_t rid;                           /* revision: if id has been reused for a new server, rid won't match */
index 9c41786522c6d320cdbaa3a9bf77e7e8ea15f960..7f99ffd5458a0bd6714839aa888b490f0cd17caf 100644 (file)
@@ -848,6 +848,9 @@ static void h2c_update_timeout(struct h2c *h2c)
 
        TRACE_ENTER(H2_EV_H2C_WAKE, h2c->conn);
 
+       /* Always reset flag for PING emission prior to refresh timeout. */
+       h2c->flags &= ~H2_CF_IDL_PING;
+
        if (!h2c->task)
                goto leave;
 
@@ -901,9 +904,13 @@ static void h2c_update_timeout(struct h2c *h2c)
 
                                        is_idle_conn = 1;
                                }
-                               else {
-                                       /* No timeout on backend idle conn. */
-                                       exp = TICK_ETERNITY;
+                               else if (!(h2c->proxy->flags & (PR_FL_DISABLED|PR_FL_STOPPED))) {
+                                       /* 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;
+                                       }
                                }
                        }
 
@@ -1308,7 +1315,8 @@ static int h2_init(struct connection *conn, struct proxy *prx, struct session *s
        h2c->next_tasklet = NULL;
        h2c->shared_rx_bufs = NULL;
        h2c->idle_start = now_ms;
-       if (tick_isset(h2c->timeout)) {
+
+       if (tick_isset(h2c->timeout) || tick_isset(conn_idle_ping(conn))) {
                t = task_new_here();
                if (!t)
                        goto fail;
@@ -2888,9 +2896,17 @@ static int h2c_ack_settings(struct h2c *h2c)
  */
 static int h2c_handle_ping(struct h2c *h2c)
 {
-       /* schedule a response */
-       if (!(h2c->dff & H2_F_PING_ACK))
+       if (h2c->dff & H2_F_PING_ACK) {
+               TRACE_PROTO("receiving H2 PING ACK frame", H2_EV_RX_FRAME|H2_EV_RX_PING, h2c->conn);
+               if ((h2c->flags & (H2_CF_IDL_PING|H2_CF_IDL_PING_SENT)) == H2_CF_IDL_PING_SENT) {
+                       h2c->flags &= ~H2_CF_IDL_PING_SENT;
+                       h2c_update_timeout(h2c);
+               }
+       }
+       else {
+               /* schedule a response */
                h2c->st0 = H2_CS_FRAME_A;
+       }
        return 1;
 }
 
@@ -4640,6 +4656,14 @@ static int h2_process_mux(struct h2c *h2c)
            h2c_send_conn_wu(h2c) < 0)
                goto fail;
 
+       /* emit PING to test connection liveliness */
+       if ((h2c->flags & (H2_CF_IDL_PING|H2_CF_IDL_PING_SENT)) == (H2_CF_IDL_PING|H2_CF_IDL_PING_SENT)) {
+               if (!h2c_send_ping(h2c, 0))
+                       goto fail;
+               TRACE_USER("sent ping", H2_EV_H2C_WAKE, h2c->conn);
+               h2c->flags &= ~H2_CF_IDL_PING;
+       }
+
        /* First we always process the flow control list because the streams
         * waiting there were already elected for immediate emission but were
         * blocked just on this.
@@ -5172,6 +5196,15 @@ struct task *h2_timeout_task(struct task *t, void *context, unsigned int state)
                        return t;
                }
 
+               if (h2c->flags & H2_CF_IDL_PING) {
+                       h2c->flags |= H2_CF_IDL_PING_SENT;
+                       tasklet_wakeup(h2c->wait_event.tasklet);
+                       TRACE_DEVEL("leaving (idle ping)", H2_EV_H2C_WAKE, h2c->conn);
+                       t->expire = conn_idle_ping(h2c->conn);
+                       HA_SPIN_UNLOCK(IDLE_CONNS_LOCK, &idle_conns[tid].idle_conns_lock);
+                       return t;
+               }
+
                /* We're about to destroy the connection, so make sure nobody attempts
                 * to steal it from us.
                 */
@@ -5469,6 +5502,12 @@ static void h2_detach(struct sedesc *sd)
 
        if (h2c->flags & H2_CF_IS_BACK) {
                if (!(h2c->flags & (H2_CF_RCVD_SHUT|H2_CF_ERR_PENDING|H2_CF_ERROR))) {
+                       /* Ensure idle-ping is activated before going to idle. */
+                       if (eb_is_empty(&h2c->streams_by_id) &&
+                           tick_isset(conn_idle_ping(h2c->conn))) {
+                               h2c_update_timeout(h2c);
+                       }
+
                        if (h2c->conn->flags & CO_FL_PRIVATE) {
                                /* Add the connection in the session server list, if not already done */
                                if (!session_add_conn(sess, h2c->conn, h2c->conn->target)) {
index a94fef190d3a46295bff7564a8e5592cce316abd..1e9b2ec84e0b1599fdf1a0de6a041f16de81a9ae 100644 (file)
@@ -1003,6 +1003,43 @@ static int srv_parse_hash_key(char **args, int *cur_arg,
        return 0;
 }
 
+/* Parse the "idle-ping" server keyword */
+static int srv_parse_idle_ping(char **args, int *cur_arg,
+                               struct proxy *curproxy, struct server *newsrv, 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> of server %s, maximum value is 2147483647 ms (~24.8 days).",
+                         args[*cur_arg+1], args[*cur_arg], newsrv->id);
+               goto error;
+       }
+       else if (res == PARSE_TIME_UNDER) {
+               memprintf(err, "timer underflow in argument <%s> to <%s> of server %s, minimum non-null value is 1 ms.",
+                         args[*cur_arg+1], args[*cur_arg], newsrv->id);
+               goto error;
+       }
+       else if (res) {
+               memprintf(err, "unexpected character '%c' in '%s' argument of server %s.",
+                         *res, args[*cur_arg], newsrv->id);
+               goto error;
+       }
+
+       newsrv->idle_ping = value;
+
+       return 0;
+
+  error:
+       return ERR_ALERT | ERR_FATAL;
+}
+
 /* Parse the "init-addr" server keyword */
 static int srv_parse_init_addr(char **args, int *cur_arg,
                                struct proxy *curproxy, struct server *newsrv, char **err)
@@ -2361,6 +2398,7 @@ static struct srv_kw_list srv_kws = { "ALL", { }, {
        { "ws",                   srv_parse_ws,                   1,  1,  1 }, /* websocket protocol */
        { "hash-key",             srv_parse_hash_key,             1,  1,  1 }, /* Configure how chash keys are computed */
        { "id",                   srv_parse_id,                   1,  0,  1 }, /* set id# of server */
+       { "idle-ping",            srv_parse_idle_ping,            1,  1,  1 }, /* Activate idle ping if mux support it */
        { "init-addr",            srv_parse_init_addr,            1,  1,  0 }, /* */
        { "init-state",           srv_parse_init_state,           1,  1,  1 }, /* Set the initial state of the server */
        { "log-bufsize",          srv_parse_log_bufsize,          1,  1,  0 }, /* Set the ring bufsize for log server (only for log backends) */