mirror of
https://github.com/facebookincubator/mvfst.git
synced 2025-08-08 09:42:06 +03:00
Add CWND and writable bytes to PacketsWrittenEvent
Summary: Extend `PacketsWrittenEvent` to include the CWND and number of writable bytes from the congestion controller after the write completed. If no congestion controller is installed, these values are left blank. Differential Revision: D32588533 fbshipit-source-id: 4b8361937fcab36daa17e5f6dc4386762987d2b4
This commit is contained in:
committed by
Facebook GitHub Bot
parent
f30df88637
commit
da71a15307
@@ -38,6 +38,18 @@ Observer::WriteEvent::Builder::setLastPacketSentTime(
|
||||
return std::move(*this);
|
||||
}
|
||||
|
||||
Observer::WriteEvent::Builder&& Observer::WriteEvent::Builder::setCwndInBytes(
|
||||
const folly::Optional<uint64_t>& maybeCwndInBytesIn) {
|
||||
maybeCwndInBytes = maybeCwndInBytesIn;
|
||||
return std::move(*this);
|
||||
}
|
||||
|
||||
Observer::WriteEvent::Builder&& Observer::WriteEvent::Builder::setWritableBytes(
|
||||
const folly::Optional<uint64_t>& maybeWritableBytesIn) {
|
||||
maybeWritableBytes = maybeWritableBytesIn;
|
||||
return std::move(*this);
|
||||
}
|
||||
|
||||
Observer::WriteEvent Observer::WriteEvent::Builder::build() && {
|
||||
return WriteEvent(*this);
|
||||
}
|
||||
@@ -46,7 +58,9 @@ Observer::WriteEvent::WriteEvent(const WriteEvent::BuilderFields& builderFields)
|
||||
: outstandingPackets(*CHECK_NOTNULL(
|
||||
builderFields.maybeOutstandingPacketsRef.get_pointer())),
|
||||
writeCount(*CHECK_NOTNULL(builderFields.maybeWriteCount.get_pointer())),
|
||||
maybeLastPacketSentTime(builderFields.maybeLastPacketSentTime) {}
|
||||
maybeLastPacketSentTime(builderFields.maybeLastPacketSentTime),
|
||||
maybeCwndInBytes(builderFields.maybeCwndInBytes),
|
||||
maybeWritableBytes(builderFields.maybeWritableBytes) {}
|
||||
|
||||
Observer::AppLimitedEvent::Builder&&
|
||||
Observer::AppLimitedEvent::Builder::setOutstandingPackets(
|
||||
@@ -75,6 +89,20 @@ Observer::AppLimitedEvent::Builder::setLastPacketSentTime(
|
||||
return std::move(*this);
|
||||
}
|
||||
|
||||
Observer::AppLimitedEvent::Builder&&
|
||||
Observer::AppLimitedEvent::Builder::setCwndInBytes(
|
||||
const folly::Optional<uint64_t>& maybeCwndInBytesIn) {
|
||||
maybeCwndInBytes = maybeCwndInBytesIn;
|
||||
return std::move(*this);
|
||||
}
|
||||
|
||||
Observer::AppLimitedEvent::Builder&&
|
||||
Observer::AppLimitedEvent::Builder::setWritableBytes(
|
||||
const folly::Optional<uint64_t>& maybeWritableBytesIn) {
|
||||
maybeWritableBytes = maybeWritableBytesIn;
|
||||
return std::move(*this);
|
||||
}
|
||||
|
||||
Observer::AppLimitedEvent Observer::AppLimitedEvent::Builder::build() && {
|
||||
return AppLimitedEvent(std::move(*this));
|
||||
}
|
||||
@@ -111,6 +139,20 @@ Observer::PacketsWrittenEvent::Builder::setLastPacketSentTime(
|
||||
return std::move(*this);
|
||||
}
|
||||
|
||||
Observer::PacketsWrittenEvent::Builder&&
|
||||
Observer::PacketsWrittenEvent::Builder::setCwndInBytes(
|
||||
const folly::Optional<uint64_t>& maybeCwndInBytesIn) {
|
||||
maybeCwndInBytes = maybeCwndInBytesIn;
|
||||
return std::move(*this);
|
||||
}
|
||||
|
||||
Observer::PacketsWrittenEvent::Builder&&
|
||||
Observer::PacketsWrittenEvent::Builder::setWritableBytes(
|
||||
const folly::Optional<uint64_t>& maybeWritableBytesIn) {
|
||||
maybeWritableBytes = maybeWritableBytesIn;
|
||||
return std::move(*this);
|
||||
}
|
||||
|
||||
Observer::PacketsWrittenEvent::Builder&&
|
||||
Observer::PacketsWrittenEvent::Builder::setNumPacketsWritten(
|
||||
const uint64_t numPacketsWrittenIn) {
|
||||
|
@@ -111,12 +111,24 @@ class Observer {
|
||||
// Timestamp when packet was last written
|
||||
const folly::Optional<TimePoint> maybeLastPacketSentTime;
|
||||
|
||||
// CWND in bytes.
|
||||
//
|
||||
// Optional to handle cases where congestion controller not used.
|
||||
const folly::Optional<uint64_t> maybeCwndInBytes;
|
||||
|
||||
// Writable bytes.
|
||||
//
|
||||
// Optional to handle cases where congestion controller not used.
|
||||
const folly::Optional<uint64_t> maybeWritableBytes;
|
||||
|
||||
struct BuilderFields {
|
||||
folly::Optional<
|
||||
std::reference_wrapper<const std::deque<OutstandingPacket>>>
|
||||
maybeOutstandingPacketsRef;
|
||||
folly::Optional<uint64_t> maybeWriteCount;
|
||||
folly::Optional<TimePoint> maybeLastPacketSentTime;
|
||||
folly::Optional<uint64_t> maybeCwndInBytes;
|
||||
folly::Optional<uint64_t> maybeWritableBytes;
|
||||
explicit BuilderFields() = default;
|
||||
};
|
||||
|
||||
@@ -127,6 +139,10 @@ class Observer {
|
||||
Builder&& setLastPacketSentTime(const TimePoint& lastPacketSentTimeIn);
|
||||
Builder&& setLastPacketSentTime(
|
||||
const folly::Optional<TimePoint>& maybeLastPacketSentTimeIn);
|
||||
Builder&& setCwndInBytes(
|
||||
const folly::Optional<uint64_t>& maybeCwndInBytesIn);
|
||||
Builder&& setWritableBytes(
|
||||
const folly::Optional<uint64_t>& maybeWritableBytesIn);
|
||||
WriteEvent build() &&;
|
||||
explicit Builder() = default;
|
||||
};
|
||||
@@ -149,6 +165,10 @@ class Observer {
|
||||
Builder&& setLastPacketSentTime(const TimePoint& lastPacketSentTimeIn);
|
||||
Builder&& setLastPacketSentTime(
|
||||
const folly::Optional<TimePoint>& maybeLastPacketSentTimeIn);
|
||||
Builder&& setCwndInBytes(
|
||||
const folly::Optional<uint64_t>& maybeCwndInBytesIn);
|
||||
Builder&& setWritableBytes(
|
||||
const folly::Optional<uint64_t>& maybeWritableBytesIn);
|
||||
AppLimitedEvent build() &&;
|
||||
explicit Builder() = default;
|
||||
};
|
||||
@@ -186,6 +206,10 @@ class Observer {
|
||||
Builder&& setNumAckElicitingPacketsWritten(
|
||||
const uint64_t numAckElicitingPacketsWrittenIn);
|
||||
Builder&& setNumBytesWritten(const uint64_t numBytesWrittenIn);
|
||||
Builder&& setCwndInBytes(
|
||||
const folly::Optional<uint64_t>& maybeCwndInBytesIn);
|
||||
Builder&& setWritableBytes(
|
||||
const folly::Optional<uint64_t>& maybeWritableBytesIn);
|
||||
PacketsWrittenEvent build() &&;
|
||||
explicit Builder() = default;
|
||||
};
|
||||
|
@@ -3547,6 +3547,16 @@ void QuicTransportBase::notifyStartWritingFromAppRateLimited() {
|
||||
.setOutstandingPackets(conn_->outstandings.packets)
|
||||
.setWriteCount(conn_->writeCount)
|
||||
.setLastPacketSentTime(conn_->lossState.maybeLastPacketSentTime)
|
||||
.setCwndInBytes(
|
||||
conn_->congestionController
|
||||
? folly::Optional<uint64_t>(
|
||||
conn_->congestionController->getCongestionWindow())
|
||||
: folly::none)
|
||||
.setWritableBytes(
|
||||
conn_->congestionController
|
||||
? folly::Optional<uint64_t>(
|
||||
conn_->congestionController->getWritableBytes())
|
||||
: folly::none)
|
||||
.build();
|
||||
for (const auto& cb : *observers_) {
|
||||
if (cb->getConfig().appRateLimitedEvents) {
|
||||
@@ -3564,6 +3574,16 @@ void QuicTransportBase::notifyPacketsWritten(
|
||||
.setOutstandingPackets(conn_->outstandings.packets)
|
||||
.setWriteCount(conn_->writeCount)
|
||||
.setLastPacketSentTime(conn_->lossState.maybeLastPacketSentTime)
|
||||
.setCwndInBytes(
|
||||
conn_->congestionController
|
||||
? folly::Optional<uint64_t>(
|
||||
conn_->congestionController->getCongestionWindow())
|
||||
: folly::none)
|
||||
.setWritableBytes(
|
||||
conn_->congestionController
|
||||
? folly::Optional<uint64_t>(
|
||||
conn_->congestionController->getWritableBytes())
|
||||
: folly::none)
|
||||
.setNumPacketsWritten(numPacketsWritten)
|
||||
.setNumAckElicitingPacketsWritten(numAckElicitingPacketsWritten)
|
||||
.setNumBytesWritten(numBytesWritten)
|
||||
@@ -3581,6 +3601,16 @@ void QuicTransportBase::notifyAppRateLimited() {
|
||||
.setOutstandingPackets(conn_->outstandings.packets)
|
||||
.setWriteCount(conn_->writeCount)
|
||||
.setLastPacketSentTime(conn_->lossState.maybeLastPacketSentTime)
|
||||
.setCwndInBytes(
|
||||
conn_->congestionController
|
||||
? folly::Optional<uint64_t>(
|
||||
conn_->congestionController->getCongestionWindow())
|
||||
: folly::none)
|
||||
.setWritableBytes(
|
||||
conn_->congestionController
|
||||
? folly::Optional<uint64_t>(
|
||||
conn_->congestionController->getWritableBytes())
|
||||
: folly::none)
|
||||
.build();
|
||||
for (const auto& cb : *observers_) {
|
||||
if (cb->getConfig().appRateLimitedEvents) {
|
||||
|
@@ -19,6 +19,7 @@
|
||||
#include <quic/common/BufUtil.h>
|
||||
#include <quic/common/Timers.h>
|
||||
#include <quic/common/test/TestUtils.h>
|
||||
#include <quic/congestion_control/StaticCwndCongestionController.h>
|
||||
#include <quic/dsr/Types.h>
|
||||
#include <quic/dsr/test/Mocks.h>
|
||||
#include <quic/handshake/test/Mocks.h>
|
||||
@@ -1060,6 +1061,266 @@ TEST_F(QuicTransportTest, ObserverPacketsWrittenCheckBytesSent) {
|
||||
transport_ = nullptr;
|
||||
}
|
||||
|
||||
TEST_F(QuicTransportTest, ObserverWriteEventsCheckCwndPacketsWritable) {
|
||||
InSequence s;
|
||||
|
||||
Observer::Config config = {};
|
||||
config.packetsWrittenEvents = true;
|
||||
config.appRateLimitedEvents = true;
|
||||
auto cb1 = std::make_unique<StrictMock<MockObserver>>(config);
|
||||
auto cb2 = std::make_unique<StrictMock<MockObserver>>(config);
|
||||
auto cb3 = std::make_unique<StrictMock<MockObserver>>(Observer::Config());
|
||||
const auto invokeForAllObservers =
|
||||
[&cb1, &cb2, &cb3](const std::function<void(MockObserver&)>& fn) {
|
||||
fn(*cb1);
|
||||
fn(*cb2);
|
||||
fn(*cb3);
|
||||
};
|
||||
const auto invokeForEachObserverWithTestEvents =
|
||||
[&cb1, &cb2](const std::function<void(MockObserver&)>& fn) {
|
||||
fn(*cb1);
|
||||
fn(*cb2);
|
||||
};
|
||||
|
||||
// install observers
|
||||
invokeForAllObservers(([this](MockObserver& observer) {
|
||||
EXPECT_CALL(observer, observerAttach(transport_.get()));
|
||||
transport_->addObserver(&observer);
|
||||
}));
|
||||
EXPECT_THAT(
|
||||
transport_->getObservers(),
|
||||
UnorderedElementsAre(cb1.get(), cb2.get(), cb3.get()));
|
||||
|
||||
auto& conn = transport_->getConnectionState();
|
||||
|
||||
// install StaticCwndCongestionController
|
||||
const auto cwndInBytes = 10000;
|
||||
conn.congestionController = std::make_unique<StaticCwndCongestionController>(
|
||||
StaticCwndCongestionController::CwndInBytes(cwndInBytes));
|
||||
|
||||
// update writeNum and upperBoundCurrentBytesWritable after each write/ACK
|
||||
uint64_t writeNum = 1;
|
||||
uint64_t upperBoundCurrentBytesWritable = cwndInBytes;
|
||||
|
||||
// write of 4000 stream bytes
|
||||
{
|
||||
const auto bytesToWrite = 4000;
|
||||
|
||||
// matcher for event from startWritingFromAppLimited
|
||||
const auto startWritingFromAppLimitedMatcher = AllOf(
|
||||
testing::Property(
|
||||
&Observer::WriteEvent::getOutstandingPackets, testing::IsEmpty()),
|
||||
testing::Field(
|
||||
&Observer::WriteEvent::writeCount, testing::Eq(writeNum)),
|
||||
testing::Field(
|
||||
&Observer::WriteEvent::maybeCwndInBytes,
|
||||
testing::Eq(folly::Optional<uint64_t>(cwndInBytes))),
|
||||
testing::Field(
|
||||
&Observer::WriteEvent::maybeWritableBytes,
|
||||
testing::Eq(folly::Optional<uint64_t>(cwndInBytes))));
|
||||
|
||||
// matcher for event from packetsWritten
|
||||
const auto packetsWrittenMatcher = AllOf(
|
||||
testing::Property(
|
||||
&Observer::WriteEvent::getOutstandingPackets, testing::SizeIs(4)),
|
||||
testing::Field(
|
||||
&Observer::WriteEvent::writeCount, testing::Eq(writeNum)),
|
||||
testing::Field(
|
||||
&Observer::WriteEvent::maybeCwndInBytes,
|
||||
testing::Eq(folly::Optional<uint64_t>(cwndInBytes))),
|
||||
testing::Field( // precise check below
|
||||
&Observer::WriteEvent::maybeWritableBytes,
|
||||
testing::Lt(folly::Optional<uint64_t>(
|
||||
upperBoundCurrentBytesWritable - bytesToWrite))),
|
||||
testing::Field(
|
||||
&Observer::PacketsWrittenEvent::numPacketsWritten, testing::Eq(4)),
|
||||
testing::Field(
|
||||
&Observer::PacketsWrittenEvent::numAckElicitingPacketsWritten,
|
||||
testing::Eq(4)),
|
||||
testing::Field(
|
||||
&Observer::PacketsWrittenEvent::numBytesWritten,
|
||||
testing::Gt(bytesToWrite)));
|
||||
|
||||
// matcher for event from appRateLimited
|
||||
const auto appRateLimitedMatcher = AllOf(
|
||||
testing::Property(
|
||||
&Observer::WriteEvent::getOutstandingPackets, testing::SizeIs(4)),
|
||||
testing::Field(
|
||||
&Observer::WriteEvent::writeCount, testing::Eq(writeNum)),
|
||||
testing::Field(
|
||||
&Observer::WriteEvent::maybeCwndInBytes,
|
||||
testing::Eq(folly::Optional<uint64_t>(cwndInBytes))),
|
||||
testing::Field( // precise check below
|
||||
&Observer::WriteEvent::maybeWritableBytes,
|
||||
testing::Lt(folly::Optional<uint64_t>(
|
||||
upperBoundCurrentBytesWritable - bytesToWrite))));
|
||||
|
||||
invokeForEachObserverWithTestEvents(
|
||||
([this, &startWritingFromAppLimitedMatcher](MockObserver& observer) {
|
||||
EXPECT_CALL(
|
||||
observer,
|
||||
startWritingFromAppLimited(
|
||||
transport_.get(), startWritingFromAppLimitedMatcher));
|
||||
}));
|
||||
|
||||
invokeForEachObserverWithTestEvents(
|
||||
([this,
|
||||
&packetsWrittenMatcher,
|
||||
cwndInBytes,
|
||||
oldTInfo = transport_->getTransportInfo()](MockObserver& observer) {
|
||||
EXPECT_CALL(
|
||||
observer, packetsWritten(transport_.get(), packetsWrittenMatcher))
|
||||
.WillOnce(([cwndInBytes, oldTInfo](
|
||||
const auto& socket, const auto& event) {
|
||||
EXPECT_EQ(
|
||||
cwndInBytes - socket->getTransportInfo().bytesSent -
|
||||
oldTInfo.bytesSent,
|
||||
event.maybeWritableBytes);
|
||||
}));
|
||||
}));
|
||||
|
||||
invokeForEachObserverWithTestEvents(
|
||||
([this,
|
||||
&appRateLimitedMatcher,
|
||||
cwndInBytes,
|
||||
oldTInfo = transport_->getTransportInfo()](MockObserver& observer) {
|
||||
EXPECT_CALL(
|
||||
observer, appRateLimited(transport_.get(), appRateLimitedMatcher))
|
||||
.WillOnce(([cwndInBytes, oldTInfo](
|
||||
const auto& socket, const auto& event) {
|
||||
EXPECT_EQ(
|
||||
cwndInBytes - socket->getTransportInfo().bytesSent -
|
||||
oldTInfo.bytesSent,
|
||||
event.maybeWritableBytes);
|
||||
}));
|
||||
}));
|
||||
|
||||
auto stream = transport_->createBidirectionalStream().value();
|
||||
transport_->writeChain(
|
||||
stream, buildRandomInputData(bytesToWrite), false, nullptr);
|
||||
transport_->updateWriteLooper(true);
|
||||
loopForWrites();
|
||||
loopForWrites();
|
||||
|
||||
// remove bytesToWrite from upperBoundCurrentBytesWritable
|
||||
upperBoundCurrentBytesWritable -= bytesToWrite;
|
||||
writeNum++;
|
||||
}
|
||||
|
||||
// another write of 1000 stream bytes
|
||||
{
|
||||
const auto bytesToWrite = 1000;
|
||||
|
||||
// matcher for event from startWritingFromAppLimited
|
||||
const auto startWritingFromAppLimitedMatcher = AllOf(
|
||||
testing::Property(
|
||||
&Observer::WriteEvent::getOutstandingPackets, testing::SizeIs(4)),
|
||||
testing::Field(
|
||||
&Observer::WriteEvent::writeCount, testing::Eq(writeNum)),
|
||||
testing::Field(
|
||||
&Observer::WriteEvent::maybeCwndInBytes,
|
||||
testing::Eq(folly::Optional<uint64_t>(cwndInBytes))),
|
||||
testing::Field(
|
||||
&Observer::WriteEvent::maybeWritableBytes,
|
||||
testing::Lt(
|
||||
folly::Optional<uint64_t>(upperBoundCurrentBytesWritable))));
|
||||
|
||||
// matcher for event from packetsWritten
|
||||
const auto packetsWrittenMatcher = AllOf(
|
||||
testing::Property(
|
||||
&Observer::WriteEvent::getOutstandingPackets, testing::SizeIs(5)),
|
||||
testing::Field(
|
||||
&Observer::WriteEvent::writeCount, testing::Eq(writeNum)),
|
||||
testing::Field(
|
||||
&Observer::WriteEvent::maybeCwndInBytes,
|
||||
testing::Eq(folly::Optional<uint64_t>(cwndInBytes))),
|
||||
testing::Field( // precise check below
|
||||
&Observer::WriteEvent::maybeWritableBytes,
|
||||
testing::Lt(folly::Optional<uint64_t>(
|
||||
upperBoundCurrentBytesWritable - bytesToWrite))),
|
||||
testing::Field(
|
||||
&Observer::PacketsWrittenEvent::numPacketsWritten, testing::Eq(1)),
|
||||
testing::Field(
|
||||
&Observer::PacketsWrittenEvent::numAckElicitingPacketsWritten,
|
||||
testing::Eq(1)),
|
||||
testing::Field(
|
||||
&Observer::PacketsWrittenEvent::numBytesWritten,
|
||||
testing::Gt(bytesToWrite)));
|
||||
|
||||
// matcher for event from appRateLimited
|
||||
const auto appRateLimitedMatcher = AllOf(
|
||||
testing::Property(
|
||||
&Observer::WriteEvent::getOutstandingPackets, testing::SizeIs(5)),
|
||||
testing::Field(
|
||||
&Observer::WriteEvent::writeCount, testing::Eq(writeNum)),
|
||||
testing::Field(
|
||||
&Observer::WriteEvent::maybeCwndInBytes,
|
||||
testing::Eq(folly::Optional<uint64_t>(cwndInBytes))),
|
||||
testing::Field( // precise check below
|
||||
&Observer::WriteEvent::maybeWritableBytes,
|
||||
testing::Lt(folly::Optional<uint64_t>(
|
||||
upperBoundCurrentBytesWritable - bytesToWrite))));
|
||||
|
||||
invokeForEachObserverWithTestEvents(
|
||||
([this, &startWritingFromAppLimitedMatcher](MockObserver& observer) {
|
||||
EXPECT_CALL(
|
||||
observer,
|
||||
startWritingFromAppLimited(
|
||||
transport_.get(), startWritingFromAppLimitedMatcher));
|
||||
}));
|
||||
|
||||
invokeForEachObserverWithTestEvents(
|
||||
([this,
|
||||
&packetsWrittenMatcher,
|
||||
oldTInfo = transport_->getTransportInfo()](MockObserver& observer) {
|
||||
EXPECT_CALL(
|
||||
observer, packetsWritten(transport_.get(), packetsWrittenMatcher))
|
||||
.WillOnce(([oldTInfo](const auto& socket, const auto& event) {
|
||||
EXPECT_EQ(
|
||||
oldTInfo.writableBytes -
|
||||
(socket->getTransportInfo().bytesSent -
|
||||
oldTInfo.bytesSent),
|
||||
event.maybeWritableBytes);
|
||||
}));
|
||||
}));
|
||||
|
||||
invokeForEachObserverWithTestEvents(
|
||||
([this,
|
||||
&appRateLimitedMatcher,
|
||||
oldTInfo = transport_->getTransportInfo()](MockObserver& observer) {
|
||||
EXPECT_CALL(
|
||||
observer, appRateLimited(transport_.get(), appRateLimitedMatcher))
|
||||
.WillOnce(([oldTInfo](const auto& socket, const auto& event) {
|
||||
EXPECT_EQ(
|
||||
oldTInfo.writableBytes -
|
||||
(socket->getTransportInfo().bytesSent -
|
||||
oldTInfo.bytesSent),
|
||||
event.maybeWritableBytes);
|
||||
}));
|
||||
}));
|
||||
|
||||
auto stream = transport_->createBidirectionalStream().value();
|
||||
transport_->writeChain(
|
||||
stream, buildRandomInputData(bytesToWrite), false, nullptr);
|
||||
transport_->updateWriteLooper(true);
|
||||
loopForWrites();
|
||||
loopForWrites();
|
||||
|
||||
// remove bytesToWrite from upperBoundCurrentBytesWritable
|
||||
upperBoundCurrentBytesWritable -= bytesToWrite;
|
||||
writeNum++;
|
||||
}
|
||||
|
||||
invokeForAllObservers(([this](MockObserver& observer) {
|
||||
EXPECT_CALL(observer, close(transport_.get(), _));
|
||||
}));
|
||||
invokeForAllObservers(([this](MockObserver& observer) {
|
||||
EXPECT_CALL(observer, destroy(transport_.get()));
|
||||
}));
|
||||
transport_->close(folly::none);
|
||||
transport_ = nullptr;
|
||||
}
|
||||
|
||||
TEST_F(QuicTransportTest, ObserverStreamEventBidirectionalLocalOpenClose) {
|
||||
Observer::Config configWithStreamEvents = {};
|
||||
configWithStreamEvents.streamEvents = true;
|
||||
|
@@ -7,11 +7,13 @@
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
|
||||
#include <quic/api/test/Mocks.h>
|
||||
#include <quic/api/test/QuicTypedTransportTestUtil.h>
|
||||
#include <quic/codec/Types.h>
|
||||
#include <quic/congestion_control/StaticCwndCongestionController.h>
|
||||
#include <quic/fizz/client/test/QuicClientTransportTestUtil.h>
|
||||
#include <quic/server/test/QuicServerTransportTestUtil.h>
|
||||
#include <quic/state/AckEvent.h>
|
||||
@@ -94,7 +96,7 @@ TYPED_TEST(QuicTypedTransportTest, TransportInfoMinRtt) {
|
||||
{
|
||||
// get the packet's send time
|
||||
const auto packetSentTime =
|
||||
CHECK_NOTNULL(this->getLastAppDataPacketWritten())->metadata.time;
|
||||
CHECK_NOTNULL(this->getNewestAppDataOutstandingPacket())->metadata.time;
|
||||
|
||||
// deliver an ACK for the outstanding packet 31 ms later
|
||||
const auto packetAckTime = packetSentTime + 31ms;
|
||||
@@ -126,7 +128,7 @@ TYPED_TEST(QuicTypedTransportTest, TransportInfoMinRtt) {
|
||||
{
|
||||
// get the packet's send time
|
||||
const auto packetSentTime =
|
||||
CHECK_NOTNULL(this->getLastAppDataPacketWritten())->metadata.time;
|
||||
CHECK_NOTNULL(this->getNewestAppDataOutstandingPacket())->metadata.time;
|
||||
|
||||
// deliver an ACK for the outstanding packet 42 ms later
|
||||
const auto packetAckTime = packetSentTime + 42ms;
|
||||
@@ -158,7 +160,7 @@ TYPED_TEST(QuicTypedTransportTest, TransportInfoMinRtt) {
|
||||
{
|
||||
// get the packet's send time
|
||||
const auto packetSentTime =
|
||||
CHECK_NOTNULL(this->getLastAppDataPacketWritten())->metadata.time;
|
||||
CHECK_NOTNULL(this->getNewestAppDataOutstandingPacket())->metadata.time;
|
||||
|
||||
// deliver an ACK for the outstanding packet 19 ms later
|
||||
const auto packetAckTime = packetSentTime + 19ms;
|
||||
@@ -1170,6 +1172,568 @@ TYPED_TEST(
|
||||
this->destroyTransport();
|
||||
}
|
||||
|
||||
TYPED_TEST(
|
||||
QuicTypedTransportTestForObservers,
|
||||
WriteEventsOutstandingPacketSent) {
|
||||
InSequence s;
|
||||
|
||||
// ACK outstanding packets so that we can switch out the congestion control
|
||||
this->ackAllOutstandingPackets();
|
||||
EXPECT_THAT(this->getConn().outstandings.packets, IsEmpty());
|
||||
|
||||
// determine the starting writeCount
|
||||
auto writeCount = this->getConn().writeCount;
|
||||
|
||||
// install StaticCwndCongestionController
|
||||
const auto cwndInBytes = 10000;
|
||||
this->getNonConstConn().congestionController =
|
||||
std::make_unique<StaticCwndCongestionController>(
|
||||
StaticCwndCongestionController::CwndInBytes(cwndInBytes));
|
||||
|
||||
MockObserver::Config configWithEventsEnabled;
|
||||
configWithEventsEnabled.appRateLimitedEvents = true;
|
||||
configWithEventsEnabled.packetsWrittenEvents = true;
|
||||
|
||||
auto transport = this->getTransport();
|
||||
auto observerWithNoEvents = std::make_unique<NiceMock<MockObserver>>();
|
||||
auto observerWithEvents1 =
|
||||
std::make_unique<NiceMock<MockObserver>>(configWithEventsEnabled);
|
||||
auto observerWithEvents2 =
|
||||
std::make_unique<NiceMock<MockObserver>>(configWithEventsEnabled);
|
||||
|
||||
EXPECT_CALL(*observerWithNoEvents, observerAttach(transport));
|
||||
transport->addObserver(observerWithNoEvents.get());
|
||||
EXPECT_CALL(*observerWithEvents1, observerAttach(transport));
|
||||
transport->addObserver(observerWithEvents1.get());
|
||||
EXPECT_CALL(*observerWithEvents2, observerAttach(transport));
|
||||
transport->addObserver(observerWithEvents2.get());
|
||||
EXPECT_THAT(
|
||||
transport->getObservers(),
|
||||
UnorderedElementsAre(
|
||||
observerWithNoEvents.get(),
|
||||
observerWithEvents1.get(),
|
||||
observerWithEvents2.get()));
|
||||
|
||||
// string to write
|
||||
const std::string str1 = "hello";
|
||||
const auto strLength = str1.length();
|
||||
|
||||
// open stream that we will write to
|
||||
const auto streamId =
|
||||
this->getTransport()->createBidirectionalStream().value();
|
||||
|
||||
// setup matchers
|
||||
{
|
||||
writeCount++; // write count will be incremented
|
||||
|
||||
// matcher for event from startWritingFromAppLimited
|
||||
const auto startWritingFromAppLimitedMatcher = AllOf(
|
||||
testing::Property(
|
||||
&Observer::WriteEvent::getOutstandingPackets, testing::IsEmpty()),
|
||||
testing::Field(
|
||||
&Observer::WriteEvent::writeCount, testing::Eq(writeCount)),
|
||||
testing::Field(
|
||||
&Observer::WriteEvent::maybeCwndInBytes,
|
||||
testing::Eq(folly::Optional<uint64_t>(cwndInBytes))),
|
||||
testing::Field(
|
||||
&Observer::WriteEvent::maybeWritableBytes,
|
||||
testing::Eq(folly::Optional<uint64_t>(cwndInBytes))));
|
||||
EXPECT_CALL(*observerWithNoEvents, startWritingFromAppLimited(_, _))
|
||||
.Times(0);
|
||||
EXPECT_CALL(
|
||||
*observerWithEvents1,
|
||||
startWritingFromAppLimited(
|
||||
transport, startWritingFromAppLimitedMatcher));
|
||||
EXPECT_CALL(
|
||||
*observerWithEvents2,
|
||||
startWritingFromAppLimited(
|
||||
transport, startWritingFromAppLimitedMatcher));
|
||||
|
||||
// matcher for event from packetsWritten
|
||||
const auto packetsWrittenMatcher = AllOf(
|
||||
testing::Property(
|
||||
&Observer::WriteEvent::getOutstandingPackets, testing::SizeIs(1)),
|
||||
testing::Field(
|
||||
&Observer::WriteEvent::writeCount, testing::Eq(writeCount)),
|
||||
testing::Field(
|
||||
&Observer::WriteEvent::maybeCwndInBytes,
|
||||
testing::Eq(folly::Optional<uint64_t>(cwndInBytes))),
|
||||
testing::Field( // precise check in WillOnce()
|
||||
&Observer::WriteEvent::maybeWritableBytes,
|
||||
testing::Lt(folly::Optional<uint64_t>(cwndInBytes - strLength))),
|
||||
testing::Field(
|
||||
&Observer::PacketsWrittenEvent::numPacketsWritten, testing::Eq(1)),
|
||||
testing::Field(
|
||||
&Observer::PacketsWrittenEvent::numAckElicitingPacketsWritten,
|
||||
testing::Eq(1)),
|
||||
testing::Field( // precise check in WillOnce()
|
||||
&Observer::PacketsWrittenEvent::numBytesWritten,
|
||||
testing::Gt(strLength)));
|
||||
EXPECT_CALL(*observerWithNoEvents, packetsWritten(_, _)).Times(0);
|
||||
EXPECT_CALL(
|
||||
*observerWithEvents1, packetsWritten(transport, packetsWrittenMatcher))
|
||||
.WillOnce([oldTInfo = this->getTransport()->getTransportInfo()](
|
||||
const auto& socket, const auto& event) {
|
||||
const auto bytesWritten =
|
||||
socket->getTransportInfo().bytesSent - oldTInfo.bytesSent;
|
||||
EXPECT_EQ(
|
||||
folly::Optional<uint64_t>(std::max(
|
||||
int64_t(cwndInBytes) - int64_t(bytesWritten), int64_t(0))),
|
||||
event.maybeWritableBytes);
|
||||
EXPECT_EQ(bytesWritten, event.numBytesWritten);
|
||||
});
|
||||
EXPECT_CALL(
|
||||
*observerWithEvents2, packetsWritten(transport, packetsWrittenMatcher))
|
||||
.WillOnce([oldTInfo = this->getTransport()->getTransportInfo()](
|
||||
const auto& socket, const auto& event) {
|
||||
const auto bytesWritten =
|
||||
socket->getTransportInfo().bytesSent - oldTInfo.bytesSent;
|
||||
EXPECT_EQ(
|
||||
folly::Optional<uint64_t>(std::max(
|
||||
int64_t(cwndInBytes) - int64_t(bytesWritten), int64_t(0))),
|
||||
event.maybeWritableBytes);
|
||||
EXPECT_EQ(bytesWritten, event.numBytesWritten);
|
||||
});
|
||||
|
||||
// matcher for event from appRateLimited
|
||||
const auto appRateLimitedMatcher = AllOf(
|
||||
testing::Property(
|
||||
&Observer::WriteEvent::getOutstandingPackets, testing::SizeIs(1)),
|
||||
testing::Field(
|
||||
&Observer::WriteEvent::writeCount, testing::Eq(writeCount)),
|
||||
testing::Field(
|
||||
&Observer::WriteEvent::maybeCwndInBytes,
|
||||
testing::Eq(folly::Optional<uint64_t>(cwndInBytes))),
|
||||
testing::Field( // precise check below
|
||||
&Observer::WriteEvent::maybeWritableBytes,
|
||||
testing::Lt(folly::Optional<uint64_t>(cwndInBytes - strLength))));
|
||||
|
||||
EXPECT_CALL(*observerWithNoEvents, appRateLimited(_, _)).Times(0);
|
||||
EXPECT_CALL(
|
||||
*observerWithEvents1, appRateLimited(transport, appRateLimitedMatcher))
|
||||
.WillOnce([oldTInfo = this->getTransport()->getTransportInfo()](
|
||||
const auto& socket, const auto& event) {
|
||||
const auto bytesWritten =
|
||||
socket->getTransportInfo().bytesSent - oldTInfo.bytesSent;
|
||||
EXPECT_EQ(
|
||||
folly::Optional<uint64_t>(std::max(
|
||||
int64_t(cwndInBytes) - int64_t(bytesWritten), int64_t(0))),
|
||||
event.maybeWritableBytes);
|
||||
});
|
||||
EXPECT_CALL(
|
||||
*observerWithEvents2, appRateLimited(transport, appRateLimitedMatcher))
|
||||
.WillOnce([oldTInfo = this->getTransport()->getTransportInfo()](
|
||||
const auto& socket, const auto& event) {
|
||||
const auto bytesWritten =
|
||||
socket->getTransportInfo().bytesSent - oldTInfo.bytesSent;
|
||||
EXPECT_EQ(
|
||||
folly::Optional<uint64_t>(std::max(
|
||||
int64_t(cwndInBytes) - int64_t(bytesWritten), int64_t(0))),
|
||||
event.maybeWritableBytes);
|
||||
});
|
||||
}
|
||||
|
||||
// open a stream and write string
|
||||
{
|
||||
this->getTransport()->writeChain(streamId, IOBuf::copyBuffer(str1), false);
|
||||
const auto maybeWrittenPackets = this->loopForWrites();
|
||||
|
||||
// should have sent one packet
|
||||
ASSERT_TRUE(maybeWrittenPackets.has_value());
|
||||
quic::PacketNum firstPacketNum = maybeWrittenPackets->start;
|
||||
quic::PacketNum lastPacketNum = maybeWrittenPackets->end;
|
||||
EXPECT_EQ(1, lastPacketNum - firstPacketNum + 1);
|
||||
}
|
||||
|
||||
this->destroyTransport();
|
||||
}
|
||||
|
||||
TYPED_TEST(
|
||||
QuicTypedTransportTestForObservers,
|
||||
WriteEventsOutstandingPacketSentWroteMoreThanCwnd) {
|
||||
InSequence s;
|
||||
|
||||
// ACK outstanding packets so that we can switch out the congestion control
|
||||
this->ackAllOutstandingPackets();
|
||||
EXPECT_THAT(this->getConn().outstandings.packets, IsEmpty());
|
||||
|
||||
// determine the starting writeCount
|
||||
auto writeCount = this->getConn().writeCount;
|
||||
|
||||
// install StaticCwndCongestionController with a CWND < MSS
|
||||
const auto cwndInBytes = 800;
|
||||
this->getNonConstConn().congestionController =
|
||||
std::make_unique<StaticCwndCongestionController>(
|
||||
StaticCwndCongestionController::CwndInBytes(cwndInBytes));
|
||||
|
||||
MockObserver::Config configWithEventsEnabled;
|
||||
configWithEventsEnabled.appRateLimitedEvents = true;
|
||||
configWithEventsEnabled.packetsWrittenEvents = true;
|
||||
|
||||
auto transport = this->getTransport();
|
||||
auto observerWithNoEvents = std::make_unique<NiceMock<MockObserver>>();
|
||||
auto observerWithEvents1 =
|
||||
std::make_unique<NiceMock<MockObserver>>(configWithEventsEnabled);
|
||||
auto observerWithEvents2 =
|
||||
std::make_unique<NiceMock<MockObserver>>(configWithEventsEnabled);
|
||||
|
||||
EXPECT_CALL(*observerWithNoEvents, observerAttach(transport));
|
||||
transport->addObserver(observerWithNoEvents.get());
|
||||
EXPECT_CALL(*observerWithEvents1, observerAttach(transport));
|
||||
transport->addObserver(observerWithEvents1.get());
|
||||
EXPECT_CALL(*observerWithEvents2, observerAttach(transport));
|
||||
transport->addObserver(observerWithEvents2.get());
|
||||
EXPECT_THAT(
|
||||
transport->getObservers(),
|
||||
UnorderedElementsAre(
|
||||
observerWithNoEvents.get(),
|
||||
observerWithEvents1.get(),
|
||||
observerWithEvents2.get()));
|
||||
|
||||
// we're going to write 1000 bytes with a smaller CWND
|
||||
// because MSS > CWND, we're going to overshoot
|
||||
const auto bufLength = 1000;
|
||||
auto buf = buildRandomInputData(bufLength);
|
||||
EXPECT_GT(bufLength, cwndInBytes);
|
||||
|
||||
// open stream that we will write to
|
||||
const auto streamId =
|
||||
this->getTransport()->createBidirectionalStream().value();
|
||||
|
||||
// setup matchers
|
||||
{
|
||||
writeCount++; // write count will be incremented
|
||||
|
||||
// matcher for event from startWritingFromAppLimited
|
||||
const auto startWritingFromAppLimitedMatcher = AllOf(
|
||||
testing::Property(
|
||||
&Observer::WriteEvent::getOutstandingPackets, testing::IsEmpty()),
|
||||
testing::Field(
|
||||
&Observer::WriteEvent::writeCount, testing::Eq(writeCount)),
|
||||
testing::Field(
|
||||
&Observer::WriteEvent::maybeCwndInBytes,
|
||||
testing::Eq(folly::Optional<uint64_t>(cwndInBytes))),
|
||||
testing::Field(
|
||||
&Observer::WriteEvent::maybeWritableBytes,
|
||||
testing::Eq(folly::Optional<uint64_t>(cwndInBytes))));
|
||||
EXPECT_CALL(*observerWithNoEvents, startWritingFromAppLimited(_, _))
|
||||
.Times(0);
|
||||
EXPECT_CALL(
|
||||
*observerWithEvents1,
|
||||
startWritingFromAppLimited(
|
||||
transport, startWritingFromAppLimitedMatcher));
|
||||
EXPECT_CALL(
|
||||
*observerWithEvents2,
|
||||
startWritingFromAppLimited(
|
||||
transport, startWritingFromAppLimitedMatcher));
|
||||
|
||||
// matcher for event from packetsWritten
|
||||
const auto packetsWrittenMatcher = AllOf(
|
||||
testing::Property(
|
||||
&Observer::WriteEvent::getOutstandingPackets, testing::SizeIs(1)),
|
||||
testing::Field(
|
||||
&Observer::WriteEvent::writeCount, testing::Eq(writeCount)),
|
||||
testing::Field(
|
||||
&Observer::WriteEvent::maybeCwndInBytes,
|
||||
testing::Eq(folly::Optional<uint64_t>(cwndInBytes))),
|
||||
testing::Field( // precise check in WillOnce()
|
||||
&Observer::WriteEvent::maybeWritableBytes,
|
||||
testing::Eq(folly::Optional<uint64_t>(0))),
|
||||
testing::Field(
|
||||
&Observer::PacketsWrittenEvent::numPacketsWritten, testing::Eq(1)),
|
||||
testing::Field(
|
||||
&Observer::PacketsWrittenEvent::numAckElicitingPacketsWritten,
|
||||
testing::Eq(1)),
|
||||
testing::Field( // precise check in WillOnce(), expect overshoot CWND
|
||||
&Observer::PacketsWrittenEvent::numBytesWritten,
|
||||
testing::AllOf(testing::Gt(bufLength), testing::Gt(cwndInBytes))));
|
||||
EXPECT_CALL(*observerWithNoEvents, packetsWritten(_, _)).Times(0);
|
||||
EXPECT_CALL(
|
||||
*observerWithEvents1, packetsWritten(transport, packetsWrittenMatcher))
|
||||
.WillOnce([oldTInfo = this->getTransport()->getTransportInfo()](
|
||||
const auto& socket, const auto& event) {
|
||||
const auto bytesWritten =
|
||||
socket->getTransportInfo().bytesSent - oldTInfo.bytesSent;
|
||||
EXPECT_EQ(
|
||||
folly::Optional<uint64_t>(std::max(
|
||||
int64_t(cwndInBytes) - int64_t(bytesWritten), int64_t(0))),
|
||||
event.maybeWritableBytes);
|
||||
EXPECT_EQ(bytesWritten, event.numBytesWritten);
|
||||
});
|
||||
EXPECT_CALL(
|
||||
*observerWithEvents2, packetsWritten(transport, packetsWrittenMatcher))
|
||||
.WillOnce([oldTInfo = this->getTransport()->getTransportInfo()](
|
||||
const auto& socket, const auto& event) {
|
||||
const auto bytesWritten =
|
||||
socket->getTransportInfo().bytesSent - oldTInfo.bytesSent;
|
||||
EXPECT_EQ(
|
||||
folly::Optional<uint64_t>(std::max(
|
||||
int64_t(cwndInBytes) - int64_t(bytesWritten), int64_t(0))),
|
||||
event.maybeWritableBytes);
|
||||
EXPECT_EQ(bytesWritten, event.numBytesWritten);
|
||||
});
|
||||
}
|
||||
|
||||
// open a stream and write string
|
||||
{
|
||||
this->getTransport()->writeChain(streamId, std::move(buf), false);
|
||||
const auto maybeWrittenPackets = this->loopForWrites();
|
||||
|
||||
// should have sent one packet
|
||||
ASSERT_TRUE(maybeWrittenPackets.has_value());
|
||||
quic::PacketNum firstPacketNum = maybeWrittenPackets->start;
|
||||
quic::PacketNum lastPacketNum = maybeWrittenPackets->end;
|
||||
EXPECT_EQ(1, lastPacketNum - firstPacketNum + 1);
|
||||
}
|
||||
|
||||
// TODO(bschlinker): Check for appRateLimited on ACK so that we get an
|
||||
// appRateLimited signal when the outstanding packet is ACKed.
|
||||
|
||||
this->destroyTransport();
|
||||
}
|
||||
|
||||
TYPED_TEST(
|
||||
QuicTypedTransportTestForObservers,
|
||||
WriteEventsOutstandingPacketsSentCwndLimited) {
|
||||
InSequence s;
|
||||
|
||||
// ACK outstanding packets so that we can switch out the congestion control
|
||||
this->ackAllOutstandingPackets();
|
||||
EXPECT_THAT(this->getConn().outstandings.packets, IsEmpty());
|
||||
|
||||
// determine the starting writeCount
|
||||
auto writeCount = this->getConn().writeCount;
|
||||
|
||||
// install StaticCwndCongestionController
|
||||
const auto cwndInBytes = 7000;
|
||||
this->getNonConstConn().congestionController =
|
||||
std::make_unique<StaticCwndCongestionController>(
|
||||
StaticCwndCongestionController::CwndInBytes(cwndInBytes));
|
||||
|
||||
MockObserver::Config configWithEventsEnabled;
|
||||
configWithEventsEnabled.appRateLimitedEvents = true;
|
||||
configWithEventsEnabled.packetsWrittenEvents = true;
|
||||
|
||||
auto transport = this->getTransport();
|
||||
auto observerWithNoEvents = std::make_unique<NiceMock<MockObserver>>();
|
||||
auto observerWithEvents1 =
|
||||
std::make_unique<NiceMock<MockObserver>>(configWithEventsEnabled);
|
||||
auto observerWithEvents2 =
|
||||
std::make_unique<NiceMock<MockObserver>>(configWithEventsEnabled);
|
||||
|
||||
EXPECT_CALL(*observerWithNoEvents, observerAttach(transport));
|
||||
transport->addObserver(observerWithNoEvents.get());
|
||||
EXPECT_CALL(*observerWithEvents1, observerAttach(transport));
|
||||
transport->addObserver(observerWithEvents1.get());
|
||||
EXPECT_CALL(*observerWithEvents2, observerAttach(transport));
|
||||
transport->addObserver(observerWithEvents2.get());
|
||||
EXPECT_THAT(
|
||||
transport->getObservers(),
|
||||
UnorderedElementsAre(
|
||||
observerWithNoEvents.get(),
|
||||
observerWithEvents1.get(),
|
||||
observerWithEvents2.get()));
|
||||
|
||||
// open stream that we will write to
|
||||
const auto streamId =
|
||||
this->getTransport()->createBidirectionalStream().value();
|
||||
|
||||
// we're going to write 10000 bytes with a CWND of 7000
|
||||
const auto bufLength = 10000;
|
||||
auto buf = buildRandomInputData(bufLength);
|
||||
EXPECT_EQ(7000, cwndInBytes);
|
||||
|
||||
// setup matchers for first write, write the entire buffer, trigger loop
|
||||
// we will NOT become app limited after this write, as CWND limited
|
||||
{
|
||||
writeCount++; // write count will be incremented
|
||||
const auto packetsExpectedWritten = 5;
|
||||
|
||||
// matcher for event from startWritingFromAppLimited
|
||||
const auto startWritingFromAppLimitedMatcher = AllOf(
|
||||
testing::Property(
|
||||
&Observer::WriteEvent::getOutstandingPackets, testing::IsEmpty()),
|
||||
testing::Field(
|
||||
&Observer::WriteEvent::writeCount, testing::Eq(writeCount)),
|
||||
testing::Field(
|
||||
&Observer::WriteEvent::maybeCwndInBytes,
|
||||
testing::Eq(folly::Optional<uint64_t>(cwndInBytes))),
|
||||
testing::Field(
|
||||
&Observer::WriteEvent::maybeWritableBytes,
|
||||
testing::Eq(folly::Optional<uint64_t>(cwndInBytes))));
|
||||
EXPECT_CALL(*observerWithNoEvents, startWritingFromAppLimited(_, _))
|
||||
.Times(0);
|
||||
EXPECT_CALL(
|
||||
*observerWithEvents1,
|
||||
startWritingFromAppLimited(
|
||||
transport, startWritingFromAppLimitedMatcher));
|
||||
EXPECT_CALL(
|
||||
*observerWithEvents2,
|
||||
startWritingFromAppLimited(
|
||||
transport, startWritingFromAppLimitedMatcher));
|
||||
|
||||
// matcher for event from packetsWritten
|
||||
const auto packetsWrittenMatcher = AllOf(
|
||||
testing::Property(
|
||||
&Observer::WriteEvent::getOutstandingPackets,
|
||||
testing::SizeIs(packetsExpectedWritten)),
|
||||
testing::Field(
|
||||
&Observer::WriteEvent::writeCount, testing::Eq(writeCount)),
|
||||
testing::Field(
|
||||
&Observer::WriteEvent::maybeCwndInBytes,
|
||||
testing::Eq(folly::Optional<uint64_t>(cwndInBytes))),
|
||||
testing::Field( // precise check in WillOnce()
|
||||
&Observer::WriteEvent::maybeWritableBytes,
|
||||
testing::Eq(folly::Optional<uint64_t>(0))), // CWND exhausted
|
||||
testing::Field(
|
||||
&Observer::PacketsWrittenEvent::numPacketsWritten,
|
||||
testing::Eq(packetsExpectedWritten)),
|
||||
testing::Field(
|
||||
&Observer::PacketsWrittenEvent::numAckElicitingPacketsWritten,
|
||||
testing::Eq(packetsExpectedWritten)),
|
||||
testing::Field( // precise check in WillOnce()
|
||||
&Observer::PacketsWrittenEvent::numBytesWritten,
|
||||
testing::Ge(cwndInBytes))); // full CWND written
|
||||
EXPECT_CALL(*observerWithNoEvents, packetsWritten(_, _)).Times(0);
|
||||
EXPECT_CALL(
|
||||
*observerWithEvents1, packetsWritten(transport, packetsWrittenMatcher))
|
||||
.WillOnce([oldTInfo = this->getTransport()->getTransportInfo()](
|
||||
const auto& socket, const auto& event) {
|
||||
const auto bytesWritten =
|
||||
socket->getTransportInfo().bytesSent - oldTInfo.bytesSent;
|
||||
EXPECT_EQ(
|
||||
folly::Optional<uint64_t>(std::max(
|
||||
int64_t(cwndInBytes) - int64_t(bytesWritten), int64_t(0))),
|
||||
event.maybeWritableBytes);
|
||||
EXPECT_EQ(bytesWritten, event.numBytesWritten);
|
||||
});
|
||||
EXPECT_CALL(
|
||||
*observerWithEvents2, packetsWritten(transport, packetsWrittenMatcher))
|
||||
.WillOnce([oldTInfo = this->getTransport()->getTransportInfo()](
|
||||
const auto& socket, const auto& event) {
|
||||
const auto bytesWritten =
|
||||
socket->getTransportInfo().bytesSent - oldTInfo.bytesSent;
|
||||
EXPECT_EQ(
|
||||
folly::Optional<uint64_t>(std::max(
|
||||
int64_t(cwndInBytes) - int64_t(bytesWritten), int64_t(0))),
|
||||
event.maybeWritableBytes);
|
||||
EXPECT_EQ(bytesWritten, event.numBytesWritten);
|
||||
});
|
||||
|
||||
this->getTransport()->writeChain(streamId, std::move(buf), false);
|
||||
const auto maybeWrittenPackets = this->loopForWrites();
|
||||
|
||||
// make sure we wrote
|
||||
ASSERT_TRUE(maybeWrittenPackets.has_value());
|
||||
quic::PacketNum firstPacketNum = maybeWrittenPackets->start;
|
||||
quic::PacketNum lastPacketNum = maybeWrittenPackets->end;
|
||||
EXPECT_EQ(packetsExpectedWritten, lastPacketNum - firstPacketNum + 1);
|
||||
}
|
||||
|
||||
// ACK all outstanding packets
|
||||
this->ackAllOutstandingPackets();
|
||||
|
||||
// setup matchers for second write, then trigger loop
|
||||
// we will become app limited after this write
|
||||
{
|
||||
writeCount++; // write count will be incremented
|
||||
const auto packetsExpectedWritten = 2;
|
||||
|
||||
// matcher for event from packetsWritten
|
||||
const auto packetsWrittenMatcher = AllOf(
|
||||
testing::Property(
|
||||
&Observer::WriteEvent::getOutstandingPackets,
|
||||
testing::SizeIs(packetsExpectedWritten)),
|
||||
testing::Field(
|
||||
&Observer::WriteEvent::writeCount, testing::Eq(writeCount)),
|
||||
testing::Field(
|
||||
&Observer::WriteEvent::maybeCwndInBytes,
|
||||
testing::Eq(folly::Optional<uint64_t>(cwndInBytes))),
|
||||
testing::Field( // precise check in WillOnce()
|
||||
&Observer::WriteEvent::maybeWritableBytes,
|
||||
testing::Lt(folly::Optional<uint64_t>(cwndInBytes))),
|
||||
testing::Field(
|
||||
&Observer::PacketsWrittenEvent::numPacketsWritten,
|
||||
testing::Eq(packetsExpectedWritten)),
|
||||
testing::Field(
|
||||
&Observer::PacketsWrittenEvent::numAckElicitingPacketsWritten,
|
||||
testing::Eq(packetsExpectedWritten)),
|
||||
testing::Field( // precise check in WillOnce()
|
||||
&Observer::PacketsWrittenEvent::numBytesWritten,
|
||||
testing::Lt(cwndInBytes)));
|
||||
EXPECT_CALL(*observerWithNoEvents, packetsWritten(_, _)).Times(0);
|
||||
EXPECT_CALL(
|
||||
*observerWithEvents1, packetsWritten(transport, packetsWrittenMatcher))
|
||||
.WillOnce([oldTInfo = this->getTransport()->getTransportInfo()](
|
||||
const auto& socket, const auto& event) {
|
||||
const auto bytesWritten =
|
||||
socket->getTransportInfo().bytesSent - oldTInfo.bytesSent;
|
||||
EXPECT_EQ(
|
||||
folly::Optional<uint64_t>(std::max(
|
||||
int64_t(cwndInBytes) - int64_t(bytesWritten), int64_t(0))),
|
||||
event.maybeWritableBytes);
|
||||
EXPECT_EQ(bytesWritten, event.numBytesWritten);
|
||||
});
|
||||
EXPECT_CALL(
|
||||
*observerWithEvents2, packetsWritten(transport, packetsWrittenMatcher))
|
||||
.WillOnce([oldTInfo = this->getTransport()->getTransportInfo()](
|
||||
const auto& socket, const auto& event) {
|
||||
const auto bytesWritten =
|
||||
socket->getTransportInfo().bytesSent - oldTInfo.bytesSent;
|
||||
EXPECT_EQ(
|
||||
folly::Optional<uint64_t>(std::max(
|
||||
int64_t(cwndInBytes) - int64_t(bytesWritten), int64_t(0))),
|
||||
event.maybeWritableBytes);
|
||||
EXPECT_EQ(bytesWritten, event.numBytesWritten);
|
||||
});
|
||||
|
||||
// matcher for event from appRateLimited
|
||||
const auto appRateLimitedMatcher = AllOf(
|
||||
testing::Property(
|
||||
&Observer::WriteEvent::getOutstandingPackets,
|
||||
testing::SizeIs(packetsExpectedWritten)),
|
||||
testing::Field(
|
||||
&Observer::WriteEvent::writeCount, testing::Eq(writeCount)),
|
||||
testing::Field(
|
||||
&Observer::WriteEvent::maybeCwndInBytes,
|
||||
testing::Eq(folly::Optional<uint64_t>(cwndInBytes))),
|
||||
testing::Field( // precise check in WillOnce()
|
||||
&Observer::WriteEvent::maybeWritableBytes,
|
||||
testing::Lt(folly::Optional<uint64_t>(cwndInBytes))));
|
||||
EXPECT_CALL(*observerWithNoEvents, appRateLimited(_, _)).Times(0);
|
||||
EXPECT_CALL(
|
||||
*observerWithEvents1, appRateLimited(transport, appRateLimitedMatcher))
|
||||
.WillOnce([oldTInfo = this->getTransport()->getTransportInfo()](
|
||||
const auto& socket, const auto& event) {
|
||||
const auto bytesWritten =
|
||||
socket->getTransportInfo().bytesSent - oldTInfo.bytesSent;
|
||||
EXPECT_EQ(
|
||||
folly::Optional<uint64_t>(std::max(
|
||||
int64_t(cwndInBytes) - int64_t(bytesWritten), int64_t(0))),
|
||||
event.maybeWritableBytes);
|
||||
});
|
||||
EXPECT_CALL(
|
||||
*observerWithEvents2, appRateLimited(transport, appRateLimitedMatcher))
|
||||
.WillOnce([oldTInfo = this->getTransport()->getTransportInfo()](
|
||||
const auto& socket, const auto& event) {
|
||||
const auto bytesWritten =
|
||||
socket->getTransportInfo().bytesSent - oldTInfo.bytesSent;
|
||||
EXPECT_EQ(
|
||||
folly::Optional<uint64_t>(std::max(
|
||||
int64_t(cwndInBytes) - int64_t(bytesWritten), int64_t(0))),
|
||||
event.maybeWritableBytes);
|
||||
});
|
||||
|
||||
const auto maybeWrittenPackets = this->loopForWrites();
|
||||
ASSERT_TRUE(maybeWrittenPackets.has_value());
|
||||
quic::PacketNum firstPacketNum = maybeWrittenPackets->start;
|
||||
quic::PacketNum lastPacketNum = maybeWrittenPackets->end;
|
||||
EXPECT_EQ(packetsExpectedWritten, lastPacketNum - firstPacketNum + 1);
|
||||
}
|
||||
|
||||
this->destroyTransport();
|
||||
}
|
||||
|
||||
TYPED_TEST(
|
||||
QuicTypedTransportTestForObservers,
|
||||
AckEventsOutstandingPacketSentThenAcked) {
|
||||
|
@@ -85,18 +85,40 @@ class QuicTypedTransportTestBase : protected QuicTransportTestClass {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last outstanding packet written of the specified type.
|
||||
* Returns the first outstanding packet written of the specified type.
|
||||
*
|
||||
* If no outstanding packets of the specified type, returns nullptr.
|
||||
*
|
||||
* Since this is a reference to a packet in the outstanding packets deque, it
|
||||
* should not be stored.
|
||||
*/
|
||||
const OutstandingPacket* FOLLY_NULLABLE
|
||||
getLastPacketWritten(const quic::PacketNumberSpace packetNumberSpace) {
|
||||
getOldestOutstandingPacket(const quic::PacketNumberSpace packetNumberSpace) {
|
||||
const auto outstandingPacketIt =
|
||||
getFirstOutstandingPacket(this->getNonConstConn(), packetNumberSpace);
|
||||
if (outstandingPacketIt ==
|
||||
this->getNonConstConn().outstandings.packets.end()) {
|
||||
return nullptr;
|
||||
}
|
||||
return &*outstandingPacketIt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last outstanding packet written of the specified type.
|
||||
*
|
||||
* If no outstanding packets of the specified type, returns nullptr.
|
||||
*
|
||||
* Since this is a reference to a packet in the outstanding packets deque, it
|
||||
* should not be stored.
|
||||
*/
|
||||
const OutstandingPacket* FOLLY_NULLABLE
|
||||
getNewestOutstandingPacket(const quic::PacketNumberSpace packetNumberSpace) {
|
||||
const auto outstandingPacketIt =
|
||||
getLastOutstandingPacket(this->getNonConstConn(), packetNumberSpace);
|
||||
CHECK(
|
||||
outstandingPacketIt !=
|
||||
this->getNonConstConn().outstandings.packets.rend());
|
||||
if (outstandingPacketIt ==
|
||||
this->getNonConstConn().outstandings.packets.rend()) {
|
||||
return nullptr;
|
||||
}
|
||||
return &*outstandingPacketIt;
|
||||
}
|
||||
|
||||
@@ -108,8 +130,41 @@ class QuicTypedTransportTestBase : protected QuicTransportTestClass {
|
||||
* Since this is a reference to a packet in the outstanding packets deque, it
|
||||
* should not be stored.
|
||||
*/
|
||||
const OutstandingPacket* FOLLY_NULLABLE getLastAppDataPacketWritten() {
|
||||
return getLastPacketWritten(PacketNumberSpace::AppData);
|
||||
const OutstandingPacket* FOLLY_NULLABLE getNewestAppDataOutstandingPacket() {
|
||||
return getNewestOutstandingPacket(PacketNumberSpace::AppData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Acks all outstanding packets for the specified packet number space.
|
||||
*/
|
||||
void ackAllOutstandingPackets(
|
||||
quic::PacketNumberSpace pnSpace,
|
||||
quic::TimePoint recvTime = TimePoint::clock::now()) {
|
||||
auto oldestOutstandingPkt = getOldestOutstandingPacket(pnSpace);
|
||||
auto newestOutstandingPkt = getNewestOutstandingPacket(pnSpace);
|
||||
CHECK_EQ(oldestOutstandingPkt == nullptr, newestOutstandingPkt == nullptr);
|
||||
if (!oldestOutstandingPkt) {
|
||||
return;
|
||||
}
|
||||
|
||||
QuicTransportTestClass::deliverData(
|
||||
NetworkData(
|
||||
buildAckPacketForSentPackets(
|
||||
pnSpace,
|
||||
oldestOutstandingPkt->packet.header.getPacketSequenceNum(),
|
||||
newestOutstandingPkt->packet.header.getPacketSequenceNum()),
|
||||
recvTime),
|
||||
false /* loopForWrites */);
|
||||
}
|
||||
|
||||
/**
|
||||
* Acks all outstanding packets for all packet number spaces.
|
||||
*/
|
||||
void ackAllOutstandingPackets(
|
||||
quic::TimePoint recvTime = TimePoint::clock::now()) {
|
||||
ackAllOutstandingPackets(quic::PacketNumberSpace::Initial, recvTime);
|
||||
ackAllOutstandingPackets(quic::PacketNumberSpace::Handshake, recvTime);
|
||||
ackAllOutstandingPackets(quic::PacketNumberSpace::AppData, recvTime);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -222,6 +277,52 @@ class QuicTypedTransportTestBase : protected QuicTransportTestClass {
|
||||
return buf;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a packet from peer with ACK frame for previously sent packets.
|
||||
*/
|
||||
quic::Buf buildAckPacketForSentPackets(
|
||||
quic::PacketNumberSpace pnSpace,
|
||||
quic::AckBlocks acks) {
|
||||
quic::PacketNum peerPacketNum{0};
|
||||
switch (pnSpace) {
|
||||
case quic::PacketNumberSpace::Initial:
|
||||
peerPacketNum = peerNextInitialPacketNum;
|
||||
peerNextInitialPacketNum++;
|
||||
break;
|
||||
case quic::PacketNumberSpace::Handshake:
|
||||
peerPacketNum = peerNextHandshakePacketNum;
|
||||
peerNextHandshakePacketNum++;
|
||||
break;
|
||||
case quic::PacketNumberSpace::AppData:
|
||||
peerPacketNum = peerNextAppDataPacketNum;
|
||||
peerNextAppDataPacketNum++;
|
||||
break;
|
||||
}
|
||||
|
||||
auto buf = quic::test::packetToBuf(quic::test::createAckPacket(
|
||||
getNonConstConn(), peerPacketNum, acks, pnSpace));
|
||||
buf->coalesce();
|
||||
return buf;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a packet from peer with ACK frame for previously sent packets.
|
||||
*/
|
||||
quic::Buf buildAckPacketForSentPackets(
|
||||
quic::PacketNumberSpace pnSpace,
|
||||
quic::PacketNum intervalStart,
|
||||
quic::PacketNum intervalEnd) {
|
||||
quic::AckBlocks acks = {{intervalStart, intervalEnd}};
|
||||
return buildAckPacketForSentPackets(pnSpace, acks);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a packet from peer with ACK frame for previously sent AppData pkts.
|
||||
*/
|
||||
quic::Buf buildAckPacketForSentAppDataPackets(quic::AckBlocks acks) {
|
||||
return buildAckPacketForSentPackets(quic::PacketNumberSpace::AppData, acks);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a packet with ACK frame for previously sent AppData packet.
|
||||
*/
|
||||
@@ -230,19 +331,6 @@ class QuicTypedTransportTestBase : protected QuicTransportTestClass {
|
||||
return buildAckPacketForSentAppDataPackets(acks);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a packet from peer with ACK frame for previously AppData packets.
|
||||
*/
|
||||
quic::Buf buildAckPacketForSentAppDataPackets(quic::AckBlocks acks) {
|
||||
auto buf = quic::test::packetToBuf(quic::test::createAckPacket(
|
||||
getNonConstConn(),
|
||||
++peerNextAppDataPacketNum,
|
||||
acks,
|
||||
quic::PacketNumberSpace::AppData));
|
||||
buf->coalesce();
|
||||
return buf;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a packet with ACK frame for previously sent AppData packets.
|
||||
*/
|
||||
@@ -603,6 +691,8 @@ class QuicTypedTransportTestBase : protected QuicTransportTestClass {
|
||||
return getPacketNumsFromIntervals(writeIntervals);
|
||||
}
|
||||
|
||||
quic::PacketNum peerNextInitialPacketNum{0};
|
||||
quic::PacketNum peerNextHandshakePacketNum{0};
|
||||
quic::PacketNum peerNextAppDataPacketNum{0};
|
||||
};
|
||||
|
||||
|
@@ -181,8 +181,11 @@ class QuicServerTransportTestBase : public virtual testing::Test {
|
||||
EXPECT_EQ(
|
||||
*server->getConn().clientConnectionId,
|
||||
server->getConn().peerConnectionIds[0].connId);
|
||||
SetUpChild();
|
||||
}
|
||||
|
||||
virtual void SetUpChild() {}
|
||||
|
||||
void destroyTransport() {
|
||||
server = nullptr;
|
||||
}
|
||||
|
Reference in New Issue
Block a user