make http/2 settings configurable (#988)#989
make http/2 settings configurable (#988)#989htuch merged 65 commits intoenvoyproxy:masterfrom jwfang:issue988
Conversation
|
i will rename CodecOptions to Http2Options, seems this is only used by http2, should we move it to Http2 namespace ? |
mattklein123
left a comment
There was a problem hiding this comment.
In general this is the right approach. Will need tests, docs, etc.
@PiotrSikora can you potentially help shepherd this change through while I'm on vacation. cc @htuch
| * A list of options that can be specified when creating a codec. | ||
| */ | ||
| class CodecOptions { | ||
| class CodecOptionFlags { |
There was a problem hiding this comment.
This whole thing is a mess and will be cleaned up as part of the v2 API effort. cc @htuch @PiotrSikora
In the interim, I think it's OK to leave this in the http namespace.
| }; | ||
|
|
||
| struct CodecOptions { | ||
| uint64_t flag_options; |
There was a problem hiding this comment.
nit: all members should end with '_'
| class CodecOptions { | ||
| class CodecOptionFlags { | ||
| public: | ||
| static const uint64_t NoCompression = 0x1; |
There was a problem hiding this comment.
It's not part of your change, but as long as you are in here, can you please rename NoCompression to DisableDynamicHPACKTable. Don't bother changing the actual JSON config. We will fix that in v2 API.
| * @see Http::CodecOptions. | ||
| */ | ||
| virtual uint64_t httpCodecOptions() const PURE; | ||
| virtual Http::CodecOptions httpCodecOptions() const PURE; |
There was a problem hiding this comment.
return const Http::CodecOptions& and fix doc comment above.
|
|
||
| if (codec_options.flag_options & CodecOptionFlags::NoCompression) { | ||
| iv.push_back({NGHTTP2_SETTINGS_HEADER_TABLE_SIZE, 0}); | ||
| conn_log_debug("disabling header compression", connection_); |
There was a problem hiding this comment.
can you fix this comment to to read: "setting HPACK dynamic table size to 0"
1. change original uint32_t CodecOptions to Http2Settings, and move CodecOptions into Http2Settings; 2. default ctor for Http2Settings, and move default consts into Http2Settings; 3. hard code maxConcurrentStreams return temporarily till PR #981.
//test/integration:http2_integration_test FAILED in 1 out of 2 in 0.2s /build/tmp/_bazel_bazel/436badd4919a15958fa3800a4e21074a/execroot/ci/bazel-out/local-dbg/testlogs/test/integration/http2_integration_test/test.log //test/integration:http2_upstream_integration_test FAILED in 1 out of 2 in 0.2s /build/tmp/_bazel_bazel/436badd4919a15958fa3800a4e21074a/execroot/ci/bazel-out/local-dbg/testlogs/test/integration/http2_upstream_integration_test/test.log //test/integration:ssl_integration_test FAILED in 1 out of 2 in 2.0s /build/tmp/_bazel_bazel/436badd4919a15958fa3800a4e21074a/execroot/ci/bazel-out/local-dbg/testlogs/test/integration/ssl_integration_test/test.log Executed 111 out of 111 tests: 108 tests pass and 3 fail locally. There were tests whose specified size is too big. Use the --test_verbose_timeout_warnings command line option to see which ones these are. root@8766e71f0008:/source# ./tools/stack_decode.py /build/tmp/_bazel_bazel/436badd4919a15958fa3800a4e21074a/execroot/ci/bazel-out/local-dbg/bin/test/integration/http2_upstream_integration_test.runfiles/ci/test/integration/http2_upstream_integration_test [2017-05-19 09:38:02.220][17271][critical][assert] assert failure: path != nullptr: test/test_common/environment.cc:48 [2017-05-19 09:38:02.220][17271][critical][backtrace] Caught Aborted, suspect faulting address 0x4377 [2017-05-19 09:38:02.223][17271][critical] Backtrace (most recent call first) from thread 0: #1 ?? ??:0 #2 ?? ??:0 #3 #4 Envoy::TestEnvironment::getCheckedEnvVar(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) at environment.cc:48 (discriminator 1) #5 main at main.cc:23 (discriminator 2) #6 #7 ?? ??:0 #8 #9 _start at ??:? #10
sync/merge from upstream, integration test failures persist.
|
i did the following to building envoy: complete test output is in commit message for bcd7ba6 this output from stack_decode.py is really confusing:
run from ./ci/run_envoy_docker.sh do the following changes failures persist. i also run from root without sudo, got the same errors. BTW, i didn't see this error from latest (56a8c03) upstream master. since my CI checks also failed, so i guess maybe it's not related to my environments/setups. is this https://github.com/jwfang/envoy/blob/431c909dcd7d70686cc9986c9ef019bef8942ae2/test/common/http/http2/codec_impl_test.cc#L28 causing the wired behaviour ? will try when at work. |
| uint32_t max_concurrent_streams_; | ||
| uint32_t initial_window_size_; | ||
|
|
||
| static const uint32_t DEFAULT_MAX_CONCURRENT_STREAMS = 1024; |
There was a problem hiding this comment.
Nit: just a minor style note while looking at this diff, it would be good to make the capitalization more consistent for the static consts in this header (below is all caps, above is camel case).
There was a problem hiding this comment.
these are copied from the old codes. :)
should i go all caps or camel case ?
There was a problem hiding this comment.
We don't really have consistent style here unfortunately. I would probably s/DisableDynamicHPACKTable/DISABLE_DYNAMIC_HPACK_TABLE
mattklein123
left a comment
There was a problem hiding this comment.
In general on the right track. Obviously needs all the JSON parsing support, doc updates, etc.
| uint32_t max_concurrent_streams_; | ||
| uint32_t initial_window_size_; | ||
|
|
||
| static const uint32_t DEFAULT_MAX_CONCURRENT_STREAMS = 1024; |
There was a problem hiding this comment.
We don't really have consistent style here unfortunately. I would probably s/DisableDynamicHPACKTable/DISABLE_DYNAMIC_HPACK_TABLE
| static const uint64_t DisableDynamicHPACKTable = 0x1; | ||
| }; | ||
|
|
||
| Http2Settings() |
There was a problem hiding this comment.
nit: drop constructor definition and just do inline init below. E.g.,
uint64_t codec_options_{0};
etc
There was a problem hiding this comment.
didn't know that could be done, from pre-2010 c++ age. thanks
|
|
||
| /** | ||
| * A list of options that can be specified when creating a codec. | ||
| * http/2 settings |
There was a problem hiding this comment.
nit: "http/2 codec settings"
| /** | ||
| * @return uint64_t HTTP codec options for HTTP connections created on behalf of this cluster. | ||
| * @see Http::CodecOptions. | ||
| * @return Http::Http2Settings for http/2 connections created on behalf of this cluster. |
There was a problem hiding this comment.
nit: const Http::Http2Settings&
There was a problem hiding this comment.
sorry, it's too obvious
| DEFAULT_WINDOW_SIZE - NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE); | ||
| http2_settings.initial_window_size_ - | ||
| NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE); | ||
| // TODO: ensure mimium initial_window_size from json schema |
There was a problem hiding this comment.
Might also be a good place for an ASSERT() checking that http2_settings.initial_window_size_ is greater than NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE.
There was a problem hiding this comment.
missed it, and exactly what i am going to do.
| MOCK_CONST_METHOD0(perConnectionBufferLimitBytes, uint32_t()); | ||
| MOCK_CONST_METHOD0(features, uint64_t()); | ||
| MOCK_CONST_METHOD0(httpCodecOptions, uint64_t()); | ||
| MOCK_CONST_METHOD0(http2Settings, const Http::Http2Settings&()); |
There was a problem hiding this comment.
The tests are most likely failing because you need to setup a default ON_CALL return a default Http2Settings object. You can verify by running the tests locally with --test_arg="--gmock_verbose=info"
There was a problem hiding this comment.
by tests failing, do you mean unit test or integration tests?
it seems all the orignal unit tests run fine, but integration tests keep failing. i guess you already saw my earier comment on this.
not familiar with gtest/gmock, will try as you suggested and invest it.
There was a problem hiding this comment.
thank you @mattklein123 , i fixed the integration tests failures by add ON_CALL.
sync/merge upstream.
|
can some explained these to me ? it seems both wired and interesting. i saw Envoy::Router::RetryPolicy and its tests don't need declarations like this. |
|
|
||
| // Satisfy linker | ||
| const uint64_t CodecOptions::NoCompression; | ||
| const uint64_t Http2Settings::CodecOptions::DISABLE_DYNAMIC_HPACK_TABLE; |
There was a problem hiding this comment.
need these, else link error with undefined symbols.
but why ?
There was a problem hiding this comment.
Without this there is no storage actually assigned to store this static variable. That may be OK: if you never require actual storage for the constant int the compiler doesn't complain. But if the static const is passed by reference or it's address taken at some point in the code then it needs an actual storage location (if it's passed by reference someone might take the address later). This means it must be defined (it's declared in the header) in a single CPP file.
Why does it matter here and not in the Envoy executable? In the Envoy executable case they're never passed by reference so the compiler lets you get away with not defining the variable.
There was a problem hiding this comment.
got it, thank you for detailed explanation.
i guess it's here that uses the const static by reference:
https://github.com/google/googletest/blob/59c795ce08be0c8b225bc894f8da6c7954ea5c14/googletest/include/gtest/gtest.h#L1419-L1420
There was a problem hiding this comment.
Yup, that looks like it. Good find.
dnoe
left a comment
There was a problem hiding this comment.
A few comments to start. Is the config actually validated against the schema minimum and maximum?
It would be good to see some more tests that exercise the feature and corner cases (try some invalid values, etc)
| initial_window_size | ||
| *(optional, integer)* `Initial flow-control window`_ size. Valid values range from 65536 | ||
| (default window size plus 1) to 2147483647 (2^31 - 1), so we only support increase default | ||
| initial window size now. Default is 268435456 (256 * 1024 * 1024). |
There was a problem hiding this comment.
"so only increases to the default initial window size are supported" would be a bit clearer.
|
|
||
| if (codec_options & CodecOptions::NoCompression) { | ||
| if (http2_settings.codec_options_ & | ||
| Http::Http2Settings::CodecOptions::DISABLE_DYNAMIC_HPACK_TABLE) { |
There was a problem hiding this comment.
Could this be moved into a boolean method in CodecOptions? It would make the code clearer to read here.
There was a problem hiding this comment.
yes, since we are using a struct now, we can add additional fields easily.
so the flags as bool no needed any more.
| DEFAULT_WINDOW_SIZE - NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE); | ||
| http2_settings.initial_window_size_ - | ||
| NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE); | ||
| // TODO: ensure mimium initial_window_size from json schema |
| static const uint32_t DEFAULT_MAX_CONCURRENT_STREAMS = 1024; | ||
| // For now just set all window sizes (stream and connection) to 256MiB. We can adjust later if | ||
| // needed. | ||
| static const uint32_t DEFAULT_INITIAL_WINDOW_SIZE = 256 * 1024 * 1024; |
There was a problem hiding this comment.
Note for myself: this is only moved to a different file, the value is unchanged.
| * @return uint64_t parse a "http_codec_options" JSON field and turn it into a bitmask of | ||
| * CodecOption values. | ||
| * @return Http2Settings parse "http_codec_options" and "http2_settings" JSON fields and return | ||
| * a Http2Settings. |
There was a problem hiding this comment.
I know it matches the phrasing from before but this PR is a good opportunity to improve this return documentation which reads awkwardly. It should say what it returns such as: "@return Http2Settings An Http2Settings populated from the http_codec_options and http2_settings JSON fields"
|
|
||
| // Satisfy linker | ||
| const uint64_t CodecOptions::NoCompression; | ||
| const uint64_t Http2Settings::CodecOptions::DISABLE_DYNAMIC_HPACK_TABLE; |
There was a problem hiding this comment.
Without this there is no storage actually assigned to store this static variable. That may be OK: if you never require actual storage for the constant int the compiler doesn't complain. But if the static const is passed by reference or it's address taken at some point in the code then it needs an actual storage location (if it's passed by reference someone might take the address later). This means it must be defined (it's declared in the header) in a single CPP file.
Why does it matter here and not in the Envoy executable? In the Envoy executable case they're never passed by reference so the compiler lets you get away with not defining the variable.
| class CodecOptions { | ||
| public: | ||
| static const uint64_t NoCompression = 0x1; | ||
| struct Http2Settings { |
There was a problem hiding this comment.
My personal preference would be to make this a class that hides a bit of it's own logic - I touched on this a bit in another comment. It would enable writing code like calling "http2settings.use_compression()" to find out if compression is enabled rather than requiring the caller to understand that codec_options_ is really a bit field.
There was a problem hiding this comment.
i remove the CodecOptions entirely, and add a new hpack_table_size config.
if this is acceptable, then i will finish the docs.
There was a problem hiding this comment.
If it's only the equivalent of the getter logic now I think it's fine (but @dnoe makes a valid point in general).
| Json::Factory::loadFromString("{\"http_codec_options\": \"no_compression\"}"); | ||
| EXPECT_EQ(CodecOptions::NoCompression, Utility::parseCodecOptions(*json)); | ||
| EXPECT_EQ(Http2Settings::CodecOptions::DisableDynamicHPACKTable, | ||
| Utility::parseHttp2Settings(*json).codec_options_); |
There was a problem hiding this comment.
I think there's more to check here. The other values should be set to their defaults?
| { | ||
| Json::ObjectSharedPtr json = Json::Factory::loadFromString("{\"http_codec_options\": \"foo\"}"); | ||
| EXPECT_THROW(Utility::parseCodecOptions(*json), EnvoyException); | ||
| EXPECT_THROW(Utility::parseHttp2Settings(*json), EnvoyException); |
There was a problem hiding this comment.
You can use EXPECT_THROW_WITH_MESSAGE here, this will make sure it's not hitting some other code that throws EnvoyException.
| Http2Settings::DEFAULT_INITIAL_WINDOW_SIZE), | ||
| Http2SettingsTestParam(Http2Settings::CodecOptions::DisableDynamicHPACKTable, | ||
| Http2Settings::DEFAULT_MAX_CONCURRENT_STREAMS, | ||
| Http2Settings::DEFAULT_INITIAL_WINDOW_SIZE))); |
There was a problem hiding this comment.
You could expand the testing by also parameterizing this test case with the edge cases of valid values for max_concurrent_streams and default_initial_window_size.
1. remove Http2Settings::CodecOptions entirely; 2. change DISABLE_DYNAMIC_HPACK_TABLE to Http2Settings.hpack_table_size_; 3. keep http_codec_options.no_compression config for compatibility, but allow overrides with http2_settings.hpack_table_size.
htuch
left a comment
There was a problem hiding this comment.
Thanks @jwfang, appreciate the effort you've made in putting together a solid PR here. Let's get this shipped, you've done a great job in this review. I think we should revert back the minimum max concurrent streams to 1, I don't think there is any reasonable use of a zero value here, given the interest of testing simplicity and getting this shipped. @PiotrSikora any objections?
| the dynamic HPACK table. Valid values range from 0 to 16777216 (2^24) and defaults to 4096, | ||
| with 0 effectively disables header compression. | ||
|
|
||
| NOTE: A 16MiB (2^24) maximum table size seems to be more than enough. |
There was a problem hiding this comment.
I think this belongs in implementation notes, not in the user facing docs.
There was a problem hiding this comment.
this seems wired, the following is not there:
- NOTE: A 16MiB (2^24) maximum table size seems to be more than enough.
i guess same problem with comment test for below:
if (http2_settings->hasObject("hpack_table_size") && ret.hpack_table_size_ != 0) {
| *(optional, integer)* `Maximum concurrent streams`_ allowed on one HTTP/2 connection. Valid values | ||
| range from 1 to 536870912 (2^29) and defaults to 1024. | ||
|
|
||
| NOTE: Total HTTP/2 streams is 2^31, one side (client/server) is 2^30. To be safe, we use 2^29. |
There was a problem hiding this comment.
all NOTE except we doesn't support decrease window are removed
| for (const std::string& option : StringUtil::split(options, ',')) { | ||
| if (option == "no_compression") { | ||
| ret |= CodecOptions::NoCompression; | ||
| if (http2_settings->hasObject("hpack_table_size") && ret.hpack_table_size_ != 0) { |
There was a problem hiding this comment.
it seems you're seeing an outdated version, :)
it's tested by this, if i am not misunderstanding you.
+ auto json = Json::Factory::loadFromString(R"raw({
+ "http_codec_options": "no_compression",
+ "http2_settings" : {
+ "hpack_table_size": 1
+ }
+ })raw");
+ EXPECT_THROW_WITH_MESSAGE(
+ Utility::parseHttp2Settings(*json), EnvoyException,
+ "'http_codec_options.no_compression' conflicts with 'http2_settings.hpack_table_size'");
} }
|
|
||
| .. _config_http_conn_man_http2_settings_initial_window_size: | ||
|
|
||
| initial_window_size |
There was a problem hiding this comment.
Nit: can you rename this initial_stream_window_size now that we also have initial_connection_window_size?
| // TODO(jwfang): support other HTTP/2 settings | ||
| uint32_t hpack_table_size_{DEFAULT_HPACK_TABLE_SIZE}; | ||
| uint32_t max_concurrent_streams_{DEFAULT_MAX_CONCURRENT_STREAMS}; | ||
| uint32_t initial_window_size_{DEFAULT_INITIAL_WINDOW_SIZE}; |
There was a problem hiding this comment.
Nit: DEFAULT_INITIAL_STREAM_WINDOW_SIZE.
|
|
||
| class Http2CodecImplTest : public testing::TestWithParam<uint64_t> { | ||
| typedef ::testing::tuple<uint32_t, uint32_t, uint32_t, uint32_t> http2SettingsTuple; | ||
| typedef ::testing::tuple<http2SettingsTuple, http2SettingsTuple> http2SettingsTupleTuple; |
There was a problem hiding this comment.
Nit: more descriptive type name than ...TupleTuple please. Also, Please start type names with capital letters, e.g. Http2SettingsTuple.
There was a problem hiding this comment.
done.
i left the client/server_http2settings_ member variables there since i thought they maybe useful
if we need to expand/adapt the test later.
| typedef ::testing::tuple<uint32_t, uint32_t, uint32_t, uint32_t> http2SettingsTuple; | ||
| typedef ::testing::tuple<http2SettingsTuple, http2SettingsTuple> http2SettingsTupleTuple; | ||
|
|
||
| static Http2Settings http2SettingsFromTuple(const http2SettingsTuple& tp) { |
There was a problem hiding this comment.
Nit: slight preference to use an anonymous namespace rather than static in C++.
There was a problem hiding this comment.
another nice trick. thanks.
| } | ||
|
|
||
| // skip test if server-side max concurrent streams is 0 | ||
| #define SKIP_TEST_IF_NOT_SUITABLE \ |
There was a problem hiding this comment.
If you're going to make this zero (and I think it's fine to leave it as 1), then you probably do want to use gmock to verify that we get the right behavior here.
| // TODO(jwfang): make this 0 to support decrease window size | ||
| static const uint32_t MIN_INITIAL_WINDOW_SIZE = (1 << 16) - 1; | ||
| // initial stream-level value from HTTP/2 spec, same as NGHTTP2_INITIAL_WINDOW_SIZE from nghttp2 | ||
| static const uint32_t DEFAULT_INITIAL_WINDOW_SIZE = (1 << 16) - 1; |
There was a problem hiding this comment.
I've noticed that you've changed the default from current 256MB to 64KB resulting in 4096x smaller window size.
As previously noted, 256MB effectively disables flow control, so I definitely agree that we should lower it, but such drastic change will result in different traffic characteristics, and therefor should be committed separately, otherwise we're risking reverting this commit as a whole.
Maybe let's leave it as 256MB in this PR and let's change the default to 1MB in subsequent PR?
@htuch any thoughts?
|
thanks @htuch @PiotrSikora . i summarized desired min/max/default values changes below for the this PR:
new PR: potential TODOs, will results in TODO comments in codes:
|
|
@jwfang SGTM. @PiotrSikora I think we'll be effectively lowering the buffer size and flow control window when implementing the buffer management proposal in #150 (which continues to exist as a P2 for me), so I'm not sure if we need to go ahead and lower it before then. I agree that if we do lower it that 64KB might be too low and have potential downside performance effects in a high bandwidth datacenter or cloud environment. |
| .. _config_http_conn_man_http_codec_options: | ||
|
|
||
| http_codec_options | ||
| **DEPRECATED**, use :ref:`http2_settings <config_http_conn_man_http2_settings>` instead. |
There was a problem hiding this comment.
Can you also update https://github.com/lyft/envoy/blob/master/DEPRECATED.md? Thanks.
|
@htuch @PiotrSikora i change minimum max_concurrent_stream to 1 and default stream/connection_window_size to 256MB. also some other changes from the review. hope nothing is left. BTW: weird the new commits show under my last comment, may need to comment before push. :) |
|
@jwfang Thanks. Maybe something with timestamps was messed up (ignore my previous comment about force push, I see the diffs above). |
|
@ccaraman @RomanDzhabarov @junr03 Do one of you want to take a look at this? |
|
|
||
| hpack_table_size | ||
| *(optional, integer)* `Maximum Table Size`_ (in octets) that the encoder is permitted to use for | ||
| the dynamic HPACK table. Valid values range from 0 to 4294967295 (2^32 - 1) and defaults to 4096, |
There was a problem hiding this comment.
defaults to 4096. 0 effectively disables header compression.
| (2^16 - 1, HTTP/2 default) to 2147483647 (2^31 - 1, HTTP/2 maximum) and defaults to 268435456 | ||
| (256 * 1024 * 1024). | ||
|
|
||
| NOTE: 65535 is the initial window size from HTTP/2 spec. We only support increase the default window |
| Currently supported settings are: | ||
|
|
||
| hpack_table_size | ||
| *(optional, integer)* `Maximum Table Size`_ (in octets) that the encoder is permitted to use for |
There was a problem hiding this comment.
for consistency, can you change the links here to `link-name <link>`_. And delete the links at the end of this section.
|
thanks @htuch . #987 is different from this one, the title is misleading. this PR is to advertise our http/2 setting to our peer, #987 is for envoy to respect peer's setting.
i am closing it and will create a new one if really needed. |
| http2_settings.initial_connection_window_size_ <= | ||
| Http2Settings::MAX_INITIAL_CONNECTION_WINDOW_SIZE); | ||
|
|
||
| std::vector<nghttp2_settings_entry> iv; |
There was a problem hiding this comment.
For posterity, for perf we should switch this to a fixed size array with a pre-defined max size. Added to #210.
Automatic merge from submit-queue. [DO NOT MERGE] Auto PR to update dependencies of proxy This PR will be merged automatically once checks are successful. ```release-note none ```
Description: Changes filter instantiation to be a per-stream basis at the Android platform layers. Risk Level: Low Testing: Local and CI Signed-off-by: Mike Schore <mike.schore@gmail.com> Signed-off-by: JP Simard <jp@jpsim.com>
Description: Changes filter instantiation to be a per-stream basis at the Android platform layers. Risk Level: Low Testing: Local and CI Signed-off-by: Mike Schore <mike.schore@gmail.com> Signed-off-by: JP Simard <jp@jpsim.com>
for #988
WIP, for your preview