| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "net/quic/congestion_control/tcp_cubic_sender.h" |
| |
| #include "net/quic/congestion_control/quic_send_scheduler.h" |
| |
| namespace { |
| // Constants based on TCP defaults. |
| const size_t kHybridStartLowWindow = 16; |
| const int kMaxSegmentSize = net::kMaxPacketSize; |
| const int kDefaultReceiveWindow = 64000; |
| const size_t kInitialCongestionWindow = 10; |
| const size_t kMaxCongestionWindow = 10000; |
| const int kMaxBurstLength = 3; |
| }; |
| |
| namespace net { |
| |
| TcpCubicSender::TcpCubicSender(const QuicClock* clock, bool reno) |
| : hybrid_slow_start_(clock), |
| cubic_(clock), |
| reno_(reno), |
| congestion_window_count_(0), |
| receiver_congestion_window_in_bytes_(kDefaultReceiveWindow), |
| last_received_accumulated_number_of_lost_packets_(0), |
| bytes_in_flight_(0), |
| update_end_sequence_number_(true), |
| end_sequence_number_(0), |
| congestion_window_(kInitialCongestionWindow), |
| slowstart_threshold_(kMaxCongestionWindow), |
| delay_min_() { |
| } |
| |
| void TcpCubicSender::OnIncomingQuicCongestionFeedbackFrame( |
| const QuicCongestionFeedbackFrame& feedback) { |
| if (last_received_accumulated_number_of_lost_packets_ != |
| feedback.tcp.accumulated_number_of_lost_packets) { |
| int recovered_lost_packets = |
| last_received_accumulated_number_of_lost_packets_ - |
| feedback.tcp.accumulated_number_of_lost_packets; |
| last_received_accumulated_number_of_lost_packets_ = |
| feedback.tcp.accumulated_number_of_lost_packets; |
| if (recovered_lost_packets > 0) { |
| OnIncomingLoss(recovered_lost_packets); |
| } |
| } |
| receiver_congestion_window_in_bytes_ = |
| feedback.tcp.receive_window << 4; |
| } |
| |
| void TcpCubicSender::OnIncomingAck( |
| QuicPacketSequenceNumber acked_sequence_number, size_t acked_bytes, |
| QuicTime::Delta rtt) { |
| bytes_in_flight_ -= acked_bytes; |
| CongestionAvoidance(acked_sequence_number); |
| AckAccounting(rtt); |
| if (end_sequence_number_ == acked_sequence_number) { |
| DVLOG(1) << "Start update end sequence number @" << acked_sequence_number; |
| update_end_sequence_number_ = true; |
| } |
| } |
| |
| void TcpCubicSender::OnIncomingLoss(int /*number_of_lost_packets*/) { |
| // In a normal TCP we would need to know the lowest missing packet to detect |
| // if we receive 3 missing packets. Here we get a missing packet for which we |
| // enter TCP Fast Retransmit immediately. |
| if (reno_) { |
| congestion_window_ = congestion_window_ >> 1; |
| slowstart_threshold_ = congestion_window_; |
| } else { |
| congestion_window_ = |
| cubic_.CongestionWindowAfterPacketLoss(congestion_window_); |
| slowstart_threshold_ = congestion_window_; |
| } |
| // Sanity, make sure that we don't end up with an empty window. |
| if (congestion_window_ == 0) { |
| congestion_window_ = 1; |
| } |
| } |
| |
| void TcpCubicSender::SentPacket(QuicPacketSequenceNumber sequence_number, |
| size_t bytes, bool retransmit) { |
| if (!retransmit) { |
| bytes_in_flight_ += bytes; |
| } |
| if (!retransmit && update_end_sequence_number_) { |
| end_sequence_number_ = sequence_number; |
| if (AvailableCongestionWindow() == 0) { |
| update_end_sequence_number_ = false; |
| DVLOG(1) << "Stop update end sequence number @" << sequence_number; |
| } |
| } |
| } |
| |
| QuicTime::Delta TcpCubicSender::TimeUntilSend(bool retransmit) { |
| if (retransmit) { |
| // For TCP we can always send a retransmit immediately. |
| return QuicTime::Delta(); |
| } |
| if (AvailableCongestionWindow() == 0) { |
| return QuicTime::Delta::Infinite(); |
| } |
| return QuicTime::Delta(); |
| } |
| |
| size_t TcpCubicSender::AvailableCongestionWindow() { |
| if (bytes_in_flight_ > CongestionWindow()) { |
| return 0; |
| } |
| return CongestionWindow() - bytes_in_flight_; |
| } |
| |
| size_t TcpCubicSender::CongestionWindow() { |
| // What's the current congestion window in bytes. |
| return std::min(receiver_congestion_window_in_bytes_, |
| static_cast<int>(congestion_window_ * kMaxSegmentSize)); |
| } |
| |
| int TcpCubicSender::BandwidthEstimate() { |
| // TODO(pwestin): make a long term estimate, based on RTT and loss rate? or |
| // instantaneous estimate? |
| // Throughput ~= (1/RTT)*sqrt(3/2p) |
| return kNoValidEstimate; |
| } |
| |
| void TcpCubicSender::Reset() { |
| delay_min_ = QuicTime::Delta(); // Reset to 0. |
| hybrid_slow_start_.Restart(); |
| } |
| |
| bool TcpCubicSender::IsCwndLimited() const { |
| const size_t congestion_window_bytes = congestion_window_ * kMaxSegmentSize; |
| if (bytes_in_flight_ >= congestion_window_bytes) { |
| return true; |
| } |
| const size_t tcp_max_burst = kMaxBurstLength * kMaxSegmentSize; |
| const size_t left = congestion_window_bytes - bytes_in_flight_; |
| return left <= tcp_max_burst; |
| } |
| |
| // Called when we receive and ack. Normal TCP tracks how many packets one ack |
| // represents, but quic has a separate ack for each packet. |
| void TcpCubicSender::CongestionAvoidance(QuicPacketSequenceNumber ack) { |
| if (!IsCwndLimited()) { |
| // We don't update the congestion window unless we are close to using the |
| // window we have available. |
| DVLOG(1) << "Congestion avoidance window not limited"; |
| return; |
| } |
| if (congestion_window_ < slowstart_threshold_) { |
| // Slow start. |
| if (hybrid_slow_start_.EndOfRound(ack)) { |
| hybrid_slow_start_.Reset(end_sequence_number_); |
| } |
| // congestion_window_cnt is the number of acks since last change of snd_cwnd |
| if (congestion_window_ < kMaxCongestionWindow) { |
| // TCP slow start, exponentail growth, increase by one for each ACK. |
| congestion_window_++; |
| } |
| DVLOG(1) << "Slow start; congestion window:" << congestion_window_; |
| } else { |
| if (congestion_window_ < kMaxCongestionWindow) { |
| if (reno_) { |
| // Classic Reno congestion avoidance provided for testing. |
| if (congestion_window_count_ >= congestion_window_) { |
| congestion_window_++; |
| congestion_window_count_ = 0; |
| } else { |
| congestion_window_count_++; |
| } |
| DVLOG(1) << "Reno; congestion window:" << congestion_window_; |
| } else { |
| congestion_window_ = cubic_.CongestionWindowAfterAck(congestion_window_, |
| delay_min_); |
| DVLOG(1) << "Cubic; congestion window:" << congestion_window_; |
| } |
| } |
| } |
| } |
| |
| // TODO(pwestin): what is the timout value? |
| void TcpCubicSender::OnTimeOut() { |
| cubic_.Reset(); |
| congestion_window_ = 1; |
| } |
| |
| void TcpCubicSender::AckAccounting(QuicTime::Delta rtt) { |
| if (rtt.ToMicroseconds() <= 0) { |
| // RTT can't be 0 or negative. |
| return; |
| } |
| // TODO(pwestin): Discard delay samples right after fast recovery, |
| // during 1 second?. |
| |
| // First time call or link delay decreases. |
| if (delay_min_.IsZero() || delay_min_ > rtt) { |
| delay_min_ = rtt; |
| } |
| // Hybrid start triggers when cwnd is larger than some threshold. |
| if (congestion_window_ <= slowstart_threshold_ && |
| congestion_window_ >= kHybridStartLowWindow) { |
| if (!hybrid_slow_start_.started()) { |
| // Time to start the hybrid slow start. |
| hybrid_slow_start_.Reset(end_sequence_number_); |
| } |
| hybrid_slow_start_.Update(rtt, delay_min_); |
| if (hybrid_slow_start_.Exit()) { |
| slowstart_threshold_ = congestion_window_; |
| } |
| } |
| } |
| |
| } // namespace net |