Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(75)

Side by Side Diff: net/proxy/proxy_resolver_v8_tracing.cc

Issue 11885009: Improve performance of proxy resolver by tracing DNS dependencies. (Closed) Base URL: http://git.chromium.org/chromium/src.git@master
Patch Set: rebase off trunk Created 7 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright (c) 2013 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/proxy/proxy_resolver_v8_tracing.h"
6
7 #include "base/bind.h"
8 #include "base/md5.h"
9 #include "base/message_loop_proxy.h"
10 #include "base/string_number_conversions.h"
11 #include "base/string_util.h"
12 #include "base/stringprintf.h"
13 #include "base/synchronization/cancellation_flag.h"
14 #include "base/synchronization/waitable_event.h"
15 #include "base/threading/thread.h"
16 #include "base/threading/thread_restrictions.h"
17 #include "base/values.h"
18 #include "net/base/address_list.h"
19 #include "net/base/host_resolver.h"
20 #include "net/base/net_errors.h"
21 #include "net/base/net_log.h"
22 #include "net/proxy/proxy_info.h"
23 #include "net/proxy/proxy_resolver_error_observer.h"
24 #include "net/proxy/proxy_resolver_v8.h"
25
26 // The intent of this class is explained in the design document:
27 // https://docs.google.com/a/chromium.org/document/d/16Ij5OcVnR3s0MH4Z5XkhI9VTPo MJdaBn9rKreAmGOdE/edit
mmenke 2013/01/18 19:59:40 "Sorry, the file you have requested does not exist
eroman 2013/01/23 03:26:02 Are you sure you copied the full line exactly? (it
mmenke 2013/01/24 21:06:33 Oops...A straight copy gets the spaces inserted by
28 //
29 // In a nutshell, PAC scripts are Javascript programs and may depend on
30 // network I/O, by calling functions like dnsResolve().
31 //
32 // This is problematic since functions such as dnsResolve() will block the
33 // Javascript execution until the DNS result is availble, thereby stalling the
34 // PAC thread, which hurts the ability to process parallel proxy resolves.
35 // An obvious solution is to simply start more PAC threads, however this scales
36 // poorly, which hurts the ability to process parallel proxy resolves.
37 //
38 // The solution in ProxyResolverV8Tracing is to model PAC scripts as being
39 // deterministic, and depending only on the inputted URL. When the script
40 // issues a dnsResolve() for a yet unresolved hostname, the Javascript
41 // execution is "aborted", and then re-started once the DNS result is
42 // known.
43 namespace net {
44
45 namespace {
46
47 // Upper bound on how many *unique* DNS resolves a PAC script is allowed
48 // to make. This is a failsafe in case the logic to detect PAC scripts
49 // incompatible with the tracing optimization doesn't work. It is not
50 // expected for well behaved scripts to hit this.
51 const size_t kMaxUniqueResolveDnsPerExec = 20;
52
53 // Approximate number of bytes to use for buffering alerts() and errors.
54 // This is a failsafe in case repeated executions of the script causes
55 // too much memory bloat. It is not expected for well behaved scripts to
56 // hit this. (In fact normal scripts should not even have alerts() or errors).
57 const size_t kMaxAlertsAndErrorsBytes = 2048;
58
59 // Compares two MD5 digests for equality. The second digest is given as a
60 // non-finalized MD5Context.
61 bool MD5DigestEquals(const base::MD5Digest& digest1,
62 const base::MD5Context& digest2_context) {
63 // Finalize the context into a digest. (Make a copy first
64 // because MD5Final() mutates the context).
65 base::MD5Digest digest2;
66 base::MD5Context digest2_copy;
67 memcpy(&digest2_copy[0], &digest2_context[0], sizeof(base::MD5Context));
68 base::MD5Final(&digest2, &digest2_copy);
69
70 return memcmp(&digest1, &digest2, sizeof(base::MD5Digest)) == 0;
71 }
72
73 // Returns event parameters for a PAC error message (line number + message).
74 base::Value* NetLogErrorCallback(int line_number,
75 const string16* message,
76 NetLog::LogLevel /* log_level */) {
77 base::DictionaryValue* dict = new base::DictionaryValue();
78 dict->SetInteger("line_number", line_number);
79 dict->SetString("message", *message);
80 return dict;
81 }
82
83 } // namespace
84
85 // The Job class is responsible for executing GetProxyForURL() and
86 // SetPacScript(), since both of these operations share similar code.
87 //
88 // The DNS for these operations can operate in either synchronous or
89 // asynchronous mode. Synchronous mode is used as a fallback when the PAC script
90 // seems to be misbehaving under the tracing optimization.
mmenke 2013/01/18 19:59:40 You call these blocking and non-blocking elsewhere
eroman 2013/01/23 03:26:02 Done -- renamed everything to blocking/non-blockin
91 //
92 // Note that this class runs on both the origin thread and a worker
93 // thread. Most methods are expected to be used exclusively on one thread
94 // or the other.
95 //
96 // The lifetiime of Jobs does not exceed that of the ProxyResolverV8Tracing that
mmenke 2013/01/18 19:59:40 nit: lifetime
eroman 2013/01/23 03:26:02 Done.
97 // spawned it. Destruction might happen on either the origin thread or the
98 // worker thread.
99 class ProxyResolverV8Tracing::Job
100 : public base::RefCountedThreadSafe<ProxyResolverV8Tracing::Job>,
101 public ProxyResolverV8::JSBindings {
102 public:
103 // |parent| is non-owned, and references the ProxyResolverV8Tracing that
104 // spawned this Job.
105 explicit Job(ProxyResolverV8Tracing* parent)
106 : origin_loop_(base::MessageLoopProxy::current()),
107 parent_(parent),
108 event_(true, false) {
109 }
110
111 void StartSetPacScript(
112 const scoped_refptr<ProxyResolverScriptData>& script_data,
113 const CompletionCallback& callback) {
mmenke 2013/01/18 19:59:40 I recommend de-inlining all these functions. With
eroman 2013/01/23 03:26:02 Done.
114 CheckIsOnOriginThread();
115
116 operation_ = SET_PAC_SCRIPT;
117 // Script initialization uses a synchronous DNS since there isn't any
118 // advantage to using asynchronous mode here. That is because the
119 // parent ProxyService can't submit any ProxyResolve requests until
120 // initialization has completed successfully!
mmenke 2013/01/18 19:59:40 Setting the PAC can call scripts?
eroman 2013/01/23 03:26:02 Setting the PAC involves "sourcing" the script. It
121 nonblocking_dns_ = false;
122
123 script_data_ = script_data;
124 callback_ = callback;
125
126 PostStart();
127 }
128
129 void StartGetProxyForURL(const GURL& url,
130 ProxyInfo* results,
131 const BoundNetLog& net_log,
132 const CompletionCallback& callback) {
133 CheckIsOnOriginThread();
134
135 operation_ = GET_PROXY_FOR_URL;
136 nonblocking_dns_ = true;
137
138 url_ = url;
139 user_results_ = results;
140 bound_net_log_ = net_log;
141 callback_ = callback;
142
143 last_num_dns_ = 0;
144
145 PostStart();
146 }
147
148 void Cancel() {
149 CheckIsOnOriginThread();
150
151 // There are three main possibility to consider for cancellation:
152 // (a) The job has been posted to the worker thread, but not yet started
153 // (b) The script is executing on the worker thread
154 // (c) The script is blocked waiting for DNS
mmenke 2013/01/18 19:59:40 (d) The script has stopped, and there's a pending
eroman 2013/01/23 03:26:02 Done.
155 //
156 // |cancelled_| is read on both the origin thread and worker thread. The
157 // code that runs on the worker thread is littered with checks on
158 // |cancelled_| to break out early. Breaking out early is not necessary
159 // for correctness, however it does avoid doing unecessary work.
mmenke 2013/01/18 19:59:40 It is necessary to in the blocking case, isn't it?
eroman 2013/01/23 03:26:02 Yes that is true, cancellation which interrupts a
160 //
161 // The important check on |cancelled_| is done from the origin thread in
162 // NotifyCallerOnOriginLoop().
163 cancelled_.Set();
164
165 // These two assignments are not necessary for correctness. Just defensive
166 // programming to make sure the user-provided data is not used beyond this
167 // point.
168 user_results_ = NULL;
169 callback_ = CompletionCallback();
170
171 if (pending_dns_.get()) {
mmenke 2013/01/18 19:59:40 nit: Preferred style is not to use get().
eroman 2013/01/23 03:26:02 Done.
172 host_resolver()->CancelRequest(pending_dns_->handle);
173 pending_dns_.reset();
174 }
175
176 // The worker thread might be blocked waiting for DNS.
177 event_.Signal();
178
179 Release();
180 }
181
182 LoadState GetLoadState() const {
183 CheckIsOnOriginThread();
184
185 if (pending_dns_.get())
mmenke 2013/01/18 19:59:40 nit: Preferred style is not to use get().
eroman 2013/01/23 03:26:02 Done.
186 return LOAD_STATE_RESOLVING_HOST_IN_PROXY_SCRIPT;
187
188 return LOAD_STATE_RESOLVING_PROXY_FOR_URL;
189 }
190
191 private:
192 friend class base::RefCountedThreadSafe<ProxyResolverV8Tracing::Job>;
193
194 enum Operation {
195 SET_PAC_SCRIPT,
196 GET_PROXY_FOR_URL,
197 };
198
199 struct AlertOrError {
200 AlertOrError(bool is_alert, int line_number, const string16& message)
201 : is_alert(is_alert), line_number(line_number), message(message) {}
202
203 bool is_alert;
204 int line_number;
205 string16 message;
mmenke 2013/01/18 19:59:40 Could you make these const? Not sure if that woul
eroman 2013/01/23 03:26:02 Yeah const makes std::vector sad, because push_bac
206 };
207
208 struct ExecutionState {
209 bool was_abandoned;
210
211 // The number of times ResolveDns() was called so far.
212 int num_dns;
213 base::MD5Context dns_digest;
214
215 size_t alerts_and_errors_bytes_;
216 std::vector<AlertOrError> alerts_and_errors_;
217 };
218
219 struct PendingDns {
220 PendingDns(const std::string& host, ResolveDnsOperation op)
221 : host(host), op(op), handle(NULL) {}
222
223 std::string host;
224 ResolveDnsOperation op;
225
226 HostResolver::RequestHandle handle;
227 net::AddressList addresses;
228 };
229
230 ~Job() {
231 DCHECK(!pending_dns_.get());
232 }
233
234 void CheckIsOnWorkerThread() {
235 DCHECK_EQ(MessageLoop::current(), parent_->thread_->message_loop());
236 }
237
238 void CheckIsOnOriginThread() const {
239 DCHECK(origin_loop_->BelongsToCurrentThread());
240 }
241
242 ProxyResolverV8* v8_resolver() {
243 return parent_->v8_resolver_.get();
244 }
245
246 MessageLoop* worker_loop() {
247 return parent_->thread_->message_loop();
248 }
249
250 HostResolver* host_resolver() {
251 return parent_->host_resolver_;
252 }
253
254 ProxyResolverErrorObserver* error_observer() {
255 return parent_->error_observer_.get();
256 }
257
258 NetLog* net_log() {
259 return parent_->net_log_;
260 }
261
262 void PostStart() {
263 AddRef(); // Balanced on completion, or cancellation.
264 worker_loop()->PostTask(FROM_HERE, base::Bind(&Job::Start, this));
265 }
266
267 // Post a task to the origin thread, to invoke the user's callback.
268 void NotifyCaller(int result) {
269 CheckIsOnWorkerThread();
270
271 origin_loop_->PostTask(
272 FROM_HERE,
273 base::Bind(&Job::NotifyCallerOnOriginLoop, this, result));
274 }
275
276 void NotifyCallerOnOriginLoop(int result) {
277 CheckIsOnOriginThread();
278
279 if (cancelled_.IsSet())
280 return;
281
282 if (operation_ == GET_PROXY_FOR_URL)
283 *user_results_ = results_;
284
285 // There is only ever 1 outstanding SET_PAC_SCRIPT job. It needs to be
286 // tracked to support cancellation.
287 if (operation_ == SET_PAC_SCRIPT) {
288 DCHECK(parent_->set_pac_script_job_);
289 parent_->set_pac_script_job_ = NULL;
290 }
291
292 callback_.Run(result);
293 callback_.Reset();
294
295 Release();
296 }
297
298 void Start() {
299 CheckIsOnWorkerThread();
300
301 if (cancelled_.IsSet())
302 return;
303
304 if (nonblocking_dns_) {
305 // Reset state pertaining to the current playthrough.
306 exec_.was_abandoned = false;
307 exec_.num_dns = 0;
308 base::MD5Init(&exec_.dns_digest);
309 exec_.alerts_and_errors_.clear();
310 exec_.alerts_and_errors_bytes_ = 0;
311 }
312
313 JSBindings* prev_bindings = v8_resolver()->js_bindings();
314 v8_resolver()->set_js_bindings(this);
315
316 int result = ERR_UNEXPECTED; // Initialized to silence warnings.
317
318 switch (operation_) {
319 case SET_PAC_SCRIPT:
320 result = v8_resolver()->SetPacScript(
321 script_data_, CompletionCallback());
322 break;
323 case GET_PROXY_FOR_URL:
324 result = v8_resolver()->GetProxyForURL(
325 url_,
326 // Important: Do not write directly into |user_results_|, since if the
327 // request were to be cancelled from the origin thread, must guarantee
328 // that |user_results| is not accessed anymore must guarantee that
329 // |user_results| is not accessed anymore.
330 &results_,
331 CompletionCallback(),
332 NULL,
333 bound_net_log_);
334 break;
335 }
336
337 v8_resolver()->set_js_bindings(prev_bindings);
338
339 if (nonblocking_dns_) {
340 if (!exec_.was_abandoned) {
341 // Some alerts() and errors may have been buffered, since it was not
342 // known at the time whether this run would be discarded. Now that it is
343 // known to be a full run, dispatch those buffered events.
344 DispatchBufferedAlertsAndErrors();
345 NotifyCaller(result);
346 }
347 } else {
348 NotifyCaller(result);
349 }
350 }
351
352 // Handler for "alert(message)"
353 virtual void Alert(const string16& message) OVERRIDE {
354 HandleAlertOrError(true, -1, message);
355 }
356
357 // Handler for when an error is encountered. |line_number| may be -1
358 // if a line number is not applicable to this error.
359 virtual void OnError(int line_number, const string16& error) OVERRIDE {
360 HandleAlertOrError(false, line_number, error);
361 }
362
363 void HandleAlertOrError(bool is_alert, int line_number,
364 const string16& message) {
365 if (cancelled_.IsSet())
366 return;
367
368 if (!nonblocking_dns_) {
369 // In blocking DNS mode the events can be dispatched immediately.
370 DispatchAlertOrError(is_alert, line_number, message);
371 return;
372 }
373
374 // Otherwise in nonblocking mode, buffer all the messages until
375 // the end.
376
377 if (exec_.was_abandoned)
378 return;
379
380 exec_.alerts_and_errors_bytes_ += message.size() * 2;
381
382 // If there have been lots of messages, enqueuing could be expensive on
383 // memory. (consider a script which does megabytes worth of alerts()).
384 // Avoid this by falling back to blocking mode.
385 if (exec_.alerts_and_errors_bytes_ > kMaxAlertsAndErrorsBytes) {
386 ScheduleRestartWithBlockingDns();
387 exec_.was_abandoned = true;
388 return;
389 }
390
391 exec_.alerts_and_errors_.push_back(
392 AlertOrError(is_alert, line_number, message));
393 }
394
395 void DispatchBufferedAlertsAndErrors() {
396 DCHECK(nonblocking_dns_);
397 DCHECK(!exec_.was_abandoned);
398
399 for (size_t i = 0; i < exec_.alerts_and_errors_.size(); ++i) {
400 const AlertOrError& x = exec_.alerts_and_errors_[i];
401 DispatchAlertOrError(x.is_alert, x.line_number, x.message);
402 }
403 }
404
405 void DispatchAlertOrError(bool is_alert, int line_number,
406 const string16& message) {
407 // Note that the handling of cancellation is racy with regard to
408 // alerts/errors. The request might get cancelled shortly after this
409 // check! (There is no lock being held to guarantee otherwise).
410 //
411 // If this happens, then some information will get written to the NetLog
412 // needlessly, however the NetLog will still be alive so it shouldn't cause
413 // problems.
414 if (cancelled_.IsSet())
415 return;
416
417 if (is_alert) {
418 // -------------------
419 // alert
420 // -------------------
421 VLOG(1) << "PAC-alert: " << message;
422
423 // Send to the NetLog.
424 LogEventToCurrentRequestAndGlobally(
425 NetLog::TYPE_PAC_JAVASCRIPT_ALERT,
426 NetLog::StringCallback("message", &message));
427 } else {
428 // -------------------
429 // error
430 // -------------------
431 if (line_number == -1)
432 VLOG(1) << "PAC-error: " << message;
433 else
434 VLOG(1) << "PAC-error: " << "line: " << line_number << ": " << message;
435
436 // Send the error to the NetLog.
437 LogEventToCurrentRequestAndGlobally(
438 NetLog::TYPE_PAC_JAVASCRIPT_ERROR,
439 base::Bind(&NetLogErrorCallback, line_number, &message));
440
441 if (error_observer())
442 error_observer()->OnPACScriptError(line_number, message);
443 }
444 }
445
446 void LogEventToCurrentRequestAndGlobally(
mmenke 2013/01/18 19:59:40 nit: Fix indent.
eroman 2013/01/23 03:26:02 Done.
447 NetLog::EventType type,
448 const NetLog::ParametersCallback& parameters_callback) {
449 bound_net_log_.AddEvent(type, parameters_callback);
450
451 // Emit to the global NetLog event stream.
452 if (net_log())
453 net_log()->AddGlobalEntry(type, parameters_callback);
454 }
455
456 virtual bool ResolveDns(const std::string& host,
457 ResolveDnsOperation op,
458 std::string* output) {
459 if (cancelled_.IsSet())
460 return false;
461
462 if ((op == DNS_RESOLVE || op == DNS_RESOLVE_EX) && host.empty()) {
463 // a DNS resolve with an empty hostname is considered an error.
464 return false;
465 }
466
467 return nonblocking_dns_ ?
468 ResolveDnsNonBlocking(host, op, output) :
469 ResolveDnsBlocking(host, op, output);
mmenke 2013/01/18 19:59:40 nit: 4 space indent.
eroman 2013/01/23 03:26:02 Done.
470 }
471
472 bool ResolveDnsBlocking(const std::string& host,
473 ResolveDnsOperation op,
474 std::string* output) {
475 CheckIsOnWorkerThread();
476
477 // Check if the DNS result for this host has already been cached.
478 bool rv;
479 if (GetDnsFromLocalCache(host, op, output, &rv)) {
480 // Yay, cache hit!
481 return rv;
482 }
483
484 // If the host was not in the local cache, this is a new hostname.
485
486 if (dns_cache_.size() > kMaxUniqueResolveDnsPerExec) {
487 // Safety net for scripts with unexpectedly many DNS calls.
488 // We will continue running to completion, but will fail every
489 // subsequent DNS request.
490 return false;
491 }
492
493 bool unused;
494 origin_loop_->PostTask(
495 FROM_HERE, base::Bind(&Job::DoDnsOperation, this, host, op, &unused));
496
497 // Wait for the DNS operation to be completed by the host resolver on the
498 // origin thread. After waking up, either the request was cancelled, or
499 // the DNS result is now available in the cache.
500 event_.Wait();
501 event_.Reset();
502
503 if (cancelled_.IsSet())
504 return false;
505
506 CHECK(GetDnsFromLocalCache(host, op, output, &rv));
507 return rv;
508 }
509
510 // To detect inconsistencies, the past sequence of ResovleDns operations are
511 // saved and enforced during each replay. Instead of saving the full sequence
512 // of hostnames, just an MD5 checksum is kept (to avoid unbounded memory
513 // growth).
514 bool ResolveDnsNonBlocking(const std::string& host,
515 ResolveDnsOperation op,
516 std::string* output) {
517 CheckIsOnWorkerThread();
518
519 if (exec_.was_abandoned) {
520 // If this execution was alread abandoned can fail right away. Only 1 DNS
mmenke 2013/01/18 19:59:40 nit "already"
eroman 2013/01/23 03:26:02 Done.
521 // dependency will be traced at a time (for more predictable outcomes).
522 return false;
523 }
524
525 exec_.num_dns += 1;
526
527 // Keep a digest of the sequence of DNS requests this execution has seen so
528 // far. This is cheaper than keeping a full list of the history, and good
529 // enough for our purposes (we want to detect when things have gone wrong in
530 // order to fallback to synchronous mode).
531 base::MD5Update(&exec_.dns_digest, host);
532 base::MD5Update(&exec_.dns_digest, base::IntToString(op));
mmenke 2013/01/18 19:59:40 Does this really get us anything we care about ove
eroman 2013/01/23 03:26:02 I chose to enforce the sequence of all DNS request
mmenke 2013/01/24 21:06:33 I was just thinking that it's not too likely that
eroman 2013/01/24 22:13:58 Thanks for discussing this issue! Indeed, none of
533
534 // Upon reaching the point of execution from the last run, make sure
535 // that the sequence of resolves has matched so far.
536 if (exec_.num_dns == last_num_dns_ &&
537 !MD5DigestEquals(last_dns_digest_, exec_.dns_digest)) {
538 // The sequence of DNS operations is different from last time!
539 ScheduleRestartWithBlockingDns();
mmenke 2013/01/18 19:59:40 Restarting instead of just switching is primarily
eroman 2013/01/23 03:26:02 Correct, restarting is to keep things simple. Ther
mmenke 2013/01/24 21:06:33 I was thinking that you could get rid of "Schedule
540 exec_.was_abandoned = true;
541 return false;
542 }
543
544 // Check if the DNS result for this host has already been cached.
545 bool rv;
546 if (GetDnsFromLocalCache(host, op, output, &rv)) {
547 // Yay, cache hit!
548 return rv;
549 }
550
551 // If the host was not in the local cache, then this is a new hostname.
552
553 if (exec_.num_dns <= last_num_dns_) {
554 // The sequence of DNS operations is different from last time!
555 ScheduleRestartWithBlockingDns();
556 exec_.was_abandoned = true;
557 return false;
558 }
559
560 if (dns_cache_.size() > kMaxUniqueResolveDnsPerExec) {
561 // Safety net for scripts with unexpectedly many DNS calls.
562 return false;
563 }
564
565 // Post the DNS request to the origin thread.
566 bool resolver_cache_hit = false;
567 origin_loop_->PostTask(
568 FROM_HERE, base::Bind(&Job::DoDnsOperation, this, host, op,
569 &resolver_cache_hit));
570
571 // As an optimization to avoid restarting too often, wait until the
572 // resolver's cache has been inspected on the origin thread.
573 event_.Wait();
574 event_.Reset();
575
576 if (cancelled_.IsSet())
577 return false;
578
579 if (resolver_cache_hit) {
580 CHECK(GetDnsFromLocalCache(host, op, output, &rv));
581 return rv;
582 }
583
584 // Otherwise if the result was not in the cache, then a DNS request has
585 // been started. Abandon this invocation of FindProxyForURL(), it will be
586 // restarted once the DNS request completes.
587 exec_.was_abandoned = true;
588 last_num_dns_ = exec_.num_dns;
589 base::MD5Final(&last_dns_digest_, &exec_.dns_digest);
590 return false;
591 }
592
593 // Builds a RequestInfo to service the specified PAC DNS operation.
594 static HostResolver::RequestInfo MakeRequestInfo(const std::string& host,
595 ResolveDnsOperation op) {
596 HostPortPair host_port = HostPortPair(host, 80);
597 if (op == MY_IP_ADDRESS || op == MY_IP_ADDRESS_EX) {
598 host_port.set_host(GetHostName());
599 }
600
601 HostResolver::RequestInfo info(host_port);
602
603 // The non-ex flavors are limited to IPv4 results.
604 if (op == MY_IP_ADDRESS || op == DNS_RESOLVE) {
605 info.set_address_family(ADDRESS_FAMILY_IPV4);
606 }
607
608 return info;
609 }
610
611 void DoDnsOperation(const std::string& host, ResolveDnsOperation op,
612 bool* out_cache_hit) {
613 CheckIsOnOriginThread();
614
615 if (cancelled_.IsSet())
616 return;
617
618 DCHECK(!pending_dns_.get());
619
620 HostResolver::RequestInfo info = MakeRequestInfo(host, op);
621
622 if (nonblocking_dns_) {
623 // Check if the DNS result can be serviced directly from the cache.
624 // (The worker thread is blocked waiting for this information).
625 net::AddressList addresses;
626 int result = host_resolver()->ResolveFromCache(
627 info, &addresses, bound_net_log_);
628
629 bool cache_hit = result != ERR_DNS_CACHE_MISS;
630 if (cache_hit) {
631 // Cool, it was in the host resolver's cache. Copy it to the
632 // per-request cache.
633 SaveDnsToLocalCache(host, op, result, addresses);
634 }
635
636 // Important: Do not read/write |out_cache_hit| after signalling, since
637 // the memory may no longer be valid.
638 *out_cache_hit = cache_hit;
639 event_.Signal();
640
641 if (cache_hit)
642 return;
mmenke 2013/01/18 19:59:40 I don't think this cache hit optimization is worth
eroman 2013/01/23 03:26:02 This cuts down quite a few restarts, and IMO is de
mmenke 2013/01/24 21:06:33 Suppose it depends on how expensive script executi
eroman 2013/01/24 23:07:15 One final comment on this issue: Waiting for the
643 }
644
645 pending_dns_.reset(new PendingDns(host, op));
646
647 int result = host_resolver()->Resolve(
648 info,
649 &pending_dns_->addresses, base::Bind(&Job::OnResolveCompletion, this),
650 &pending_dns_->handle, bound_net_log_);
651
652 if (result != ERR_IO_PENDING)
653 OnResolveCompletion(result);
654 }
655
656 void OnResolveCompletion(int result) {
657 CheckIsOnOriginThread();
658
659 DCHECK(!cancelled_.IsSet());
660
661 SaveDnsToLocalCache(pending_dns_->host, pending_dns_->op, result,
662 pending_dns_->addresses);
663 pending_dns_.reset();
664
665 if (nonblocking_dns_) {
666 // Restart. This time it should make more progress due to having
667 // cached items.
668 worker_loop()->PostTask(FROM_HERE, base::Bind(&Job::Start, this));
669 } else {
670 // Otherwise wakeup the blocked worker thread.
671 event_.Signal();
672 }
673 }
674
675 void ScheduleRestartWithBlockingDns() {
676 MessageLoop::current()->PostTask(
677 FROM_HERE, base::Bind(&Job::RestartWithBlockingDns, this));
678 }
679
680 void RestartWithBlockingDns() {
681 DCHECK(nonblocking_dns_);
682 nonblocking_dns_ = false;
683 Start();
684 }
685
686 // Make a key for looking up |host, op| in |dns_cache_|. Strings are used for
687 // convenience, to avoid defining custom comparators.
688 static std::string GetCacheKey(const std::string& host,
689 ResolveDnsOperation op) {
690 return StringPrintf("%d:%s", op, host.c_str());
691 }
692
693 // Serialize |net_error, address_list| into something that can be saved into
694 // |dns_cache|.
695 static std::string MakeCacheValue(ResolveDnsOperation op,
696 int net_error,
697 const AddressList& address_list) {
698 if (net_error != OK)
699 return std::string();
700
701 // dnsResolve() and myIpAddress() are expected to return a single IP
702 // address. Whereas the *Ex versions are expected to return a semi-colon
703 // separated list.
704
705 if (op == DNS_RESOLVE || op == MY_IP_ADDRESS) {
706 return address_list.front().ToStringWithoutPort();
707 }
708
709 std::string address_list_str;
710 for (AddressList::const_iterator iter = address_list.begin();
711 iter != address_list.end(); ++iter) {
712 if (!address_list_str.empty())
713 address_list_str += ";";
714 const std::string address_string = iter->ToStringWithoutPort();
715 if (address_string.empty())
716 return std::string();
717 address_list_str += address_string;
718 }
719
720 return address_list_str;
721 }
722
723 bool GetDnsFromLocalCache(const std::string& host, ResolveDnsOperation op,
724 std::string* output, bool* return_value) {
725 DnsCache::const_iterator it = dns_cache_.find(GetCacheKey(host, op));
726 if (it == dns_cache_.end())
727 return false;
728
729 *output = it->second;
730 *return_value = !it->second.empty();
731 return true;
732 }
733
734 void SaveDnsToLocalCache(const std::string& host, ResolveDnsOperation op,
735 int net_error, const net::AddressList& addresses) {
736 dns_cache_[GetCacheKey(host, op)] =
737 MakeCacheValue(op, net_error, addresses);
738 }
739
740 Operation operation_;
741 bool nonblocking_dns_;
mmenke 2013/01/18 19:59:40 Wonder if this should be a property of the PAC scr
mmenke 2013/01/18 19:59:40 Suggest using blocking_dns_ instead. "!non_blocki
eroman 2013/01/23 03:26:02 To make sure I understand: Do you mean keep track
eroman 2013/01/23 03:26:02 Done.
742
743 scoped_refptr<base::MessageLoopProxy> origin_loop_;
744 CompletionCallback callback_;
745 ProxyResolverV8Tracing* parent_;
746 base::CancellationFlag cancelled_;
747
748 scoped_refptr<ProxyResolverScriptData> script_data_;
mmenke 2013/01/18 19:59:40 What do you think of splitting this class into two
mmenke 2013/01/18 20:02:39 If you think that's too ugly, clearly separating o
eroman 2013/01/23 03:26:02 I couldn't get it any cleaner splitting off into s
749
750 base::WaitableEvent event_;
751
752 GURL url_;
753 ProxyInfo results_;
754 ProxyInfo* user_results_;
755 BoundNetLog bound_net_log_;
756
757 ExecutionState exec_;
758
759 int last_num_dns_;
760 base::MD5Digest last_dns_digest_;
761
762 typedef std::map<std::string, std::string> DnsCache;
763 DnsCache dns_cache_;
764
765 scoped_ptr<PendingDns> pending_dns_;
766 };
767
768 ProxyResolverV8Tracing::ProxyResolverV8Tracing(
769 HostResolver* host_resolver,
770 ProxyResolverErrorObserver* error_observer,
771 NetLog* net_log)
772 : ProxyResolver(true /*expects_pac_bytes*/),
773 host_resolver_(host_resolver),
774 error_observer_(error_observer),
775 net_log_(net_log) {
776 DCHECK(host_resolver);
777 // Start up the thread.
778 thread_.reset(new base::Thread("Proxy resolver"));
779 CHECK(thread_->Start());
780
781 v8_resolver_.reset(new ProxyResolverV8);
782 }
783
784 ProxyResolverV8Tracing::~ProxyResolverV8Tracing() {
785 // Note, all requests should have been cancelled.
786 DCHECK(!set_pac_script_job_);
787
788 // Join the worker thread.
789 // See http://crbug.com/69710.
790 base::ThreadRestrictions::ScopedAllowIO allow_io;
791 thread_.reset();
792 }
793
794 int ProxyResolverV8Tracing::GetProxyForURL(const GURL& url,
795 ProxyInfo* results,
796 const CompletionCallback& callback,
797 RequestHandle* request,
798 const BoundNetLog& net_log) {
799 DCHECK(CalledOnValidThread());
800 DCHECK(!callback.is_null());
801
802 scoped_refptr<Job> job = new Job(this);
803
804 if (request)
805 *request = job.get();
806
807 job->StartGetProxyForURL(url, results, net_log, callback);
808 return ERR_IO_PENDING;
809 }
810
811 void ProxyResolverV8Tracing::CancelRequest(RequestHandle request) {
812 Job* job = reinterpret_cast<Job*>(request);
813 job->Cancel();
814 }
815
816 LoadState ProxyResolverV8Tracing::GetLoadState(RequestHandle request) const {
817 Job* job = reinterpret_cast<Job*>(request);
818 return job->GetLoadState();
819 }
820
821 void ProxyResolverV8Tracing::CancelSetPacScript() {
822 set_pac_script_job_->Cancel();
823 set_pac_script_job_ = NULL;
824 }
825
826 void ProxyResolverV8Tracing::PurgeMemory() {
827 thread_->message_loop()->PostTask(
828 FROM_HERE,
829 base::Bind(&ProxyResolverV8::PurgeMemory,
830 // The use of unretained is safe, since the worker thread
831 // cannot outlive |this|.
832 base::Unretained(v8_resolver_.get())));
833 }
834
835 int ProxyResolverV8Tracing::SetPacScript(
836 const scoped_refptr<ProxyResolverScriptData>& script_data,
837 const CompletionCallback& callback) {
838 DCHECK(CalledOnValidThread());
839 DCHECK(!callback.is_null());
840
841 set_pac_script_job_ = new Job(this);
842 set_pac_script_job_->StartSetPacScript(script_data, callback);
843
844 return ERR_IO_PENDING;
845 }
846
847 } // namespace net
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698