quic/congestion_control/QuicCubic.h (116 lines of code) (raw):
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <quic/QuicException.h>
#include <quic/congestion_control/CongestionControlFunctions.h>
#include <quic/congestion_control/CongestionController.h>
#include <quic/state/AckEvent.h>
#include <quic/state/StateData.h>
namespace quic {
constexpr float kCubicHystartPacingGain = 2.0f;
constexpr float kCubicRecoveryPacingGain = 1.25f;
enum class CubicStates : uint8_t {
Hystart,
Steady,
FastRecovery,
};
/**
*
* |--------| |-----|
* | [Ack] [Ack] |
* | | | |
* -->Hystart------------[Ack]---------->Cubic<--|
* | | |
* | | |
* | ->[ACK/Loss] | |
* | | | | |
* | | | | |
* -[Loss]---->Fast Recovery<--[Loss]- |
* | |
* | |
* | |
* |->-----[Ack]------|
*
*/
class Cubic : public CongestionController {
public:
static constexpr uint64_t INIT_SSTHRESH =
std::numeric_limits<uint64_t>::max();
/**
* initSsthresh: the initial value of ssthresh
* ssreduction: how should cwnd be reduced when loss happens during slow
* start
* tcpFriendly: if cubic cwnd calculation should be friendly to Reno TCP
* spreadacrossRtt: if the pacing bursts should be spread across RTT or all
* close to the beginning of an RTT round
*/
explicit Cubic(
QuicConnectionStateBase& conn,
uint64_t initCwndBytes = 0,
uint64_t initSsthresh = INIT_SSTHRESH,
bool tcpFriendly = true,
bool ackTrain = false);
CubicStates state() const noexcept;
enum class ExitReason : uint8_t {
SSTHRESH,
EXITPOINT,
};
// if hybrid slow start exit point is found
enum class HystartFound : uint8_t {
No,
FoundByAckTrainMethod,
FoundByDelayIncreaseMethod
};
void onPacketAckOrLoss(
const AckEvent* FOLLY_NULLABLE,
const LossEvent* FOLLY_NULLABLE) override;
void onPacketAckOrLoss(
folly::Optional<AckEvent> ack,
folly::Optional<LossEvent> loss) {
onPacketAckOrLoss(ack.get_pointer(), loss.get_pointer());
}
void onRemoveBytesFromInflight(uint64_t) override;
void onPacketSent(const OutstandingPacket& packet) override;
uint64_t getWritableBytes() const noexcept override;
uint64_t getCongestionWindow() const noexcept override;
void setAppIdle(bool idle, TimePoint eventTime) noexcept override;
void setAppLimited() override;
void setBandwidthUtilizationFactor(
float /*bandwidthUtilizationFactor*/) noexcept override {}
bool isInBackgroundMode() const noexcept override {
return false;
}
bool isAppLimited() const noexcept override;
void getStats(CongestionControllerStats& stats) const override;
void setExperimental(bool experimental) override;
void handoff(uint64_t newCwnd, uint64_t newInflight) noexcept;
CongestionControlType type() const noexcept override;
protected:
CubicStates state_{CubicStates::Hystart};
private:
bool isAppIdle() const noexcept;
void onPacketAcked(const AckEvent& ack);
void onPacketAckedInHystart(const AckEvent& ack);
void onPacketAckedInSteady(const AckEvent& ack);
void onPacketAckedInRecovery(const AckEvent& ack);
void onPacketLoss(const LossEvent& loss);
void onPacketLossInRecovery(const LossEvent& loss);
void onPersistentCongestion();
float pacingGain() const noexcept;
void startHystartRttRound(TimePoint time) noexcept;
void cubicReduction(TimePoint lossTime) noexcept;
void updateTimeToOrigin() noexcept;
int64_t calculateCubicCwndDelta(TimePoint timePoint) noexcept;
uint64_t calculateCubicCwnd(int64_t delta) noexcept;
bool isRecovered(TimePoint packetSentTime) noexcept;
QuicConnectionStateBase& conn_;
uint64_t cwndBytes_;
// the value of cwndBytes_ at last loss event
folly::Optional<uint64_t> lossCwndBytes_;
// the value of ssthresh_ at the last loss event
folly::Optional<uint64_t> lossSsthresh_;
uint64_t ssthresh_;
struct HystartState {
// If AckTrain method will be used to exit SlowStart
bool ackTrain{false};
// If we are currently in a RTT round
bool inRttRound{false};
// If we have found the exit point
HystartFound found{HystartFound::No};
// The starting timestamp of a RTT round
TimePoint roundStart;
// Last timestamp when closed space Ack happens
TimePoint lastJiffy;
// The minimal of sampled RTT in current RTT round. Hystart only samples
// first a few RTTs in a round
folly::Optional<std::chrono::microseconds> currSampledRtt;
// End value of currSampledRtt at the end of a RTT round:
folly::Optional<std::chrono::microseconds> lastSampledRtt;
// Estimated minimal delay of a path
folly::Optional<std::chrono::microseconds> delayMin;
// Ack sampling count
uint8_t ackCount{0};
// When a packet with sent time >= rttRoundEndTarget is acked, end the
// current RTT round
TimePoint rttRoundEndTarget;
};
struct SteadyState {
// time takes for cwnd to increase to lastMaxCwndBytes
double timeToOrigin{0.0};
// The cwnd value that timeToOrigin is calculated based on
folly::Optional<uint64_t> originPoint;
bool tcpFriendly{true};
folly::Optional<TimePoint> lastReductionTime;
// This is Wmax, it could be different from lossCwndBytes if cwnd never
// reaches last lastMaxCwndBytes before loss event:
folly::Optional<uint64_t> lastMaxCwndBytes;
uint64_t estRenoCwnd;
// cache reduction/increase factors based on numEmulatedConnections_
float reductionFactor{kDefaultCubicReductionFactor};
float lastMaxReductionFactor{kDefaultLastMaxReductionFactor};
float tcpEstimationIncreaseFactor{kCubicTCPFriendlyEstimateIncreaseFactor};
};
struct RecoveryState {
// The time point after which Quic will no longer be in current recovery
folly::Optional<TimePoint> endOfRecovery;
};
// if quiescenceStart_ has a value, then the connection is app limited
folly::Optional<TimePoint> quiescenceStart_;
HystartState hystartState_;
SteadyState steadyState_;
RecoveryState recoveryState_;
// Experimental settings for CUBIC
std::chrono::microseconds delayIncreaseUpperBound{kDelayIncreaseUpperBound};
std::chrono::microseconds delayIncreaseLowerBound{kDelayIncreaseLowerBound};
};
folly::StringPiece cubicStateToString(CubicStates state);
} // namespace quic