| // Copyright 2014 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 "quiche/quic/core/quic_flow_controller.h" | |
| #include <cstdint> | |
| #include "absl/strings/str_cat.h" | |
| #include "quiche/quic/core/quic_connection.h" | |
| #include "quiche/quic/core/quic_packets.h" | |
| #include "quiche/quic/core/quic_session.h" | |
| #include "quiche/quic/core/quic_utils.h" | |
| #include "quiche/quic/platform/api/quic_bug_tracker.h" | |
| #include "quiche/quic/platform/api/quic_flag_utils.h" | |
| #include "quiche/quic/platform/api/quic_flags.h" | |
| #include "quiche/quic/platform/api/quic_logging.h" | |
| namespace quic { | |
| #define ENDPOINT \ | |
| (perspective_ == Perspective::IS_SERVER ? "Server: " : "Client: ") | |
| std::string QuicFlowController::LogLabel() { | |
| if (is_connection_flow_controller_) { | |
| return "connection"; | |
| } | |
| return absl::StrCat("stream ", id_); | |
| } | |
| QuicFlowController::QuicFlowController( | |
| QuicSession* session, QuicStreamId id, bool is_connection_flow_controller, | |
| QuicStreamOffset send_window_offset, QuicStreamOffset receive_window_offset, | |
| QuicByteCount receive_window_size_limit, | |
| bool should_auto_tune_receive_window, | |
| QuicFlowControllerInterface* session_flow_controller) | |
| : session_(session), | |
| connection_(session->connection()), | |
| id_(id), | |
| is_connection_flow_controller_(is_connection_flow_controller), | |
| perspective_(session->perspective()), | |
| bytes_sent_(0), | |
| send_window_offset_(send_window_offset), | |
| bytes_consumed_(0), | |
| highest_received_byte_offset_(0), | |
| receive_window_offset_(receive_window_offset), | |
| receive_window_size_(receive_window_offset), | |
| receive_window_size_limit_(receive_window_size_limit), | |
| auto_tune_receive_window_(should_auto_tune_receive_window), | |
| session_flow_controller_(session_flow_controller), | |
| last_blocked_send_window_offset_(0), | |
| prev_window_update_time_(QuicTime::Zero()) { | |
| QUICHE_DCHECK_LE(receive_window_size_, receive_window_size_limit_); | |
| QUICHE_DCHECK_EQ( | |
| is_connection_flow_controller_, | |
| QuicUtils::GetInvalidStreamId(session_->transport_version()) == id_); | |
| QUIC_DVLOG(1) << ENDPOINT << "Created flow controller for " << LogLabel() | |
| << ", setting initial receive window offset to: " | |
| << receive_window_offset_ | |
| << ", max receive window to: " << receive_window_size_ | |
| << ", max receive window limit to: " | |
| << receive_window_size_limit_ | |
| << ", setting send window offset to: " << send_window_offset_; | |
| } | |
| void QuicFlowController::AddBytesConsumed(QuicByteCount bytes_consumed) { | |
| bytes_consumed_ += bytes_consumed; | |
| QUIC_DVLOG(1) << ENDPOINT << LogLabel() << " consumed " << bytes_consumed_ | |
| << " bytes."; | |
| MaybeSendWindowUpdate(); | |
| } | |
| bool QuicFlowController::UpdateHighestReceivedOffset( | |
| QuicStreamOffset new_offset) { | |
| // Only update if offset has increased. | |
| if (new_offset <= highest_received_byte_offset_) { | |
| return false; | |
| } | |
| QUIC_DVLOG(1) << ENDPOINT << LogLabel() | |
| << " highest byte offset increased from " | |
| << highest_received_byte_offset_ << " to " << new_offset; | |
| highest_received_byte_offset_ = new_offset; | |
| return true; | |
| } | |
| void QuicFlowController::AddBytesSent(QuicByteCount bytes_sent) { | |
| if (bytes_sent_ + bytes_sent > send_window_offset_) { | |
| QUIC_BUG(quic_bug_10836_1) | |
| << ENDPOINT << LogLabel() << " Trying to send an extra " << bytes_sent | |
| << " bytes, when bytes_sent = " << bytes_sent_ | |
| << ", and send_window_offset_ = " << send_window_offset_; | |
| bytes_sent_ = send_window_offset_; | |
| // This is an error on our side, close the connection as soon as possible. | |
| connection_->CloseConnection( | |
| QUIC_FLOW_CONTROL_SENT_TOO_MUCH_DATA, | |
| absl::StrCat(send_window_offset_ - (bytes_sent_ + bytes_sent), | |
| "bytes over send window offset"), | |
| ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET); | |
| return; | |
| } | |
| bytes_sent_ += bytes_sent; | |
| QUIC_DVLOG(1) << ENDPOINT << LogLabel() << " sent " << bytes_sent_ | |
| << " bytes."; | |
| } | |
| bool QuicFlowController::FlowControlViolation() { | |
| if (highest_received_byte_offset_ > receive_window_offset_) { | |
| QUIC_DLOG(INFO) << ENDPOINT << "Flow control violation on " << LogLabel() | |
| << ", receive window offset: " << receive_window_offset_ | |
| << ", highest received byte offset: " | |
| << highest_received_byte_offset_; | |
| return true; | |
| } | |
| return false; | |
| } | |
| void QuicFlowController::MaybeIncreaseMaxWindowSize() { | |
| // Core of receive window auto tuning. This method should be called before a | |
| // WINDOW_UPDATE frame is sent. Ideally, window updates should occur close to | |
| // once per RTT. If a window update happens much faster than RTT, it implies | |
| // that the flow control window is imposing a bottleneck. To prevent this, | |
| // this method will increase the receive window size (subject to a reasonable | |
| // upper bound). For simplicity this algorithm is deliberately asymmetric, in | |
| // that it may increase window size but never decreases. | |
| // Keep track of timing between successive window updates. | |
| QuicTime now = connection_->clock()->ApproximateNow(); | |
| QuicTime prev = prev_window_update_time_; | |
| prev_window_update_time_ = now; | |
| if (!prev.IsInitialized()) { | |
| QUIC_DVLOG(1) << ENDPOINT << "first window update for " << LogLabel(); | |
| return; | |
| } | |
| if (!auto_tune_receive_window_) { | |
| return; | |
| } | |
| // Get outbound RTT. | |
| QuicTime::Delta rtt = | |
| connection_->sent_packet_manager().GetRttStats()->smoothed_rtt(); | |
| if (rtt.IsZero()) { | |
| QUIC_DVLOG(1) << ENDPOINT << "rtt zero for " << LogLabel(); | |
| return; | |
| } | |
| // Now we can compare timing of window updates with RTT. | |
| QuicTime::Delta since_last = now - prev; | |
| QuicTime::Delta two_rtt = 2 * rtt; | |
| if (since_last >= two_rtt) { | |
| // If interval between window updates is sufficiently large, there | |
| // is no need to increase receive_window_size_. | |
| return; | |
| } | |
| QuicByteCount old_window = receive_window_size_; | |
| IncreaseWindowSize(); | |
| if (receive_window_size_ > old_window) { | |
| QUIC_DVLOG(1) << ENDPOINT << "New max window increase for " << LogLabel() | |
| << " after " << since_last.ToMicroseconds() | |
| << " us, and RTT is " << rtt.ToMicroseconds() | |
| << "us. max wndw: " << receive_window_size_; | |
| if (session_flow_controller_ != nullptr) { | |
| session_flow_controller_->EnsureWindowAtLeast( | |
| kSessionFlowControlMultiplier * receive_window_size_); | |
| } | |
| } else { | |
| // TODO(ckrasic) - add a varz to track this (?). | |
| QUIC_LOG_FIRST_N(INFO, 1) | |
| << ENDPOINT << "Max window at limit for " << LogLabel() << " after " | |
| << since_last.ToMicroseconds() << " us, and RTT is " | |
| << rtt.ToMicroseconds() << "us. Limit size: " << receive_window_size_; | |
| } | |
| } | |
| void QuicFlowController::IncreaseWindowSize() { | |
| receive_window_size_ *= 2; | |
| receive_window_size_ = | |
| std::min(receive_window_size_, receive_window_size_limit_); | |
| } | |
| QuicByteCount QuicFlowController::WindowUpdateThreshold() { | |
| return receive_window_size_ / 2; | |
| } | |
| void QuicFlowController::MaybeSendWindowUpdate() { | |
| if (!session_->connection()->connected()) { | |
| return; | |
| } | |
| // Send WindowUpdate to increase receive window if | |
| // (receive window offset - consumed bytes) < (max window / 2). | |
| // This is behaviour copied from SPDY. | |
| QUICHE_DCHECK_LE(bytes_consumed_, receive_window_offset_); | |
| QuicStreamOffset available_window = receive_window_offset_ - bytes_consumed_; | |
| QuicByteCount threshold = WindowUpdateThreshold(); | |
| if (!prev_window_update_time_.IsInitialized()) { | |
| // Treat the initial window as if it is a window update, so if 1/2 the | |
| // window is used in less than 2 RTTs, the window is increased. | |
| prev_window_update_time_ = connection_->clock()->ApproximateNow(); | |
| } | |
| if (available_window >= threshold) { | |
| QUIC_DVLOG(1) << ENDPOINT << "Not sending WindowUpdate for " << LogLabel() | |
| << ", available window: " << available_window | |
| << " >= threshold: " << threshold; | |
| return; | |
| } | |
| MaybeIncreaseMaxWindowSize(); | |
| UpdateReceiveWindowOffsetAndSendWindowUpdate(available_window); | |
| } | |
| void QuicFlowController::UpdateReceiveWindowOffsetAndSendWindowUpdate( | |
| QuicStreamOffset available_window) { | |
| // Update our receive window. | |
| receive_window_offset_ += (receive_window_size_ - available_window); | |
| QUIC_DVLOG(1) << ENDPOINT << "Sending WindowUpdate frame for " << LogLabel() | |
| << ", consumed bytes: " << bytes_consumed_ | |
| << ", available window: " << available_window | |
| << ", and threshold: " << WindowUpdateThreshold() | |
| << ", and receive window size: " << receive_window_size_ | |
| << ". New receive window offset is: " << receive_window_offset_; | |
| SendWindowUpdate(); | |
| } | |
| void QuicFlowController::MaybeSendBlocked() { | |
| if (SendWindowSize() != 0 || | |
| last_blocked_send_window_offset_ >= send_window_offset_) { | |
| return; | |
| } | |
| QUIC_DLOG(INFO) << ENDPOINT << LogLabel() << " is flow control blocked. " | |
| << "Send window: " << SendWindowSize() | |
| << ", bytes sent: " << bytes_sent_ | |
| << ", send limit: " << send_window_offset_; | |
| // The entire send_window has been consumed, we are now flow control | |
| // blocked. | |
| // Keep track of when we last sent a BLOCKED frame so that we only send one | |
| // at a given send offset. | |
| last_blocked_send_window_offset_ = send_window_offset_; | |
| session_->SendBlocked(id_, last_blocked_send_window_offset_); | |
| } | |
| bool QuicFlowController::UpdateSendWindowOffset( | |
| QuicStreamOffset new_send_window_offset) { | |
| // Only update if send window has increased. | |
| if (new_send_window_offset <= send_window_offset_) { | |
| return false; | |
| } | |
| QUIC_DVLOG(1) << ENDPOINT << "UpdateSendWindowOffset for " << LogLabel() | |
| << " with new offset " << new_send_window_offset | |
| << " current offset: " << send_window_offset_ | |
| << " bytes_sent: " << bytes_sent_; | |
| // The flow is now unblocked but could have also been unblocked | |
| // before. Return true iff this update caused a change from blocked | |
| // to unblocked. | |
| const bool was_previously_blocked = IsBlocked(); | |
| send_window_offset_ = new_send_window_offset; | |
| return was_previously_blocked; | |
| } | |
| void QuicFlowController::EnsureWindowAtLeast(QuicByteCount window_size) { | |
| if (receive_window_size_limit_ >= window_size) { | |
| return; | |
| } | |
| QuicStreamOffset available_window = receive_window_offset_ - bytes_consumed_; | |
| IncreaseWindowSize(); | |
| UpdateReceiveWindowOffsetAndSendWindowUpdate(available_window); | |
| } | |
| bool QuicFlowController::IsBlocked() const { return SendWindowSize() == 0; } | |
| uint64_t QuicFlowController::SendWindowSize() const { | |
| if (bytes_sent_ > send_window_offset_) { | |
| return 0; | |
| } | |
| return send_window_offset_ - bytes_sent_; | |
| } | |
| void QuicFlowController::UpdateReceiveWindowSize(QuicStreamOffset size) { | |
| QUICHE_DCHECK_LE(size, receive_window_size_limit_); | |
| QUIC_DVLOG(1) << ENDPOINT << "UpdateReceiveWindowSize for " << LogLabel() | |
| << ": " << size; | |
| if (receive_window_size_ != receive_window_offset_) { | |
| QUIC_BUG(quic_bug_10836_2) | |
| << "receive_window_size_:" << receive_window_size_ | |
| << " != receive_window_offset:" << receive_window_offset_; | |
| return; | |
| } | |
| receive_window_size_ = size; | |
| receive_window_offset_ = size; | |
| } | |
| void QuicFlowController::SendWindowUpdate() { | |
| QuicStreamId id = id_; | |
| if (is_connection_flow_controller_) { | |
| id = QuicUtils::GetInvalidStreamId(connection_->transport_version()); | |
| } | |
| session_->SendWindowUpdate(id, receive_window_offset_); | |
| } | |
| } // namespace quic |