From 0f9b3daf98a80004f90e9540b0385d419f0e4e16 Mon Sep 17 00:00:00 2001 From: Amaury Denoyelle Date: Tue, 29 Apr 2025 11:39:42 +0200 Subject: [PATCH] MEDIUM: quic: limit global Tx memory Define a new settings tune.quic.frontend.max-tot-window. It contains a size argument which can be used to set a limit on the sum of all QUIC connections congestion window. This is applied both on quic_cc_path_set() and quic_cc_path_inc(). Note that this limitation cannot reduce a congestion window more than the minimal limit which is set to 2 datagrams. --- doc/configuration.txt | 9 +++++++++ include/haproxy/defaults.h | 9 +++++++++ include/haproxy/global-t.h | 1 + src/cfgparse-quic.c | 12 ++++++++++++ src/haproxy.c | 1 + src/quic_cc.c | 23 +++++++++++++++++++++-- 6 files changed, 53 insertions(+), 2 deletions(-) diff --git a/doc/configuration.txt b/doc/configuration.txt index 79a3aa2bc..1a1f3a13a 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -1697,6 +1697,7 @@ The following keywords are supported in the "global" section : - tune.quic.frontend.max-data-size - tune.quic.frontend.max-idle-timeout - tune.quic.frontend.max-streams-bidi + - tune.quic.frontend.max-tx-mem - tune.quic.frontend.stream-data-ratio - tune.quic.frontend.default-max-window-size - tune.quic.max-frame-loss @@ -4446,6 +4447,14 @@ tune.quic.frontend.max-streams-bidi See also: "tune.quic.frontend.max-data-size", "tune.quic.frontend.stream-data-ratio" +tune.quic.frontend.max-tx-mem + Sets the maximum amount of memory usable by QUIC stack at the transport layer + for emission. This serves both as a limit of in flight bytes and multiplexer + output buffers. Note that to prevent threads contention this limit is not + strictly enforced so it can be exceeded on some occasions. Also, each + connection will always be able to use a window of at least 2 datagrams, so a + proper maxconn should be used in conjunction. + tune.quic.frontend.stream-data-ratio <0..100, in percent> This setting allows to configure the hard limit of the number of data bytes in flight over each stream. It is expressed as a percentage relative to diff --git a/include/haproxy/defaults.h b/include/haproxy/defaults.h index 1de038e3a..462793eff 100644 --- a/include/haproxy/defaults.h +++ b/include/haproxy/defaults.h @@ -645,4 +645,13 @@ #define FWLC_MIN_FREE_ENTRIES 500 #endif /* FWLC_MIN_FREE_ENTRIES */ +/* + * QUIC + */ + +/* Memory usage in bytes on Tx side, 0 for unlimited. */ +#ifndef QUIC_MAX_TX_MEM +#define QUIC_MAX_TX_MEM 0 +#endif + #endif /* _HAPROXY_DEFAULTS_H */ diff --git a/include/haproxy/global-t.h b/include/haproxy/global-t.h index 365482372..4d169e268 100644 --- a/include/haproxy/global-t.h +++ b/include/haproxy/global-t.h @@ -217,6 +217,7 @@ struct global { unsigned int quic_frontend_glitches_threshold; unsigned int quic_frontend_max_data; unsigned int quic_frontend_max_streams_bidi; + uint64_t quic_frontend_max_tx_mem; size_t quic_frontend_max_window_size; unsigned int quic_frontend_stream_data_ratio; unsigned int quic_retry_threshold; diff --git a/src/cfgparse-quic.c b/src/cfgparse-quic.c index 115684e10..d7c027c28 100644 --- a/src/cfgparse-quic.c +++ b/src/cfgparse-quic.c @@ -316,6 +316,17 @@ static int cfg_parse_quic_tune_setting(char **args, int section_type, } else if (strcmp(suffix, "frontend.max-streams-bidi") == 0) global.tune.quic_frontend_max_streams_bidi = arg; + else if (strcmp(suffix, "frontend.max-tx-mem") == 0) { + ullong max_mem; + + if ((errptr = parse_size_err(args[1], &max_mem))) { + memprintf(err, "'%s': unexpected character '%c' in size argument '%s'.", + args[0], *errptr, args[1]); + return -1; + } + + global.tune.quic_frontend_max_tx_mem = max_mem; + } else if (strcmp(suffix, "frontend.default-max-window-size") == 0) { unsigned long cwnd; char *end_opt; @@ -430,6 +441,7 @@ static struct cfg_kw_list cfg_kws = {ILH, { { CFG_GLOBAL, "tune.quic.frontend.max-data-size", cfg_parse_quic_tune_setting }, { CFG_GLOBAL, "tune.quic.frontend.max-streams-bidi", cfg_parse_quic_tune_setting }, { CFG_GLOBAL, "tune.quic.frontend.max-idle-timeout", cfg_parse_quic_time }, + { CFG_GLOBAL, "tune.quic.frontend.max-tx-mem", cfg_parse_quic_tune_setting }, { CFG_GLOBAL, "tune.quic.frontend.default-max-window-size", cfg_parse_quic_tune_setting }, { CFG_GLOBAL, "tune.quic.frontend.stream-data-ratio", cfg_parse_quic_tune_setting }, { CFG_GLOBAL, "tune.quic.max-frame-loss", cfg_parse_quic_tune_setting }, diff --git a/src/haproxy.c b/src/haproxy.c index d2a9c8e6a..658645ae8 100644 --- a/src/haproxy.c +++ b/src/haproxy.c @@ -199,6 +199,7 @@ struct global global = { .quic_frontend_max_idle_timeout = QUIC_TP_DFLT_FRONT_MAX_IDLE_TIMEOUT, .quic_frontend_max_data = 0, .quic_frontend_max_streams_bidi = QUIC_TP_DFLT_FRONT_MAX_STREAMS_BIDI, + .quic_frontend_max_tx_mem = QUIC_MAX_TX_MEM, .quic_frontend_max_window_size = QUIC_DFLT_MAX_WINDOW_SIZE, .quic_frontend_stream_data_ratio = QUIC_DFLT_FRONT_STREAM_DATA_RATIO, .quic_reorder_ratio = QUIC_DFLT_REORDER_RATIO, diff --git a/src/quic_cc.c b/src/quic_cc.c index 31bb6e797..de9a4c649 100644 --- a/src/quic_cc.c +++ b/src/quic_cc.c @@ -78,6 +78,22 @@ static int quic_cwnd_may_increase(const struct quic_cc_path *path) return 2 * path->in_flight >= path->cwnd || path->cwnd < 16384; } +/* Calculate ratio of free memory relative to the maximum configured limit. */ +static int quic_cc_max_win_ratio(void) +{ + uint64_t tot, free = 0; + int ratio = 100; + + if (global.tune.quic_frontend_max_tx_mem) { + tot = cshared_read(&quic_mem_diff); + if (global.tune.quic_frontend_max_tx_mem > tot) + free = global.tune.quic_frontend_max_tx_mem - tot; + ratio = free * 100 / global.tune.quic_frontend_max_tx_mem; + } + + return ratio; +} + /* Restore congestion window for to its minimal value. */ void quic_cc_path_reset(struct quic_cc_path *path) { @@ -90,8 +106,9 @@ void quic_cc_path_reset(struct quic_cc_path *path) void quic_cc_path_set(struct quic_cc_path *path, uint64_t val) { const uint64_t old = path->cwnd; + const uint64_t limit_max = path->limit_max * quic_cc_max_win_ratio() / 100; - path->cwnd = QUIC_MIN(val, path->limit_max); + path->cwnd = QUIC_MIN(val, limit_max); path->cwnd = QUIC_MAX(path->cwnd, path->limit_min); cshared_add(&quic_mem_diff, path->cwnd - old); @@ -105,9 +122,11 @@ void quic_cc_path_set(struct quic_cc_path *path, uint64_t val) void quic_cc_path_inc(struct quic_cc_path *path, uint64_t val) { const uint64_t old = path->cwnd; + uint64_t limit_max; if (quic_cwnd_may_increase(path)) { - path->cwnd = QUIC_MIN(path->cwnd + val, path->limit_max); + limit_max = path->limit_max * quic_cc_max_win_ratio() / 100; + path->cwnd = QUIC_MIN(path->cwnd + val, limit_max); path->cwnd = QUIC_MAX(path->cwnd, path->limit_min); cshared_add(&quic_mem_diff, path->cwnd - old); -- 2.47.2