MEDIUM: listener/mux-h2: implement idle-ping on frontend side

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.
This commit is contained in:
Amaury Denoyelle 2025-04-08 16:08:17 +02:00
parent a78a04cfae
commit 52246249ab
5 changed files with 67 additions and 8 deletions

View 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

View 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;
}
}

View 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 */

View 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 */

View 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;
}
}