Coalesce adjacent stream frames.
gfe-relnote: protected by gfe2_reloadable_flag_quic_coalesce_stream_frames.
PiperOrigin-RevId: 275506579
Change-Id: I94697cc2ceaffc05d03511a342fcf8743780dd77
diff --git a/quic/core/http/quic_spdy_stream.cc b/quic/core/http/quic_spdy_stream.cc
index f90f3c7..c684916 100644
--- a/quic/core/http/quic_spdy_stream.cc
+++ b/quic/core/http/quic_spdy_stream.cc
@@ -310,7 +310,7 @@
// Write body.
QUIC_DLOG(INFO) << ENDPOINT << "Stream " << id()
<< " is writing DATA frame payload of length "
- << data.length();
+ << data.length() << " with fin " << fin;
WriteOrBufferData(data, fin, nullptr);
}
@@ -1056,7 +1056,7 @@
QUIC_DLOG(INFO) << ENDPOINT << "Stream " << id()
<< " is writing HEADERS frame payload of length "
- << encoded_headers.length();
+ << encoded_headers.length() << " with fin " << fin;
WriteOrBufferData(encoded_headers, fin, nullptr);
QuicSpdySession::LogHeaderCompressionRatioHistogram(
diff --git a/quic/core/quic_packet_creator.cc b/quic/core/quic_packet_creator.cc
index 07fa4b1..ee2dc02 100644
--- a/quic/core/quic_packet_creator.cc
+++ b/quic/core/quic_packet_creator.cc
@@ -11,7 +11,9 @@
#include <utility>
#include "net/third_party/quiche/src/quic/core/crypto/crypto_protocol.h"
+#include "net/third_party/quiche/src/quic/core/frames/quic_frame.h"
#include "net/third_party/quiche/src/quic/core/frames/quic_path_challenge_frame.h"
+#include "net/third_party/quiche/src/quic/core/frames/quic_stream_frame.h"
#include "net/third_party/quiche/src/quic/core/quic_connection_id.h"
#include "net/third_party/quiche/src/quic/core/quic_constants.h"
#include "net/third_party/quiche/src/quic/core/quic_data_writer.h"
@@ -1369,6 +1371,14 @@
QUIC_ATTEMPT_TO_SEND_UNENCRYPTED_STREAM_DATA, error_details);
return false;
}
+
+ if (GetQuicReloadableFlag(quic_coalesce_stream_frames) &&
+ frame.type == STREAM_FRAME &&
+ MaybeCoalesceStreamFrame(frame.stream_frame)) {
+ QUIC_RELOADABLE_FLAG_COUNT(quic_coalesce_stream_frames);
+ return true;
+ }
+
size_t frame_len = framer_->GetSerializedFrameLength(
frame, BytesFree(), queued_frames_.empty(),
/* last_frame_in_packet= */ true, GetPacketNumberLength());
@@ -1412,6 +1422,36 @@
return true;
}
+bool QuicPacketCreator::MaybeCoalesceStreamFrame(const QuicStreamFrame& frame) {
+ if (queued_frames_.empty() || queued_frames_.back().type != STREAM_FRAME) {
+ return false;
+ }
+ QuicStreamFrame* candidate = &queued_frames_.back().stream_frame;
+ if (candidate->stream_id != frame.stream_id ||
+ candidate->offset + candidate->data_length != frame.offset ||
+ frame.data_length > BytesFree()) {
+ return false;
+ }
+ candidate->data_length += frame.data_length;
+ candidate->fin = frame.fin;
+
+ // The back of retransmittable frames must be the same as the original
+ // queued frames' back.
+ DCHECK_EQ(packet_.retransmittable_frames.back().type, STREAM_FRAME);
+ QuicStreamFrame* retransmittable =
+ &packet_.retransmittable_frames.back().stream_frame;
+ DCHECK_EQ(retransmittable->stream_id, frame.stream_id);
+ DCHECK_EQ(retransmittable->offset + retransmittable->data_length,
+ frame.offset);
+ retransmittable->data_length = candidate->data_length;
+ retransmittable->fin = candidate->fin;
+ packet_size_ += frame.data_length;
+ if (debug_delegate_ != nullptr) {
+ debug_delegate_->OnStreamFrameCoalesced(*candidate);
+ }
+ return true;
+}
+
void QuicPacketCreator::MaybeAddPadding() {
// The current packet should have no padding bytes because padding is only
// added when this method is called just before the packet is serialized.
diff --git a/quic/core/quic_packet_creator.h b/quic/core/quic_packet_creator.h
index f202af0..9b4b665 100644
--- a/quic/core/quic_packet_creator.h
+++ b/quic/core/quic_packet_creator.h
@@ -13,6 +13,7 @@
#include <utility>
#include <vector>
+#include "net/third_party/quiche/src/quic/core/frames/quic_stream_frame.h"
#include "net/third_party/quiche/src/quic/core/quic_framer.h"
#include "net/third_party/quiche/src/quic/core/quic_packets.h"
#include "net/third_party/quiche/src/quic/core/quic_pending_retransmission.h"
@@ -60,6 +61,10 @@
// Called when a frame has been added to the current packet.
virtual void OnFrameAddedToPacket(const QuicFrame& /*frame*/) {}
+
+ // Called when a stream frame is coalesced with an existing stream frame.
+ // |frame| is the new stream frame.
+ virtual void OnStreamFrameCoalesced(const QuicStreamFrame& /*frame*/) {}
};
QuicPacketCreator(QuicConnectionId server_connection_id,
@@ -468,6 +473,10 @@
// Clears all fields of packet_ that should be cleared between serializations.
void ClearPacket();
+ // Tries to coalesce |frame| with the back of |queued_frames_|.
+ // Returns true on success.
+ bool MaybeCoalesceStreamFrame(const QuicStreamFrame& frame);
+
// Returns true if a diversification nonce should be included in the current
// packet's header.
bool IncludeNonceInPublicHeader() const;
diff --git a/quic/core/quic_packet_creator_test.cc b/quic/core/quic_packet_creator_test.cc
index a647994..b9779cc 100644
--- a/quic/core/quic_packet_creator_test.cc
+++ b/quic/core/quic_packet_creator_test.cc
@@ -14,6 +14,7 @@
#include "net/third_party/quiche/src/quic/core/crypto/null_encrypter.h"
#include "net/third_party/quiche/src/quic/core/crypto/quic_decrypter.h"
#include "net/third_party/quiche/src/quic/core/crypto/quic_encrypter.h"
+#include "net/third_party/quiche/src/quic/core/frames/quic_stream_frame.h"
#include "net/third_party/quiche/src/quic/core/quic_data_writer.h"
#include "net/third_party/quiche/src/quic/core/quic_pending_retransmission.h"
#include "net/third_party/quiche/src/quic/core/quic_simple_buffer_allocator.h"
@@ -21,6 +22,7 @@
#include "net/third_party/quiche/src/quic/core/quic_utils.h"
#include "net/third_party/quiche/src/quic/platform/api/quic_arraysize.h"
#include "net/third_party/quiche/src/quic/platform/api/quic_expect_bug.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_flags.h"
#include "net/third_party/quiche/src/quic/platform/api/quic_socket_address.h"
#include "net/third_party/quiche/src/quic/platform/api/quic_string_piece.h"
#include "net/third_party/quiche/src/quic/platform/api/quic_test.h"
@@ -79,6 +81,8 @@
~MockDebugDelegate() override = default;
MOCK_METHOD1(OnFrameAddedToPacket, void(const QuicFrame& frame));
+
+ MOCK_METHOD1(OnStreamFrameCoalesced, void(const QuicStreamFrame& frame));
};
class TestPacketCreator : public QuicPacketCreator {
@@ -318,8 +322,10 @@
if (level != ENCRYPTION_INITIAL && level != ENCRYPTION_HANDSHAKE) {
frames_.push_back(
QuicFrame(QuicStreamFrame(stream_id, false, 0u, QuicStringPiece())));
- frames_.push_back(
- QuicFrame(QuicStreamFrame(stream_id, true, 0u, QuicStringPiece())));
+ if (!GetQuicReloadableFlag(quic_coalesce_stream_frames)) {
+ frames_.push_back(
+ QuicFrame(QuicStreamFrame(stream_id, true, 0u, QuicStringPiece())));
+ }
}
SerializedPacket serialized = SerializeAllFrames(frames_);
EXPECT_EQ(level, serialized.encryption_level);
@@ -342,7 +348,9 @@
.WillOnce(Return(true));
if (level != ENCRYPTION_INITIAL && level != ENCRYPTION_HANDSHAKE) {
EXPECT_CALL(framer_visitor_, OnStreamFrame(_));
- EXPECT_CALL(framer_visitor_, OnStreamFrame(_));
+ if (!GetQuicReloadableFlag(quic_coalesce_stream_frames)) {
+ EXPECT_CALL(framer_visitor_, OnStreamFrame(_));
+ }
}
if (client_framer_.version().HasHeaderProtection()) {
EXPECT_CALL(framer_visitor_, OnPaddingFrame(_))
@@ -2334,6 +2342,90 @@
EXPECT_EQ(TestConnectionId(0x33), creator_.GetSourceConnectionId());
}
+TEST_P(QuicPacketCreatorTest, CoalesceStreamFrames) {
+ InSequence s;
+ if (!GetParam().version_serialization) {
+ creator_.StopSendingVersion();
+ }
+ SetQuicReloadableFlag(quic_coalesce_stream_frames, true);
+ const size_t max_plaintext_size =
+ client_framer_.GetMaxPlaintextSize(creator_.max_packet_length());
+ EXPECT_FALSE(creator_.HasPendingFrames());
+ creator_.set_encryption_level(ENCRYPTION_FORWARD_SECURE);
+ QuicStreamId stream_id1 = QuicUtils::GetFirstBidirectionalStreamId(
+ client_framer_.transport_version(), Perspective::IS_CLIENT);
+ QuicStreamId stream_id2 = GetNthClientInitiatedStreamId(1);
+ EXPECT_FALSE(creator_.HasPendingStreamFramesOfStream(stream_id1));
+ EXPECT_EQ(max_plaintext_size -
+ GetPacketHeaderSize(
+ client_framer_.transport_version(),
+ creator_.GetDestinationConnectionIdLength(),
+ creator_.GetSourceConnectionIdLength(),
+ QuicPacketCreatorPeer::SendVersionInPacket(&creator_),
+ !kIncludeDiversificationNonce,
+ QuicPacketCreatorPeer::GetPacketNumberLength(&creator_),
+ QuicPacketCreatorPeer::GetRetryTokenLengthLength(&creator_),
+ 0, QuicPacketCreatorPeer::GetLengthLength(&creator_)),
+ creator_.BytesFree());
+ StrictMock<MockDebugDelegate> debug;
+ creator_.set_debug_delegate(&debug);
+
+ MakeIOVector("test", &iov_);
+ QuicFrame frame;
+ EXPECT_CALL(debug, OnFrameAddedToPacket(_));
+ ASSERT_TRUE(creator_.ConsumeDataToFillCurrentPacket(
+ stream_id1, &iov_, 1u, iov_.iov_len, 0u, 0u, false, false,
+ NOT_RETRANSMISSION, &frame));
+ EXPECT_TRUE(creator_.HasPendingFrames());
+ EXPECT_TRUE(creator_.HasPendingStreamFramesOfStream(stream_id1));
+
+ MakeIOVector("coalesce", &iov_);
+ // frame will be coalesced with the first frame.
+ const auto previous_size = creator_.PacketSize();
+ EXPECT_CALL(debug, OnStreamFrameCoalesced(_));
+ ASSERT_TRUE(creator_.ConsumeDataToFillCurrentPacket(
+ stream_id1, &iov_, 1u, iov_.iov_len, 0u, 4u, true, false,
+ NOT_RETRANSMISSION, &frame));
+ EXPECT_EQ(frame.stream_frame.data_length,
+ creator_.PacketSize() - previous_size);
+ auto queued_frames = QuicPacketCreatorPeer::GetQueuedFrames(&creator_);
+ EXPECT_EQ(1u, queued_frames.size());
+ EXPECT_EQ(12u, queued_frames.front().stream_frame.data_length);
+ EXPECT_TRUE(queued_frames.front().stream_frame.fin);
+
+ // frame is for another stream, so it won't be coalesced.
+ const auto length = creator_.BytesFree() - 10u;
+ std::string large_data("x", length);
+ MakeIOVector(large_data, &iov_);
+ EXPECT_CALL(debug, OnFrameAddedToPacket(_));
+ ASSERT_TRUE(creator_.ConsumeDataToFillCurrentPacket(
+ stream_id2, &iov_, 1u, iov_.iov_len, 0u, 0u, false, false,
+ NOT_RETRANSMISSION, &frame));
+ EXPECT_TRUE(creator_.HasPendingStreamFramesOfStream(stream_id2));
+
+ // The packet doesn't have enough free bytes for all data, but will still be
+ // able to consume and coalesce part of them.
+ EXPECT_CALL(debug, OnStreamFrameCoalesced(_));
+ MakeIOVector("somerandomdata", &iov_);
+ ASSERT_TRUE(creator_.ConsumeDataToFillCurrentPacket(
+ stream_id2, &iov_, 1u, iov_.iov_len, 0u, length, false, false,
+ NOT_RETRANSMISSION, &frame));
+
+ EXPECT_CALL(delegate_, OnSerializedPacket(_))
+ .WillOnce(Invoke(this, &QuicPacketCreatorTest::SaveSerializedPacket));
+ creator_.FlushCurrentPacket();
+ EXPECT_CALL(framer_visitor_, OnPacket());
+ EXPECT_CALL(framer_visitor_, OnUnauthenticatedPublicHeader(_));
+ EXPECT_CALL(framer_visitor_, OnUnauthenticatedHeader(_));
+ EXPECT_CALL(framer_visitor_, OnDecryptedPacket(_));
+ EXPECT_CALL(framer_visitor_, OnPacketHeader(_));
+ // The packet should only have 2 stream frames.
+ EXPECT_CALL(framer_visitor_, OnStreamFrame(_));
+ EXPECT_CALL(framer_visitor_, OnStreamFrame(_));
+ EXPECT_CALL(framer_visitor_, OnPacketComplete());
+ ProcessPacket(serialized_packet_);
+}
+
} // namespace
} // namespace test
} // namespace quic
diff --git a/quic/core/quic_packet_generator_test.cc b/quic/core/quic_packet_generator_test.cc
index b098958..5749ce0 100644
--- a/quic/core/quic_packet_generator_test.cc
+++ b/quic/core/quic_packet_generator_test.cc
@@ -600,14 +600,14 @@
generator_.ConsumeData(
QuicUtils::GetFirstBidirectionalStreamId(framer_.transport_version(),
Perspective::IS_CLIENT),
- &iov_, 1u, iov_.iov_len, 0, FIN);
+ &iov_, 1u, iov_.iov_len, 0, NO_FIN);
MakeIOVector("quux", &iov_);
QuicConsumedData consumed = generator_.ConsumeData(
QuicUtils::GetFirstBidirectionalStreamId(framer_.transport_version(),
Perspective::IS_CLIENT),
- &iov_, 1u, iov_.iov_len, 3, NO_FIN);
+ &iov_, 1u, iov_.iov_len, 3, FIN);
EXPECT_EQ(4u, consumed.bytes_consumed);
- EXPECT_FALSE(consumed.fin_consumed);
+ EXPECT_TRUE(consumed.fin_consumed);
EXPECT_TRUE(generator_.HasPendingFrames());
EXPECT_TRUE(generator_.HasRetransmittableFrames());
@@ -619,7 +619,8 @@
EXPECT_FALSE(generator_.HasRetransmittableFrames());
PacketContents contents;
- contents.num_stream_frames = 2;
+ contents.num_stream_frames =
+ GetQuicReloadableFlag(quic_coalesce_stream_frames) ? 1 : 2;
CheckPacketContains(contents, 0);
}