-
Notifications
You must be signed in to change notification settings - Fork 5.3k
http: fixing a watermark bug #4553
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -21,6 +21,12 @@ namespace Http1 { | |
| const std::string StreamEncoderImpl::CRLF = "\r\n"; | ||
| const std::string StreamEncoderImpl::LAST_CHUNK = "0\r\n\r\n"; | ||
|
|
||
| StreamEncoderImpl::StreamEncoderImpl(ConnectionImpl& connection) : connection_(connection) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same question as above about when this call happens in relation to the |
||
| if (connection_.connection().aboveHighWatermark()) { | ||
| runHighWatermarkCallbacks(); | ||
| } | ||
| } | ||
|
|
||
| void StreamEncoderImpl::encodeHeader(const char* key, uint32_t key_size, const char* value, | ||
| uint32_t value_size) { | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10,18 +10,23 @@ | |
| #include "envoy/buffer/buffer.h" | ||
| #include "envoy/event/dispatcher.h" | ||
| #include "envoy/http/header_map.h" | ||
| #include "envoy/registry/registry.h" | ||
|
|
||
| #include "common/api/api_impl.h" | ||
| #include "common/buffer/buffer_impl.h" | ||
| #include "common/common/fmt.h" | ||
| #include "common/common/thread_annotations.h" | ||
| #include "common/http/headers.h" | ||
| #include "common/network/connection_impl.h" | ||
| #include "common/network/utility.h" | ||
| #include "common/protobuf/utility.h" | ||
| #include "common/upstream/upstream_impl.h" | ||
|
|
||
| #include "extensions/filters/http/common/empty_http_filter_config.h" | ||
|
|
||
| #include "test/common/upstream/utility.h" | ||
| #include "test/integration/autonomous_upstream.h" | ||
| #include "test/integration/pass_through_filter.h" | ||
| #include "test/integration/test_host_predicate_config.h" | ||
| #include "test/integration/utility.h" | ||
| #include "test/mocks/upstream/mocks.h" | ||
|
|
@@ -1275,7 +1280,94 @@ void HttpIntegrationTest::testUpstreamDisconnectWithTwoRequests() { | |
| test_server_->waitForCounterGe("cluster.cluster_0.upstream_rq_200", 2); | ||
| } | ||
|
|
||
| void HttpIntegrationTest::testTwoRequests() { | ||
| // This filter exists to synthetically test network backup by faking TCP | ||
| // connection back-up when an encode is finished, and unblocking it when the | ||
| // next stream starts to decode headers. | ||
| // Allows regression tests for https://github.com/envoyproxy/envoy/issues/4541 | ||
| // TODO(alyssawilk) move this somewhere general and turn it up for more tests. | ||
| class TestPauseFilter : public PassThroughFilter { | ||
| public: | ||
| // Pass in a some global filter state to ensure the Network::Connection is | ||
| // blocked and unblocked exactly once. | ||
| TestPauseFilter(absl::Mutex& encode_lock, uint32_t& number_of_encode_calls_ref, | ||
| uint32_t& number_of_decode_calls_ref) | ||
| : encode_lock_(encode_lock), number_of_encode_calls_ref_(number_of_encode_calls_ref), | ||
| number_of_decode_calls_ref_(number_of_decode_calls_ref) {} | ||
|
|
||
| Http::FilterDataStatus decodeData(Buffer::Instance& buf, bool end_stream) override { | ||
| if (end_stream) { | ||
| absl::WriterMutexLock m(&encode_lock_); | ||
| number_of_decode_calls_ref_++; | ||
| if (number_of_decode_calls_ref_ == 2) { | ||
| // If this is the second stream to decode headers, force low watermark state. | ||
| connection()->onLowWatermark(); | ||
| } | ||
| } | ||
| return PassThroughFilter::decodeData(buf, end_stream); | ||
| } | ||
|
|
||
| Http::FilterDataStatus encodeData(Buffer::Instance& buf, bool end_stream) override { | ||
| if (end_stream) { | ||
| absl::WriterMutexLock m(&encode_lock_); | ||
| number_of_encode_calls_ref_++; | ||
| if (number_of_encode_calls_ref_ == 1) { | ||
| // If this is the first stream to encode headers, force high watermark state. | ||
| connection()->onHighWatermark(); | ||
| } | ||
| } | ||
| return PassThroughFilter::encodeData(buf, end_stream); | ||
| } | ||
|
|
||
| Network::ConnectionImpl* connection() { | ||
| // As long as we're doing horrible things let's do *all* the horrible things. | ||
| // Assert the connection we have is a ConnectionImpl and const cast it so we | ||
| // can force watermark changes. | ||
| auto conn_impl = dynamic_cast<const Network::ConnectionImpl*>(decoder_callbacks_->connection()); | ||
| return const_cast<Network::ConnectionImpl*>(conn_impl); | ||
| } | ||
|
|
||
| absl::Mutex& encode_lock_; | ||
| uint32_t& number_of_encode_calls_ref_; | ||
| uint32_t& number_of_decode_calls_ref_; | ||
| }; | ||
|
|
||
| class TestPauseFilterConfig : public Extensions::HttpFilters::Common::EmptyHttpFilterConfig { | ||
| public: | ||
| TestPauseFilterConfig() : EmptyHttpFilterConfig("pause-filter") {} | ||
|
|
||
| Http::FilterFactoryCb createFilter(const std::string&, Server::Configuration::FactoryContext&) { | ||
| return [&](Http::FilterChainFactoryCallbacks& callbacks) -> void { | ||
| // GUARDED_BY insists the lock be held when the guarded variables are passed by reference. | ||
| absl::WriterMutexLock m(&encode_lock_); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Q: What is the acquiring the lock here required for? Can you add a comment? |
||
| callbacks.addStreamFilter(std::make_shared<::Envoy::TestPauseFilter>( | ||
| encode_lock_, number_of_encode_calls_, number_of_decode_calls_)); | ||
| }; | ||
| } | ||
|
|
||
| absl::Mutex encode_lock_; | ||
| uint32_t number_of_encode_calls_ GUARDED_BY(encode_lock_) = 0; | ||
| uint32_t number_of_decode_calls_ GUARDED_BY(encode_lock_) = 0; | ||
| }; | ||
|
|
||
| // perform static registration | ||
| static Registry::RegisterFactory<TestPauseFilterConfig, | ||
| Server::Configuration::NamedHttpFilterConfigFactory> | ||
| register_; | ||
|
|
||
| void HttpIntegrationTest::testTwoRequests(bool network_backup) { | ||
| // if network_backup is false, this simply tests that Envoy can handle multiple | ||
| // requests on a connection. | ||
| // | ||
| // If network_backup is true, the first request will explicitly set the TCP level flow control | ||
| // as blocked as it finishes the encode and set a timer to unblock. The second stream should be | ||
| // created while the socket appears to be in the high watermark state, and regression tests that | ||
| // flow control will be corrected as the socket "becomes unblocked" | ||
| if (network_backup) { | ||
| config_helper_.addFilter(R"EOF( | ||
| name: pause-filter | ||
| config: {} | ||
| )EOF"); | ||
| } | ||
| initialize(); | ||
|
|
||
| codec_client_ = makeHttpConnection(lookupPort("http")); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| #pragma once | ||
|
|
||
| #include "envoy/http/filter.h" | ||
|
|
||
| namespace Envoy { | ||
|
|
||
| // TODO(alyssawilk) move add_trailers_filter to use this. | ||
| // A filter which passes all data through with Continue status. | ||
| class PassThroughFilter : public Http::StreamFilter { | ||
| public: | ||
| // Http::StreamFilterBase | ||
| void onDestroy() override {} | ||
|
|
||
| // Http::StreamDecoderFilter | ||
| Http::FilterHeadersStatus decodeHeaders(Http::HeaderMap&, bool) override { | ||
| return Http::FilterHeadersStatus::Continue; | ||
| } | ||
| Http::FilterDataStatus decodeData(Buffer::Instance&, bool) override { | ||
| return Http::FilterDataStatus::Continue; | ||
| } | ||
|
|
||
| Http::FilterTrailersStatus decodeTrailers(Http::HeaderMap&) override { | ||
| return Http::FilterTrailersStatus::Continue; | ||
| } | ||
| void setDecoderFilterCallbacks(Http::StreamDecoderFilterCallbacks& callbacks) override { | ||
| decoder_callbacks_ = &callbacks; | ||
| } | ||
|
|
||
| // Http::StreamEncoderFilter | ||
| Http::FilterHeadersStatus encode100ContinueHeaders(Http::HeaderMap&) override { | ||
| return Http::FilterHeadersStatus::Continue; | ||
| } | ||
| Http::FilterHeadersStatus encodeHeaders(Http::HeaderMap&, bool) override { | ||
| return Http::FilterHeadersStatus::Continue; | ||
| } | ||
| Http::FilterDataStatus encodeData(Buffer::Instance&, bool) override { | ||
| return Http::FilterDataStatus::Continue; | ||
| } | ||
| Http::FilterTrailersStatus encodeTrailers(Http::HeaderMap&) override { | ||
| return Http::FilterTrailersStatus::Continue; | ||
| } | ||
| void setEncoderFilterCallbacks(Http::StreamEncoderFilterCallbacks& callbacks) override { | ||
| encoder_callbacks_ = &callbacks; | ||
| } | ||
|
|
||
| protected: | ||
| Http::StreamDecoderFilterCallbacks* decoder_callbacks_{}; | ||
| Http::StreamEncoderFilterCallbacks* encoder_callbacks_{}; | ||
| }; | ||
| } // namespace Envoy |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm confused when/how
high_watermark_count_gets incremented in this call chain in order to make this assert true. From looking at the HTTP/2 codec it looks like we run high watermark callbacks inServerConnectionImpl::onBeginHeadersbefore we callnewStream. Wouldn't we have to run the callbacks instead when we calladdCallbacksfor this to work properly? I'm probably missing something here. If so can you add more comments?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I can interject, the callback are in fact run in
addCallbacks.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah yes, I see it, thanks. Can we potentially clarify the comment? This makes sense. I will take a look at the tests now.