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() {}