• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2011 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/url_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/url_request_throttler_header_interface.h"
13 #include "net/url_request/url_request_throttler_manager.h"
14 
15 namespace net {
16 
17 const int URLRequestThrottlerEntry::kDefaultSlidingWindowPeriodMs = 2000;
18 const int URLRequestThrottlerEntry::kDefaultMaxSendThreshold = 20;
19 
20 // This set of back-off parameters will (at maximum values, i.e. without
21 // the reduction caused by jitter) add 0-41% (distributed uniformly
22 // in that range) to the "perceived downtime" of the remote server, once
23 // exponential back-off kicks in and is throttling requests for more than
24 // about a second at a time.  Once the maximum back-off is reached, the added
25 // perceived downtime decreases rapidly, percentage-wise.
26 //
27 // Another way to put it is that the maximum additional perceived downtime
28 // with these numbers is a couple of seconds shy of 15 minutes, and such
29 // a delay would not occur until the remote server has been actually
30 // unavailable at the end of each back-off period for a total of about
31 // 48 minutes.
32 //
33 // Ignoring the first 4 errors helps avoid back-off from kicking in on
34 // flaky connections.
35 const int URLRequestThrottlerEntry::kDefaultNumErrorsToIgnore = 4;
36 const int URLRequestThrottlerEntry::kDefaultInitialBackoffMs = 700;
37 const double URLRequestThrottlerEntry::kDefaultMultiplyFactor = 1.4;
38 const double URLRequestThrottlerEntry::kDefaultJitterFactor = 0.4;
39 const int URLRequestThrottlerEntry::kDefaultMaximumBackoffMs = 15 * 60 * 1000;
40 const int URLRequestThrottlerEntry::kDefaultEntryLifetimeMs = 2 * 60 * 1000;
41 const char URLRequestThrottlerEntry::kRetryHeaderName[] = "X-Retry-After";
42 const char URLRequestThrottlerEntry::kExponentialThrottlingHeader[] =
43     "X-Chrome-Exponential-Throttling";
44 const char URLRequestThrottlerEntry::kExponentialThrottlingDisableValue[] =
45     "disable";
46 
URLRequestThrottlerEntry(URLRequestThrottlerManager * manager)47 URLRequestThrottlerEntry::URLRequestThrottlerEntry(
48     URLRequestThrottlerManager* manager)
49     : sliding_window_period_(
50           base::TimeDelta::FromMilliseconds(kDefaultSlidingWindowPeriodMs)),
51       max_send_threshold_(kDefaultMaxSendThreshold),
52       is_backoff_disabled_(false),
53       backoff_entry_(&backoff_policy_),
54       manager_(manager) {
55   DCHECK(manager_);
56   Initialize();
57 }
58 
URLRequestThrottlerEntry(URLRequestThrottlerManager * manager,int sliding_window_period_ms,int max_send_threshold,int initial_backoff_ms,double multiply_factor,double jitter_factor,int maximum_backoff_ms)59 URLRequestThrottlerEntry::URLRequestThrottlerEntry(
60     URLRequestThrottlerManager* manager,
61     int sliding_window_period_ms,
62     int max_send_threshold,
63     int initial_backoff_ms,
64     double multiply_factor,
65     double jitter_factor,
66     int maximum_backoff_ms)
67     : sliding_window_period_(
68           base::TimeDelta::FromMilliseconds(sliding_window_period_ms)),
69       max_send_threshold_(max_send_threshold),
70       is_backoff_disabled_(false),
71       backoff_entry_(&backoff_policy_),
72       manager_(manager) {
73   DCHECK_GT(sliding_window_period_ms, 0);
74   DCHECK_GT(max_send_threshold_, 0);
75   DCHECK_GE(initial_backoff_ms, 0);
76   DCHECK_GT(multiply_factor, 0);
77   DCHECK_GE(jitter_factor, 0.0);
78   DCHECK_LT(jitter_factor, 1.0);
79   DCHECK_GE(maximum_backoff_ms, 0);
80   DCHECK(manager_);
81 
82   Initialize();
83   backoff_policy_.initial_backoff_ms = initial_backoff_ms;
84   backoff_policy_.multiply_factor = multiply_factor;
85   backoff_policy_.jitter_factor = jitter_factor;
86   backoff_policy_.maximum_backoff_ms = maximum_backoff_ms;
87   backoff_policy_.entry_lifetime_ms = -1;
88   backoff_policy_.num_errors_to_ignore = 0;
89 }
90 
IsEntryOutdated() const91 bool URLRequestThrottlerEntry::IsEntryOutdated() const {
92   // This function is called by the URLRequestThrottlerManager to determine
93   // whether entries should be discarded from its url_entries_ map.  We
94   // want to ensure that it does not remove entries from the map while there
95   // are clients (objects other than the manager) holding references to
96   // the entry, otherwise separate clients could end up holding separate
97   // entries for a request to the same URL, which is undesirable.  Therefore,
98   // if an entry has more than one reference (the map will always hold one),
99   // it should not be considered outdated.
100   //
101   // TODO(joi): Once the manager is not a Singleton, revisit whether
102   // refcounting is needed at all.
103   if (!HasOneRef())
104     return false;
105 
106   // If there are send events in the sliding window period, we still need this
107   // entry.
108   if (!send_log_.empty() &&
109       send_log_.back() + sliding_window_period_ > GetTimeNow()) {
110     return false;
111   }
112 
113   return GetBackoffEntry()->CanDiscard();
114 }
115 
DisableBackoffThrottling()116 void URLRequestThrottlerEntry::DisableBackoffThrottling() {
117   is_backoff_disabled_ = true;
118 }
119 
DetachManager()120 void URLRequestThrottlerEntry::DetachManager() {
121   manager_ = NULL;
122 }
123 
IsDuringExponentialBackoff() const124 bool URLRequestThrottlerEntry::IsDuringExponentialBackoff() const {
125   if (is_backoff_disabled_)
126     return false;
127 
128   return GetBackoffEntry()->ShouldRejectRequest();
129 }
130 
ReserveSendingTimeForNextRequest(const base::TimeTicks & earliest_time)131 int64 URLRequestThrottlerEntry::ReserveSendingTimeForNextRequest(
132     const base::TimeTicks& earliest_time) {
133   base::TimeTicks now = GetTimeNow();
134 
135   // If a lot of requests were successfully made recently,
136   // sliding_window_release_time_ may be greater than
137   // exponential_backoff_release_time_.
138   base::TimeTicks recommended_sending_time =
139       std::max(std::max(now, earliest_time),
140                std::max(GetBackoffEntry()->GetReleaseTime(),
141                         sliding_window_release_time_));
142 
143   DCHECK(send_log_.empty() ||
144          recommended_sending_time >= send_log_.back());
145   // Log the new send event.
146   send_log_.push(recommended_sending_time);
147 
148   sliding_window_release_time_ = recommended_sending_time;
149 
150   // Drop the out-of-date events in the event list.
151   // We don't need to worry that the queue may become empty during this
152   // operation, since the last element is sliding_window_release_time_.
153   while ((send_log_.front() + sliding_window_period_ <=
154           sliding_window_release_time_) ||
155          send_log_.size() > static_cast<unsigned>(max_send_threshold_)) {
156     send_log_.pop();
157   }
158 
159   // Check if there are too many send events in recent time.
160   if (send_log_.size() == static_cast<unsigned>(max_send_threshold_))
161     sliding_window_release_time_ = send_log_.front() + sliding_window_period_;
162 
163   return (recommended_sending_time - now).InMillisecondsRoundedUp();
164 }
165 
166 base::TimeTicks
GetExponentialBackoffReleaseTime() const167     URLRequestThrottlerEntry::GetExponentialBackoffReleaseTime() const {
168   // If a site opts out, it's likely because they have problems that trigger
169   // the back-off mechanism when it shouldn't be triggered, in which case
170   // returning the calculated back-off release time would probably be the
171   // wrong thing to do (i.e. it would likely be too long).  Therefore, we
172   // return "now" so that retries are not delayed.
173   if (is_backoff_disabled_)
174     return GetTimeNow();
175 
176   return GetBackoffEntry()->GetReleaseTime();
177 }
178 
UpdateWithResponse(const std::string & host,const URLRequestThrottlerHeaderInterface * response)179 void URLRequestThrottlerEntry::UpdateWithResponse(
180     const std::string& host,
181     const URLRequestThrottlerHeaderInterface* response) {
182   if (response->GetResponseCode() >= 500) {
183     GetBackoffEntry()->InformOfRequest(false);
184   } else {
185     GetBackoffEntry()->InformOfRequest(true);
186 
187     std::string retry_header = response->GetNormalizedValue(kRetryHeaderName);
188     if (!retry_header.empty())
189       HandleCustomRetryAfter(retry_header);
190 
191     std::string throttling_header = response->GetNormalizedValue(
192         kExponentialThrottlingHeader);
193     if (!throttling_header.empty())
194       HandleThrottlingHeader(throttling_header, host);
195   }
196 }
197 
ReceivedContentWasMalformed()198 void URLRequestThrottlerEntry::ReceivedContentWasMalformed() {
199   // A malformed body can only occur when the request to fetch a resource
200   // was successful.  Therefore, in such a situation, we will receive one
201   // call to ReceivedContentWasMalformed() and one call to UpdateWithResponse()
202   // with a response categorized as "good".  To end up counting one failure,
203   // we need to count two failures here against the one success in
204   // UpdateWithResponse().
205   GetBackoffEntry()->InformOfRequest(false);
206   GetBackoffEntry()->InformOfRequest(false);
207 }
208 
~URLRequestThrottlerEntry()209 URLRequestThrottlerEntry::~URLRequestThrottlerEntry() {
210 }
211 
Initialize()212 void URLRequestThrottlerEntry::Initialize() {
213   sliding_window_release_time_ = base::TimeTicks::Now();
214   backoff_policy_.num_errors_to_ignore = kDefaultNumErrorsToIgnore;
215   backoff_policy_.initial_backoff_ms = kDefaultInitialBackoffMs;
216   backoff_policy_.multiply_factor = kDefaultMultiplyFactor;
217   backoff_policy_.jitter_factor = kDefaultJitterFactor;
218   backoff_policy_.maximum_backoff_ms = kDefaultMaximumBackoffMs;
219   backoff_policy_.entry_lifetime_ms = kDefaultEntryLifetimeMs;
220 }
221 
GetTimeNow() const222 base::TimeTicks URLRequestThrottlerEntry::GetTimeNow() const {
223   return base::TimeTicks::Now();
224 }
225 
HandleCustomRetryAfter(const std::string & header_value)226 void URLRequestThrottlerEntry::HandleCustomRetryAfter(
227     const std::string& header_value) {
228   // Input parameter is the number of seconds to wait in a floating point value.
229   double time_in_sec = 0;
230   bool conversion_is_ok = base::StringToDouble(header_value, &time_in_sec);
231 
232   // Conversion of custom retry-after header value failed.
233   if (!conversion_is_ok)
234     return;
235 
236   // We must use an int value later so we transform this in milliseconds.
237   int64 value_ms = static_cast<int64>(0.5 + time_in_sec * 1000);
238 
239   // We do not check for an upper bound; the server can set any Retry-After it
240   // desires. Recovery from error would involve restarting the browser.
241   if (value_ms < 0)
242     return;
243 
244   GetBackoffEntry()->SetCustomReleaseTime(
245       GetTimeNow() + base::TimeDelta::FromMilliseconds(value_ms));
246 }
247 
HandleThrottlingHeader(const std::string & header_value,const std::string & host)248 void URLRequestThrottlerEntry::HandleThrottlingHeader(
249     const std::string& header_value,
250     const std::string& host) {
251   if (header_value == kExponentialThrottlingDisableValue) {
252     DisableBackoffThrottling();
253     if (manager_)
254       manager_->AddToOptOutList(host);
255   } else {
256     // TODO(joi): Log this.
257   }
258 }
259 
GetBackoffEntry() const260 const BackoffEntry* URLRequestThrottlerEntry::GetBackoffEntry() const {
261   return &backoff_entry_;
262 }
263 
GetBackoffEntry()264 BackoffEntry* URLRequestThrottlerEntry::GetBackoffEntry() {
265   return &backoff_entry_;
266 }
267 
268 }  // namespace net
269