1 // Copyright (c) 2012 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.
5 #include "net/url_request/url_request_throttler_manager.h"
7 #include "base/logging.h"
8 #include "base/metrics/field_trial.h"
9 #include "base/metrics/histogram.h"
10 #include "base/strings/string_util.h"
11 #include "net/base/net_log.h"
12 #include "net/base/net_util.h"
16 const unsigned int URLRequestThrottlerManager::kMaximumNumberOfEntries
= 1500;
17 const unsigned int URLRequestThrottlerManager::kRequestsBetweenCollecting
= 200;
19 URLRequestThrottlerManager::URLRequestThrottlerManager()
20 : requests_since_last_gc_(0),
21 enable_thread_checks_(false),
22 logged_for_localhost_disabled_(false),
23 registered_from_thread_(base::kInvalidThreadId
) {
24 url_id_replacements_
.ClearPassword();
25 url_id_replacements_
.ClearUsername();
26 url_id_replacements_
.ClearQuery();
27 url_id_replacements_
.ClearRef();
29 NetworkChangeNotifier::AddIPAddressObserver(this);
30 NetworkChangeNotifier::AddConnectionTypeObserver(this);
33 URLRequestThrottlerManager::~URLRequestThrottlerManager() {
34 NetworkChangeNotifier::RemoveIPAddressObserver(this);
35 NetworkChangeNotifier::RemoveConnectionTypeObserver(this);
37 // Since the manager object might conceivably go away before the
38 // entries, detach the entries' back-pointer to the manager.
39 UrlEntryMap::iterator i
= url_entries_
.begin();
40 while (i
!= url_entries_
.end()) {
41 if (i
->second
.get() != NULL
) {
42 i
->second
->DetachManager();
47 // Delete all entries.
51 scoped_refptr
<URLRequestThrottlerEntryInterface
>
52 URLRequestThrottlerManager::RegisterRequestUrl(const GURL
&url
) {
53 DCHECK(!enable_thread_checks_
|| CalledOnValidThread());
56 std::string url_id
= GetIdFromUrl(url
);
58 // Periodically garbage collect old entries.
59 GarbageCollectEntriesIfNecessary();
61 // Find the entry in the map or create a new NULL entry.
62 scoped_refptr
<URLRequestThrottlerEntry
>& entry
= url_entries_
[url_id
];
64 // If the entry exists but could be garbage collected at this point, we
65 // start with a fresh entry so that we possibly back off a bit less
66 // aggressively (i.e. this resets the error count when the entry's URL
67 // hasn't been requested in long enough).
68 if (entry
.get() && entry
->IsEntryOutdated()) {
72 // Create the entry if needed.
73 if (entry
.get() == NULL
) {
74 entry
= new URLRequestThrottlerEntry(this, url_id
);
76 // We only disable back-off throttling on an entry that we have
77 // just constructed. This is to allow unit tests to explicitly override
78 // the entry for localhost URLs. Given that we do not attempt to
79 // disable throttling for entries already handed out (see comment
80 // in AddToOptOutList), this is not a problem.
81 std::string host
= url
.host();
82 if (opt_out_hosts_
.find(host
) != opt_out_hosts_
.end() ||
84 if (!logged_for_localhost_disabled_
&& IsLocalhost(host
)) {
85 logged_for_localhost_disabled_
= true;
86 net_log_
.AddEvent(NetLog::TYPE_THROTTLING_DISABLED_FOR_HOST
,
87 NetLog::StringCallback("host", &host
));
90 // TODO(joi): Once sliding window is separate from back-off throttling,
91 // we can simply return a dummy implementation of
92 // URLRequestThrottlerEntryInterface here that never blocks anything (and
93 // not keep entries in url_entries_ for opted-out sites).
94 entry
->DisableBackoffThrottling();
101 void URLRequestThrottlerManager::AddToOptOutList(const std::string
& host
) {
102 // There is an edge case here that we are not handling, to keep things
103 // simple. If a host starts adding the opt-out header to its responses
104 // after there are already one or more entries in url_entries_ for that
105 // host, the pre-existing entries may still perform back-off throttling.
106 // In practice, this would almost never occur.
107 if (opt_out_hosts_
.find(host
) == opt_out_hosts_
.end()) {
108 UMA_HISTOGRAM_COUNTS("Throttling.SiteOptedOut", 1);
110 net_log_
.EndEvent(NetLog::TYPE_THROTTLING_DISABLED_FOR_HOST
,
111 NetLog::StringCallback("host", &host
));
112 opt_out_hosts_
.insert(host
);
116 void URLRequestThrottlerManager::OverrideEntryForTests(
118 URLRequestThrottlerEntry
* entry
) {
119 // Normalize the url.
120 std::string url_id
= GetIdFromUrl(url
);
122 // Periodically garbage collect old entries.
123 GarbageCollectEntriesIfNecessary();
125 url_entries_
[url_id
] = entry
;
128 void URLRequestThrottlerManager::EraseEntryForTests(const GURL
& url
) {
129 // Normalize the url.
130 std::string url_id
= GetIdFromUrl(url
);
131 url_entries_
.erase(url_id
);
134 void URLRequestThrottlerManager::set_enable_thread_checks(bool enable
) {
135 enable_thread_checks_
= enable
;
138 bool URLRequestThrottlerManager::enable_thread_checks() const {
139 return enable_thread_checks_
;
142 void URLRequestThrottlerManager::set_net_log(NetLog
* net_log
) {
144 net_log_
= BoundNetLog::Make(net_log
,
145 NetLog::SOURCE_EXPONENTIAL_BACKOFF_THROTTLING
);
148 NetLog
* URLRequestThrottlerManager::net_log() const {
149 return net_log_
.net_log();
152 void URLRequestThrottlerManager::OnIPAddressChanged() {
156 void URLRequestThrottlerManager::OnConnectionTypeChanged(
157 NetworkChangeNotifier::ConnectionType type
) {
161 std::string
URLRequestThrottlerManager::GetIdFromUrl(const GURL
& url
) const {
163 return url
.possibly_invalid_spec();
165 GURL id
= url
.ReplaceComponents(url_id_replacements_
);
166 return base::StringToLowerASCII(id
.spec()).c_str();
169 void URLRequestThrottlerManager::GarbageCollectEntriesIfNecessary() {
170 requests_since_last_gc_
++;
171 if (requests_since_last_gc_
< kRequestsBetweenCollecting
)
173 requests_since_last_gc_
= 0;
175 GarbageCollectEntries();
178 void URLRequestThrottlerManager::GarbageCollectEntries() {
179 UrlEntryMap::iterator i
= url_entries_
.begin();
180 while (i
!= url_entries_
.end()) {
181 if ((i
->second
)->IsEntryOutdated()) {
182 url_entries_
.erase(i
++);
188 // In case something broke we want to make sure not to grow indefinitely.
189 while (url_entries_
.size() > kMaximumNumberOfEntries
) {
190 url_entries_
.erase(url_entries_
.begin());
194 void URLRequestThrottlerManager::OnNetworkChange() {
195 // Remove all entries. Any entries that in-flight requests have a reference
196 // to will live until those requests end, and these entries may be
197 // inconsistent with new entries for the same URLs, but since what we
198 // want is a clean slate for the new connection type, this is OK.
199 url_entries_
.clear();
200 requests_since_last_gc_
= 0;