http: fixing upstream stream limit bug#14820
Conversation
Signed-off-by: Alyssa Wilk <alyssar@chromium.org>
yanavlasov
left a comment
There was a problem hiding this comment.
Some initial nits. Will review the rest later.
include/envoy/http/codec.h
Outdated
| }; | ||
|
|
||
| /** | ||
| * A class for sharing what HTTP/1 SETTINGS were received from the peer. |
| // | ||
| // Note that if more HTTP/2 streams have been established than are allowed by | ||
| // a late-received SETTINGS frame, this MAY BE NEGATIVE. | ||
| int64_t connecting_stream_capacity_{}; |
There was a problem hiding this comment.
There is a bit of gotcha here. The reason checkAnd... methods work correctly is because connecting_stream_capacity_ is 64 bit. If it was 32 bit the arithmetic would be unsigned and ASSERTs would fire. Not sure what to do here. Maybe leave a comment that it needs to be 64 bit. Up to you.
There was a problem hiding this comment.
It is easy to overflow capacity when talking to a server that sets the number of streams to UINT32_MAX and the proxy ends up creating multiple upstream connections for that cluster.
| /** | ||
| * A class for sharing what HTTP/1 SETTINGS were received from the peer. | ||
| */ | ||
| class ReceivedSettings { |
There was a problem hiding this comment.
It does not look like you are actually gaining anything by having this interface. In tests you can just construct ReceivedSettingImpl. Might be worthwhile to make it a concrete class without the need for the interface, since it only carries state and no behavior.
There was a problem hiding this comment.
Sorry I'm not sure what you mean by this. The codec interface onSettings needs some way to describe what is passed. Are you suggesting we forward declare ReceivedSettingsImpl there? That doesn't seem consistent with general Envoy API policy.
| // Returns the number of active streams on this connection. | ||
| virtual uint32_t numActiveStreams() const PURE; | ||
|
|
||
| virtual bool hadNegativeDeltaOnStreamClosed() { return false; } |
There was a problem hiding this comment.
nit: maybe leave a comment that h2 pool overrides this and h1 and tcp clients can not have negative concurrent stream count.
|
Thanks, integration test explains this change really well. |
|
|
||
| // Settings frame reducing capacity to one stream per connection results in -1 capacity. | ||
| NiceMock<MockReceivedSettings> settings; | ||
| settings.max_concurrent_streams_ = 1; |
There was a problem hiding this comment.
nit: I would consider:
MockReceivedSettings settings;
EXPECT_CALL(settings, max_concurrent_streams()).WillOnce(Return(1));
But use of max_concurrent_streams_ seems fine too. I'm not seeing tests that don't set max_concurrent_streams_ and exercise the case where the max_concurrent_streams setting is missing.
|
|
||
| template <class T> void checkAndIncrement(T& value, uint32_t delta) { | ||
| ASSERT(std::numeric_limits<T>::max() - value > delta); | ||
| ASSERT(value + delta >= value); |
There was a problem hiding this comment.
These overflow checks may depend on undefined overflow behavior if T is a signed integer type. ASAN may complain if a test were to trigger the ASSERT. Consider instead:
ASSERT(std::numeric_limits<T>::max() - delta > value);
| } | ||
| void decrConnectingStreamCapacity(uint32_t delta) { | ||
| checkAndDecrement<uint64_t>(connecting_stream_capacity_, delta); | ||
| checkAndDecrement<int64_t>(connecting_stream_capacity_, delta); |
There was a problem hiding this comment.
optional: You can omit the template argument on this call. The compiler will deduce the right template argument from the type of the first argument.
checkAndDecrement(connecting_stream_capacity_, delta);
|
|
||
| class ReceivedSettingsImpl : public ReceivedSettings { | ||
| public: | ||
| ReceivedSettingsImpl(const nghttp2_settings& settings); |
There was a problem hiding this comment.
nit: Consider marking this constructor explicit.
| } | ||
| bool hasPendingStreams() const { return !pending_streams_.empty(); } | ||
|
|
||
| void decrClusterStreamCapacity(int32_t delta) { state_.decrConnectingStreamCapacity(delta); } |
There was a problem hiding this comment.
signed to unsigned conversion since decrConnectingStreamCapacity takes an uint32_t. Should this method take a uint32_t delta?
| // Settings frame reducing capacity to one stream per connection results in -1 capacity. | ||
| NiceMock<MockReceivedSettings> settings; | ||
| settings.max_concurrent_streams_ = 1; | ||
| test_clients_[0].codec_client_->onSettings(settings); |
There was a problem hiding this comment.
Verify that a second onSettings call with max_concurrent_streams_ == 1; does not decrease capacity a second time.
Coverage for the case where settings increases max_concurrent_streams_; either above the max concurrency configured at Envoy or the limit set by a previous SETTINGs frame on the same upstream connection.
Signed-off-by: Alyssa Wilk <alyssar@chromium.org>
|
It needs main merge to unblock CI. |
Signed-off-by: Alyssa Wilk <alyssar@chromium.org>
|
/wait |
Signed-off-by: Alyssa Wilk <alyssar@chromium.org>
Signed-off-by: Alyssa Wilk <alyssar@chromium.org>
Signed-off-by: Alyssa Wilk <alyssar@chromium.org>
Signed-off-by: Alyssa Wilk <alyssar@chromium.org>
Signed-off-by: Alyssa Wilk <alyssar@chromium.org>
|
I believe comments are addressed. PTAL when you get a chance! |
7564935 to
4965ca4
Compare
|
@htuch can I get a fresh API stamp? |
antoniovicente
left a comment
There was a problem hiding this comment.
Changes look good, just some minor comments.
|
|
||
| template <class T> void checkAndDecrement(T& value, uint32_t delta) { | ||
| ASSERT(delta <= value); | ||
| ASSERT(value - delta <= value); |
There was a problem hiding this comment.
The reason for my comment is that signed integer overflow is undefined behavior according to the C++ standard. The ASSERT condition you have is only well defined for unsigned integer types.
connecting_stream_capacity_ has a signed integer type.
| bool hadNegativeDeltaOnStreamClosed() override { | ||
| int ret = negative_capacity_ != 0; | ||
| if (negative_capacity_ > 0) { | ||
| negative_capacity_--; |
There was a problem hiding this comment.
Neither the comment in the base class method nor the function name implies that this method has side effects. Would it be possible to document this interface method?
Signed-off-by: Alyssa Wilk <alyssar@chromium.org>
Signed-off-by: Alyssa Wilk <alyssar@chromium.org>
Signed-off-by: Alyssa Wilk <alyssar@chromium.org>
Signed-off-by: Alyssa Wilk <alyssar@chromium.org>
Signed-off-by: Alyssa Wilk <alyssar@chromium.org>
Fixing a bug where the Envoy upstream logic didn't respect stream limits capped by upstream. Backport of 06d0780 into v1.17 Signed-off-by: Yan Avlasov <yavlasov@google.com>
Fixing a bug where the Envoy upstream logic didn't respect stream limits capped by upstream.
Risk Level: Medium (it's complicated code)
Testing: new unit, integration tests
Docs Changes: n/a
Release Notes: inline
Runtime guard: envoy.reloadable_features.improved_stream_limit_handling
Fixes #13933