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

Quic busy write loop detector

Summary:
This diff adds a DebugState in the QuicConnectionState to track the
reason we schedule transport WriteLooper to run, the reason we end up not
writing and the number of times such write happens consecutively. And when it
reaches a predefined limit, we trigger a callback on a loop detector interface.

Reviewed By: kvtsoy

Differential Revision: D15433881

fbshipit-source-id: d903342c00bc4dccf8d7320726368d23295bec66
This commit is contained in:
Yang Chi
2019-07-17 15:15:09 -07:00
committed by Facebook Github Bot
parent cbccfb9f84
commit 2a97d4533c
12 changed files with 277 additions and 53 deletions

View File

@@ -476,7 +476,7 @@ TEST_F(QuicTransportTest, WriteSmall) {
conn.transportSettings.writeConnectionDataPacketsLimit);
verifyCorrectness(conn, 0, stream, *buf);
EXPECT_FALSE(shouldWriteData(conn));
EXPECT_EQ(WriteDataReason::NO_WRITE, shouldWriteData(conn));
}
TEST_F(QuicTransportTest, WriteLarge) {
@@ -511,7 +511,7 @@ TEST_F(QuicTransportTest, WriteLarge) {
conn.transportSettings.writeConnectionDataPacketsLimit);
EXPECT_EQ(NumFullPackets + 1, conn.outstandingPackets.size());
verifyCorrectness(conn, 0, stream, *buf);
EXPECT_FALSE(shouldWriteData(conn));
EXPECT_EQ(WriteDataReason::NO_WRITE, shouldWriteData(conn));
}
TEST_F(QuicTransportTest, WriteMultipleTimes) {
@@ -532,7 +532,7 @@ TEST_F(QuicTransportTest, WriteMultipleTimes) {
transport_->writeChain(stream, buf->clone(), false, false);
loopForWrites();
verifyCorrectness(conn, originalWriteOffset, stream, *buf);
EXPECT_FALSE(shouldWriteData(conn));
EXPECT_EQ(WriteDataReason::NO_WRITE, shouldWriteData(conn));
}
TEST_F(QuicTransportTest, WriteMultipleStreams) {
@@ -695,7 +695,7 @@ TEST_F(QuicTransportTest, WriteFin) {
transport_->getVersion(),
conn.transportSettings.writeConnectionDataPacketsLimit);
verifyCorrectness(conn, 0, stream, *buf, true);
EXPECT_FALSE(shouldWriteData(conn));
EXPECT_EQ(WriteDataReason::NO_WRITE, shouldWriteData(conn));
}
TEST_F(QuicTransportTest, WriteOnlyFin) {
@@ -723,7 +723,7 @@ TEST_F(QuicTransportTest, WriteOnlyFin) {
transport_->getVersion(),
conn.transportSettings.writeConnectionDataPacketsLimit);
verifyCorrectness(conn, 0, stream, *buf, true);
EXPECT_FALSE(shouldWriteData(conn));
EXPECT_EQ(WriteDataReason::NO_WRITE, shouldWriteData(conn));
}
TEST_F(QuicTransportTest, WriteDataWithRetransmission) {
@@ -744,7 +744,7 @@ TEST_F(QuicTransportTest, WriteDataWithRetransmission) {
// lost data and new data
buf->appendChain(std::move(buf2));
verifyCorrectness(conn, 0, stream, *buf);
EXPECT_FALSE(shouldWriteData(conn));
EXPECT_EQ(WriteDataReason::NO_WRITE, shouldWriteData(conn));
}
TEST_F(QuicTransportTest, WriteImmediateAcks) {
@@ -780,7 +780,7 @@ TEST_F(QuicTransportTest, WriteImmediateAcks) {
EXPECT_EQ(conn.ackStates.appDataAckState.largestAckScheduled, end);
EXPECT_FALSE(conn.ackStates.appDataAckState.needsToSendAckImmediately);
EXPECT_EQ(0, conn.ackStates.appDataAckState.numNonRxPacketsRecvd);
EXPECT_FALSE(shouldWriteData(conn));
EXPECT_EQ(WriteDataReason::NO_WRITE, shouldWriteData(conn));
}
TEST_F(QuicTransportTest, NotWriteAcksIfNoData) {
@@ -1397,6 +1397,56 @@ TEST_F(QuicTransportTest, CloneNewConnectionIdFrame) {
EXPECT_EQ(numNewConnIdPackets, 3);
}
TEST_F(QuicTransportTest, BusyWriteLoopDetection) {
auto& conn = transport_->getConnectionState();
conn.transportSettings.writeConnectionDataPacketsLimit = 1;
auto mockLoopDetectorCallback = std::make_unique<MockLoopDetectorCallback>();
auto rawLoopDetectorCallback = mockLoopDetectorCallback.get();
conn.loopDetectorCallback = std::move(mockLoopDetectorCallback);
ASSERT_FALSE(conn.debugState.needsWriteLoopDetect);
ASSERT_EQ(0, conn.debugState.currentEmptyLoopCount);
auto mockCongestionController = std::make_unique<MockCongestionController>();
auto rawCongestionController = mockCongestionController.get();
conn.congestionController = std::move(mockCongestionController);
EXPECT_CALL(*rawCongestionController, getWritableBytes())
.WillRepeatedly(Return(1000));
// There should be no data to send at this point
transport_->updateWriteLooper(true);
EXPECT_FALSE(conn.debugState.needsWriteLoopDetect);
EXPECT_EQ(WriteDataReason::NO_WRITE, conn.debugState.writeDataReason);
EXPECT_EQ(0, conn.debugState.currentEmptyLoopCount);
loopForWrites();
auto stream = transport_->createBidirectionalStream().value();
auto buf = buildRandomInputData(100);
transport_->writeChain(stream, buf->clone(), true, false);
transport_->updateWriteLooper(true);
EXPECT_TRUE(conn.debugState.needsWriteLoopDetect);
EXPECT_EQ(0, conn.debugState.currentEmptyLoopCount);
EXPECT_EQ(WriteDataReason::STREAM, conn.debugState.writeDataReason);
EXPECT_CALL(*socket_, write(_, _)).WillOnce(Return(1000));
loopForWrites();
EXPECT_EQ(1, conn.outstandingPackets.size());
EXPECT_EQ(0, conn.debugState.currentEmptyLoopCount);
// Queue a window update for a stream doesn't exist
conn.streamManager->queueWindowUpdate(stream + 1);
transport_->updateWriteLooper(true);
EXPECT_TRUE(
WriteDataReason::STREAM_WINDOW_UPDATE == conn.debugState.writeDataReason);
EXPECT_CALL(*socket_, write(_, _)).Times(0);
EXPECT_CALL(
*rawLoopDetectorCallback,
onSuspiciousLoops(1, WriteDataReason::STREAM_WINDOW_UPDATE, _, _))
.Times(1);
loopForWrites();
EXPECT_EQ(1, conn.outstandingPackets.size());
EXPECT_EQ(1, conn.debugState.currentEmptyLoopCount);
transport_->close(folly::none);
}
TEST_F(QuicTransportTest, ResendNewConnectionIdOnLoss) {
auto& conn = transport_->getConnectionState();
@@ -2389,7 +2439,9 @@ TEST_F(QuicTransportTest, CloseWithDrainWillKeepSocketAround) {
}
TEST_F(QuicTransportTest, PacedWriteNoDataToWrite) {
ASSERT_FALSE(shouldWriteData(transport_->getConnectionState()));
ASSERT_EQ(
WriteDataReason::NO_WRITE,
shouldWriteData(transport_->getConnectionState()));
EXPECT_CALL(*socket_, write(_, _)).Times(0);
transport_->pacedWrite(true);
}
@@ -2441,7 +2493,7 @@ TEST_F(QuicTransportTest, AlreadyScheduledPacingNoWrite) {
// schedule a pacing timeout.
loopForWrites();
ASSERT_TRUE(shouldWriteData(conn));
ASSERT_NE(WriteDataReason::NO_WRITE, shouldWriteData(conn));
EXPECT_TRUE(transport_->isPacingScheduled());
EXPECT_CALL(*socket_, write(_, _)).Times(0);
transport_->pacedWrite(true);
@@ -2469,7 +2521,7 @@ TEST_F(QuicTransportTest, NoScheduleIfNoNewData) {
// FunctionLooper won't schedule a pacing timeout.
transport_->pacedWrite(true);
ASSERT_FALSE(shouldWriteData(conn));
ASSERT_EQ(WriteDataReason::NO_WRITE, shouldWriteData(conn));
EXPECT_FALSE(transport_->isPacingScheduled());
}