Chromium Code Reviews
|
| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #include "net/url_request/request_throttler_entry.h" | |
| 6 | |
| 7 #include <cmath> | |
| 8 | |
| 9 #include "base/logging.h" | |
| 10 #include "base/rand_util.h" | |
| 11 #include "base/string_number_conversions.h" | |
| 12 #include "net/url_request/request_throttler_header_interface.h" | |
| 13 | |
| 14 const int RequestThrottlerEntry::kDefaultSlidingWindowPeriodMs = 2000; | |
| 15 const int RequestThrottlerEntry::kDefaultMaxSendThreshold = 20; | |
| 16 const int RequestThrottlerEntry::kDefaultInitialBackoffMs = 700; | |
| 17 const int RequestThrottlerEntry::kDefaultAdditionalConstantMs = 100; | |
| 18 const double RequestThrottlerEntry::kDefaultMultiplyFactor = 2.0; | |
| 19 const double RequestThrottlerEntry::kDefaultJitterFactor = 0.4; | |
| 20 const int RequestThrottlerEntry::kDefaultMaximumBackoffMs = 24 * 60 * 60 * 1000; | |
| 21 const int RequestThrottlerEntry::kDefaultEntryLifetimeMs = 120000; | |
| 22 const char RequestThrottlerEntry::kRetryHeaderName[] = "X-Retry-After"; | |
| 23 | |
| 24 RequestThrottlerEntry::RequestThrottlerEntry() | |
| 25 : sliding_window_period_ms_(kDefaultSlidingWindowPeriodMs), | |
| 26 max_send_threshold_(kDefaultMaxSendThreshold), | |
| 27 initial_backoff_ms_(kDefaultInitialBackoffMs), | |
| 28 additional_constant_ms_(kDefaultAdditionalConstantMs), | |
| 29 multiply_factor_(kDefaultMultiplyFactor), | |
| 30 jitter_factor_(kDefaultJitterFactor), | |
| 31 maximum_backoff_ms_(kDefaultMaximumBackoffMs), | |
| 32 entry_lifetime_ms_(kDefaultEntryLifetimeMs) { | |
| 33 Initialize(); | |
| 34 } | |
| 35 | |
| 36 RequestThrottlerEntry::RequestThrottlerEntry( | |
| 37 int sliding_window_period_ms, | |
| 38 int max_send_threshold, | |
| 39 int initial_backoff_ms, | |
| 40 int additional_constant_ms, | |
| 41 double multiply_factor, | |
| 42 double jitter_factor, | |
| 43 int maximum_backoff_ms) | |
| 44 : sliding_window_period_ms_(sliding_window_period_ms), | |
| 45 max_send_threshold_(max_send_threshold), | |
| 46 initial_backoff_ms_(initial_backoff_ms), | |
| 47 additional_constant_ms_(additional_constant_ms), | |
| 48 multiply_factor_(multiply_factor), | |
| 49 jitter_factor_(jitter_factor), | |
| 50 maximum_backoff_ms_(maximum_backoff_ms), | |
| 51 entry_lifetime_ms_(-1) { | |
| 52 DCHECK(sliding_window_period_ms_ > 0 && | |
| 53 max_send_threshold_ > 0 && | |
| 54 initial_backoff_ms_ >= 0 && | |
| 55 additional_constant_ms_ >= 0 && | |
| 56 multiply_factor_ > 0 && | |
| 57 jitter_factor_ >= 0 && | |
| 58 maximum_backoff_ms_ >= 0); | |
| 59 | |
| 60 Initialize(); | |
| 61 } | |
| 62 | |
| 63 RequestThrottlerEntry::~RequestThrottlerEntry() { | |
| 64 } | |
| 65 | |
| 66 void RequestThrottlerEntry::Initialize() { | |
| 67 // Since this method is called by the constructors, GetTimeNow() (a virtual | |
| 68 // method) is not used. | |
| 69 exponential_backoff_release_time_ = base::TimeTicks::Now(); | |
| 70 failure_count_ = 0; | |
| 71 tracking_exponential_backoff_ = false; | |
| 72 | |
| 73 old_values_.exponential_backoff_release_time = | |
| 74 exponential_backoff_release_time_; | |
| 75 old_values_.failure_count = failure_count_; | |
| 76 | |
| 77 sliding_window_release_time_ = base::TimeTicks::Now(); | |
| 78 } | |
| 79 | |
| 80 bool RequestThrottlerEntry::IsDuringExponentialBackoff() const { | |
| 81 AutoLock auto_lock(lock_); | |
| 82 return exponential_backoff_release_time_ > GetTimeNow(); | |
| 83 } | |
| 84 | |
| 85 int64 RequestThrottlerEntry::GetRecommendedDelayForNextRequest() { | |
| 86 AutoLock auto_lock(lock_); | |
| 87 | |
| 88 base::TimeTicks now = GetTimeNow(); | |
| 89 sliding_window_release_time_ = | |
| 90 std::max(now, std::max(exponential_backoff_release_time_, | |
|
Jói
2010/11/12 19:14:24
It would help the readability of the code to expla
yzshen
2010/11/12 21:28:33
If a lot of requests are successfully made in the
| |
| 91 sliding_window_release_time_)); | |
| 92 | |
| 93 int64 result = (sliding_window_release_time_ - now).InMillisecondsRoundedUp(); | |
| 94 | |
| 95 DCHECK(send_log_.empty() || | |
| 96 sliding_window_release_time_ >= send_log_.back()); | |
| 97 | |
| 98 // Log the new send event. | |
| 99 send_log_.push(sliding_window_release_time_); | |
|
Jói
2010/11/12 19:14:24
It seems strange that send_log_ is only updated in
yzshen
2010/11/12 21:28:33
I thought about this problem before I did it this
| |
| 100 | |
| 101 base::TimeDelta sliding_window_period = base::TimeDelta::FromMilliseconds( | |
|
Jói
2010/11/12 19:14:24
can we make the constant a base::TimeDelta instead
yzshen
2010/11/17 07:31:43
Thanks! Done.
| |
| 102 sliding_window_period_ms_); | |
| 103 | |
| 104 // Drop the out-of-date events in the event list. | |
| 105 // We don't need to worry that the queue may become empty during this | |
| 106 // operation, since the last element is sliding_window_release_time_. | |
| 107 while ((send_log_.front() + sliding_window_period <= | |
| 108 sliding_window_release_time_) || | |
| 109 send_log_.size() > static_cast<unsigned>(max_send_threshold_)) { | |
| 110 send_log_.pop(); | |
| 111 } | |
| 112 | |
| 113 // Check if there are too many send events in recent time. | |
| 114 if (send_log_.size() == static_cast<unsigned>(max_send_threshold_)) | |
| 115 sliding_window_release_time_ = send_log_.front() + sliding_window_period; | |
| 116 | |
| 117 return result; | |
| 118 } | |
| 119 | |
| 120 void RequestThrottlerEntry::UpdateWithResponse( | |
| 121 const RequestThrottlerHeaderInterface* response) { | |
| 122 AutoLock auto_lock(lock_); | |
| 123 | |
| 124 SaveState(); | |
| 125 if (response->GetResponseCode() >= 500) { | |
| 126 failure_count_++; | |
| 127 exponential_backoff_release_time_ = | |
| 128 CalculateExponentialBackoffReleaseTime(); | |
| 129 tracking_exponential_backoff_ = true; | |
| 130 } else { | |
| 131 // We slowly decay the number of times delayed instead of resetting it to 0 | |
| 132 // in order to stay stable if we received lots of requests with | |
| 133 // malformed bodies at the same time. | |
| 134 if (failure_count_ > 0) | |
| 135 failure_count_--; | |
| 136 | |
| 137 tracking_exponential_backoff_ = false; | |
| 138 | |
| 139 // The reason why we are not just cutting the release time to GetTimeNow() | |
| 140 // is on the one hand, it would unset delay put by our custom retry-after | |
| 141 // header and on the other we would like to push every request up to our | |
| 142 // "horizon" when dealing with multiple in-flight requests. Ex: If we send | |
| 143 // three requests and we receive 2 failures and 1 success. The success that | |
| 144 // follows those failures will not reset the release time, further requests | |
| 145 // will then need to wait the delay caused by the 2 failures. | |
| 146 exponential_backoff_release_time_ = std::max( | |
| 147 GetTimeNow(), exponential_backoff_release_time_); | |
| 148 | |
| 149 std::string retry_header = response->GetNormalizedValue(kRetryHeaderName); | |
| 150 if (!retry_header.empty()) | |
| 151 HandleCustomRetryAfter(retry_header); | |
| 152 } | |
| 153 } | |
| 154 | |
| 155 bool RequestThrottlerEntry::IsEntryOutdated() const { | |
| 156 AutoLock auto_lock(lock_); | |
| 157 | |
| 158 if (entry_lifetime_ms_ == -1) | |
| 159 return false; | |
| 160 | |
| 161 base::TimeTicks now = GetTimeNow(); | |
| 162 | |
| 163 // If there are send events in the sliding window period, we still need this | |
| 164 // entry. | |
| 165 base::TimeDelta sliding_window_period = base::TimeDelta::FromMilliseconds( | |
| 166 sliding_window_period_ms_); | |
| 167 if (send_log_.size() > 0 && | |
| 168 send_log_.back() + sliding_window_period > now) { | |
| 169 return false; | |
| 170 } | |
| 171 | |
| 172 int64 unused_since_ms = | |
| 173 (now - exponential_backoff_release_time_).InMilliseconds(); | |
| 174 | |
| 175 // Release time is further than now, we are managing it. | |
| 176 if (unused_since_ms < 0) | |
| 177 return false; | |
| 178 | |
| 179 // tracking_exponential_backoff_ is true indicates that the latest one or | |
| 180 // more requests encountered server errors or had malformed response bodies. | |
| 181 // In that case, we are doing exponential back-off and should not collect the | |
| 182 // entry unless it hasn't been used for longer than the maximum allowed | |
| 183 // back-off. | |
| 184 if (tracking_exponential_backoff_) | |
| 185 return unused_since_ms > std::max(maximum_backoff_ms_, entry_lifetime_ms_); | |
| 186 | |
| 187 // Otherwise, consider the entry is outdated if it hasn't been used for the | |
| 188 // specified lifetime period. | |
| 189 return unused_since_ms > entry_lifetime_ms_; | |
| 190 } | |
| 191 | |
| 192 void RequestThrottlerEntry::ReceivedContentWasMalformed() { | |
| 193 AutoLock auto_lock(lock_); | |
| 194 | |
| 195 // We should never revert to less back-off or else an attacker could put a | |
| 196 // malformed body in cache and replay it to decrease delay. | |
| 197 failure_count_ = | |
| 198 std::max(old_values_.failure_count, failure_count_); | |
| 199 failure_count_++; | |
| 200 tracking_exponential_backoff_ = true; | |
| 201 exponential_backoff_release_time_ = | |
| 202 std::max(CalculateExponentialBackoffReleaseTime(), | |
| 203 old_values_.exponential_backoff_release_time); | |
| 204 } | |
| 205 | |
| 206 base::TimeTicks | |
| 207 RequestThrottlerEntry::CalculateExponentialBackoffReleaseTime() { | |
| 208 lock_.AssertAcquired(); | |
| 209 | |
| 210 double delay = initial_backoff_ms_; | |
| 211 delay *= pow(multiply_factor_, failure_count_); | |
| 212 delay += additional_constant_ms_; | |
| 213 delay -= base::RandDouble() * jitter_factor_ * delay; | |
| 214 | |
| 215 // Ensure that we do not exceed maximum delay. | |
| 216 int64 delay_int = static_cast<int64>(delay + 0.5); | |
| 217 delay_int = std::min(delay_int, static_cast<int64>(maximum_backoff_ms_)); | |
| 218 | |
| 219 return std::max(GetTimeNow() + base::TimeDelta::FromMilliseconds(delay_int), | |
| 220 exponential_backoff_release_time_); | |
| 221 } | |
| 222 | |
| 223 base::TimeTicks RequestThrottlerEntry::GetTimeNow() const { | |
| 224 return base::TimeTicks::Now(); | |
| 225 } | |
| 226 | |
| 227 void RequestThrottlerEntry::HandleCustomRetryAfter( | |
| 228 const std::string& header_value) { | |
| 229 lock_.AssertAcquired(); | |
| 230 | |
| 231 // Input parameter is the number of seconds to wait in a floating point value. | |
| 232 double time_in_sec = 0; | |
| 233 bool conversion_is_ok = base::StringToDouble(header_value, &time_in_sec); | |
| 234 | |
| 235 // Conversion of custom retry-after header value failed. | |
| 236 if (!conversion_is_ok) | |
| 237 return; | |
| 238 | |
| 239 // We must use an int value later so we transform this in milliseconds. | |
| 240 int64 value_ms = static_cast<int64>(0.5 + time_in_sec * 1000); | |
| 241 | |
| 242 if (maximum_backoff_ms_ < value_ms || value_ms < 0) | |
| 243 return; | |
| 244 | |
| 245 exponential_backoff_release_time_ = std::max( | |
| 246 (GetTimeNow() + base::TimeDelta::FromMilliseconds(value_ms)), | |
| 247 exponential_backoff_release_time_); | |
| 248 } | |
| 249 | |
| 250 void RequestThrottlerEntry::SaveState() { | |
| 251 lock_.AssertAcquired(); | |
| 252 | |
| 253 old_values_.exponential_backoff_release_time = | |
| 254 exponential_backoff_release_time_; | |
| 255 old_values_.failure_count = failure_count_; | |
| 256 } | |
| OLD | NEW |