dcsctp: Compute RTO with higher precision
Since the code measuring the RTT has been converted to using TimeDelta which internally stores the duration in microseconds, from DurationMs which uses milliseconds, the RTO calculation can use the higher precision to calculate lower non-zero durations on really fast networks such within a data center. Before this CL, which is from the initial drop of dcSCTP, the RTO calculation was done using the algorithm from the paper "V. Jacobson: Congestion avoidance and control", but now we're using the original algorith from https://tools.ietf.org/html/rfc4960#section-6.3.1, which comes from https://datatracker.ietf.org/doc/html/rfc6298#section-2. Two issues were found and corrected: 1. The min RTT variance that is specified in the config file was previously incorrectly divided by 8. That was not its intention, but we're keeping that behaviour as other clients have actually measured a good value to put there. This represents "G" in the "basic algorithm" above, and since that is multiplied with K, which is four, the default value of 220 wouldn't make sense if it wasn't scaled down, as that would make the RTO easily saturate to the RTO_max (800ms). 2. The previous algorithm had large round-off errors (probably because the code used milliseconds), which makes fairly big changes to the calculated RTO in some situations. Bug: webrtc:15593 Change-Id: I95a3e137c2bbbe7bf8b99c016381e9e63fd01d87 Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/349000 Reviewed-by: Florent Castelli <orphis@webrtc.org> Reviewed-by: Harald Alvestrand <hta@webrtc.org> Commit-Queue: Victor Boivie <boivie@webrtc.org> Cr-Commit-Position: refs/heads/main@{#42170}
This commit is contained in:
parent
1a3120f3fd
commit
28d07ddbfd
@ -148,8 +148,11 @@ struct DcSctpOptions {
|
|||||||
// processing time of received packets and the clock granularity when setting
|
// processing time of received packets and the clock granularity when setting
|
||||||
// the delayed ack timer on the peer.
|
// the delayed ack timer on the peer.
|
||||||
//
|
//
|
||||||
// This is described for TCP in
|
// This is defined as "G" in the algorithm for TCP in
|
||||||
// https://datatracker.ietf.org/doc/html/rfc6298#section-4.
|
// https://datatracker.ietf.org/doc/html/rfc6298#section-4.
|
||||||
|
//
|
||||||
|
// Note that this value will be further adjusted by scaling factors, so if you
|
||||||
|
// intend to change this, do it incrementally and measure the results.
|
||||||
DurationMs min_rtt_variance = DurationMs(220);
|
DurationMs min_rtt_variance = DurationMs(220);
|
||||||
|
|
||||||
// The initial congestion window size, in number of MTUs.
|
// The initial congestion window size, in number of MTUs.
|
||||||
|
|||||||
@ -17,48 +17,50 @@
|
|||||||
|
|
||||||
namespace dcsctp {
|
namespace dcsctp {
|
||||||
|
|
||||||
|
// https://datatracker.ietf.org/doc/html/rfc4960#section-15.
|
||||||
|
constexpr double kRtoAlpha = 0.125;
|
||||||
|
constexpr double kRtoBeta = 0.25;
|
||||||
|
|
||||||
|
// A factor that the `min_rtt_variance` configuration option will be divided by
|
||||||
|
// (before later multiplied with K, which is 4 according to RFC6298). When this
|
||||||
|
// value was introduced, it was unintentionally divided by 8 since that code
|
||||||
|
// worked with scaled numbers (to avoid floating point math). That behavior is
|
||||||
|
// kept as downstream users have measured good values for their use-cases.
|
||||||
|
constexpr double kHeuristicVarianceAdjustment = 8.0;
|
||||||
|
|
||||||
RetransmissionTimeout::RetransmissionTimeout(const DcSctpOptions& options)
|
RetransmissionTimeout::RetransmissionTimeout(const DcSctpOptions& options)
|
||||||
: min_rto_(options.rto_min.ToTimeDelta()),
|
: min_rto_(options.rto_min.ToTimeDelta()),
|
||||||
max_rto_(options.rto_max.ToTimeDelta()),
|
max_rto_(options.rto_max.ToTimeDelta()),
|
||||||
max_rtt_(options.rtt_max.ToTimeDelta()),
|
max_rtt_(options.rtt_max.ToTimeDelta()),
|
||||||
min_rtt_variance_(*options.min_rtt_variance),
|
min_rtt_variance_(options.min_rtt_variance.ToTimeDelta() /
|
||||||
scaled_srtt_(*options.rto_initial << kRttShift),
|
kHeuristicVarianceAdjustment),
|
||||||
rto_(*options.rto_initial) {}
|
srtt_(options.rto_initial.ToTimeDelta()),
|
||||||
|
rto_(options.rto_initial.ToTimeDelta()) {}
|
||||||
|
|
||||||
void RetransmissionTimeout::ObserveRTT(webrtc::TimeDelta measured_rtt) {
|
void RetransmissionTimeout::ObserveRTT(webrtc::TimeDelta rtt) {
|
||||||
// Unrealistic values will be skipped. If a wrongly measured (or otherwise
|
// Unrealistic values will be skipped. If a wrongly measured (or otherwise
|
||||||
// corrupt) value was processed, it could change the state in a way that would
|
// corrupt) value was processed, it could change the state in a way that would
|
||||||
// take a very long time to recover.
|
// take a very long time to recover.
|
||||||
if (measured_rtt < webrtc::TimeDelta::Zero() || measured_rtt > max_rtt_) {
|
if (rtt < webrtc::TimeDelta::Zero() || rtt > max_rtt_) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const int64_t rtt = measured_rtt.ms();
|
// https://tools.ietf.org/html/rfc4960#section-6.3.1.
|
||||||
|
|
||||||
// From https://tools.ietf.org/html/rfc4960#section-6.3.1, but avoiding
|
|
||||||
// floating point math by implementing algorithm from "V. Jacobson: Congestion
|
|
||||||
// avoidance and control", but adapted for SCTP.
|
|
||||||
if (first_measurement_) {
|
if (first_measurement_) {
|
||||||
scaled_srtt_ = rtt << kRttShift;
|
srtt_ = rtt;
|
||||||
scaled_rtt_var_ = (rtt / 2) << kRttVarShift;
|
rtt_var_ = rtt / 2;
|
||||||
first_measurement_ = false;
|
first_measurement_ = false;
|
||||||
} else {
|
} else {
|
||||||
int64_t rtt_diff = rtt - (scaled_srtt_ >> kRttShift);
|
webrtc::TimeDelta rtt_diff = (srtt_ - rtt).Abs();
|
||||||
scaled_srtt_ += rtt_diff;
|
rtt_var_ = (1 - kRtoBeta) * rtt_var_ + kRtoBeta * rtt_diff;
|
||||||
if (rtt_diff < 0) {
|
srtt_ = (1 - kRtoAlpha) * srtt_ + kRtoAlpha * rtt;
|
||||||
rtt_diff = -rtt_diff;
|
|
||||||
}
|
|
||||||
rtt_diff -= (scaled_rtt_var_ >> kRttVarShift);
|
|
||||||
scaled_rtt_var_ += rtt_diff;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (scaled_rtt_var_ < min_rtt_variance_) {
|
if (rtt_var_ < min_rtt_variance_) {
|
||||||
scaled_rtt_var_ = min_rtt_variance_;
|
rtt_var_ = min_rtt_variance_;
|
||||||
}
|
}
|
||||||
|
|
||||||
rto_ = (scaled_srtt_ >> kRttShift) + scaled_rtt_var_;
|
rto_ = srtt_ + 4 * rtt_var_;
|
||||||
|
rto_ = std::clamp(rto_, min_rto_, max_rto_);
|
||||||
// Clamp RTO between min and max.
|
|
||||||
rto_ = std::min(std::max(rto_, min_rto_.ms()), max_rto_.ms());
|
|
||||||
}
|
}
|
||||||
} // namespace dcsctp
|
} // namespace dcsctp
|
||||||
|
|||||||
@ -27,34 +27,30 @@ namespace dcsctp {
|
|||||||
// a lot, which is an indicator of a bad connection.
|
// a lot, which is an indicator of a bad connection.
|
||||||
class RetransmissionTimeout {
|
class RetransmissionTimeout {
|
||||||
public:
|
public:
|
||||||
static constexpr int kRttShift = 3;
|
|
||||||
static constexpr int kRttVarShift = 2;
|
|
||||||
explicit RetransmissionTimeout(const DcSctpOptions& options);
|
explicit RetransmissionTimeout(const DcSctpOptions& options);
|
||||||
|
|
||||||
// To be called when a RTT has been measured, to update the RTO value.
|
// To be called when a RTT has been measured, to update the RTO value.
|
||||||
void ObserveRTT(webrtc::TimeDelta measured_rtt);
|
void ObserveRTT(webrtc::TimeDelta rtt);
|
||||||
|
|
||||||
// Returns the Retransmission Timeout (RTO) value, in milliseconds.
|
// Returns the Retransmission Timeout (RTO) value.
|
||||||
webrtc::TimeDelta rto() const { return webrtc::TimeDelta::Millis(rto_); }
|
webrtc::TimeDelta rto() const { return rto_; }
|
||||||
|
|
||||||
// Returns the smoothed RTT value, in milliseconds.
|
// Returns the smoothed RTT value.
|
||||||
webrtc::TimeDelta srtt() const {
|
webrtc::TimeDelta srtt() const { return srtt_; }
|
||||||
return webrtc::TimeDelta::Millis(scaled_srtt_ >> kRttShift);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
const webrtc::TimeDelta min_rto_;
|
const webrtc::TimeDelta min_rto_;
|
||||||
const webrtc::TimeDelta max_rto_;
|
const webrtc::TimeDelta max_rto_;
|
||||||
const webrtc::TimeDelta max_rtt_;
|
const webrtc::TimeDelta max_rtt_;
|
||||||
const int64_t min_rtt_variance_;
|
const webrtc::TimeDelta min_rtt_variance_;
|
||||||
// If this is the first measurement
|
// If this is the first measurement
|
||||||
bool first_measurement_ = true;
|
bool first_measurement_ = true;
|
||||||
// Smoothed Round-Trip Time, shifted by kRttShift
|
// Smoothed Round-Trip Time.
|
||||||
int64_t scaled_srtt_;
|
webrtc::TimeDelta srtt_;
|
||||||
// Round-Trip Time Variation, shifted by kRttVarShift
|
// Round-Trip Time Variation.
|
||||||
int64_t scaled_rtt_var_ = 0;
|
webrtc::TimeDelta rtt_var_ = webrtc::TimeDelta::Zero();
|
||||||
// Retransmission Timeout
|
// Retransmission Timeout
|
||||||
int64_t rto_;
|
webrtc::TimeDelta rto_;
|
||||||
};
|
};
|
||||||
} // namespace dcsctp
|
} // namespace dcsctp
|
||||||
|
|
||||||
|
|||||||
@ -49,10 +49,10 @@ TEST(RetransmissionTimeoutTest, NegativeValuesDoNotAffectRTO) {
|
|||||||
rto_.ObserveRTT(TimeDelta::Millis(-10));
|
rto_.ObserveRTT(TimeDelta::Millis(-10));
|
||||||
EXPECT_EQ(rto_.rto(), kInitialRto);
|
EXPECT_EQ(rto_.rto(), kInitialRto);
|
||||||
rto_.ObserveRTT(TimeDelta::Millis(124));
|
rto_.ObserveRTT(TimeDelta::Millis(124));
|
||||||
EXPECT_EQ(rto_.rto(), TimeDelta::Millis(372));
|
EXPECT_EQ(rto_.rto().ms(), 372);
|
||||||
// Subsequent negative value
|
// Subsequent negative value
|
||||||
rto_.ObserveRTT(TimeDelta::Millis(-10));
|
rto_.ObserveRTT(TimeDelta::Millis(-10));
|
||||||
EXPECT_EQ(rto_.rto(), TimeDelta::Millis(372));
|
EXPECT_EQ(rto_.rto().ms(), 372);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(RetransmissionTimeoutTest, TooLargeValuesDoNotAffectRTO) {
|
TEST(RetransmissionTimeoutTest, TooLargeValuesDoNotAffectRTO) {
|
||||||
@ -61,10 +61,10 @@ TEST(RetransmissionTimeoutTest, TooLargeValuesDoNotAffectRTO) {
|
|||||||
rto_.ObserveRTT(kMaxRtt + TimeDelta::Millis(100));
|
rto_.ObserveRTT(kMaxRtt + TimeDelta::Millis(100));
|
||||||
EXPECT_EQ(rto_.rto(), kInitialRto);
|
EXPECT_EQ(rto_.rto(), kInitialRto);
|
||||||
rto_.ObserveRTT(TimeDelta::Millis(124));
|
rto_.ObserveRTT(TimeDelta::Millis(124));
|
||||||
EXPECT_EQ(rto_.rto(), TimeDelta::Millis(372));
|
EXPECT_EQ(rto_.rto().ms(), 372);
|
||||||
// Subsequent too large value
|
// Subsequent too large value
|
||||||
rto_.ObserveRTT(kMaxRtt + TimeDelta::Millis(100));
|
rto_.ObserveRTT(kMaxRtt + TimeDelta::Millis(100));
|
||||||
EXPECT_EQ(rto_.rto(), TimeDelta::Millis(372));
|
EXPECT_EQ(rto_.rto().ms(), 372);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(RetransmissionTimeoutTest, WillNeverGoBelowMinimumRto) {
|
TEST(RetransmissionTimeoutTest, WillNeverGoBelowMinimumRto) {
|
||||||
@ -88,29 +88,29 @@ TEST(RetransmissionTimeoutTest, WillNeverGoAboveMaximumRto) {
|
|||||||
TEST(RetransmissionTimeoutTest, CalculatesRtoForStableRtt) {
|
TEST(RetransmissionTimeoutTest, CalculatesRtoForStableRtt) {
|
||||||
RetransmissionTimeout rto_(MakeOptions());
|
RetransmissionTimeout rto_(MakeOptions());
|
||||||
rto_.ObserveRTT(TimeDelta::Millis(124));
|
rto_.ObserveRTT(TimeDelta::Millis(124));
|
||||||
EXPECT_EQ(rto_.rto(), TimeDelta::Millis(372));
|
EXPECT_EQ(rto_.rto().ms(), 372);
|
||||||
rto_.ObserveRTT(TimeDelta::Millis(128));
|
rto_.ObserveRTT(TimeDelta::Millis(128));
|
||||||
EXPECT_EQ(rto_.rto(), TimeDelta::Millis(344));
|
EXPECT_EQ(rto_.rto().ms(), 315);
|
||||||
rto_.ObserveRTT(TimeDelta::Millis(123));
|
rto_.ObserveRTT(TimeDelta::Millis(123));
|
||||||
EXPECT_EQ(rto_.rto(), TimeDelta::Millis(344));
|
EXPECT_EQ(rto_.rto().ms(), 268);
|
||||||
rto_.ObserveRTT(TimeDelta::Millis(125));
|
rto_.ObserveRTT(TimeDelta::Millis(125));
|
||||||
EXPECT_EQ(rto_.rto(), TimeDelta::Millis(344));
|
EXPECT_EQ(rto_.rto().ms(), 234);
|
||||||
rto_.ObserveRTT(TimeDelta::Millis(127));
|
rto_.ObserveRTT(TimeDelta::Millis(127));
|
||||||
EXPECT_EQ(rto_.rto(), TimeDelta::Millis(344));
|
EXPECT_EQ(rto_.rto().ms(), 235);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(RetransmissionTimeoutTest, CalculatesRtoForUnstableRtt) {
|
TEST(RetransmissionTimeoutTest, CalculatesRtoForUnstableRtt) {
|
||||||
RetransmissionTimeout rto_(MakeOptions());
|
RetransmissionTimeout rto_(MakeOptions());
|
||||||
rto_.ObserveRTT(TimeDelta::Millis(124));
|
rto_.ObserveRTT(TimeDelta::Millis(124));
|
||||||
EXPECT_EQ(rto_.rto(), TimeDelta::Millis(372));
|
EXPECT_EQ(rto_.rto().ms(), 372);
|
||||||
rto_.ObserveRTT(TimeDelta::Millis(402));
|
rto_.ObserveRTT(TimeDelta::Millis(402));
|
||||||
EXPECT_EQ(rto_.rto(), TimeDelta::Millis(622));
|
EXPECT_EQ(rto_.rto().ms(), 623);
|
||||||
rto_.ObserveRTT(TimeDelta::Millis(728));
|
rto_.ObserveRTT(TimeDelta::Millis(728));
|
||||||
EXPECT_EQ(rto_.rto(), TimeDelta::Millis(800));
|
EXPECT_EQ(rto_.rto().ms(), 800);
|
||||||
rto_.ObserveRTT(TimeDelta::Millis(89));
|
rto_.ObserveRTT(TimeDelta::Millis(89));
|
||||||
EXPECT_EQ(rto_.rto(), TimeDelta::Millis(800));
|
EXPECT_EQ(rto_.rto().ms(), 800);
|
||||||
rto_.ObserveRTT(TimeDelta::Millis(126));
|
rto_.ObserveRTT(TimeDelta::Millis(126));
|
||||||
EXPECT_EQ(rto_.rto(), TimeDelta::Millis(800));
|
EXPECT_EQ(rto_.rto().ms(), 800);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(RetransmissionTimeoutTest, WillStabilizeAfterAWhile) {
|
TEST(RetransmissionTimeoutTest, WillStabilizeAfterAWhile) {
|
||||||
@ -120,25 +120,25 @@ TEST(RetransmissionTimeoutTest, WillStabilizeAfterAWhile) {
|
|||||||
rto_.ObserveRTT(TimeDelta::Millis(728));
|
rto_.ObserveRTT(TimeDelta::Millis(728));
|
||||||
rto_.ObserveRTT(TimeDelta::Millis(89));
|
rto_.ObserveRTT(TimeDelta::Millis(89));
|
||||||
rto_.ObserveRTT(TimeDelta::Millis(126));
|
rto_.ObserveRTT(TimeDelta::Millis(126));
|
||||||
EXPECT_EQ(rto_.rto(), TimeDelta::Millis(800));
|
EXPECT_EQ(rto_.rto().ms(), 800);
|
||||||
rto_.ObserveRTT(TimeDelta::Millis(124));
|
rto_.ObserveRTT(TimeDelta::Millis(124));
|
||||||
EXPECT_EQ(rto_.rto(), TimeDelta::Millis(800));
|
EXPECT_EQ(rto_.rto().ms(), 800);
|
||||||
rto_.ObserveRTT(TimeDelta::Millis(122));
|
rto_.ObserveRTT(TimeDelta::Millis(122));
|
||||||
EXPECT_EQ(rto_.rto(), TimeDelta::Millis(710));
|
EXPECT_EQ(rto_.rto().ms(), 709);
|
||||||
rto_.ObserveRTT(TimeDelta::Millis(123));
|
rto_.ObserveRTT(TimeDelta::Millis(123));
|
||||||
EXPECT_EQ(rto_.rto(), TimeDelta::Millis(631));
|
EXPECT_EQ(rto_.rto().ms(), 630);
|
||||||
rto_.ObserveRTT(TimeDelta::Millis(124));
|
rto_.ObserveRTT(TimeDelta::Millis(124));
|
||||||
EXPECT_EQ(rto_.rto(), TimeDelta::Millis(562));
|
EXPECT_EQ(rto_.rto().ms(), 562);
|
||||||
rto_.ObserveRTT(TimeDelta::Millis(122));
|
rto_.ObserveRTT(TimeDelta::Millis(122));
|
||||||
EXPECT_EQ(rto_.rto(), TimeDelta::Millis(505));
|
EXPECT_EQ(rto_.rto().ms(), 505);
|
||||||
rto_.ObserveRTT(TimeDelta::Millis(124));
|
rto_.ObserveRTT(TimeDelta::Millis(124));
|
||||||
EXPECT_EQ(rto_.rto(), TimeDelta::Millis(454));
|
EXPECT_EQ(rto_.rto().ms(), 454);
|
||||||
rto_.ObserveRTT(TimeDelta::Millis(124));
|
rto_.ObserveRTT(TimeDelta::Millis(124));
|
||||||
EXPECT_EQ(rto_.rto(), TimeDelta::Millis(410));
|
EXPECT_EQ(rto_.rto().ms(), 410);
|
||||||
rto_.ObserveRTT(TimeDelta::Millis(124));
|
rto_.ObserveRTT(TimeDelta::Millis(124));
|
||||||
EXPECT_EQ(rto_.rto(), TimeDelta::Millis(372));
|
EXPECT_EQ(rto_.rto().ms(), 372);
|
||||||
rto_.ObserveRTT(TimeDelta::Millis(124));
|
rto_.ObserveRTT(TimeDelta::Millis(124));
|
||||||
EXPECT_EQ(rto_.rto(), TimeDelta::Millis(367));
|
EXPECT_EQ(rto_.rto().ms(), 340);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(RetransmissionTimeoutTest, WillAlwaysStayAboveRTT) {
|
TEST(RetransmissionTimeoutTest, WillAlwaysStayAboveRTT) {
|
||||||
@ -152,7 +152,7 @@ TEST(RetransmissionTimeoutTest, WillAlwaysStayAboveRTT) {
|
|||||||
for (int i = 0; i < 1000; ++i) {
|
for (int i = 0; i < 1000; ++i) {
|
||||||
rto_.ObserveRTT(TimeDelta::Millis(124));
|
rto_.ObserveRTT(TimeDelta::Millis(124));
|
||||||
}
|
}
|
||||||
EXPECT_EQ(rto_.rto(), TimeDelta::Millis(344));
|
EXPECT_EQ(rto_.rto().ms(), 234);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(RetransmissionTimeoutTest, CanSpecifySmallerMinimumRttVariance) {
|
TEST(RetransmissionTimeoutTest, CanSpecifySmallerMinimumRttVariance) {
|
||||||
@ -164,7 +164,7 @@ TEST(RetransmissionTimeoutTest, CanSpecifySmallerMinimumRttVariance) {
|
|||||||
for (int i = 0; i < 1000; ++i) {
|
for (int i = 0; i < 1000; ++i) {
|
||||||
rto_.ObserveRTT(TimeDelta::Millis(124));
|
rto_.ObserveRTT(TimeDelta::Millis(124));
|
||||||
}
|
}
|
||||||
EXPECT_EQ(rto_.rto(), TimeDelta::Millis(244));
|
EXPECT_EQ(rto_.rto().ms(), 184);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(RetransmissionTimeoutTest, CanSpecifyLargerMinimumRttVariance) {
|
TEST(RetransmissionTimeoutTest, CanSpecifyLargerMinimumRttVariance) {
|
||||||
@ -176,7 +176,7 @@ TEST(RetransmissionTimeoutTest, CanSpecifyLargerMinimumRttVariance) {
|
|||||||
for (int i = 0; i < 1000; ++i) {
|
for (int i = 0; i < 1000; ++i) {
|
||||||
rto_.ObserveRTT(TimeDelta::Millis(124));
|
rto_.ObserveRTT(TimeDelta::Millis(124));
|
||||||
}
|
}
|
||||||
EXPECT_EQ(rto_.rto(), TimeDelta::Millis(444));
|
EXPECT_EQ(rto_.rto().ms(), 284);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user