mirror of
https://github.com/facebookincubator/mvfst.git
synced 2025-08-01 01:44:22 +03:00
Do not emit empty PTO packets
Summary: Do not emit empty PTO packets Reviewed By: mjoras Differential Revision: D34944343 fbshipit-source-id: a49d82cc3d137ed7f113a8e5adab54c84e0e72d0
This commit is contained in:
committed by
Facebook GitHub Bot
parent
f4c103b1c5
commit
42d13f54ae
@ -285,6 +285,7 @@ enum class QuicVersion : uint32_t {
|
|||||||
MVFST_EXPERIMENTAL = 0xfaceb00e, // Experimental alias for MVFST
|
MVFST_EXPERIMENTAL = 0xfaceb00e, // Experimental alias for MVFST
|
||||||
MVFST_ALIAS = 0xfaceb010,
|
MVFST_ALIAS = 0xfaceb010,
|
||||||
MVFST_INVALID = 0xfaceb00f,
|
MVFST_INVALID = 0xfaceb00f,
|
||||||
|
MVFST_EXPERIMENTAL2 = 0xfaceb011, // Experimental alias for MVFST
|
||||||
};
|
};
|
||||||
|
|
||||||
using QuicVersionType = std::underlying_type<QuicVersion>::type;
|
using QuicVersionType = std::underlying_type<QuicVersion>::type;
|
||||||
|
@ -312,11 +312,19 @@ SchedulingResult FrameScheduler::scheduleFramesForPacket(
|
|||||||
return SchedulingResult(folly::none, std::move(builder).buildPacket());
|
return SchedulingResult(folly::none, std::move(builder).buildPacket());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void FrameScheduler::writeNextAcks(PacketBuilderInterface& builder) {
|
||||||
|
ackScheduler_->writeNextAcks(builder);
|
||||||
|
}
|
||||||
|
|
||||||
bool FrameScheduler::hasData() const {
|
bool FrameScheduler::hasData() const {
|
||||||
return (ackScheduler_ && ackScheduler_->hasPendingAcks()) ||
|
return (ackScheduler_ && ackScheduler_->hasPendingAcks()) ||
|
||||||
hasImmediateData();
|
hasImmediateData();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool FrameScheduler::hasPendingAcks() const {
|
||||||
|
return ackScheduler_ && ackScheduler_->hasPendingAcks();
|
||||||
|
}
|
||||||
|
|
||||||
bool FrameScheduler::hasImmediateData() const {
|
bool FrameScheduler::hasImmediateData() const {
|
||||||
return (cryptoStreamScheduler_ && cryptoStreamScheduler_->hasData()) ||
|
return (cryptoStreamScheduler_ && cryptoStreamScheduler_->hasData()) ||
|
||||||
(streamFrameScheduler_ && streamFrameScheduler_->hasPendingData()) ||
|
(streamFrameScheduler_ && streamFrameScheduler_->hasPendingData()) ||
|
||||||
@ -794,7 +802,12 @@ SchedulingResult CloningScheduler::scheduleFramesForPacket(
|
|||||||
// The writableBytes in this function shouldn't be limited by cwnd, since
|
// The writableBytes in this function shouldn't be limited by cwnd, since
|
||||||
// we only use CloningScheduler for the cases that we want to bypass cwnd for
|
// we only use CloningScheduler for the cases that we want to bypass cwnd for
|
||||||
// now.
|
// now.
|
||||||
if (frameScheduler_.hasData()) {
|
bool hasData = frameScheduler_.hasData();
|
||||||
|
if (conn_.version.has_value() &&
|
||||||
|
conn_.version.value() == QuicVersion::MVFST_EXPERIMENTAL2) {
|
||||||
|
hasData = frameScheduler_.hasImmediateData();
|
||||||
|
}
|
||||||
|
if (hasData) {
|
||||||
// Note that there is a possibility that we end up writing nothing here. But
|
// Note that there is a possibility that we end up writing nothing here. But
|
||||||
// if frameScheduler_ hasData() to write, we shouldn't invoke the cloning
|
// if frameScheduler_ hasData() to write, we shouldn't invoke the cloning
|
||||||
// path if the write fails.
|
// path if the write fails.
|
||||||
@ -874,6 +887,14 @@ SchedulingResult CloningScheduler::scheduleFramesForPacket(
|
|||||||
// Rebuilder will write the rest of frames
|
// Rebuilder will write the rest of frames
|
||||||
auto rebuildResult = rebuilder.rebuildFromPacket(outstandingPacket);
|
auto rebuildResult = rebuilder.rebuildFromPacket(outstandingPacket);
|
||||||
if (rebuildResult) {
|
if (rebuildResult) {
|
||||||
|
if (conn_.version.has_value() &&
|
||||||
|
conn_.version.value() == QuicVersion::MVFST_EXPERIMENTAL2) {
|
||||||
|
// Check if we have any acks pending and write those into the cloned
|
||||||
|
// packet as well.
|
||||||
|
if (frameScheduler_.hasPendingAcks()) {
|
||||||
|
frameScheduler_.writeNextAcks(*internalBuilder);
|
||||||
|
}
|
||||||
|
}
|
||||||
return SchedulingResult(
|
return SchedulingResult(
|
||||||
std::move(rebuildResult), std::move(*internalBuilder).buildPacket());
|
std::move(rebuildResult), std::move(*internalBuilder).buildPacket());
|
||||||
} else if (
|
} else if (
|
||||||
|
@ -292,11 +292,17 @@ class FrameScheduler : public QuicPacketScheduler {
|
|||||||
// If any scheduler, including AckScheduler, has pending data to send
|
// If any scheduler, including AckScheduler, has pending data to send
|
||||||
FOLLY_NODISCARD bool hasData() const override;
|
FOLLY_NODISCARD bool hasData() const override;
|
||||||
|
|
||||||
|
// If AckScheduler has any pending acks to write.
|
||||||
|
FOLLY_NODISCARD bool hasPendingAcks() const;
|
||||||
|
|
||||||
// If any of the non-Ack scheduler has pending data to send
|
// If any of the non-Ack scheduler has pending data to send
|
||||||
FOLLY_NODISCARD virtual bool hasImmediateData() const;
|
FOLLY_NODISCARD virtual bool hasImmediateData() const;
|
||||||
|
|
||||||
FOLLY_NODISCARD folly::StringPiece name() const override;
|
FOLLY_NODISCARD folly::StringPiece name() const override;
|
||||||
|
|
||||||
|
// Writes outstanding acks.
|
||||||
|
void writeNextAcks(PacketBuilderInterface& builder);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
folly::Optional<StreamFrameScheduler> streamFrameScheduler_;
|
folly::Optional<StreamFrameScheduler> streamFrameScheduler_;
|
||||||
folly::Optional<AckScheduler> ackScheduler_;
|
folly::Optional<AckScheduler> ackScheduler_;
|
||||||
@ -313,11 +319,11 @@ class FrameScheduler : public QuicPacketScheduler {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* A packet scheduler wrapping a normal FrameScheduler with the ability to clone
|
* A packet scheduler wrapping a normal FrameScheduler with the ability to clone
|
||||||
* exiting packets that are still outstanding. A CloningScheduler first trie to
|
* exiting packets that are still outstanding. A CloningScheduler first tries to
|
||||||
* write new farmes with new data into a packet. If that fails due to the lack
|
* write new frames with new data into a packet. If that fails due to the lack
|
||||||
* of new data, it falls back to cloning one inflight packet from a connection's
|
* of new data, it falls back to cloning one inflight packet from a connection's
|
||||||
* oustanding packets if there is at least one outstanding packet that's smaller
|
* oustanding packets if there is at least one outstanding packet that's smaller
|
||||||
* than the writableBytes limit, and isn't a Handshake packet.
|
* than the writableBytes limit.
|
||||||
*/
|
*/
|
||||||
class CloningScheduler : public QuicPacketScheduler {
|
class CloningScheduler : public QuicPacketScheduler {
|
||||||
public:
|
public:
|
||||||
|
@ -35,6 +35,7 @@ class MockFrameScheduler : public FrameScheduler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
MOCK_METHOD((bool), hasData, (), (const));
|
MOCK_METHOD((bool), hasData, (), (const));
|
||||||
|
MOCK_METHOD((bool), hasImmediateData, (), (const));
|
||||||
MOCK_METHOD(
|
MOCK_METHOD(
|
||||||
SchedulingResult,
|
SchedulingResult,
|
||||||
_scheduleFramesForPacket,
|
_scheduleFramesForPacket,
|
||||||
|
@ -726,6 +726,94 @@ TEST_F(QuicPacketSchedulerTest, CloneSchedulerHasHandshakeData) {
|
|||||||
EXPECT_TRUE(cloningScheduler.hasData());
|
EXPECT_TRUE(cloningScheduler.hasData());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This test case covers the following scenario:
|
||||||
|
1) conn sent out a handshake packet that did not get acked yet
|
||||||
|
2) conn received some handshake data that needs to be acked
|
||||||
|
3) imitate that we're emitting a PTO packet (that is generated via cloning
|
||||||
|
scheduler)
|
||||||
|
4) emitted cloned packet MUST have both cloned crypto data AND ack
|
||||||
|
frame(s)
|
||||||
|
|
||||||
|
There was a bug that would result in mvfst emit a "empty" PTO packet with
|
||||||
|
acks; this is the test case to cover that scenario.
|
||||||
|
*/
|
||||||
|
TEST_F(QuicPacketSchedulerTest, CloneSchedulerHasHandshakeDataAndAcks) {
|
||||||
|
QuicServerConnectionState conn(
|
||||||
|
FizzServerQuicHandshakeContext::Builder().build());
|
||||||
|
conn.version = QuicVersion::MVFST_EXPERIMENTAL2;
|
||||||
|
|
||||||
|
FrameScheduler noopScheduler = std::move(FrameScheduler::Builder(
|
||||||
|
conn,
|
||||||
|
EncryptionLevel::Handshake,
|
||||||
|
PacketNumberSpace::Handshake,
|
||||||
|
"testScheduler")
|
||||||
|
.ackFrames())
|
||||||
|
.build();
|
||||||
|
addHandshakeOutstandingPacket(conn);
|
||||||
|
|
||||||
|
// Add some crypto data for the outstanding packet to make it look legit.
|
||||||
|
// This is so cloning scheduler can actually copy something.
|
||||||
|
getCryptoStream(*conn.cryptoState, EncryptionLevel::Handshake)
|
||||||
|
->retransmissionBuffer.emplace(
|
||||||
|
0,
|
||||||
|
std::make_unique<StreamBuffer>(
|
||||||
|
folly::IOBuf::copyBuffer("test"), 0, false));
|
||||||
|
conn.outstandings.packets.back().packet.frames.push_back(
|
||||||
|
WriteCryptoFrame(0, 4));
|
||||||
|
|
||||||
|
// Make it look like we received some acks from the peer.
|
||||||
|
conn.ackStates.handshakeAckState.acks.insert(10);
|
||||||
|
conn.ackStates.handshakeAckState.largestRecvdPacketTime = Clock::now();
|
||||||
|
|
||||||
|
// Create cloning scheduler.
|
||||||
|
CloningScheduler cloningScheduler(noopScheduler, conn, "CopyCat", 0);
|
||||||
|
EXPECT_TRUE(cloningScheduler.hasData());
|
||||||
|
|
||||||
|
// Get the packet builder going for the clone packet.
|
||||||
|
PacketNum nextPacketNum =
|
||||||
|
getNextPacketNum(conn, PacketNumberSpace::Handshake);
|
||||||
|
std::vector<uint8_t> zeroConnIdData(quic::kDefaultConnectionIdSize, 0);
|
||||||
|
ConnectionId srcConnId(zeroConnIdData);
|
||||||
|
LongHeader header(
|
||||||
|
LongHeader::Types::Handshake,
|
||||||
|
srcConnId,
|
||||||
|
conn.clientConnectionId.value_or(quic::test::getTestConnectionId()),
|
||||||
|
nextPacketNum,
|
||||||
|
QuicVersion::QUIC_DRAFT);
|
||||||
|
RegularQuicPacketBuilder builder(
|
||||||
|
conn.udpSendPacketLen,
|
||||||
|
std::move(header),
|
||||||
|
conn.ackStates.appDataAckState.largestAckedByPeer.value_or(0));
|
||||||
|
|
||||||
|
// Clone the packet.
|
||||||
|
auto result = cloningScheduler.scheduleFramesForPacket(
|
||||||
|
std::move(builder), kDefaultUDPSendPacketLen);
|
||||||
|
EXPECT_TRUE(result.packetEvent.has_value());
|
||||||
|
EXPECT_TRUE(result.packet.has_value());
|
||||||
|
|
||||||
|
// Cloned packet has to have at least one ack frame AND the crypto data.
|
||||||
|
bool hasAckFrame = false;
|
||||||
|
bool hasCryptoFrame = false;
|
||||||
|
for (auto iter = result.packet->packet.frames.cbegin();
|
||||||
|
iter != result.packet->packet.frames.cend();
|
||||||
|
iter++) {
|
||||||
|
const QuicWriteFrame& frame = *iter;
|
||||||
|
switch (frame.type()) {
|
||||||
|
case QuicWriteFrame::Type::WriteAckFrame:
|
||||||
|
hasAckFrame = true;
|
||||||
|
break;
|
||||||
|
case QuicWriteFrame::Type::WriteCryptoFrame:
|
||||||
|
hasCryptoFrame = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EXPECT_TRUE(hasAckFrame);
|
||||||
|
EXPECT_TRUE(hasCryptoFrame);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_F(QuicPacketSchedulerTest, CloneSchedulerHasInitialData) {
|
TEST_F(QuicPacketSchedulerTest, CloneSchedulerHasInitialData) {
|
||||||
QuicClientConnectionState conn(
|
QuicClientConnectionState conn(
|
||||||
FizzClientQuicHandshakeContext::Builder().build());
|
FizzClientQuicHandshakeContext::Builder().build());
|
||||||
@ -779,13 +867,16 @@ TEST_F(QuicPacketSchedulerTest, DoNotCloneHandshake) {
|
|||||||
TEST_F(QuicPacketSchedulerTest, CloneSchedulerUseNormalSchedulerFirst) {
|
TEST_F(QuicPacketSchedulerTest, CloneSchedulerUseNormalSchedulerFirst) {
|
||||||
QuicClientConnectionState conn(
|
QuicClientConnectionState conn(
|
||||||
FizzClientQuicHandshakeContext::Builder().build());
|
FizzClientQuicHandshakeContext::Builder().build());
|
||||||
|
conn.version = QuicVersion::MVFST_EXPERIMENTAL2;
|
||||||
NiceMock<MockFrameScheduler> mockScheduler(&conn);
|
NiceMock<MockFrameScheduler> mockScheduler(&conn);
|
||||||
CloningScheduler cloningScheduler(mockScheduler, conn, "Mocker", 0);
|
CloningScheduler cloningScheduler(mockScheduler, conn, "Mocker", 0);
|
||||||
ShortHeader header(
|
ShortHeader header(
|
||||||
ProtectionType::KeyPhaseOne,
|
ProtectionType::KeyPhaseOne,
|
||||||
conn.clientConnectionId.value_or(getTestConnectionId()),
|
conn.clientConnectionId.value_or(getTestConnectionId()),
|
||||||
getNextPacketNum(conn, PacketNumberSpace::AppData));
|
getNextPacketNum(conn, PacketNumberSpace::AppData));
|
||||||
EXPECT_CALL(mockScheduler, hasData()).Times(1).WillOnce(Return(true));
|
EXPECT_CALL(mockScheduler, hasImmediateData())
|
||||||
|
.Times(1)
|
||||||
|
.WillOnce(Return(true));
|
||||||
|
|
||||||
EXPECT_CALL(mockScheduler, _scheduleFramesForPacket(_, _))
|
EXPECT_CALL(mockScheduler, _scheduleFramesForPacket(_, _))
|
||||||
.Times(1)
|
.Times(1)
|
||||||
|
@ -451,6 +451,8 @@ std::string toString(QuicVersion version) {
|
|||||||
return "MVFST_ALIAS";
|
return "MVFST_ALIAS";
|
||||||
case QuicVersion::MVFST_INVALID:
|
case QuicVersion::MVFST_INVALID:
|
||||||
return "MVFST_INVALID";
|
return "MVFST_INVALID";
|
||||||
|
case QuicVersion::MVFST_EXPERIMENTAL2:
|
||||||
|
return "MVFST_EXPERIMENTAL2";
|
||||||
}
|
}
|
||||||
LOG(WARNING) << "toString has unhandled version type";
|
LOG(WARNING) << "toString has unhandled version type";
|
||||||
return "UNKNOWN";
|
return "UNKNOWN";
|
||||||
|
@ -398,6 +398,7 @@ class QuicServer : public QuicServerWorker::WorkerCallback,
|
|||||||
std::vector<QuicVersion> supportedVersions_{
|
std::vector<QuicVersion> supportedVersions_{
|
||||||
{QuicVersion::MVFST,
|
{QuicVersion::MVFST,
|
||||||
QuicVersion::MVFST_EXPERIMENTAL,
|
QuicVersion::MVFST_EXPERIMENTAL,
|
||||||
|
QuicVersion::MVFST_EXPERIMENTAL2,
|
||||||
QuicVersion::MVFST_ALIAS,
|
QuicVersion::MVFST_ALIAS,
|
||||||
QuicVersion::QUIC_V1,
|
QuicVersion::QUIC_V1,
|
||||||
QuicVersion::QUIC_DRAFT}};
|
QuicVersion::QUIC_DRAFT}};
|
||||||
|
@ -157,6 +157,7 @@ struct QuicServerConnectionState : public QuicConnectionStateBase {
|
|||||||
supportedVersions = std::vector<QuicVersion>{
|
supportedVersions = std::vector<QuicVersion>{
|
||||||
{QuicVersion::MVFST,
|
{QuicVersion::MVFST,
|
||||||
QuicVersion::MVFST_EXPERIMENTAL,
|
QuicVersion::MVFST_EXPERIMENTAL,
|
||||||
|
QuicVersion::MVFST_EXPERIMENTAL2,
|
||||||
QuicVersion::MVFST_ALIAS,
|
QuicVersion::MVFST_ALIAS,
|
||||||
QuicVersion::QUIC_V1,
|
QuicVersion::QUIC_V1,
|
||||||
QuicVersion::QUIC_DRAFT}};
|
QuicVersion::QUIC_DRAFT}};
|
||||||
|
Reference in New Issue
Block a user