From: Willy Tarreau Date: Thu, 19 Mar 2026 15:00:05 +0000 (+0100) Subject: MINOR: mux-h2: permit to fix a minimum value for the advertised streams limit X-Git-Tag: v3.4-dev7~3 X-Git-Url: http://www.kaiwu.me/postgresql/commit/?a=commitdiff_plain;h=932d77e2871b1258420805b3522c88e60123b7f5;p=haproxy.git MINOR: mux-h2: permit to fix a minimum value for the advertised streams limit When using rq-load on tune.h2.fe.max-concurrent-streams, it's easy to reach a situation where only one stream is allowed. There's nothing wrong with this but it turns out that slightly higher values do not necessarily cause significantly higher loads and will improve the user experience. For this reason the keyword now also supports "min" to specify a value. Experimentation shows that values from 5 to 15 remain very effective at protecting the run queue while allowing a great level of parallelism that keeps a site fluid. --- diff --git a/doc/configuration.txt b/doc/configuration.txt index c774aface..96702e0f1 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -4447,6 +4447,17 @@ tune.h2.fe.max-concurrent-streams [args...] value, which generally provides good results without having to tweak the configuration any further. + - min : + This sets the minimum advertised concurrency level when rq-load is used, + even if this results in a higher load than the configured target. This + allows to maintain a good level of interactivity on a site under very + heavy load. The minimum and default value is 1, but values between 5 + and 15 can improve user experience. + + Example: + + tune.h2.fe.max-concurrent-streams 100 rq-load auto min 15 + tune.h2.fe.max-total-streams Sets the HTTP/2 maximum number of total streams processed per incoming connection. Once this limit is reached, HAProxy will send a graceful GOAWAY diff --git a/src/mux_h2.c b/src/mux_h2.c index 302c4fe99..99c832ceb 100644 --- a/src/mux_h2.c +++ b/src/mux_h2.c @@ -492,6 +492,7 @@ static uint h2_fe_rxbuf = 0; /* frontend's default tot static unsigned int h2_settings_max_concurrent_streams = 100; /* default value */ static unsigned int h2_be_settings_max_concurrent_streams = 0; /* backend value */ static unsigned int h2_fe_settings_max_concurrent_streams = 0; /* frontend value */ +static unsigned int h2_fe_min_concurrent_streams = 1; /* minimum concurrent streams when using rq-load */ static unsigned int h2_fe_max_rq_load = ~0; /* max rq for FE dynamic MCS sizing. 0=def rq */ static int h2_settings_max_frame_size = 0; /* unset */ static int h2_settings_log_errors = H2_ERR_LOG_ERR_STRM; @@ -771,7 +772,8 @@ static inline int h2c_max_concurrent_streams(const struct h2c *h2c) */ if (load > limit) ret = (uint64_t)ret * limit / load * limit / load; - ret = ret ? ret : 1; + if (ret < h2_fe_min_concurrent_streams) + ret = h2_fe_min_concurrent_streams; } return ret; } @@ -8724,6 +8726,7 @@ static int h2_parse_max_concurrent_streams(char **args, int section_type, struct char **err) { uint *vptr; + int arg; /* backend/frontend/default */ vptr = (args[0][8] == 'b') ? &h2_be_settings_max_concurrent_streams : @@ -8731,8 +8734,7 @@ static int h2_parse_max_concurrent_streams(char **args, int section_type, struct &h2_settings_max_concurrent_streams; - if ((args[0][8] != 'f' && too_many_args(1, args, err, NULL)) || - too_many_args(3, args, err, NULL)) + if (args[0][8] != 'f' && too_many_args(1, args, err, NULL)) return -1; *vptr = atoi(args[1]); @@ -8745,21 +8747,34 @@ static int h2_parse_max_concurrent_streams(char **args, int section_type, struct goto leave; /* tune.h2.fe. here */ - if (strcmp(args[2], "rq-load") == 0) { - if (strcmp(args[3], "ignore") == 0) - h2_fe_max_rq_load = ~0; - else if (strcmp(args[3], "auto") == 0) - h2_fe_max_rq_load = 0; - else if (!*args[3] || (h2_fe_max_rq_load = atoi(args[3])) <= 0) { - memprintf(err, "'%s' expects a strictly positive run-queue length, or 'auto' or 'ignore'.", args[0]); + for (arg = 2; *args[arg]; arg += 2) { + if (strcmp(args[arg], "rq-load") == 0) { + if (strcmp(args[arg + 1], "ignore") == 0) + h2_fe_max_rq_load = ~0; + else if (strcmp(args[arg + 1], "auto") == 0) + h2_fe_max_rq_load = 0; + else if (!*args[arg + 1] || (h2_fe_max_rq_load = atoi(args[arg + 1])) <= 0) { + memprintf(err, "'%s' expects a strictly positive run-queue length, or 'auto' or 'ignore'.", args[0]); + return -1; + } + } + else if (strcmp(args[arg], "min") == 0) { + if (!*args[arg + 1] || (h2_fe_min_concurrent_streams = atoi(args[arg + 1])) <= 0) { + memprintf(err, "'%s' expects a strictly positive minimum number of streams'.", args[0]); + return -1; + } + + if (h2_fe_min_concurrent_streams > h2_fe_settings_max_concurrent_streams) { + memprintf(err, "'%s' minimum number of streams (%u) cannot be higher than the maximum (%u)'.", + args[0], h2_fe_min_concurrent_streams, h2_fe_settings_max_concurrent_streams); + return -1; + } + } + else if (*args[arg]) { + memprintf(err, "'%s' only supports 'rq-load' or 'min' after the numeric value, but found '%s'.", args[0], args[arg]); return -1; } } - else if (*args[2]) { - memprintf(err, "'%s' only supports 'rq-load' after the numeric value, but found '%s'.", args[0], args[2]); - return -1; - } - leave: return 0; }