Buffer priority values in PRIORITY_UPDATE frames for not yet open streams. gfe-relnote: n/a, change to QUIC v99-only code. Protected by existing disabled gfe2_reloadable_flag_quic_enable_version_99. PiperOrigin-RevId: 291618753 Change-Id: Ibf7d8545cc122f66bd9d1b7032b2aece751c3ed9
diff --git a/quic/core/http/quic_spdy_session.cc b/quic/core/http/quic_spdy_session.cc index db93a86..c446160 100644 --- a/quic/core/http/quic_spdy_session.cc +++ b/quic/core/http/quic_spdy_session.cc
@@ -518,9 +518,32 @@ return false; } - MaybeSetStreamPriority(stream_id, spdy::SpdyStreamPrecedence(urgency)); + if (MaybeSetStreamPriority(stream_id, spdy::SpdyStreamPrecedence(urgency))) { + return true; + } - // TODO(b/147306124): Buffer |urgency| for streams not open yet. + if (IsClosedStream(stream_id)) { + return true; + } + + buffered_stream_priorities_[stream_id] = urgency; + + if (buffered_stream_priorities_.size() > + 10 * max_open_incoming_bidirectional_streams()) { + // This should never happen, because |buffered_stream_priorities_| should + // only contain entries for streams that are allowed to be open by the peer + // but have not been opened yet. + std::string error_message = quiche::QuicheStrCat( + "Too many stream priority values buffered: ", + buffered_stream_priorities_.size(), + ", which should not exceed the incoming stream limit of ", + max_open_incoming_bidirectional_streams()); + QUIC_BUG << error_message; + connection()->CloseConnection( + QUIC_INTERNAL_ERROR, error_message, + ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET); + return false; + } return true; } @@ -657,6 +680,16 @@ return qpack_decoder_.get(); } +void QuicSpdySession::OnStreamCreated(QuicSpdyStream* stream) { + auto it = buffered_stream_priorities_.find(stream->id()); + if (it == buffered_stream_priorities_.end()) { + return; + } + + stream->SetPriority(spdy::SpdyStreamPrecedence(it->second)); + buffered_stream_priorities_.erase(it); +} + QuicSpdyStream* QuicSpdySession::GetOrCreateSpdyDataStream( const QuicStreamId stream_id) { QuicStream* stream = GetOrCreateStream(stream_id);
diff --git a/quic/core/http/quic_spdy_session.h b/quic/core/http/quic_spdy_session.h index 53c7c09..71a5c61 100644 --- a/quic/core/http/quic_spdy_session.h +++ b/quic/core/http/quic_spdy_session.h
@@ -281,6 +281,8 @@ (qpack_decoder_ && qpack_decoder_->dynamic_table_entry_referenced()); } + void OnStreamCreated(QuicSpdyStream* stream); + protected: // Override CreateIncomingStream(), CreateOutgoingBidirectionalStream() and // CreateOutgoingUnidirectionalStream() with QuicSpdyStream return type to @@ -451,6 +453,10 @@ // If the endpoint has sent the initial HTTP/3 MAX_PUSH_ID frame. bool http3_max_push_id_sent_; + + // Priority values received in PRIORITY_UPDATE frames for streams that are not + // open yet. + QuicUnorderedMap<QuicStreamId, int> buffered_stream_priorities_; }; } // namespace quic
diff --git a/quic/core/http/quic_spdy_session_test.cc b/quic/core/http/quic_spdy_session_test.cc index 1082633..11c5fda 100644 --- a/quic/core/http/quic_spdy_session_test.cc +++ b/quic/core/http/quic_spdy_session_test.cc
@@ -420,6 +420,15 @@ return std::string(buffer.get(), header_length); } + std::string SerializePriorityUpdateFrame( + const PriorityUpdateFrame& priority_update) { + std::unique_ptr<char[]> priority_buffer; + QuicByteCount priority_frame_length = + HttpEncoder::SerializePriorityUpdateFrame(priority_update, + &priority_buffer); + return std::string(priority_buffer.get(), priority_frame_length); + } + QuicStreamId StreamCountToId(QuicStreamCount stream_count, Perspective perspective, bool bidirectional) { @@ -2134,6 +2143,68 @@ stream->precedence()); } +TEST_P(QuicSpdySessionTestServer, OnPriorityUpdateFrame) { + if (!VersionUsesHttp3(transport_version())) { + return; + } + + // Create control stream. + QuicStreamId receive_control_stream_id = + GetNthClientInitiatedUnidirectionalStreamId(transport_version(), 3); + char type[] = {kControlStream}; + quiche::QuicheStringPiece stream_type(type, 1); + QuicStreamOffset offset = 0; + QuicStreamFrame data1(receive_control_stream_id, false, offset, stream_type); + offset += stream_type.length(); + session_.OnStreamFrame(data1); + EXPECT_EQ(receive_control_stream_id, + QuicSpdySessionPeer::GetReceiveControlStream(&session_)->id()); + + // Send SETTINGS frame. + std::string serialized_settings = EncodeSettings({}); + QuicStreamFrame data2(receive_control_stream_id, false, offset, + serialized_settings); + offset += serialized_settings.length(); + session_.OnStreamFrame(data2); + + // PRIORITY_UPDATE frame for first request stream. + const QuicStreamId stream_id1 = GetNthClientInitiatedBidirectionalId(0); + struct PriorityUpdateFrame priority_update1; + priority_update1.prioritized_element_type = REQUEST_STREAM; + priority_update1.prioritized_element_id = stream_id1; + priority_update1.priority_field_value = "u=1"; + std::string serialized_priority_update1 = + SerializePriorityUpdateFrame(priority_update1); + QuicStreamFrame data3(receive_control_stream_id, + /* fin = */ false, offset, serialized_priority_update1); + offset += serialized_priority_update1.size(); + + // PRIORITY_UPDATE frame arrives after stream creation. + TestStream* stream1 = session_.CreateIncomingStream(stream_id1); + EXPECT_EQ(3u, stream1->precedence().spdy3_priority()); + session_.OnStreamFrame(data3); + EXPECT_EQ(1u, stream1->precedence().spdy3_priority()); + + // PRIORITY_UPDATE frame for second request stream. + const QuicStreamId stream_id2 = GetNthClientInitiatedBidirectionalId(1); + struct PriorityUpdateFrame priority_update2; + priority_update2.prioritized_element_type = REQUEST_STREAM; + priority_update2.prioritized_element_id = stream_id2; + priority_update2.priority_field_value = "u=2"; + std::string serialized_priority_update2 = + SerializePriorityUpdateFrame(priority_update2); + QuicStreamFrame stream_frame3(receive_control_stream_id, + /* fin = */ false, offset, + serialized_priority_update2); + + // PRIORITY_UPDATE frame arrives before stream creation, + // priority value is buffered. + session_.OnStreamFrame(stream_frame3); + // Priority is applied upon stream construction. + TestStream* stream2 = session_.CreateIncomingStream(stream_id2); + EXPECT_EQ(2u, stream2->precedence().spdy3_priority()); +} + TEST_P(QuicSpdySessionTestServer, SimplePendingStreamType) { if (!VersionUsesHttp3(transport_version())) { return;
diff --git a/quic/core/http/quic_spdy_stream.cc b/quic/core/http/quic_spdy_stream.cc index 5d5c176..29e0660 100644 --- a/quic/core/http/quic_spdy_stream.cc +++ b/quic/core/http/quic_spdy_stream.cc
@@ -217,6 +217,8 @@ if (VersionUsesHttp3(transport_version())) { sequencer()->set_level_triggered(true); } + + spdy_session_->OnStreamCreated(this); } QuicSpdyStream::QuicSpdyStream(PendingStream* pending, @@ -251,6 +253,8 @@ if (VersionUsesHttp3(transport_version())) { sequencer()->set_level_triggered(true); } + + spdy_session_->OnStreamCreated(this); } QuicSpdyStream::~QuicSpdyStream() {}