1
0
mirror of https://github.com/facebookincubator/mvfst.git synced 2025-08-06 22:22:38 +03:00

Handle lossTime before crypto timer in Quic

Summary:
Currently we handle crypto timer before loss timer. And currently loss
time is for AppData only since for the other two Packet Number spaces, crypto
timer will take care of it. This diff extends loss times to 3 loss times, and
handle them before handle crypto timer. The rational here is that loss time is
shorter than crypto timer, so this will make retransmission during crypto
handshake more aggressive. For app data, this diff doesn't change anything.

Reviewed By: sharma95

Differential Revision: D15552405

fbshipit-source-id: bd5c24b0622c72325ffdea36d0802d4939bae854
This commit is contained in:
Yang Chi
2019-06-17 18:03:41 -07:00
committed by Facebook Github Bot
parent cb8d280620
commit 42b2617e44
8 changed files with 161 additions and 33 deletions

View File

@@ -1001,7 +1001,9 @@ TEST_F(QuicTransportTest, ClonePathChallenge) {
conn.outstandingHandshakePacketsCount = 0; conn.outstandingHandshakePacketsCount = 0;
conn.outstandingPureAckPacketsCount = 0; conn.outstandingPureAckPacketsCount = 0;
conn.outstandingPackets.clear(); conn.outstandingPackets.clear();
conn.lossState.lossTime.clear(); conn.lossState.initialLossTime.clear();
conn.lossState.handshakeLossTime.clear();
conn.lossState.appDataLossTime.clear();
PathChallengeFrame pathChallenge(123); PathChallengeFrame pathChallenge(123);
conn.pendingEvents.pathChallenge = pathChallenge; conn.pendingEvents.pathChallenge = pathChallenge;
@@ -1062,7 +1064,9 @@ TEST_F(QuicTransportTest, OnlyClonePathValidationIfOutstanding) {
conn.outstandingHandshakePacketsCount = 0; conn.outstandingHandshakePacketsCount = 0;
conn.outstandingPureAckPacketsCount = 0; conn.outstandingPureAckPacketsCount = 0;
conn.outstandingPackets.clear(); conn.outstandingPackets.clear();
conn.lossState.lossTime.clear(); conn.lossState.initialLossTime.clear();
conn.lossState.handshakeLossTime.clear();
conn.lossState.appDataLossTime.clear();
PathChallengeFrame pathChallenge(123); PathChallengeFrame pathChallenge(123);
conn.pendingEvents.pathChallenge = pathChallenge; conn.pendingEvents.pathChallenge = pathChallenge;
@@ -1225,7 +1229,9 @@ TEST_F(QuicTransportTest, ClonePathResponse) {
conn.outstandingHandshakePacketsCount = 0; conn.outstandingHandshakePacketsCount = 0;
conn.outstandingPureAckPacketsCount = 0; conn.outstandingPureAckPacketsCount = 0;
conn.outstandingPackets.clear(); conn.outstandingPackets.clear();
conn.lossState.lossTime.clear(); conn.lossState.initialLossTime.clear();
conn.lossState.handshakeLossTime.clear();
conn.lossState.appDataLossTime.clear();
EXPECT_EQ(conn.pendingEvents.frames.size(), 0); EXPECT_EQ(conn.pendingEvents.frames.size(), 0);
PathResponseFrame pathResponse(123); PathResponseFrame pathResponse(123);
@@ -1337,7 +1343,9 @@ TEST_F(QuicTransportTest, CloneNewConnectionIdFrame) {
conn.outstandingHandshakePacketsCount = 0; conn.outstandingHandshakePacketsCount = 0;
conn.outstandingPureAckPacketsCount = 0; conn.outstandingPureAckPacketsCount = 0;
conn.outstandingPackets.clear(); conn.outstandingPackets.clear();
conn.lossState.lossTime.clear(); conn.lossState.initialLossTime.clear();
conn.lossState.handshakeLossTime.clear();
conn.lossState.appDataLossTime.clear();
NewConnectionIdFrame newConnId( NewConnectionIdFrame newConnId(
1, ConnectionId({2, 4, 2, 3}), StatelessResetToken()); 1, ConnectionId({2, 4, 2, 3}), StatelessResetToken());

View File

@@ -65,7 +65,18 @@ calculateAlarmDuration(const QuicConnectionStateBase& conn) {
folly::Optional<LossState::AlarmMethod> alarmMethod; folly::Optional<LossState::AlarmMethod> alarmMethod;
TimePoint lastSentPacketTime = TimePoint lastSentPacketTime =
conn.lossState.lastRetransmittablePacketSentTime; conn.lossState.lastRetransmittablePacketSentTime;
if (conn.outstandingHandshakePacketsCount > 0) { auto lossTimeAndSpace = earliestLossTimer(conn);
if (lossTimeAndSpace.first) {
if (*lossTimeAndSpace.first > lastSentPacketTime) {
// We do this so that lastSentPacketTime + alarmDuration = lossTime
alarmDuration = std::chrono::duration_cast<std::chrono::microseconds>(
*lossTimeAndSpace.first - lastSentPacketTime);
} else {
// This should trigger an immediate alarm.
alarmDuration = 0us;
}
alarmMethod = LossState::AlarmMethod::EarlyRetransmitOrReordering;
} else if (conn.outstandingHandshakePacketsCount > 0) {
if (conn.lossState.srtt == 0us) { if (conn.lossState.srtt == 0us) {
alarmDuration = kDefaultInitialRtt * 2; alarmDuration = kDefaultInitialRtt * 2;
} else { } else {
@@ -78,16 +89,6 @@ calculateAlarmDuration(const QuicConnectionStateBase& conn) {
// Handshake packet loss timer shouldn't be affected by other packets. // Handshake packet loss timer shouldn't be affected by other packets.
lastSentPacketTime = conn.lossState.lastHandshakePacketSentTime; lastSentPacketTime = conn.lossState.lastHandshakePacketSentTime;
DCHECK_NE(lastSentPacketTime.time_since_epoch().count(), 0); DCHECK_NE(lastSentPacketTime.time_since_epoch().count(), 0);
} else if (conn.lossState.lossTime) {
if (*conn.lossState.lossTime > lastSentPacketTime) {
// We do this so that lastSentPacketTime + alarmDuration = lossTime
alarmDuration = std::chrono::duration_cast<std::chrono::microseconds>(
*conn.lossState.lossTime - lastSentPacketTime);
} else {
// This should trigger an immediate alarm.
alarmDuration = 0us;
}
alarmMethod = LossState::AlarmMethod::EarlyRetransmitOrReordering;
} else { } else {
auto ptoTimeout = calculatePTO(conn); auto ptoTimeout = calculatePTO(conn);
ptoTimeout *= 1 << std::min(conn.lossState.ptoCount, (uint32_t)31); ptoTimeout *= 1 << std::min(conn.lossState.ptoCount, (uint32_t)31);
@@ -208,7 +209,7 @@ folly::Optional<CongestionController::LossEvent> detectLossPackets(
const LossVisitor& lossVisitor, const LossVisitor& lossVisitor,
TimePoint lossTime, TimePoint lossTime,
PacketNumberSpace pnSpace) { PacketNumberSpace pnSpace) {
conn.lossState.lossTime.clear(); getLossTime(conn, pnSpace).clear();
std::chrono::microseconds delayUntilLost = std::chrono::microseconds delayUntilLost =
std::max(conn.lossState.srtt, conn.lossState.lrtt) * 9 / 8; std::max(conn.lossState.srtt, conn.lossState.lrtt) * 9 / 8;
VLOG(10) << __func__ << " outstanding=" << conn.outstandingPackets.size() VLOG(10) << __func__ << " outstanding=" << conn.outstandingPackets.size()
@@ -272,10 +273,9 @@ folly::Optional<CongestionController::LossEvent> detectLossPackets(
iter = conn.outstandingPackets.erase(iter); iter = conn.outstandingPackets.erase(iter);
} }
// Because we handle handshake timer before lossTime, lossTime is used by
// AppData space only. So this is fine.
auto earliest = getFirstOutstandingPacket(conn, pnSpace); auto earliest = getFirstOutstandingPacket(conn, pnSpace);
for (; earliest != conn.outstandingPackets.end(); earliest++) { for (; earliest != conn.outstandingPackets.end();
earliest = getNextOutstandingPacket(conn, pnSpace, earliest + 1)) {
if (!earliest->pureAck && if (!earliest->pureAck &&
(!earliest->associatedEvent || (!earliest->associatedEvent ||
conn.outstandingPacketEvents.count(*earliest->associatedEvent))) { conn.outstandingPacketEvents.count(*earliest->associatedEvent))) {
@@ -289,7 +289,7 @@ folly::Optional<CongestionController::LossEvent> detectLossPackets(
<< conn.outstandingPackets.empty() << " delayUntilLost" << conn.outstandingPackets.empty() << " delayUntilLost"
<< delayUntilLost.count() << "us" << delayUntilLost.count() << "us"
<< " " << conn; << " " << conn;
conn.lossState.lossTime = delayUntilLost + earliest->time; getLossTime(conn, pnSpace) = delayUntilLost + earliest->time;
} }
if (lossEvent.largestLostPacketNum.hasValue()) { if (lossEvent.largestLostPacketNum.hasValue()) {
DCHECK(lossEvent.largestLostSentTime && lossEvent.smallestLostSentTime); DCHECK(lossEvent.largestLostSentTime && lossEvent.smallestLostSentTime);
@@ -374,18 +374,16 @@ void onLossDetectionAlarm(
VLOG(10) << "Transmission alarm fired with no outstanding packets " << conn; VLOG(10) << "Transmission alarm fired with no outstanding packets " << conn;
return; return;
} }
// TODO: The specs prioritize EarlyRetransmitOrReordering over crypto timer. if (conn.lossState.currentAlarmMethod ==
if (conn.lossState.currentAlarmMethod == LossState::AlarmMethod::Handshake) {
onHandshakeAlarm<LossVisitor, ClockType>(conn, lossVisitor);
} else if (
conn.lossState.currentAlarmMethod ==
LossState::AlarmMethod::EarlyRetransmitOrReordering) { LossState::AlarmMethod::EarlyRetransmitOrReordering) {
auto lossTimeAndSpace = earliestLossTimer(conn);
CHECK(lossTimeAndSpace.first);
auto lossEvent = detectLossPackets<LossVisitor>( auto lossEvent = detectLossPackets<LossVisitor>(
conn, conn,
getAckState(conn, PacketNumberSpace::AppData).largestAckedByPeer, getAckState(conn, lossTimeAndSpace.second).largestAckedByPeer,
lossVisitor, lossVisitor,
now, now,
PacketNumberSpace::AppData); lossTimeAndSpace.second);
if (conn.congestionController && lossEvent) { if (conn.congestionController && lossEvent) {
DCHECK(lossEvent->largestLostSentTime && lossEvent->smallestLostSentTime); DCHECK(lossEvent->largestLostSentTime && lossEvent->smallestLostSentTime);
lossEvent->persistentCongestion = isPersistentCongestion( lossEvent->persistentCongestion = isPersistentCongestion(
@@ -395,6 +393,9 @@ void onLossDetectionAlarm(
conn.congestionController->onPacketAckOrLoss( conn.congestionController->onPacketAckOrLoss(
folly::none, std::move(lossEvent)); folly::none, std::move(lossEvent));
} }
} else if (
conn.lossState.currentAlarmMethod == LossState::AlarmMethod::Handshake) {
onHandshakeAlarm<LossVisitor, ClockType>(conn, lossVisitor);
} else { } else {
onPTOAlarm(conn); onPTOAlarm(conn);
} }

View File

@@ -766,7 +766,52 @@ TEST_F(QuicLossFunctionsTest, TestTimeReordering) {
->packet.header, ->packet.header,
[](const auto& h) { return h.getPacketSequenceNum(); }); [](const auto& h) { return h.getPacketSequenceNum(); });
EXPECT_EQ(packetNum, 6); EXPECT_EQ(packetNum, 6);
EXPECT_TRUE(conn->lossState.lossTime); EXPECT_TRUE(conn->lossState.appDataLossTime);
}
TEST_F(QuicLossFunctionsTest, LossTimePreemptsCryptoTimer) {
std::vector<PacketNum> lostPackets;
auto conn = createConn();
conn->lossState.srtt = 100ms;
conn->lossState.lrtt = 100ms;
auto expectedDelayUntilLost = 900000us / 8;
auto sendTime = Clock::now();
// Send two:
sendPacket(*conn, sendTime, false, folly::none, PacketType::Handshake);
PacketNum second = sendPacket(
*conn, sendTime + 1ms, false, folly::none, PacketType::Handshake);
auto lossTime = sendTime + 50ms;
detectLossPackets<decltype(testingLossMarkFunc(lostPackets))>(
*conn,
second,
testingLossMarkFunc(lostPackets),
lossTime,
PacketNumberSpace::Handshake);
EXPECT_TRUE(lostPackets.empty());
EXPECT_TRUE(conn->lossState.handshakeLossTime.hasValue());
EXPECT_EQ(
expectedDelayUntilLost + sendTime,
conn->lossState.handshakeLossTime.value());
MockClock::mockNow = [=]() { return sendTime; };
auto alarm = calculateAlarmDuration<MockClock>(*conn);
EXPECT_EQ(
std::chrono::duration_cast<std::chrono::milliseconds>(
expectedDelayUntilLost),
alarm.first);
EXPECT_EQ(LossState::AlarmMethod::EarlyRetransmitOrReordering, alarm.second);
// Manual set lossState. Calling setLossDetectionAlarm requries a Timeout
conn->lossState.currentAlarmMethod = alarm.second;
// Second packet gets acked:
getAckState(*conn, PacketNumberSpace::Handshake).largestAckedByPeer = second;
conn->outstandingPackets.pop_back();
MockClock::mockNow = [=]() { return sendTime + expectedDelayUntilLost + 5s; };
onLossDetectionAlarm<decltype(testingLossMarkFunc(lostPackets)), MockClock>(
*conn, testingLossMarkFunc(lostPackets));
EXPECT_EQ(1, lostPackets.size());
EXPECT_FALSE(conn->lossState.handshakeLossTime.hasValue());
EXPECT_TRUE(conn->outstandingPackets.empty());
} }
TEST_F(QuicLossFunctionsTest, PTONoLongerMarksPacketsToBeRetransmitted) { TEST_F(QuicLossFunctionsTest, PTONoLongerMarksPacketsToBeRetransmitted) {
@@ -805,6 +850,7 @@ TEST_F(
.WillRepeatedly(Return()); .WillRepeatedly(Return());
std::vector<PacketNum> lostPackets; std::vector<PacketNum> lostPackets;
PacketNum expectedLargestLostNum = 0; PacketNum expectedLargestLostNum = 0;
conn->lossState.currentAlarmMethod = LossState::AlarmMethod::Handshake;
for (auto i = 0; i < 10; i++) { for (auto i = 0; i < 10; i++) {
// Half are handshakes // Half are handshakes
auto sentPacketNum = sendPacket( auto sentPacketNum = sendPacket(
@@ -840,6 +886,7 @@ TEST_F(
TEST_F(QuicLossFunctionsTest, HandshakeAlarmWithOneRttCipher) { TEST_F(QuicLossFunctionsTest, HandshakeAlarmWithOneRttCipher) {
auto conn = createClientConn(); auto conn = createClientConn();
conn->oneRttWriteCipher = createNoOpAead(); conn->oneRttWriteCipher = createNoOpAead();
conn->lossState.currentAlarmMethod = LossState::AlarmMethod::Handshake;
std::vector<PacketNum> lostPackets; std::vector<PacketNum> lostPackets;
sendPacket( sendPacket(
*conn, TimePoint(100ms), false, folly::none, PacketType::Handshake); *conn, TimePoint(100ms), false, folly::none, PacketType::Handshake);
@@ -933,7 +980,7 @@ TEST_F(QuicLossFunctionsTest, AlarmDurationHasLossTime) {
TimePoint lastPacketSentTime = Clock::now(); TimePoint lastPacketSentTime = Clock::now();
auto thisMoment = lastPacketSentTime; auto thisMoment = lastPacketSentTime;
MockClock::mockNow = [=]() { return thisMoment; }; MockClock::mockNow = [=]() { return thisMoment; };
conn->lossState.lossTime = thisMoment + 100ms; conn->lossState.appDataLossTime = thisMoment + 100ms;
conn->lossState.srtt = 200ms; conn->lossState.srtt = 200ms;
conn->lossState.lrtt = 150ms; conn->lossState.lrtt = 150ms;
@@ -951,7 +998,7 @@ TEST_F(QuicLossFunctionsTest, AlarmDurationLossTimeIsZero) {
TimePoint lastPacketSentTime = Clock::now(); TimePoint lastPacketSentTime = Clock::now();
auto thisMoment = lastPacketSentTime + 200ms; auto thisMoment = lastPacketSentTime + 200ms;
MockClock::mockNow = [=]() { return thisMoment; }; MockClock::mockNow = [=]() { return thisMoment; };
conn->lossState.lossTime = lastPacketSentTime + 100ms; conn->lossState.appDataLossTime = lastPacketSentTime + 100ms;
conn->lossState.srtt = 200ms; conn->lossState.srtt = 200ms;
conn->lossState.lrtt = 150ms; conn->lossState.lrtt = 150ms;

View File

@@ -1540,7 +1540,9 @@ TEST_F(QuicServerTransportTest, TestCloneStopSending) {
server->getNonConstConn().outstandingHandshakePacketsCount = 0; server->getNonConstConn().outstandingHandshakePacketsCount = 0;
server->getNonConstConn().outstandingPureAckPacketsCount = 0; server->getNonConstConn().outstandingPureAckPacketsCount = 0;
server->getNonConstConn().outstandingPackets.clear(); server->getNonConstConn().outstandingPackets.clear();
server->getNonConstConn().lossState.lossTime.clear(); server->getNonConstConn().lossState.initialLossTime.clear();
server->getNonConstConn().lossState.handshakeLossTime.clear();
server->getNonConstConn().lossState.appDataLossTime.clear();
server->stopSending(streamId, GenericApplicationErrorCode::UNKNOWN); server->stopSending(streamId, GenericApplicationErrorCode::UNKNOWN);
loopForWrites(); loopForWrites();

View File

@@ -11,6 +11,24 @@
#include <quic/common/TimeUtil.h> #include <quic/common/TimeUtil.h>
#include <quic/logging/QuicLogger.h> #include <quic/logging/QuicLogger.h>
namespace {
template <typename V, typename A>
std::pair<folly::Optional<V>, A> minOptional(
std::pair<folly::Optional<V>, A> p1,
std::pair<folly::Optional<V>, A> p2) {
if (!p1.first && !p2.first) {
return std::make_pair(folly::none, p1.second);
}
if (!p1.first) {
return p2;
}
if (!p2.first) {
return p1;
}
return *p1.first < *p2.first ? p1 : p2;
}
} // namespace
namespace quic { namespace quic {
void updateRtt( void updateRtt(
@@ -240,4 +258,30 @@ bool hasReceivedPackets(const QuicConnectionStateBase& conn) noexcept {
conn.ackStates.handshakeAckState.largestReceivedPacketNum || conn.ackStates.handshakeAckState.largestReceivedPacketNum ||
conn.ackStates.appDataAckState.largestReceivedPacketNum; conn.ackStates.appDataAckState.largestReceivedPacketNum;
} }
folly::Optional<TimePoint>& getLossTime(
QuicConnectionStateBase& conn,
PacketNumberSpace pnSpace) noexcept {
switch (pnSpace) {
case PacketNumberSpace::Initial:
return conn.lossState.initialLossTime;
case PacketNumberSpace::Handshake:
return conn.lossState.handshakeLossTime;
case PacketNumberSpace::AppData:
return conn.lossState.appDataLossTime;
}
folly::assume_unreachable();
}
std::pair<folly::Optional<TimePoint>, PacketNumberSpace> earliestLossTimer(
const QuicConnectionStateBase& conn) noexcept {
return minOptional(
minOptional(
std::make_pair(
conn.lossState.initialLossTime, PacketNumberSpace::Initial),
std::make_pair(
conn.lossState.handshakeLossTime, PacketNumberSpace::Handshake)),
std::make_pair(
conn.lossState.appDataLossTime, PacketNumberSpace::AppData));
}
} // namespace quic } // namespace quic

View File

@@ -114,4 +114,11 @@ bool hasNotReceivedNewPacketsSinceLastCloseSent(
void updateLargestReceivedPacketsAtLastCloseSent( void updateLargestReceivedPacketsAtLastCloseSent(
QuicConnectionStateBase& conn) noexcept; QuicConnectionStateBase& conn) noexcept;
folly::Optional<TimePoint>& getLossTime(
QuicConnectionStateBase& conn,
PacketNumberSpace pnSpace) noexcept;
std::pair<folly::Optional<TimePoint>, PacketNumberSpace> earliestLossTimer(
const QuicConnectionStateBase& conn) noexcept;
} // namespace quic } // namespace quic

View File

@@ -313,9 +313,10 @@ struct LossState {
// Reordering threshold used // Reordering threshold used
uint32_t reorderingThreshold{kReorderingThreshold}; uint32_t reorderingThreshold{kReorderingThreshold};
// Timer for time reordering detection or early retransmit alarm. // Timer for time reordering detection or early retransmit alarm.
folly::Optional<TimePoint> lossTime; folly::Optional<TimePoint> initialLossTime, handshakeLossTime,
appDataLossTime;
// Current method by which the loss detection alarm is set. // Current method by which the loss detection alarm is set.
AlarmMethod currentAlarmMethod{AlarmMethod::Handshake}; AlarmMethod currentAlarmMethod{AlarmMethod::EarlyRetransmitOrReordering};
// Total number of packet retransmitted on this connection, including packet // Total number of packet retransmitted on this connection, including packet
// clones, retransmitted clones, handshake and rejected zero rtt packets. // clones, retransmitted clones, handshake and rejected zero rtt packets.
uint32_t rtxCount{0}; uint32_t rtxCount{0};

View File

@@ -529,6 +529,24 @@ TEST_P(QuicStateFunctionsTest, HasNotReceivedNewPacketsSinceLastClose) {
EXPECT_TRUE(hasReceivedPacketsAtLastCloseSent(conn)); EXPECT_TRUE(hasReceivedPacketsAtLastCloseSent(conn));
} }
TEST_F(QuicStateFunctionsTest, EarliestLossTimer) {
QuicConnectionStateBase conn(QuicNodeType::Server);
EXPECT_FALSE(earliestLossTimer(conn).first.hasValue());
auto currentTime = Clock::now();
conn.lossState.initialLossTime = currentTime;
EXPECT_EQ(PacketNumberSpace::Initial, earliestLossTimer(conn).second);
EXPECT_EQ(currentTime, earliestLossTimer(conn).first.value());
conn.lossState.appDataLossTime = currentTime - 1s;
EXPECT_EQ(PacketNumberSpace::AppData, earliestLossTimer(conn).second);
EXPECT_EQ(currentTime - 1s, earliestLossTimer(conn).first.value());
conn.lossState.handshakeLossTime = currentTime + 1s;
EXPECT_EQ(PacketNumberSpace::AppData, earliestLossTimer(conn).second);
EXPECT_EQ(currentTime - 1s, earliestLossTimer(conn).first.value());
conn.lossState.appDataLossTime= currentTime + 1s;
EXPECT_EQ(PacketNumberSpace::Initial, earliestLossTimer(conn).second);
EXPECT_EQ(currentTime, earliestLossTimer(conn).first.value());
}
INSTANTIATE_TEST_CASE_P( INSTANTIATE_TEST_CASE_P(
QuicStateFunctionsTests, QuicStateFunctionsTests,
QuicStateFunctionsTest, QuicStateFunctionsTest,