Probably broke Win7 Tests (dbg)(6). http://build.chromium.org/p/chromium.win/builders...
[chromium-blink-merge.git] / net / proxy / dhcp_proxy_script_fetcher_win.cc
blob54e2bdfcb7104632a3c678694f52e1c500b4bbc0
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/metrics/histogram.h"
10 #include "base/threading/sequenced_worker_pool.h"
11 #include "base/timer/elapsed_timer.h"
12 #include "net/base/net_errors.h"
13 #include "net/proxy/dhcp_proxy_script_adapter_fetcher_win.h"
15 #include <winsock2.h>
16 #include <iphlpapi.h>
17 #pragma comment(lib, "iphlpapi.lib")
19 namespace {
21 // How many threads to use at maximum to do DHCP lookups. This is
22 // chosen based on the following UMA data:
23 // - When OnWaitTimer fires, ~99.8% of users have 6 or fewer network
24 // adapters enabled for DHCP in total.
25 // - At the same measurement point, ~99.7% of users have 3 or fewer pending
26 // DHCP adapter lookups.
27 // - There is however a very long and thin tail of users who have
28 // systems reporting up to 100+ adapters (this must be some very weird
29 // OS bug (?), probably the cause of http://crbug.com/240034).
31 // The maximum number of threads is chosen such that even systems that
32 // report a huge number of network adapters should not run out of
33 // memory from this number of threads, while giving a good chance of
34 // getting back results for any responsive adapters.
36 // The ~99.8% of systems that have 6 or fewer network adapters will
37 // not grow the thread pool to its maximum size (rather, they will
38 // grow it to 6 or fewer threads) so setting the limit lower would not
39 // improve performance or memory usage on those systems.
40 const int kMaxDhcpLookupThreads = 12;
42 // How long to wait at maximum after we get results (a PAC file or
43 // knowledge that no PAC file is configured) from whichever network
44 // adapter finishes first.
45 const int kMaxWaitAfterFirstResultMs = 400;
47 const int kGetAdaptersAddressesErrors[] = {
48 ERROR_ADDRESS_NOT_ASSOCIATED,
49 ERROR_BUFFER_OVERFLOW,
50 ERROR_INVALID_PARAMETER,
51 ERROR_NOT_ENOUGH_MEMORY,
52 ERROR_NO_DATA,
55 } // namespace
57 namespace net {
59 DhcpProxyScriptFetcherWin::DhcpProxyScriptFetcherWin(
60 URLRequestContext* url_request_context)
61 : state_(STATE_START),
62 num_pending_fetchers_(0),
63 destination_string_(NULL),
64 url_request_context_(url_request_context) {
65 DCHECK(url_request_context_);
67 worker_pool_ = new base::SequencedWorkerPool(kMaxDhcpLookupThreads,
68 "PacDhcpLookup");
71 DhcpProxyScriptFetcherWin::~DhcpProxyScriptFetcherWin() {
72 // Count as user-initiated if we are not yet in STATE_DONE.
73 Cancel();
75 worker_pool_->Shutdown();
78 int DhcpProxyScriptFetcherWin::Fetch(base::string16* utf16_text,
79 const CompletionCallback& callback) {
80 DCHECK(CalledOnValidThread());
81 if (state_ != STATE_START && state_ != STATE_DONE) {
82 NOTREACHED();
83 return ERR_UNEXPECTED;
86 fetch_start_time_ = base::TimeTicks::Now();
88 state_ = STATE_WAIT_ADAPTERS;
89 callback_ = callback;
90 destination_string_ = utf16_text;
92 last_query_ = ImplCreateAdapterQuery();
93 GetTaskRunner()->PostTaskAndReply(
94 FROM_HERE,
95 base::Bind(
96 &DhcpProxyScriptFetcherWin::AdapterQuery::GetCandidateAdapterNames,
97 last_query_.get()),
98 base::Bind(
99 &DhcpProxyScriptFetcherWin::OnGetCandidateAdapterNamesDone,
100 AsWeakPtr(),
101 last_query_));
103 return ERR_IO_PENDING;
106 void DhcpProxyScriptFetcherWin::Cancel() {
107 DCHECK(CalledOnValidThread());
109 if (state_ != STATE_DONE) {
110 // We only count this stat if the cancel was explicitly initiated by
111 // our client, and if we weren't already in STATE_DONE.
112 UMA_HISTOGRAM_TIMES("Net.DhcpWpadCancelTime",
113 base::TimeTicks::Now() - fetch_start_time_);
116 CancelImpl();
119 void DhcpProxyScriptFetcherWin::CancelImpl() {
120 DCHECK(CalledOnValidThread());
122 if (state_ != STATE_DONE) {
123 callback_.Reset();
124 wait_timer_.Stop();
125 state_ = STATE_DONE;
127 for (FetcherVector::iterator it = fetchers_.begin();
128 it != fetchers_.end();
129 ++it) {
130 (*it)->Cancel();
133 fetchers_.clear();
137 void DhcpProxyScriptFetcherWin::OnGetCandidateAdapterNamesDone(
138 scoped_refptr<AdapterQuery> query) {
139 DCHECK(CalledOnValidThread());
141 // This can happen if this object is reused for multiple queries,
142 // and a previous query was cancelled before it completed.
143 if (query.get() != last_query_.get())
144 return;
145 last_query_ = NULL;
147 // Enable unit tests to wait for this to happen; in production this function
148 // call is a no-op.
149 ImplOnGetCandidateAdapterNamesDone();
151 // We may have been cancelled.
152 if (state_ != STATE_WAIT_ADAPTERS)
153 return;
155 state_ = STATE_NO_RESULTS;
157 const std::set<std::string>& adapter_names = query->adapter_names();
159 if (adapter_names.empty()) {
160 TransitionToDone();
161 return;
164 for (std::set<std::string>::const_iterator it = adapter_names.begin();
165 it != adapter_names.end();
166 ++it) {
167 DhcpProxyScriptAdapterFetcher* fetcher(ImplCreateAdapterFetcher());
168 fetcher->Fetch(
169 *it, base::Bind(&DhcpProxyScriptFetcherWin::OnFetcherDone,
170 base::Unretained(this)));
171 fetchers_.push_back(fetcher);
173 num_pending_fetchers_ = fetchers_.size();
176 std::string DhcpProxyScriptFetcherWin::GetFetcherName() const {
177 DCHECK(CalledOnValidThread());
178 return "win";
181 const GURL& DhcpProxyScriptFetcherWin::GetPacURL() const {
182 DCHECK(CalledOnValidThread());
183 DCHECK_EQ(state_, STATE_DONE);
185 return pac_url_;
188 void DhcpProxyScriptFetcherWin::OnFetcherDone(int result) {
189 DCHECK(state_ == STATE_NO_RESULTS || state_ == STATE_SOME_RESULTS);
191 if (--num_pending_fetchers_ == 0) {
192 TransitionToDone();
193 return;
196 // If the only pending adapters are those less preferred than one
197 // with a valid PAC script, we do not need to wait any longer.
198 for (FetcherVector::iterator it = fetchers_.begin();
199 it != fetchers_.end();
200 ++it) {
201 bool did_finish = (*it)->DidFinish();
202 int result = (*it)->GetResult();
203 if (did_finish && result == OK) {
204 TransitionToDone();
205 return;
207 if (!did_finish || result != ERR_PAC_NOT_IN_DHCP) {
208 break;
212 // Once we have a single result, we set a maximum on how long to wait
213 // for the rest of the results.
214 if (state_ == STATE_NO_RESULTS) {
215 state_ = STATE_SOME_RESULTS;
216 wait_timer_.Start(FROM_HERE,
217 ImplGetMaxWait(), this, &DhcpProxyScriptFetcherWin::OnWaitTimer);
221 void DhcpProxyScriptFetcherWin::OnWaitTimer() {
222 DCHECK_EQ(state_, STATE_SOME_RESULTS);
224 // These are intended to help us understand whether our timeout may
225 // be too aggressive or not aggressive enough.
226 UMA_HISTOGRAM_COUNTS_100("Net.DhcpWpadNumAdaptersAtWaitTimer",
227 fetchers_.size());
228 UMA_HISTOGRAM_COUNTS_100("Net.DhcpWpadNumPendingAdaptersAtWaitTimer",
229 num_pending_fetchers_);
231 TransitionToDone();
234 void DhcpProxyScriptFetcherWin::TransitionToDone() {
235 DCHECK(state_ == STATE_NO_RESULTS || state_ == STATE_SOME_RESULTS);
237 int result = ERR_PAC_NOT_IN_DHCP; // Default if no fetchers.
238 if (!fetchers_.empty()) {
239 // Scan twice for the result; once through the whole list for success,
240 // then if no success, return result for most preferred network adapter,
241 // preferring "real" network errors to the ERR_PAC_NOT_IN_DHCP error.
242 // Default to ERR_ABORTED if no fetcher completed.
243 result = ERR_ABORTED;
244 for (FetcherVector::iterator it = fetchers_.begin();
245 it != fetchers_.end();
246 ++it) {
247 if ((*it)->DidFinish() && (*it)->GetResult() == OK) {
248 result = OK;
249 *destination_string_ = (*it)->GetPacScript();
250 pac_url_ = (*it)->GetPacURL();
251 break;
254 if (result != OK) {
255 destination_string_->clear();
256 for (FetcherVector::iterator it = fetchers_.begin();
257 it != fetchers_.end();
258 ++it) {
259 if ((*it)->DidFinish()) {
260 result = (*it)->GetResult();
261 if (result != ERR_PAC_NOT_IN_DHCP) {
262 break;
269 CompletionCallback callback = callback_;
270 CancelImpl();
271 DCHECK_EQ(state_, STATE_DONE);
272 DCHECK(fetchers_.empty());
273 DCHECK(callback_.is_null()); // Invariant of data.
275 UMA_HISTOGRAM_TIMES("Net.DhcpWpadCompletionTime",
276 base::TimeTicks::Now() - fetch_start_time_);
278 if (result != OK) {
279 UMA_HISTOGRAM_CUSTOM_ENUMERATION(
280 "Net.DhcpWpadFetchError", std::abs(result), GetAllErrorCodesForUma());
283 // We may be deleted re-entrantly within this outcall.
284 callback.Run(result);
287 int DhcpProxyScriptFetcherWin::num_pending_fetchers() const {
288 return num_pending_fetchers_;
291 URLRequestContext* DhcpProxyScriptFetcherWin::url_request_context() const {
292 return url_request_context_;
295 scoped_refptr<base::TaskRunner> DhcpProxyScriptFetcherWin::GetTaskRunner() {
296 return worker_pool_->GetTaskRunnerWithShutdownBehavior(
297 base::SequencedWorkerPool::CONTINUE_ON_SHUTDOWN);
300 DhcpProxyScriptAdapterFetcher*
301 DhcpProxyScriptFetcherWin::ImplCreateAdapterFetcher() {
302 return new DhcpProxyScriptAdapterFetcher(url_request_context_,
303 GetTaskRunner());
306 DhcpProxyScriptFetcherWin::AdapterQuery*
307 DhcpProxyScriptFetcherWin::ImplCreateAdapterQuery() {
308 return new AdapterQuery();
311 base::TimeDelta DhcpProxyScriptFetcherWin::ImplGetMaxWait() {
312 return base::TimeDelta::FromMilliseconds(kMaxWaitAfterFirstResultMs);
315 bool DhcpProxyScriptFetcherWin::GetCandidateAdapterNames(
316 std::set<std::string>* adapter_names) {
317 DCHECK(adapter_names);
318 adapter_names->clear();
320 // The GetAdaptersAddresses MSDN page recommends using a size of 15000 to
321 // avoid reallocation.
322 ULONG adapters_size = 15000;
323 scoped_ptr<IP_ADAPTER_ADDRESSES, base::FreeDeleter> adapters;
324 ULONG error = ERROR_SUCCESS;
325 int num_tries = 0;
327 base::ElapsedTimer time_api_access;
328 do {
329 adapters.reset(static_cast<IP_ADAPTER_ADDRESSES*>(malloc(adapters_size)));
330 // Return only unicast addresses, and skip information we do not need.
331 error = GetAdaptersAddresses(AF_UNSPEC,
332 GAA_FLAG_SKIP_ANYCAST |
333 GAA_FLAG_SKIP_MULTICAST |
334 GAA_FLAG_SKIP_DNS_SERVER |
335 GAA_FLAG_SKIP_FRIENDLY_NAME,
336 NULL,
337 adapters.get(),
338 &adapters_size);
339 ++num_tries;
340 } while (error == ERROR_BUFFER_OVERFLOW && num_tries <= 3);
342 // This is primarily to validate our belief that the GetAdaptersAddresses API
343 // function is fast enough to call synchronously from the network thread.
344 UMA_HISTOGRAM_TIMES("Net.DhcpWpadGetAdaptersAddressesTime",
345 time_api_access.Elapsed());
347 if (error != ERROR_SUCCESS) {
348 UMA_HISTOGRAM_CUSTOM_ENUMERATION(
349 "Net.DhcpWpadGetAdaptersAddressesError",
350 error,
351 base::CustomHistogram::ArrayToCustomRanges(
352 kGetAdaptersAddressesErrors,
353 arraysize(kGetAdaptersAddressesErrors)));
356 if (error == ERROR_NO_DATA) {
357 // There are no adapters that we care about.
358 return true;
361 if (error != ERROR_SUCCESS) {
362 LOG(WARNING) << "Unexpected error retrieving WPAD configuration from DHCP.";
363 return false;
366 IP_ADAPTER_ADDRESSES* adapter = NULL;
367 for (adapter = adapters.get(); adapter; adapter = adapter->Next) {
368 if (adapter->IfType == IF_TYPE_SOFTWARE_LOOPBACK)
369 continue;
370 if ((adapter->Flags & IP_ADAPTER_DHCP_ENABLED) == 0)
371 continue;
373 DCHECK(adapter->AdapterName);
374 adapter_names->insert(adapter->AdapterName);
377 return true;
380 DhcpProxyScriptFetcherWin::AdapterQuery::AdapterQuery() {
383 DhcpProxyScriptFetcherWin::AdapterQuery::~AdapterQuery() {
386 void DhcpProxyScriptFetcherWin::AdapterQuery::GetCandidateAdapterNames() {
387 ImplGetCandidateAdapterNames(&adapter_names_);
390 const std::set<std::string>&
391 DhcpProxyScriptFetcherWin::AdapterQuery::adapter_names() const {
392 return adapter_names_;
395 bool DhcpProxyScriptFetcherWin::AdapterQuery::ImplGetCandidateAdapterNames(
396 std::set<std::string>* adapter_names) {
397 return DhcpProxyScriptFetcherWin::GetCandidateAdapterNames(adapter_names);
400 } // namespace net