diff --git a/doc/configuration.txt b/doc/configuration.txt index 082102b9e..5ea498d0a 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -2895,6 +2895,20 @@ tune.h2.be.initial-window-size between clients. It doesn't affect resource usage. See also: tune.h2.initial-window-size. +tune.h2.be.max-concurrent-streams + Sets the HTTP/2 maximum number of concurrent streams per outgoing connection + (i.e. the number of outstanding requests on a single connection to a server). + When not set, the default set by tune.h2.max-concurrent-streams applies. A + smaller value than the default 100 may improve a site's responsiveness at the + expense of maintaining more established connections to the servers. When the + "http-reuse" setting is set to "always", it is recommended to reduce this + value so as not to mix too many different clients over the same connection, + because if a client is slower than others, a mechanism known as "head of + line blocking" tends to cause cascade effect on download speed for all + clients sharing a connection (keep tune.h2.be.initial-window-size low in this + case). It is highly recommended not to increase this value; some might find + it optimal to run at low values (1..5 typically). + tune.h2.fe.initial-window-size Sets the HTTP/2 initial window size for incoming connections, which is the number of bytes the client can upload before waiting for an acknowledgment @@ -2907,6 +2921,16 @@ tune.h2.fe.initial-window-size clients to experience a lack of responsiveness if pages are accessed in parallel to large uploads. See also: tune.h2.initial-window-size. +tune.h2.fe.max-concurrent-streams + Sets the HTTP/2 maximum number of concurrent streams per incoming connection + (i.e. the number of outstanding requests on a single connection from a + client). When not set, the default set by tune.h2.max-concurrent-streams + applies. A larger value than the default 100 may sometimes slightly improve + the page load time for complex sites with lots of small objects over high + latency networks but can also result in using more memory by allowing a + client to allocate more resources at once. The default value of 100 is + generally good and it is recommended not to change this value. + tune.h2.header-table-size Sets the HTTP/2 dynamic header table size. It defaults to 4096 bytes and cannot be larger than 65536 bytes. A larger value may help certain clients @@ -2927,13 +2951,15 @@ tune.h2.initial-window-size tune.h2.be.initial-window-size. tune.h2.max-concurrent-streams - Sets the HTTP/2 maximum number of concurrent streams per connection (ie the - number of outstanding requests on a single connection). The default value is - 100. A larger one may slightly improve page load time for complex sites when - visited over high latency networks, but increases the amount of resources a - single client may allocate. A value of zero disables the limit so a single - client may create as many streams as allocatable by HAProxy. It is highly - recommended not to change this value. + Sets the default HTTP/2 maximum number of concurrent streams per connection + (i.e. the number of outstanding requests on a single connection). Ths value + is used for incoming connections when tune.h2.fe.max-concurrent-streams is + not set, and for outgoing connections when tune.h2.be.max-concurrent-streams + is not set. The default value is 100. The impact varies depending on the side + so please see the two settings above for more details. It is recommended not + to use this setting and to switch to the per-side ones instead. A value of + zero disables the limit so a single client may create as many streams as + allocatable by HAProxy. It is highly recommended not to change this value. tune.h2.max-frame-size Sets the HTTP/2 maximum frame size that HAProxy announces it is willing to diff --git a/src/mux_h2.c b/src/mux_h2.c index 3510c3b24..c6667b339 100644 --- a/src/mux_h2.c +++ b/src/mux_h2.c @@ -406,7 +406,9 @@ static int h2_settings_header_table_size = 4096; /* initial value */ static int h2_settings_initial_window_size = 65536; /* default initial value */ static int h2_be_settings_initial_window_size = 0; /* backend's default initial value */ static int h2_fe_settings_initial_window_size = 0; /* frontend's default initial value */ -static unsigned int h2_settings_max_concurrent_streams = 100; +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 int h2_settings_max_frame_size = 0; /* unset */ /* a dummy closed endpoint */ @@ -572,6 +574,23 @@ static inline int h2c_may_expire(const struct h2c *h2c) return !h2c->nb_sc; } +/* returns the number of max concurrent streams permitted on a connection, + * depending on its side (frontend or backend), falling back to the default + * h2_settings_max_concurrent_streams. It may even be zero. + */ +static inline int h2c_max_concurrent_streams(const struct h2c *h2c) +{ + int ret; + + ret = (h2c->flags & H2_CF_IS_BACK) ? + h2_be_settings_max_concurrent_streams : + h2_fe_settings_max_concurrent_streams; + + ret = ret ? ret : h2_settings_max_concurrent_streams; + return ret; +} + + /* update h2c timeout if needed */ static void h2c_update_timeout(struct h2c *h2c) { @@ -715,7 +734,7 @@ static inline void h2c_restart_reading(const struct h2c *h2c, int consider_buffe /* returns true if the front connection has too many stream connectors attached */ static inline int h2_frt_has_too_many_sc(const struct h2c *h2c) { - return h2c->nb_sc > h2_settings_max_concurrent_streams; + return h2c->nb_sc > h2c_max_concurrent_streams(h2c); } /* Tries to grab a buffer and to re-enable processing on mux . The h2c @@ -1001,7 +1020,7 @@ static int h2_init(struct connection *conn, struct proxy *prx, struct session *s /* Initialise the context. */ h2c->st0 = H2_CS_PREFACE; h2c->conn = conn; - h2c->streams_limit = h2_settings_max_concurrent_streams; + h2c->streams_limit = h2c_max_concurrent_streams(h2c); h2c->max_id = -1; h2c->errcode = H2_ERR_NO_ERROR; h2c->rcvd_c = 0; @@ -1528,7 +1547,7 @@ static struct h2s *h2c_frt_stream_new(struct h2c *h2c, int id, struct buffer *in TRACE_ENTER(H2_EV_H2S_NEW, h2c->conn); - if (h2c->nb_streams >= h2_settings_max_concurrent_streams) { + if (h2c->nb_streams >= h2c_max_concurrent_streams(h2c)) { TRACE_ERROR("HEADERS frame causing MAX_CONCURRENT_STREAMS to be exceeded", H2_EV_H2S_NEW|H2_EV_RX_FRAME|H2_EV_RX_HDR, h2c->conn); goto out; } @@ -1645,6 +1664,7 @@ static int h2c_send_settings(struct h2c *h2c) struct buffer buf; int iws; int mfs; + int mcs; int ret = 0; TRACE_ENTER(H2_EV_TX_FRAME|H2_EV_TX_SETTINGS, h2c->conn); @@ -1685,13 +1705,14 @@ static int h2c_send_settings(struct h2c *h2c) chunk_memcat(&buf, str, 6); } - if (h2_settings_max_concurrent_streams != 0) { + mcs = h2c_max_concurrent_streams(h2c); + if (mcs != 0) { char str[6] = "\x00\x03"; /* max_concurrent_streams */ /* Note: 0 means "unlimited" for haproxy's config but not for * the protocol, so never send this value! */ - write_n32(str + 2, h2_settings_max_concurrent_streams); + write_n32(str + 2, mcs); chunk_memcat(&buf, str, 6); } @@ -2247,8 +2268,8 @@ static int h2c_handle_settings(struct h2c *h2c) case H2_SETTINGS_MAX_CONCURRENT_STREAMS: if (h2c->flags & H2_CF_IS_BACK) { /* the limit is only for the backend; for the frontend it is our limit */ - if ((unsigned int)arg > h2_settings_max_concurrent_streams) - arg = h2_settings_max_concurrent_streams; + if ((unsigned int)arg > h2c_max_concurrent_streams(h2c)) + arg = h2c_max_concurrent_streams(h2c); h2c->streams_limit = arg; } break; @@ -6924,16 +6945,23 @@ static int h2_parse_initial_window_size(char **args, int section_type, struct pr return 0; } -/* config parser for global "tune.h2.max-concurrent-streams" */ +/* config parser for global "tune.h2.{be.,fe.,}max-concurrent-streams" */ static int h2_parse_max_concurrent_streams(char **args, int section_type, struct proxy *curpx, const struct proxy *defpx, const char *file, int line, char **err) { + uint *vptr; + if (too_many_args(1, args, err, NULL)) return -1; - h2_settings_max_concurrent_streams = atoi(args[1]); - if ((int)h2_settings_max_concurrent_streams < 0) { + /* backend/frontend/default */ + vptr = (args[0][8] == 'b') ? &h2_be_settings_max_concurrent_streams : + (args[0][8] == 'f') ? &h2_fe_settings_max_concurrent_streams : + &h2_settings_max_concurrent_streams; + + *vptr = atoi(args[1]); + if ((int)*vptr < 0) { memprintf(err, "'%s' expects a positive numeric value.", args[0]); return -1; } @@ -6993,7 +7021,9 @@ INITCALL1(STG_REGISTER, register_mux_proto, &mux_proto_h2); /* config keyword parsers */ static struct cfg_kw_list cfg_kws = {ILH, { { CFG_GLOBAL, "tune.h2.be.initial-window-size", h2_parse_initial_window_size }, + { CFG_GLOBAL, "tune.h2.be.max-concurrent-streams", h2_parse_max_concurrent_streams }, { CFG_GLOBAL, "tune.h2.fe.initial-window-size", h2_parse_initial_window_size }, + { CFG_GLOBAL, "tune.h2.fe.max-concurrent-streams", h2_parse_max_concurrent_streams }, { CFG_GLOBAL, "tune.h2.header-table-size", h2_parse_header_table_size }, { CFG_GLOBAL, "tune.h2.initial-window-size", h2_parse_initial_window_size }, { CFG_GLOBAL, "tune.h2.max-concurrent-streams", h2_parse_max_concurrent_streams },