Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / net / proxy / dhcp_proxy_script_fetcher_win.cc
blobbf3e9d65836de478749a1beaf1ab036c10f065e7
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/proxy/dhcp_proxy_script_fetcher_win.h"
7 #include "base/bind.h"
8 #include "base/bind_helpers.h"
9 #include "base/profiler/scoped_tracker.h"
10 #include "base/threading/sequenced_worker_pool.h"
11 #include "net/base/net_errors.h"
12 #include "net/proxy/dhcp_proxy_script_adapter_fetcher_win.h"
14 #include <winsock2.h>
15 #include <iphlpapi.h>
16 #pragma comment(lib, "iphlpapi.lib")
18 namespace {
20 // How many threads to use at maximum to do DHCP lookups. This is
21 // chosen based on the following UMA data:
22 // - When OnWaitTimer fires, ~99.8% of users have 6 or fewer network
23 // adapters enabled for DHCP in total.
24 // - At the same measurement point, ~99.7% of users have 3 or fewer pending
25 // DHCP adapter lookups.
26 // - There is however a very long and thin tail of users who have
27 // systems reporting up to 100+ adapters (this must be some very weird
28 // OS bug (?), probably the cause of http://crbug.com/240034).
30 // The maximum number of threads is chosen such that even systems that
31 // report a huge number of network adapters should not run out of
32 // memory from this number of threads, while giving a good chance of
33 // getting back results for any responsive adapters.
35 // The ~99.8% of systems that have 6 or fewer network adapters will
36 // not grow the thread pool to its maximum size (rather, they will
37 // grow it to 6 or fewer threads) so setting the limit lower would not
38 // improve performance or memory usage on those systems.
39 const int kMaxDhcpLookupThreads = 12;
41 // How long to wait at maximum after we get results (a PAC file or
42 // knowledge that no PAC file is configured) from whichever network
43 // adapter finishes first.
44 const int kMaxWaitAfterFirstResultMs = 400;
46 } // namespace
48 namespace net {
50 DhcpProxyScriptFetcherWin::DhcpProxyScriptFetcherWin(
51 URLRequestContext* url_request_context)
52 : state_(STATE_START),
53 num_pending_fetchers_(0),
54 destination_string_(NULL),
55 url_request_context_(url_request_context) {
56 DCHECK(url_request_context_);
58 worker_pool_ = new base::SequencedWorkerPool(kMaxDhcpLookupThreads,
59 "PacDhcpLookup");
62 DhcpProxyScriptFetcherWin::~DhcpProxyScriptFetcherWin() {
63 // Count as user-initiated if we are not yet in STATE_DONE.
64 Cancel();
66 worker_pool_->Shutdown();
69 int DhcpProxyScriptFetcherWin::Fetch(base::string16* utf16_text,
70 const CompletionCallback& callback) {
71 // TODO(joi): Remove ScopedTracker below once crbug.com/476182 is fixed.
72 tracked_objects::ScopedTracker tracking_profile1(
73 FROM_HERE_WITH_EXPLICIT_FUNCTION(
74 "476182 DhcpProxyScriptFetcherWin::Fetch 1"));
76 DCHECK(CalledOnValidThread());
77 if (state_ != STATE_START && state_ != STATE_DONE) {
78 NOTREACHED();
79 return ERR_UNEXPECTED;
82 state_ = STATE_WAIT_ADAPTERS;
83 callback_ = callback;
84 destination_string_ = utf16_text;
86 last_query_ = ImplCreateAdapterQuery();
87 // TODO(joi): Remove ScopedTracker below once crbug.com/476182 is fixed.
88 tracked_objects::ScopedTracker tracking_profile2(
89 FROM_HERE_WITH_EXPLICIT_FUNCTION(
90 "476182 DhcpProxyScriptFetcherWin::Fetch 2"));
91 GetTaskRunner()->PostTaskAndReply(
92 FROM_HERE,
93 base::Bind(
94 &DhcpProxyScriptFetcherWin::AdapterQuery::GetCandidateAdapterNames,
95 last_query_.get()),
96 base::Bind(
97 &DhcpProxyScriptFetcherWin::OnGetCandidateAdapterNamesDone,
98 AsWeakPtr(),
99 last_query_));
101 return ERR_IO_PENDING;
104 void DhcpProxyScriptFetcherWin::Cancel() {
105 DCHECK(CalledOnValidThread());
107 CancelImpl();
110 void DhcpProxyScriptFetcherWin::CancelImpl() {
111 DCHECK(CalledOnValidThread());
113 if (state_ != STATE_DONE) {
114 callback_.Reset();
115 wait_timer_.Stop();
116 state_ = STATE_DONE;
118 for (FetcherVector::iterator it = fetchers_.begin();
119 it != fetchers_.end();
120 ++it) {
121 (*it)->Cancel();
124 fetchers_.clear();
128 void DhcpProxyScriptFetcherWin::OnGetCandidateAdapterNamesDone(
129 scoped_refptr<AdapterQuery> query) {
130 // TODO(joi): Remove ScopedTracker below once crbug.com/476182 is fixed.
131 tracked_objects::ScopedTracker tracking_profile1(
132 FROM_HERE_WITH_EXPLICIT_FUNCTION(
133 "476182 "
134 "DhcpProxyScriptFetcherWin::OnGetCandidateAdapterNamesDone 1"));
136 DCHECK(CalledOnValidThread());
138 // This can happen if this object is reused for multiple queries,
139 // and a previous query was cancelled before it completed.
140 if (query.get() != last_query_.get())
141 return;
142 last_query_ = NULL;
144 // Enable unit tests to wait for this to happen; in production this function
145 // call is a no-op.
146 ImplOnGetCandidateAdapterNamesDone();
148 // We may have been cancelled.
149 if (state_ != STATE_WAIT_ADAPTERS)
150 return;
152 state_ = STATE_NO_RESULTS;
154 // TODO(joi): Remove ScopedTracker below once crbug.com/476182 is fixed.
155 tracked_objects::ScopedTracker tracking_profile2(
156 FROM_HERE_WITH_EXPLICIT_FUNCTION(
157 "476182 "
158 "DhcpProxyScriptFetcherWin::OnGetCandidateAdapterNamesDone 2"));
160 const std::set<std::string>& adapter_names = query->adapter_names();
162 if (adapter_names.empty()) {
163 TransitionToDone();
164 return;
167 // TODO(joi): Remove ScopedTracker below once crbug.com/476182 is fixed.
168 tracked_objects::ScopedTracker tracking_profile3(
169 FROM_HERE_WITH_EXPLICIT_FUNCTION(
170 "476182 "
171 "DhcpProxyScriptFetcherWin::OnGetCandidateAdapterNamesDone 3"));
173 for (std::set<std::string>::const_iterator it = adapter_names.begin();
174 it != adapter_names.end();
175 ++it) {
176 DhcpProxyScriptAdapterFetcher* fetcher(ImplCreateAdapterFetcher());
177 fetcher->Fetch(
178 *it, base::Bind(&DhcpProxyScriptFetcherWin::OnFetcherDone,
179 base::Unretained(this)));
180 fetchers_.push_back(fetcher);
182 num_pending_fetchers_ = fetchers_.size();
185 std::string DhcpProxyScriptFetcherWin::GetFetcherName() const {
186 DCHECK(CalledOnValidThread());
187 return "win";
190 const GURL& DhcpProxyScriptFetcherWin::GetPacURL() const {
191 DCHECK(CalledOnValidThread());
192 DCHECK_EQ(state_, STATE_DONE);
194 return pac_url_;
197 void DhcpProxyScriptFetcherWin::OnFetcherDone(int result) {
198 DCHECK(state_ == STATE_NO_RESULTS || state_ == STATE_SOME_RESULTS);
200 if (--num_pending_fetchers_ == 0) {
201 TransitionToDone();
202 return;
205 // If the only pending adapters are those less preferred than one
206 // with a valid PAC script, we do not need to wait any longer.
207 for (FetcherVector::iterator it = fetchers_.begin();
208 it != fetchers_.end();
209 ++it) {
210 bool did_finish = (*it)->DidFinish();
211 int result = (*it)->GetResult();
212 if (did_finish && result == OK) {
213 TransitionToDone();
214 return;
216 if (!did_finish || result != ERR_PAC_NOT_IN_DHCP) {
217 break;
221 // Once we have a single result, we set a maximum on how long to wait
222 // for the rest of the results.
223 if (state_ == STATE_NO_RESULTS) {
224 state_ = STATE_SOME_RESULTS;
225 wait_timer_.Start(FROM_HERE,
226 ImplGetMaxWait(), this, &DhcpProxyScriptFetcherWin::OnWaitTimer);
230 void DhcpProxyScriptFetcherWin::OnWaitTimer() {
231 DCHECK_EQ(state_, STATE_SOME_RESULTS);
233 TransitionToDone();
236 void DhcpProxyScriptFetcherWin::TransitionToDone() {
237 DCHECK(state_ == STATE_NO_RESULTS || state_ == STATE_SOME_RESULTS);
239 int result = ERR_PAC_NOT_IN_DHCP; // Default if no fetchers.
240 if (!fetchers_.empty()) {
241 // Scan twice for the result; once through the whole list for success,
242 // then if no success, return result for most preferred network adapter,
243 // preferring "real" network errors to the ERR_PAC_NOT_IN_DHCP error.
244 // Default to ERR_ABORTED if no fetcher completed.
245 result = ERR_ABORTED;
246 for (FetcherVector::iterator it = fetchers_.begin();
247 it != fetchers_.end();
248 ++it) {
249 if ((*it)->DidFinish() && (*it)->GetResult() == OK) {
250 result = OK;
251 *destination_string_ = (*it)->GetPacScript();
252 pac_url_ = (*it)->GetPacURL();
253 break;
256 if (result != OK) {
257 destination_string_->clear();
258 for (FetcherVector::iterator it = fetchers_.begin();
259 it != fetchers_.end();
260 ++it) {
261 if ((*it)->DidFinish()) {
262 result = (*it)->GetResult();
263 if (result != ERR_PAC_NOT_IN_DHCP) {
264 break;
271 CompletionCallback callback = callback_;
272 CancelImpl();
273 DCHECK_EQ(state_, STATE_DONE);
274 DCHECK(fetchers_.empty());
275 DCHECK(callback_.is_null()); // Invariant of data.
277 // We may be deleted re-entrantly within this outcall.
278 callback.Run(result);
281 int DhcpProxyScriptFetcherWin::num_pending_fetchers() const {
282 return num_pending_fetchers_;
285 URLRequestContext* DhcpProxyScriptFetcherWin::url_request_context() const {
286 return url_request_context_;
289 scoped_refptr<base::TaskRunner> DhcpProxyScriptFetcherWin::GetTaskRunner() {
290 return worker_pool_->GetTaskRunnerWithShutdownBehavior(
291 base::SequencedWorkerPool::CONTINUE_ON_SHUTDOWN);
294 DhcpProxyScriptAdapterFetcher*
295 DhcpProxyScriptFetcherWin::ImplCreateAdapterFetcher() {
296 return new DhcpProxyScriptAdapterFetcher(url_request_context_,
297 GetTaskRunner());
300 DhcpProxyScriptFetcherWin::AdapterQuery*
301 DhcpProxyScriptFetcherWin::ImplCreateAdapterQuery() {
302 return new AdapterQuery();
305 base::TimeDelta DhcpProxyScriptFetcherWin::ImplGetMaxWait() {
306 return base::TimeDelta::FromMilliseconds(kMaxWaitAfterFirstResultMs);
309 bool DhcpProxyScriptFetcherWin::GetCandidateAdapterNames(
310 std::set<std::string>* adapter_names) {
311 DCHECK(adapter_names);
312 adapter_names->clear();
314 // The GetAdaptersAddresses MSDN page recommends using a size of 15000 to
315 // avoid reallocation.
316 ULONG adapters_size = 15000;
317 scoped_ptr<IP_ADAPTER_ADDRESSES, base::FreeDeleter> adapters;
318 ULONG error = ERROR_SUCCESS;
319 int num_tries = 0;
321 do {
322 adapters.reset(static_cast<IP_ADAPTER_ADDRESSES*>(malloc(adapters_size)));
323 // Return only unicast addresses, and skip information we do not need.
324 error = GetAdaptersAddresses(AF_UNSPEC,
325 GAA_FLAG_SKIP_ANYCAST |
326 GAA_FLAG_SKIP_MULTICAST |
327 GAA_FLAG_SKIP_DNS_SERVER |
328 GAA_FLAG_SKIP_FRIENDLY_NAME,
329 NULL,
330 adapters.get(),
331 &adapters_size);
332 ++num_tries;
333 } while (error == ERROR_BUFFER_OVERFLOW && num_tries <= 3);
335 if (error == ERROR_NO_DATA) {
336 // There are no adapters that we care about.
337 return true;
340 if (error != ERROR_SUCCESS) {
341 LOG(WARNING) << "Unexpected error retrieving WPAD configuration from DHCP.";
342 return false;
345 IP_ADAPTER_ADDRESSES* adapter = NULL;
346 for (adapter = adapters.get(); adapter; adapter = adapter->Next) {
347 if (adapter->IfType == IF_TYPE_SOFTWARE_LOOPBACK)
348 continue;
349 if ((adapter->Flags & IP_ADAPTER_DHCP_ENABLED) == 0)
350 continue;
352 DCHECK(adapter->AdapterName);
353 adapter_names->insert(adapter->AdapterName);
356 return true;
359 DhcpProxyScriptFetcherWin::AdapterQuery::AdapterQuery() {
362 void DhcpProxyScriptFetcherWin::AdapterQuery::GetCandidateAdapterNames() {
363 ImplGetCandidateAdapterNames(&adapter_names_);
366 const std::set<std::string>&
367 DhcpProxyScriptFetcherWin::AdapterQuery::adapter_names() const {
368 return adapter_names_;
371 bool DhcpProxyScriptFetcherWin::AdapterQuery::ImplGetCandidateAdapterNames(
372 std::set<std::string>* adapter_names) {
373 return DhcpProxyScriptFetcherWin::GetCandidateAdapterNames(adapter_names);
376 DhcpProxyScriptFetcherWin::AdapterQuery::~AdapterQuery() {
379 } // namespace net