mirror of
https://github.com/facebookincubator/mvfst.git
synced 2025-08-08 09:42:06 +03:00
Incorporate throttling signal into BBRv1: limit the writable bytes by the available tokens
Summary: This limits the writableBytes by the available tokens (ie `ThrottlingSignalProvider::bytesToSend`), if the connection is being throttled and that value is provided by the throttling signal provider. Note that these throttling signals to the transport are provided only for the connections that belong to a specific QE experiment. Reviewed By: silver23arrow Differential Revision: D47850261 fbshipit-source-id: 8c52e66198db6d1fee252cacea69b82963a1601a
This commit is contained in:
committed by
Facebook GitHub Bot
parent
4862982004
commit
7af4ddc0e0
@@ -969,6 +969,19 @@ uint64_t congestionControlWritableBytes(QuicConnectionStateBase& conn) {
|
|||||||
if (conn.congestionController) {
|
if (conn.congestionController) {
|
||||||
writableBytes = std::min<uint64_t>(
|
writableBytes = std::min<uint64_t>(
|
||||||
writableBytes, conn.congestionController->getWritableBytes());
|
writableBytes, conn.congestionController->getWritableBytes());
|
||||||
|
|
||||||
|
if (conn.throttlingSignalProvider &&
|
||||||
|
conn.throttlingSignalProvider->getCurrentThrottlingSignal()
|
||||||
|
.has_value()) {
|
||||||
|
const auto& throttlingSignal =
|
||||||
|
conn.throttlingSignalProvider->getCurrentThrottlingSignal();
|
||||||
|
if (throttlingSignal.value().maybeBytesToSend.has_value()) {
|
||||||
|
// Cap the writable bytes by the amount of tokens available in the
|
||||||
|
// throttler's bucket if one found to be throttling the connection.
|
||||||
|
writableBytes = std::min(
|
||||||
|
throttlingSignal.value().maybeBytesToSend.value(), writableBytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (writableBytes == std::numeric_limits<uint64_t>::max()) {
|
if (writableBytes == std::numeric_limits<uint64_t>::max()) {
|
||||||
|
@@ -4761,5 +4761,36 @@ TEST_F(QuicTransportFunctionsTest, CustomTransportParamTest) {
|
|||||||
EXPECT_EQ(customTransportParameters.size(), 2);
|
EXPECT_EQ(customTransportParameters.size(), 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(
|
||||||
|
QuicTransportFunctionsTest,
|
||||||
|
StaticCapOnWritableBytesFromThrottlingSignalProvider) {
|
||||||
|
auto conn = createConn();
|
||||||
|
conn->udpSendPacketLen = 2000;
|
||||||
|
auto mockCongestionController =
|
||||||
|
std::make_unique<NiceMock<MockCongestionController>>();
|
||||||
|
auto rawCongestionController = mockCongestionController.get();
|
||||||
|
conn->congestionController = std::move(mockCongestionController);
|
||||||
|
|
||||||
|
auto mockThrottlingSignalProvider =
|
||||||
|
std::make_shared<MockThrottlingSignalProvider>();
|
||||||
|
ThrottlingSignalProvider::ThrottlingSignal expectedSignal;
|
||||||
|
expectedSignal.state =
|
||||||
|
ThrottlingSignalProvider::ThrottlingSignal::State::Throttled;
|
||||||
|
expectedSignal.maybeBytesToSend = 16000;
|
||||||
|
mockThrottlingSignalProvider->useFakeThrottlingSignal(expectedSignal);
|
||||||
|
conn->throttlingSignalProvider = mockThrottlingSignalProvider;
|
||||||
|
|
||||||
|
EXPECT_CALL(*rawCongestionController, getWritableBytes())
|
||||||
|
.WillOnce(Return(10000));
|
||||||
|
EXPECT_EQ(10000, congestionControlWritableBytes(*conn));
|
||||||
|
|
||||||
|
// Since cwnd is larger than available tokens, the writable bytes is capped by
|
||||||
|
// the available tokens
|
||||||
|
EXPECT_CALL(*rawCongestionController, getWritableBytes())
|
||||||
|
.WillOnce(Return(20000));
|
||||||
|
EXPECT_EQ(
|
||||||
|
expectedSignal.maybeBytesToSend, congestionControlWritableBytes(*conn));
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace test
|
} // namespace test
|
||||||
} // namespace quic
|
} // namespace quic
|
||||||
|
@@ -6,6 +6,9 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
|
#include <quic/api/QuicTransportFunctions.h>
|
||||||
|
#include <quic/common/test/TestUtils.h>
|
||||||
|
#include <quic/congestion_control/SimulatedTBF.h>
|
||||||
#include <quic/congestion_control/ThrottlingSignalProvider.h>
|
#include <quic/congestion_control/ThrottlingSignalProvider.h>
|
||||||
#include <quic/state/test/Mocks.h>
|
#include <quic/state/test/Mocks.h>
|
||||||
|
|
||||||
@@ -13,6 +16,35 @@ using namespace ::testing;
|
|||||||
|
|
||||||
namespace quic::test {
|
namespace quic::test {
|
||||||
|
|
||||||
|
// A simple implementation of throttling signal provider, which has a single
|
||||||
|
// static SimTBF that consumes the packets on send.
|
||||||
|
class SimpleThrottlingSignalProvider : public PacketProcessor,
|
||||||
|
public ThrottlingSignalProvider {
|
||||||
|
public:
|
||||||
|
explicit SimpleThrottlingSignalProvider(SimulatedTBF::Config config)
|
||||||
|
: stbf_(std::move(config)) {}
|
||||||
|
~SimpleThrottlingSignalProvider() override = default;
|
||||||
|
void onPacketSent(const quic::OutstandingPacketWrapper& packet) override {
|
||||||
|
stbf_.consumeWithBorrowNonBlockingAndUpdateState(
|
||||||
|
packet.metadata.encodedSize, packet.metadata.time);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] folly::Optional<ThrottlingSignal> getCurrentThrottlingSignal()
|
||||||
|
override {
|
||||||
|
auto availTokens = stbf_.getNumAvailableTokensInBytes(quic::Clock::now());
|
||||||
|
ThrottlingSignal signal = {};
|
||||||
|
signal.state = availTokens > 0 ? ThrottlingSignal::State::Unthrottled
|
||||||
|
: ThrottlingSignal::State::Throttled;
|
||||||
|
signal.maybeBytesToSend.assign((uint64_t)availTokens);
|
||||||
|
signal.maybeThrottledRateBytesPerSecond.assign(
|
||||||
|
(uint64_t)stbf_.getRateBytesPerSecond());
|
||||||
|
return signal;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
SimulatedTBF stbf_;
|
||||||
|
};
|
||||||
|
|
||||||
TEST(ThrottlingSignalProviderTest, BasicInitSetGetTest) {
|
TEST(ThrottlingSignalProviderTest, BasicInitSetGetTest) {
|
||||||
auto mockThrottlingSignalProvider =
|
auto mockThrottlingSignalProvider =
|
||||||
std::make_shared<MockThrottlingSignalProvider>();
|
std::make_shared<MockThrottlingSignalProvider>();
|
||||||
@@ -38,4 +70,51 @@ TEST(ThrottlingSignalProviderTest, BasicInitSetGetTest) {
|
|||||||
expectedSignal.maybeThrottledRateBytesPerSecond);
|
expectedSignal.maybeThrottledRateBytesPerSecond);
|
||||||
EXPECT_FALSE(signal.maybeUnthrottledRateBytesPerSecond.has_value());
|
EXPECT_FALSE(signal.maybeUnthrottledRateBytesPerSecond.has_value());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(ThrottlingSignalProviderTest, TokenBasedDynamicCapOnWritableBytes) {
|
||||||
|
QuicConnectionStateBase conn(QuicNodeType::Server);
|
||||||
|
conn.udpSendPacketLen = 2000;
|
||||||
|
auto signalProvider =
|
||||||
|
std::make_shared<SimpleThrottlingSignalProvider>(SimulatedTBF::Config{
|
||||||
|
.rateBytesPerSecond = 100 * 1000,
|
||||||
|
.burstSizeBytes = 500 * 1000,
|
||||||
|
.maybeMaxDebtQueueSizeBytes = 50 * 1000,
|
||||||
|
.trackEmptyIntervals = false});
|
||||||
|
conn.throttlingSignalProvider = signalProvider;
|
||||||
|
auto mockCongestionController = std::make_unique<MockCongestionController>();
|
||||||
|
conn.congestionController = std::move(mockCongestionController);
|
||||||
|
|
||||||
|
auto now = Clock::now();
|
||||||
|
uint64_t totalBytesSent = 0;
|
||||||
|
// Send 1000 packets, each with 2000 bytes every 10ms = 200KBps, which is
|
||||||
|
// enough to consume the SimTBF's burst.
|
||||||
|
for (PacketNum pn = 0; pn < 1000; pn++) {
|
||||||
|
auto packet = makeTestingWritePacket(pn, 2000, totalBytesSent, now);
|
||||||
|
signalProvider->onPacketSent(packet);
|
||||||
|
conn.congestionController->onPacketSent(packet);
|
||||||
|
totalBytesSent += 2000;
|
||||||
|
|
||||||
|
// Ack each sent packet after 5ms
|
||||||
|
auto ack = makeAck(
|
||||||
|
pn, 2000, now + std::chrono::milliseconds{5}, packet.metadata.time);
|
||||||
|
conn.congestionController->onPacketAckOrLoss(&ack, nullptr);
|
||||||
|
auto writableBytes = congestionControlWritableBytes(conn);
|
||||||
|
auto maybeSignal = signalProvider->getCurrentThrottlingSignal();
|
||||||
|
ASSERT_TRUE(maybeSignal.has_value());
|
||||||
|
auto maybeBytesToSend = maybeSignal.value().maybeBytesToSend;
|
||||||
|
ASSERT_TRUE(maybeBytesToSend.has_value());
|
||||||
|
// Since congestionControlWritableBytes is rounded to the nearest
|
||||||
|
// multiple of udpSendPacketLen, which is 2000 bytes, do the same for
|
||||||
|
// bytesToSend.
|
||||||
|
auto roundedBytesToSend =
|
||||||
|
(maybeBytesToSend.value() + conn.udpSendPacketLen - 1) /
|
||||||
|
conn.udpSendPacketLen * conn.udpSendPacketLen;
|
||||||
|
// writable bytes must be less than bytes to sends, which is the same as the
|
||||||
|
// number of tokens.
|
||||||
|
EXPECT_GE(roundedBytesToSend, writableBytes);
|
||||||
|
|
||||||
|
now += std::chrono::milliseconds{10};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace quic::test
|
} // namespace quic::test
|
||||||
|
Reference in New Issue
Block a user