| // Copyright (c) 2010 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/url_request/url_request_throttler_entry.h" |
| |
| #include <cmath> |
| |
| #include "base/logging.h" |
| #include "base/rand_util.h" |
| #include "base/string_number_conversions.h" |
| #include "net/url_request/url_request_throttler_header_interface.h" |
| |
| namespace net { |
| |
| const int URLRequestThrottlerEntry::kDefaultSlidingWindowPeriodMs = 2000; |
| const int URLRequestThrottlerEntry::kDefaultMaxSendThreshold = 20; |
| const int URLRequestThrottlerEntry::kDefaultInitialBackoffMs = 700; |
| const int URLRequestThrottlerEntry::kDefaultAdditionalConstantMs = 100; |
| const double URLRequestThrottlerEntry::kDefaultMultiplyFactor = 1.4; |
| const double URLRequestThrottlerEntry::kDefaultJitterFactor = 0.4; |
| const int URLRequestThrottlerEntry::kDefaultMaximumBackoffMs = 60 * 60 * 1000; |
| const int URLRequestThrottlerEntry::kDefaultEntryLifetimeMs = 120000; |
| const char URLRequestThrottlerEntry::kRetryHeaderName[] = "X-Retry-After"; |
| |
| URLRequestThrottlerEntry::URLRequestThrottlerEntry() |
| : sliding_window_period_( |
| base::TimeDelta::FromMilliseconds(kDefaultSlidingWindowPeriodMs)), |
| max_send_threshold_(kDefaultMaxSendThreshold), |
| initial_backoff_ms_(kDefaultInitialBackoffMs), |
| additional_constant_ms_(kDefaultAdditionalConstantMs), |
| multiply_factor_(kDefaultMultiplyFactor), |
| jitter_factor_(kDefaultJitterFactor), |
| maximum_backoff_ms_(kDefaultMaximumBackoffMs), |
| entry_lifetime_ms_(kDefaultEntryLifetimeMs) { |
| Initialize(); |
| } |
| |
| URLRequestThrottlerEntry::URLRequestThrottlerEntry( |
| int sliding_window_period_ms, |
| int max_send_threshold, |
| int initial_backoff_ms, |
| int additional_constant_ms, |
| double multiply_factor, |
| double jitter_factor, |
| int maximum_backoff_ms) |
| : sliding_window_period_( |
| base::TimeDelta::FromMilliseconds(sliding_window_period_ms)), |
| max_send_threshold_(max_send_threshold), |
| initial_backoff_ms_(initial_backoff_ms), |
| additional_constant_ms_(additional_constant_ms), |
| multiply_factor_(multiply_factor), |
| jitter_factor_(jitter_factor), |
| maximum_backoff_ms_(maximum_backoff_ms), |
| entry_lifetime_ms_(-1) { |
| DCHECK_GT(sliding_window_period_ms, 0); |
| DCHECK_GT(max_send_threshold_, 0); |
| DCHECK_GE(initial_backoff_ms_, 0); |
| DCHECK_GE(additional_constant_ms_, 0); |
| DCHECK_GT(multiply_factor_, 0); |
| DCHECK_GE(jitter_factor_, 0); |
| DCHECK_LT(jitter_factor_, 1); |
| DCHECK_GE(maximum_backoff_ms_, 0); |
| |
| Initialize(); |
| } |
| |
| URLRequestThrottlerEntry::~URLRequestThrottlerEntry() { |
| } |
| |
| void URLRequestThrottlerEntry::Initialize() { |
| // Since this method is called by the constructors, GetTimeNow() (a virtual |
| // method) is not used. |
| exponential_backoff_release_time_ = base::TimeTicks::Now(); |
| failure_count_ = 0; |
| latest_response_was_failure_ = false; |
| |
| sliding_window_release_time_ = base::TimeTicks::Now(); |
| } |
| |
| bool URLRequestThrottlerEntry::IsDuringExponentialBackoff() const { |
| return exponential_backoff_release_time_ > GetTimeNow(); |
| } |
| |
| int64 URLRequestThrottlerEntry::ReserveSendingTimeForNextRequest( |
| const base::TimeTicks& earliest_time) { |
| base::TimeTicks now = GetTimeNow(); |
| // If a lot of requests were successfully made recently, |
| // sliding_window_release_time_ may be greater than |
| // exponential_backoff_release_time_. |
| base::TimeTicks recommended_sending_time = |
| std::max(std::max(now, earliest_time), |
| std::max(exponential_backoff_release_time_, |
| sliding_window_release_time_)); |
| |
| DCHECK(send_log_.empty() || |
| recommended_sending_time >= send_log_.back()); |
| // Log the new send event. |
| send_log_.push(recommended_sending_time); |
| |
| sliding_window_release_time_ = recommended_sending_time; |
| |
| // Drop the out-of-date events in the event list. |
| // We don't need to worry that the queue may become empty during this |
| // operation, since the last element is sliding_window_release_time_. |
| while ((send_log_.front() + sliding_window_period_ <= |
| sliding_window_release_time_) || |
| send_log_.size() > static_cast<unsigned>(max_send_threshold_)) { |
| send_log_.pop(); |
| } |
| |
| // Check if there are too many send events in recent time. |
| if (send_log_.size() == static_cast<unsigned>(max_send_threshold_)) |
| sliding_window_release_time_ = send_log_.front() + sliding_window_period_; |
| |
| return (recommended_sending_time - now).InMillisecondsRoundedUp(); |
| } |
| |
| base::TimeTicks |
| URLRequestThrottlerEntry::GetExponentialBackoffReleaseTime() const { |
| return exponential_backoff_release_time_; |
| } |
| |
| void URLRequestThrottlerEntry::UpdateWithResponse( |
| const URLRequestThrottlerHeaderInterface* response) { |
| if (response->GetResponseCode() >= 500) { |
| failure_count_++; |
| latest_response_was_failure_ = true; |
| exponential_backoff_release_time_ = |
| CalculateExponentialBackoffReleaseTime(); |
| } else { |
| // We slowly decay the number of times delayed instead of resetting it to 0 |
| // in order to stay stable if we received lots of requests with |
| // malformed bodies at the same time. |
| if (failure_count_ > 0) |
| failure_count_--; |
| |
| latest_response_was_failure_ = false; |
| |
| // The reason why we are not just cutting the release time to GetTimeNow() |
| // is on the one hand, it would unset delay put by our custom retry-after |
| // header and on the other we would like to push every request up to our |
| // "horizon" when dealing with multiple in-flight requests. Ex: If we send |
| // three requests and we receive 2 failures and 1 success. The success that |
| // follows those failures will not reset the release time, further requests |
| // will then need to wait the delay caused by the 2 failures. |
| exponential_backoff_release_time_ = std::max( |
| GetTimeNow(), exponential_backoff_release_time_); |
| |
| std::string retry_header = response->GetNormalizedValue(kRetryHeaderName); |
| if (!retry_header.empty()) |
| HandleCustomRetryAfter(retry_header); |
| } |
| } |
| |
| bool URLRequestThrottlerEntry::IsEntryOutdated() const { |
| if (entry_lifetime_ms_ == -1) |
| return false; |
| |
| base::TimeTicks now = GetTimeNow(); |
| |
| // If there are send events in the sliding window period, we still need this |
| // entry. |
| if (send_log_.size() > 0 && |
| send_log_.back() + sliding_window_period_ > now) { |
| return false; |
| } |
| |
| int64 unused_since_ms = |
| (now - exponential_backoff_release_time_).InMilliseconds(); |
| |
| // Release time is further than now, we are managing it. |
| if (unused_since_ms < 0) |
| return false; |
| |
| // latest_response_was_failure_ is true indicates that the latest one or |
| // more requests encountered server errors or had malformed response bodies. |
| // In that case, we don't want to collect the entry unless it hasn't been used |
| // for longer than the maximum allowed back-off. |
| if (latest_response_was_failure_) |
| return unused_since_ms > std::max(maximum_backoff_ms_, entry_lifetime_ms_); |
| |
| // Otherwise, consider the entry is outdated if it hasn't been used for the |
| // specified lifetime period. |
| return unused_since_ms > entry_lifetime_ms_; |
| } |
| |
| void URLRequestThrottlerEntry::ReceivedContentWasMalformed() { |
| // For any response that is marked as malformed now, we have probably |
| // considered it as a success when receiving it and decreased the failure |
| // count by 1. As a result, we increase the failure count by 2 here to undo |
| // the effect and record a failure. |
| // |
| // Please note that this may lead to a larger failure count than expected, |
| // because we don't decrease the failure count for successful responses when |
| // it has already reached 0. |
| failure_count_ += 2; |
| latest_response_was_failure_ = true; |
| exponential_backoff_release_time_ = CalculateExponentialBackoffReleaseTime(); |
| } |
| |
| base::TimeTicks |
| URLRequestThrottlerEntry::CalculateExponentialBackoffReleaseTime() { |
| double delay = initial_backoff_ms_; |
| delay *= pow(multiply_factor_, failure_count_); |
| delay += additional_constant_ms_; |
| delay -= base::RandDouble() * jitter_factor_ * delay; |
| |
| // Ensure that we do not exceed maximum delay. |
| int64 delay_int = static_cast<int64>(delay + 0.5); |
| delay_int = std::min(delay_int, static_cast<int64>(maximum_backoff_ms_)); |
| |
| return std::max(GetTimeNow() + base::TimeDelta::FromMilliseconds(delay_int), |
| exponential_backoff_release_time_); |
| } |
| |
| base::TimeTicks URLRequestThrottlerEntry::GetTimeNow() const { |
| return base::TimeTicks::Now(); |
| } |
| |
| void URLRequestThrottlerEntry::HandleCustomRetryAfter( |
| const std::string& header_value) { |
| // Input parameter is the number of seconds to wait in a floating point value. |
| double time_in_sec = 0; |
| bool conversion_is_ok = base::StringToDouble(header_value, &time_in_sec); |
| |
| // Conversion of custom retry-after header value failed. |
| if (!conversion_is_ok) |
| return; |
| |
| // We must use an int value later so we transform this in milliseconds. |
| int64 value_ms = static_cast<int64>(0.5 + time_in_sec * 1000); |
| |
| if (maximum_backoff_ms_ < value_ms || value_ms < 0) |
| return; |
| |
| exponential_backoff_release_time_ = std::max( |
| (GetTimeNow() + base::TimeDelta::FromMilliseconds(value_ms)), |
| exponential_backoff_release_time_); |
| } |
| |
| } // namespace net |