Do not increase HTTP/3 MAX_STREAMS after sending a GOAWAY. Protected by quic_reloadable_flag_quic_do_not_increase_max_streams_after_h3_goaway. PiperOrigin-RevId: 571187342
diff --git a/quiche/quic/core/http/quic_spdy_session.cc b/quiche/quic/core/http/quic_spdy_session.cc index 4a4bcd9..4789073 100644 --- a/quiche/quic/core/http/quic_spdy_session.cc +++ b/quiche/quic/core/http/quic_spdy_session.cc
@@ -482,7 +482,9 @@ VersionUsesHttp3(transport_version())), force_buffer_requests_until_settings_(false), quic_enable_h3_datagrams_flag_( - GetQuicReloadableFlag(quic_enable_h3_datagrams)) { + GetQuicReloadableFlag(quic_enable_h3_datagrams)), + do_not_increase_max_streams_after_h3_goaway_flag_(GetQuicReloadableFlag( + quic_do_not_increase_max_streams_after_h3_goaway)) { h2_deframer_.set_visitor(spdy_framer_visitor_.get()); h2_deframer_.set_debug_visitor(spdy_framer_visitor_.get()); spdy_framer_.set_debug_visitor(spdy_framer_visitor_.get()); @@ -821,10 +823,14 @@ ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET); return; } - QuicStreamId stream_id; - - stream_id = QuicUtils::GetMaxClientInitiatedBidirectionalStreamId( - transport_version()); + if (do_not_increase_max_streams_after_h3_goaway_flag_) { + QUICHE_RELOADABLE_FLAG_COUNT( + quic_do_not_increase_max_streams_after_h3_goaway); + ietf_streamid_manager().StopIncreasingIncomingMaxStreams(); + } + QuicStreamId stream_id = + QuicUtils::GetMaxClientInitiatedBidirectionalStreamId( + transport_version()); if (last_sent_http3_goaway_id_.has_value() && last_sent_http3_goaway_id_.value() <= stream_id) { // Do not send GOAWAY frame with a higher id, because it is forbidden.
diff --git a/quiche/quic/core/http/quic_spdy_session.h b/quiche/quic/core/http/quic_spdy_session.h index 9592614..9d21b5d 100644 --- a/quiche/quic/core/http/quic_spdy_session.h +++ b/quiche/quic/core/http/quic_spdy_session.h
@@ -714,6 +714,10 @@ // Latched value of quic_enable_h3_datagrams reloadable flag. bool quic_enable_h3_datagrams_flag_; + + // Latched value of quic_do_not_increase_max_streams_after_h3_goaway + // reloadable flag. + bool do_not_increase_max_streams_after_h3_goaway_flag_; }; } // namespace quic
diff --git a/quiche/quic/core/http/quic_spdy_session_test.cc b/quiche/quic/core/http/quic_spdy_session_test.cc index 0ccb217..2d368c2 100644 --- a/quiche/quic/core/http/quic_spdy_session_test.cc +++ b/quiche/quic/core/http/quic_spdy_session_test.cc
@@ -1153,6 +1153,48 @@ session_.SendHttp3GoAway(QUIC_PEER_GOING_AWAY, "Goaway"); } +TEST_P(QuicSpdySessionTestServer, SendHttp3GoAwayAndNoMoreMaxStreams) { + if (!VersionUsesHttp3(transport_version()) || + !GetQuicReloadableFlag( + quic_do_not_increase_max_streams_after_h3_goaway)) { + return; + } + + CompleteHandshake(); + StrictMock<MockHttp3DebugVisitor> debug_visitor; + session_.set_debug_visitor(&debug_visitor); + + EXPECT_CALL(*writer_, WritePacket(_, _, _, _, _, _)) + .WillOnce(Return(WriteResult(WRITE_STATUS_OK, 0))); + // Send max stream id (currently 32 bits). + EXPECT_CALL(debug_visitor, OnGoAwayFrameSent(/* stream_id = */ 0xfffffffc)); + session_.SendHttp3GoAway(QUIC_PEER_GOING_AWAY, "Goaway"); + EXPECT_TRUE(session_.goaway_sent()); + + // No MAX_STREAMS frames should be sent, even after all available + // streams are opened and then closed. + EXPECT_CALL(*connection_, SendControlFrame(_)).Times(0); + + const QuicStreamCount max_streams = + QuicSessionPeer::ietf_streamid_manager(&session_) + ->max_incoming_bidirectional_streams(); + for (QuicStreamCount i = 0; i < max_streams; ++i) { + QuicStreamId stream_id = StreamCountToId( + i + 1, + Perspective::IS_CLIENT, // Client initates stream, allocs stream id. + /*bidirectional=*/true); + EXPECT_NE(nullptr, session_.GetOrCreateStream(stream_id)); + + CloseStream(stream_id); + QuicRstStreamFrame rst_frame(kInvalidControlFrameId, stream_id, + QUIC_STREAM_CANCELLED, + /* bytes_written = */ 0); + session_.OnRstStream(rst_frame); + } + EXPECT_EQ(max_streams, QuicSessionPeer::ietf_streamid_manager(&session_) + ->max_incoming_bidirectional_streams()); +} + TEST_P(QuicSpdySessionTestServer, SendHttp3GoAwayWithoutEncryption) { if (!VersionUsesHttp3(transport_version())) { return;
diff --git a/quiche/quic/core/quic_flags_list.h b/quiche/quic/core/quic_flags_list.h index 5f3b46b..e76cb60 100644 --- a/quiche/quic/core/quic_flags_list.h +++ b/quiche/quic/core/quic_flags_list.h
@@ -77,6 +77,8 @@ QUIC_FLAG(quic_reloadable_flag_quic_disable_batch_write, false) // If true, set burst token to 2 in cwnd bootstrapping experiment. QUIC_FLAG(quic_reloadable_flag_quic_conservative_bursts, false) +// If true, then the HTTP/3 max streams limit will not be increased after sending a GOAWAY +QUIC_FLAG(quic_reloadable_flag_quic_do_not_increase_max_streams_after_h3_goaway, true) // If true, use BBRv2 as the default congestion controller. Takes precedence over --quic_default_to_bbr. QUIC_FLAG(quic_reloadable_flag_quic_default_to_bbr_v2, false) // If true, use a LRU cache to record client addresses of packets received on server\'s original address.
diff --git a/quiche/quic/core/quic_session.h b/quiche/quic/core/quic_session.h index 180d842..f347153 100644 --- a/quiche/quic/core/quic_session.h +++ b/quiche/quic/core/quic_session.h
@@ -836,7 +836,7 @@ connection()->SetLossDetectionTuner(std::move(tuner)); } - const UberQuicStreamIdManager& ietf_streamid_manager() const { + UberQuicStreamIdManager& ietf_streamid_manager() { QUICHE_DCHECK(VersionHasIetfQuicFrames(transport_version())); return ietf_streamid_manager_; }
diff --git a/quiche/quic/core/uber_quic_stream_id_manager.cc b/quiche/quic/core/uber_quic_stream_id_manager.cc index 2c8b1a5..c4b1cea 100644 --- a/quiche/quic/core/uber_quic_stream_id_manager.cc +++ b/quiche/quic/core/uber_quic_stream_id_manager.cc
@@ -98,6 +98,11 @@ return unidirectional_stream_id_manager_.IsAvailableStream(id); } +void UberQuicStreamIdManager::StopIncreasingIncomingMaxStreams() { + unidirectional_stream_id_manager_.StopIncreasingIncomingMaxStreams(); + bidirectional_stream_id_manager_.StopIncreasingIncomingMaxStreams(); +} + QuicStreamCount UberQuicStreamIdManager::GetMaxAllowdIncomingBidirectionalStreams() const { return bidirectional_stream_id_manager_.incoming_initial_max_open_streams();
diff --git a/quiche/quic/core/uber_quic_stream_id_manager.h b/quiche/quic/core/uber_quic_stream_id_manager.h index 7b836d4..8775851 100644 --- a/quiche/quic/core/uber_quic_stream_id_manager.h +++ b/quiche/quic/core/uber_quic_stream_id_manager.h
@@ -68,6 +68,9 @@ // Returns true if |id| is still available. bool IsAvailableStream(QuicStreamId id) const; + // Once called, the incoming max streams limit will never be increased. + void StopIncreasingIncomingMaxStreams(); + QuicStreamCount GetMaxAllowdIncomingBidirectionalStreams() const; QuicStreamCount GetMaxAllowdIncomingUnidirectionalStreams() const;