Skip to content
18 changes: 13 additions & 5 deletions source/common/conn_pool/conn_pool_base.cc
Original file line number Diff line number Diff line change
Expand Up @@ -175,18 +175,23 @@ void ConnPoolImplBase::attachStreamToClient(Envoy::ConnectionPool::ActiveClient&
}
ENVOY_CONN_LOG(debug, "creating stream", client);

// Latch capacity before updating remaining streams.
uint64_t capacity = client.currentUnusedCapacity();
client.remaining_streams_--;
if (client.remaining_streams_ == 0) {
ENVOY_CONN_LOG(debug, "maximum streams per connection, DRAINING", client);
host_->cluster().stats().upstream_cx_max_requests_.inc();
transitionActiveClientState(client, Envoy::ConnectionPool::ActiveClient::State::DRAINING);
} else if (client.numActiveStreams() + 1 >= client.concurrent_stream_limit_) {
} else if (capacity <= 1) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm confused by this check. How do we get here if capacity == 0? Wouldn't we not be attaching to this client if we don't have any capacity? Should this by capacity == 1 with an ASSERT that capacity != 0? I'm probably missing something.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, it can't be 0, I was just trying to be as consistent as possible with the prior >=. I'll update to == 1 since it's confusing.

// As soon as the new stream is created, the client will be maxed out.
transitionActiveClientState(client, Envoy::ConnectionPool::ActiveClient::State::BUSY);
}

// Decrement the capacity, as there's one less stream available for serving.
state_.decrConnectingAndConnectedStreamCapacity(1);
// For HTTP/3, the capacity is updated in newStreamEncoder.
if (!quic()) {
state_.decrConnectingAndConnectedStreamCapacity(1);
}
// Track the new active stream.
state_.incrActiveStreams(1);
num_active_streams_++;
Expand All @@ -213,14 +218,17 @@ void ConnPoolImplBase::onStreamClosed(Envoy::ConnectionPool::ActiveClient& clien
// If the effective client capacity was limited by concurrency, increase connecting capacity.
// If the effective client capacity was limited by max total streams, this will not result in an
// increment as no capacity is freed up.
if (client.remaining_streams_ > client.concurrent_stream_limit_ - client.numActiveStreams() - 1 ||
had_negative_capacity) {
// We don't update the capacity for HTTP/3 as the stream count should only
// increase when a MAX_STREAMS frame is received.
if (!quic() && (client.remaining_streams_ >
client.concurrent_stream_limit_ - client.numActiveStreams() - 1 ||
had_negative_capacity)) {
state_.incrConnectingAndConnectedStreamCapacity(1);
}
if (client.state() == ActiveClient::State::DRAINING && client.numActiveStreams() == 0) {
// Close out the draining client if we no longer have active streams.
client.close();
} else if (client.state() == ActiveClient::State::BUSY) {
} else if (client.state() == ActiveClient::State::BUSY && client.currentUnusedCapacity() != 0) {
transitionActiveClientState(client, ActiveClient::State::READY);
if (!delay_attaching_stream) {
onUpstreamReady();
Expand Down
6 changes: 5 additions & 1 deletion source/common/conn_pool/conn_pool_base.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ class ActiveClient : public LinkedObject<ActiveClient>,
// Returns the application protocol, or absl::nullopt for TCP.
virtual absl::optional<Http::Protocol> protocol() const PURE;

int64_t currentUnusedCapacity() const {
virtual int64_t currentUnusedCapacity() const {
int64_t remaining_concurrent_streams =
static_cast<int64_t>(concurrent_stream_limit_) - numActiveStreams();

Comment thread
RyanTheOptimist marked this conversation as resolved.
Expand Down Expand Up @@ -148,6 +148,7 @@ class ConnPoolImplBase : protected Logger::Loggable<Logger::Id::pool> {
virtual ~ConnPoolImplBase();

void deleteIsPendingImpl();
virtual bool quic() { return false; }
Comment thread
RyanTheOptimist marked this conversation as resolved.
Outdated

// A helper function to get the specific context type from the base class context.
template <class T> T& typedContext(AttachContext& context) {
Expand Down Expand Up @@ -234,6 +235,9 @@ class ConnPoolImplBase : protected Logger::Loggable<Logger::Id::pool> {
void decrClusterStreamCapacity(uint32_t delta) {
state_.decrConnectingAndConnectedStreamCapacity(delta);
}
void incrClusterStreamCapacity(uint32_t delta) {
state_.incrConnectingAndConnectedStreamCapacity(delta);
}
void dumpState(std::ostream& os, int indent_level = 0) const {
const char* spaces = spacesForLevel(indent_level);
os << spaces << "ConnPoolImplBase " << this << DUMP_MEMBER(ready_clients_.size())
Expand Down
2 changes: 2 additions & 0 deletions source/common/http/codec_client.h
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,8 @@ class CodecClient : protected Logger::Loggable<Logger::Id::client>,
// Note this is the L4 stream info, not L7.
const StreamInfo::StreamInfo& streamInfo() { return connection_->streamInfo(); }

const Network::ClientConnectionPtr& connection() { return connection_; }

protected:
/**
* Create a codec client and connect to a remote host/port.
Expand Down
13 changes: 13 additions & 0 deletions source/common/http/http3/conn_pool.cc
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,19 @@ ActiveClient::ActiveClient(Envoy::Http::HttpConnPoolImplBase& parent,
Upstream::Host::CreateConnectionData& data)
: MultiplexedActiveClientBase(parent, getMaxStreams(parent.host()->cluster()),
parent.host()->cluster().stats().upstream_cx_http3_total_, data) {
auto& connection = dynamic_cast<ActiveClient*>(this)->codec_client_->connection();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the dyanmic_cast actually needed here? It looks like it's casting from ActiveClient to ActiveClient?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah but due to Envoy "name everything the same thing" this is actually casting from the base class Active Client to the HTTP/3 active client :-P

Quic::EnvoyQuicClientSession* session = const_cast<Quic::EnvoyQuicClientSession*>(
dynamic_cast<const Quic::EnvoyQuicClientSession*>(connection.get()));
Comment thread
RyanTheOptimist marked this conversation as resolved.
Outdated
ASSERT(session != nullptr);
notifier_ = std::make_unique<Quic::ScopedStreamNotifier>(
[this](int32_t s) -> void { onMaxStreamsChanged(s); }, *session);
}

void ActiveClient::onMaxStreamsChanged(uint32_t num_streams) {
updateCapacity(num_streams);
if (state() == ActiveClient::State::BUSY && currentUnusedCapacity() != 0) {
parent_.transitionActiveClientState(*this, ActiveClient::State::READY);
}
}

void Http3ConnPoolImpl::setQuicConfigFromClusterConfig(const Upstream::ClusterInfo& cluster,
Expand Down
52 changes: 52 additions & 0 deletions source/common/http/http3/conn_pool.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,57 @@ class ActiveClient : public MultiplexedActiveClientBase {
public:
ActiveClient(Envoy::Http::HttpConnPoolImplBase& parent,
Upstream::Host::CreateConnectionData& data);

// Update quiche_capacity_ when a MAX_STREAMS frame arrives.
void onMaxStreamsChanged(uint32_t num_streams);

RequestEncoder& newStreamEncoder(ResponseDecoder& response_decoder) override {
ASSERT(quiche_capacity_ != 0);
// Each time a quic stream is allocated the quic capacity needs to get
// decremented. See comments by quiche_capacity_.
updateCapacity(quiche_capacity_ - 1);
return MultiplexedActiveClientBase::newStreamEncoder(response_decoder);
}

// Overload the default capacity calculations to return the quic capacity
// (modified by any stream limits in Envoy config)
int64_t currentUnusedCapacity() const override {
return std::min<int64_t>(quiche_capacity_, effectiveConcurrentStreamLimit());
}

void updateCapacity(uint64_t new_quiche_capacity) {
// Each time we update the capacity make sure to reflect the update in the
// connection pool.
//
// Due to interplay between the max number of concurrent streams Envoy will
// allow and the max number of streams per connection this is not as simple
// as just updating based on the delta between quiche_capacity_ and
// new_quiche_capacity, so we use the delta between the actual calculated
// capacity before and after the update.
Comment thread
RyanTheOptimist marked this conversation as resolved.
uint64_t old_capacity = currentUnusedCapacity();
quiche_capacity_ = new_quiche_capacity;
uint64_t new_capacity = currentUnusedCapacity();

if (new_capacity < old_capacity) {
parent_.decrClusterStreamCapacity(old_capacity - new_capacity);
} else if (old_capacity < new_capacity) {
parent_.incrClusterStreamCapacity(new_capacity - old_capacity);
}
}

std::unique_ptr<Quic::ScopedStreamNotifier> notifier_;
// Unlike HTTP/2 and HTTP/1, rather than having a cap on the number of active
// streams, QUIC has a fixed number of streams available which is updated via
// the MAX_STREAMS frame.
Comment thread
RyanTheOptimist marked this conversation as resolved.
//
// As such each time we create a new stream for QUIC, the capacity goes down
// by one, but unlike the other two codecs it is _not_ restored on stream
// closure.
//
// We track the QUIC capacity here, and overload currentUnusedCapacity so the
// connection pool can accurately keep track of when it is safe to create new
// streams.
uint64_t quiche_capacity_ = 100;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure that this initialization to 100 is quite right. At the QUIC protocol layer, this starts at 0 until the initial_max_streams_bidi transport parameter is received to specify the initial value. After that, it's set to the value from MAX_STREAMS. So I guess this makes me think 2 things.

  1. Should this be initialized to 0?
  2. Should we hook into QuicSession::OnConfigNegotiated() to grab the initial outgoing max streams value?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we need to be at 100 or we'll prefetch a connection for each incoming stream: commented
I would assume that we get a stream update on the initial frame as well, else we should fix upstream.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm. I definitely appreciate the point about the connection prefetch issue. That makes sense to me. We have to be careful that we don't actually send streams on the wire that are above the peers MAX_STREAMS limit or we will commit a protocol violation and the connection will be closed. In quiche, we do this via ShouldCreateOutgoingBidirectionalStream() returning false until we have stream capacity, but with #18614 that will return true. With 0-RTT in particular, we could send a bunch of requests in the first flight and trigger this.

But maybe we already have logic which is queueing requests somewhere to avoid this?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a test to future-proof that we update this before we raise the connected event.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a test to future-proof that we update this before we raise the connected event.

But do we send 0-RTT request before updating this? initial_max_streams_bidi in transport parameters will update this initially. And it seems reasonable to me that 0-RTT can be sent before receiving transport param.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we don't support 0-rtt right now. When we do, we'll want to set this based on the cached params.

};

// Http3 subclass of FixedHttpConnPoolImpl which exists to store quic data.
Expand All @@ -45,6 +96,7 @@ class Http3ConnPoolImpl : public FixedHttpConnPoolImpl {
quic::QuicConfig& quic_config);

Quic::PersistentQuicInfoImpl& quicInfo() { return *quic_info_; }
bool quic() override { return true; }

private:
// Store quic helpers which can be shared between connections and must live
Expand Down
30 changes: 30 additions & 0 deletions source/common/quic/envoy_quic_client_session.cc
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,31 @@

#include "quic_filter_manager_connection_impl.h"

namespace quic {
namespace test {

// TODO(alyssawilk) add the necessary accessors to quiche and remove this.
class QuicSessionPeer {
public:
static quic::QuicStreamIdManager& getStream(Envoy::Quic::EnvoyQuicClientSession* session) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: getStream() => getStreamManager()?

I'm slightly curious if this peer will end up violating the C++ One Definition Rule. Maybe it's never in the same build as the test peer so maybe this is fine? Anyway, it's short lived in any case.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll see your getStreamManager and raise you a getStreamIdManager :-)

return session->ietf_streamid_manager_.bidirectional_stream_id_manager_;
}
};

} // namespace test
} // namespace quic

namespace Envoy {
namespace Quic {

ScopedStreamNotifier::ScopedStreamNotifier(std::function<void(uint32_t)> notify,
EnvoyQuicClientSession& session)
: on_can_create_streams_(notify), session_(session) {
session.setNotifier(*this);
}

ScopedStreamNotifier::~ScopedStreamNotifier() { session_.clearNotifier(); }

EnvoyQuicClientSession::EnvoyQuicClientSession(
const quic::QuicConfig& config, const quic::ParsedQuicVersionVector& supported_versions,
std::unique_ptr<EnvoyQuicClientConnection> connection, const quic::QuicServerId& server_id,
Expand Down Expand Up @@ -87,6 +109,14 @@ void EnvoyQuicClientSession::SetDefaultEncryptionLevel(quic::EncryptionLevel lev
}
}

void EnvoyQuicClientSession::OnCanCreateNewOutgoingStream(bool) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh! I just realized we should probably pay attention to this param. It's unidirectional which is basically for push, in this context. So I think we probably want to just do:

if (unidirectional) {
  return;
}

at the top of this method.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep - I fixed that in one of the later pushes so if you look at all changes, it's a no-op for unidirectional streams :-)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤦 I'm so bad at github UI.

if (notifier_.has_value()) {
quic::QuicStreamIdManager& manager = quic::test::QuicSessionPeer::getStream(this);
uint32_t streams_available = manager.outgoing_max_streams() - manager.outgoing_stream_count();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be nice to ASSERT(manager.outgoing_max_streams() > manager.outgoing_stream_count()) (which should clearly be true given that this method says we can create an outgoing stream).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ack

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we want > not >= here (Because if they're equal then we should NOT be able to open a stream, right?)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah I started with that, but unfortunately it doesn't pan out i
we get at least one call with both numbers equal to 100 Protocols/Http2UpstreamIntegrationTest.ManySimultaneousRequest/IPv4_Http2Downstream_Http3Upstream
seems like a quiche bug but this assert is the best I can do for today

[2021-10-20 20:45:07.866][402][critical][assert] [source/common/quic/envoy_quic_client_session.cc:109] assert failure: manager.outgoing_max_streams() > manager.outgoing_stream_count().
[2021-10-20 20:45:07.866][402][critical][backtrace] [./source/server/backtrace.h:104] Caught Aborted, suspect faulting address 0xad370000000e
[2021-10-20 20:45:07.866][402][critical][backtrace] [./source/server/backtrace.h:91] Backtrace (use tools/stack_decode.py to get line numbers):
[2021-10-20 20:45:07.866][402][critical][backtrace] [./source/server/backtrace.h:92] Envoy version: 0/1.21.0-dev/test/DEBUG/BoringSSL
[2021-10-20 20:45:07.898][402][critical][backtrace] [./source/server/backtrace.h:96] #0: Envoy::SignalAction::sigHandler() [0x3f95079]
[2021-10-20 20:45:07.898][402][critical][backtrace] [./source/server/backtrace.h:96] #1: __restore_rt [0x7f27a3e9e8e0]
[2021-10-20 20:45:07.926][402][critical][backtrace] [./source/server/backtrace.h:96] #2: quic::QuicSession::StreamDraining() [0x35dbdfb]
[2021-10-20 20:45:07.953][402][critical][backtrace] [./source/server/backtrace.h:96] #3: quic::QuicStream::OnStreamFrame() [0x35f3152]
[2021-10-20 20:45:07.978][402][critical][backtrace] [./source/server/backtrace.h:96] #4: quic::QuicSession::OnStreamFrame() [0x35cbc09]
[2021-10-20 20:45:08.004][402][critical][backtrace] [./source/server/backtrace.h:96] #5: quic::QuicConnection::OnStreamFrame() [0x362ce54]
[2021-10-20 20:45:08.029][402][critical][backtrace] [./source/server/backtrace.h:96] #6: quic::QuicFramer::ProcessIetfFrameData() [0x373b77d]
[2021-10-20 20:45:08.055][402][critical][backtrace] [./source/server/backtrace.h:96] #7: quic::QuicFramer::ProcessIetfDataPacket() [0x3734cf0]
[2021-10-20 20:45:08.080][402][critical][backtrace] [./source/server/backtrace.h:96] #8: quic::QuicFramer::ProcessPacketInternal() [0x3731638]
[2021-10-20 20:45:08.106][402][critical][backtrace] [./source/server/backtrace.h:96] #9: quic::QuicFramer::ProcessPacket() [0x3730697]
[2021-10-20 20:45:08.131][402][critical][backtrace] [./source/server/backtrace.h:96] #10: quic::QuicConnection::ProcessUdpPacket() [0x363d12a]
[2021-10-20 20:45:08.152][402][critical][backtrace] [./source/server/backtrace.h:96] #11: Envoy::Quic::EnvoyQuicClientConnection::processPacket() [0x340d85f]
[2021-10-20 20:45:08.173][402][critical][backtrace] [./source/server/backtrace.h:96] #12: Envoy::Network::passPayloadToProcessor() [0x3f1d297]
[2021-10-20 20:45:08.193][402][critical][backtrace] [./source/server/backtrace.h:96] #13: Envoy::Network::Utility::readFromSocket() [0x3f1dc1a]
[2021-10-20 20:45:08.214][402][critical][backtrace] [./source/server/backtrace.h:96] #14: Envoy::Network::Utility::readPacketsFromSocket() [0x3f2041c]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I think you're right that this is a quiche bug. In particular, I think QuicSession::StreamDraining() is doing the wrong thing and needs to wrap the call to OnCanCreateNewOutgoingStream(unidirectional) inside of if (!VersionHasIetfQuicFrames(transport_version())) like the other calls.

So the assert you have sounds fine for now and I'll look into fixing that in quiche, if that SGTY?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SGTM :-)

notifier_->notify(streams_available);
}
}

std::unique_ptr<quic::QuicSpdyClientStream> EnvoyQuicClientSession::CreateClientStream() {
ASSERT(codec_stats_.has_value() && http3_options_.has_value());
return std::make_unique<EnvoyQuicClientStream>(GetNextOutgoingBidirectionalStreamId(), this,
Expand Down
24 changes: 24 additions & 0 deletions source/common/quic/envoy_quic_client_session.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,18 @@
namespace Envoy {
namespace Quic {

class EnvoyQuicClientSession;

struct ScopedStreamNotifier {
Comment thread
RyanTheOptimist marked this conversation as resolved.
Outdated
ScopedStreamNotifier(std::function<void(uint32_t)> notify, EnvoyQuicClientSession& session);
~ScopedStreamNotifier();

void notify(uint32_t streams_available) { on_can_create_streams_(streams_available); }

std::function<void(uint32_t)> on_can_create_streams_;
EnvoyQuicClientSession& session_;
};

// Act as a Network::ClientConnection to ClientCodec.
// TODO(danzh) This class doesn't need to inherit Network::FilterManager
// interface but need all other Network::Connection implementation in
Expand Down Expand Up @@ -71,6 +83,17 @@ class EnvoyQuicClientSession : public QuicFilterManagerConnectionImpl,
// QuicFilterManagerConnectionImpl
void setHttp3Options(const envoy::config::core::v3::Http3ProtocolOptions& http3_options) override;

bool QuicheShouldCreateOutgoingBidirectionalStream() {
return quic::QuicSpdyClientSession::ShouldCreateOutgoingBidirectionalStream();
}

// Notify any registered connection pool when new streams are available.
void OnCanCreateNewOutgoingStream(bool) override;

void setNotifier(ScopedStreamNotifier& notifier) { notifier_ = notifier; }

void clearNotifier() { notifier_.reset(); }

using quic::QuicSpdyClientSession::PerformActionOnActiveStreams;

protected:
Expand Down Expand Up @@ -102,6 +125,7 @@ class EnvoyQuicClientSession : public QuicFilterManagerConnectionImpl,
EnvoyQuicCryptoClientStreamFactoryInterface& crypto_stream_factory_;
QuicStatNames& quic_stat_names_;
Stats::Scope& scope_;
OptRef<ScopedStreamNotifier> notifier_;
Comment thread
alyssawilk marked this conversation as resolved.
Outdated
};

} // namespace Quic
Expand Down
4 changes: 3 additions & 1 deletion source/common/quic/envoy_quic_client_stream.cc
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,9 @@ void EnvoyQuicClientStream::ResetWithError(quic::QuicResetStreamError error) {
stats_.tx_reset_.inc();
// Upper layers expect calling resetStream() to immediately raise reset callbacks.
runResetCallbacks(quicRstErrorToEnvoyLocalResetReason(error.internal_code()));
quic::QuicSpdyClientStream::ResetWithError(error);
if (session()->connection()->connected()) {
quic::QuicSpdyClientStream::ResetWithError(error);
Comment thread
RyanTheOptimist marked this conversation as resolved.
}
}

void EnvoyQuicClientStream::OnConnectionClosed(quic::QuicErrorCode error,
Expand Down
12 changes: 2 additions & 10 deletions test/integration/multiplexed_upstream_integration_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,6 @@ TEST_P(Http2UpstreamIntegrationTest, LargeSimultaneousRequestWithBufferLimits) {
void Http2UpstreamIntegrationTest::manySimultaneousRequests(uint32_t request_bytes,
uint32_t max_response_bytes,
uint32_t num_requests) {
autonomous_allow_incomplete_streams_ = true;
TestRandomGenerator rand;
std::vector<Http::RequestEncoder*> encoders;
std::vector<IntegrationStreamDecoderPtr> responses;
Expand Down Expand Up @@ -236,11 +235,8 @@ void Http2UpstreamIntegrationTest::manySimultaneousRequests(uint32_t request_byt
ASSERT_TRUE(responses[i]->waitForEndStream());
if (i % 2 != 0) {
EXPECT_TRUE(responses[i]->complete());
// TODO(18160) remove this if and always check for 200 and body length.
if (num_requests <= 100 || upstreamProtocol() != Http::CodecType::HTTP3) {
EXPECT_EQ("200", responses[i]->headers().getStatusValue());
EXPECT_EQ(response_bytes[i], responses[i]->body().length());
}
EXPECT_EQ("200", responses[i]->headers().getStatusValue());
EXPECT_EQ(response_bytes[i], responses[i]->body().length());
} else {
// Upstream stream reset.
EXPECT_EQ("503", responses[i]->headers().getStatusValue());
Expand All @@ -255,13 +251,9 @@ TEST_P(Http2UpstreamIntegrationTest, ManySimultaneousRequest) {
manySimultaneousRequests(1024, 1024, 100);
}

#ifdef NDEBUG
Comment thread
RyanTheOptimist marked this conversation as resolved.
// TODO(alyssawilk) this causes crashes in debug mode for QUIC due to a race
// condition between Envoy's stream accounting and QUICE's. Debug and fix.
TEST_P(Http2UpstreamIntegrationTest, TooManySimultaneousRequests) {
manySimultaneousRequests(1024, 1024, 200);
}
#endif

TEST_P(Http2UpstreamIntegrationTest, ManyLargeSimultaneousRequestWithBufferLimits) {
config_helper_.setBufferLimits(1024, 1024); // Set buffer limits upstream and downstream.
Expand Down