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

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: that's what she said Created 7 years, 10 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/message_loop_proxy.h"
9 #include "base/string_number_conversions.h"
mmenke 2013/01/29 20:19:08 Is this used anywhere?
eroman 2013/01/29 22:51:23 Done.
10 #include "base/string_util.h"
mmenke 2013/01/29 20:19:08 Not sure about this one, either.
eroman 2013/01/29 22:51:23 Done.
11 #include "base/stringprintf.h"
12 #include "base/synchronization/cancellation_flag.h"
13 #include "base/synchronization/waitable_event.h"
14 #include "base/threading/thread.h"
15 #include "base/threading/thread_restrictions.h"
16 #include "base/values.h"
17 #include "net/base/address_list.h"
18 #include "net/base/host_resolver.h"
19 #include "net/base/net_errors.h"
20 #include "net/base/net_log.h"
21 #include "net/proxy/proxy_info.h"
22 #include "net/proxy/proxy_resolver_error_observer.h"
23 #include "net/proxy/proxy_resolver_v8.h"
24
25 // The intent of this class is explained in the design document:
26 // https://docs.google.com/a/chromium.org/document/d/16Ij5OcVnR3s0MH4Z5XkhI9VTPo MJdaBn9rKreAmGOdE/edit
27 //
28 // In a nutshell, PAC scripts are Javascript programs and may depend on
29 // network I/O, by calling functions like dnsResolve().
30 //
31 // This is problematic since functions such as dnsResolve() will block the
32 // Javascript execution until the DNS result is availble, thereby stalling the
33 // PAC thread, which hurts the ability to process parallel proxy resolves.
34 // An obvious solution is to simply start more PAC threads, however this scales
35 // poorly, which hurts the ability to process parallel proxy resolves.
36 //
37 // The solution in ProxyResolverV8Tracing is to model PAC scripts as being
38 // deterministic, and depending only on the inputted URL. When the script
39 // issues a dnsResolve() for a yet unresolved hostname, the Javascript
40 // execution is "aborted", and then re-started once the DNS result is
41 // known.
42 namespace net {
43
44 namespace {
45
46 // Upper bound on how many *unique* DNS resolves a PAC script is allowed
47 // to make. This is a failsafe both for scripts that do a ridiculous
48 // number of DNS resolves, as well as scripts which are misbehaving
49 // under the tracing optimization. It is not expected to hit this normally.
50 const size_t kMaxUniqueResolveDnsPerExec = 20;
51
52 // Approximate number of bytes to use for buffering alerts() and errors.
53 // This is a failsafe in case repeated executions of the script causes
54 // too much memory bloat. It is not expected for well behaved scripts to
55 // hit this. (In fact normal scripts should not even have alerts() or errors).
56 const size_t kMaxAlertsAndErrorsBytes = 2048;
57
58 // Returns event parameters for a PAC error message (line number + message).
59 base::Value* NetLogErrorCallback(int line_number,
60 const string16* message,
61 NetLog::LogLevel /* log_level */) {
62 base::DictionaryValue* dict = new base::DictionaryValue();
63 dict->SetInteger("line_number", line_number);
64 dict->SetString("message", *message);
65 return dict;
66 }
67
68 } // namespace
69
70 // The Job class is responsible for executing GetProxyForURL() and
71 // SetPacScript(), since both of these operations share similar code.
72 //
73 // The DNS for these operations can operate in either blocking or
74 // non-blocking mode. Blocking mode is used as a fallback when the PAC script
75 // seems to be misbehaving under the tracing optimization.
76 //
77 // Note that this class runs on both the origin thread and a worker
78 // thread. Most methods are expected to be used exclusively on one thread
79 // or the other.
80 //
81 // The lifetime of Jobs does not exceed that of the ProxyResolverV8Tracing that
82 // spawned it. Destruction might happen on either the origin thread or the
83 // worker thread.
84 class ProxyResolverV8Tracing::Job
85 : public base::RefCountedThreadSafe<ProxyResolverV8Tracing::Job>,
86 public ProxyResolverV8::JSBindings {
87 public:
88 // |parent| is non-owned. It is the ProxyResolverV8Tracing that spawned this
89 // Job, and must oulive it.
90 explicit Job(ProxyResolverV8Tracing* parent);
91
92 // Called from origin thread.
93 void StartSetPacScript(
94 const scoped_refptr<ProxyResolverScriptData>& script_data,
95 const CompletionCallback& callback);
96
97 // Called from origin thread.
98 void StartGetProxyForURL(const GURL& url,
99 ProxyInfo* results,
100 const BoundNetLog& net_log,
101 const CompletionCallback& callback);
102
103 // Called from origin thread.
104 void Cancel();
105
106 // Called from origin thread.
107 LoadState GetLoadState() const;
108
109 private:
110 typedef std::map<std::string, std::string> DnsCache;
111 friend class base::RefCountedThreadSafe<ProxyResolverV8Tracing::Job>;
112
113 enum Operation {
114 SET_PAC_SCRIPT,
115 GET_PROXY_FOR_URL,
116 };
117
118 struct AlertOrError {
119 bool is_alert;
120 int line_number;
121 string16 message;
122 };
123
124 ~Job();
125
126 void CheckIsOnWorkerThread() const;
127 void CheckIsOnOriginThread() const;
128
129 void SetCallback(const CompletionCallback& callback);
130 void ReleaseCallback();
131
132 ProxyResolverV8* v8_resolver();
133 MessageLoop* worker_loop();
134 HostResolver* host_resolver();
135 ProxyResolverErrorObserver* error_observer();
136 NetLog* net_log();
137
138 // Invokes the user's callback.
139 void NotifyCaller(int result);
140 void NotifyCallerOnOriginLoop(int result);
141
142 void Start(Operation op, bool blocking_dns,
143 const CompletionCallback& callback);
144
145 void ExecuteBlocking();
146 void ExecuteNonBlocking();
147 int ExecuteProxyResolver();
148
149 // Implementation of ProxyResolverv8::JSBindings
150 virtual bool ResolveDns(const std::string& host,
151 ResolveDnsOperation op,
152 std::string* output) OVERRIDE;
153 virtual void Alert(const string16& message) OVERRIDE;
154 virtual void OnError(int line_number, const string16& error) OVERRIDE;
155
156 bool ResolveDnsBlocking(const std::string& host,
157 ResolveDnsOperation op,
158 std::string* output);
159
160 bool ResolveDnsNonBlocking(const std::string& host,
161 ResolveDnsOperation op,
162 std::string* output);
163
164 void DoDnsOperation(const std::string& host, ResolveDnsOperation op,
165 bool* out_cache_hit);
166 void OnDnsOperationComplete(int result);
167
168 void ScheduleRestartWithBlockingDns();
169
170 bool GetDnsFromLocalCache(const std::string& host, ResolveDnsOperation op,
171 std::string* output, bool* return_value);
172
173 void SaveDnsToLocalCache(const std::string& host, ResolveDnsOperation op,
174 int net_error, const net::AddressList& addresses);
175
176 // Builds a RequestInfo to service the specified PAC DNS operation.
177 static HostResolver::RequestInfo MakeDnsRequestInfo(const std::string& host,
178 ResolveDnsOperation op);
179
180 // Makes a key for looking up |host, op| in |dns_cache_|. Strings are used for
181 // convenience, to avoid defining custom comparators.
182 static std::string MakeDnsCacheKey(const std::string& host,
183 ResolveDnsOperation op);
184
185 void HandleAlertOrError(bool is_alert, int line_number,
186 const string16& message);
187 void DispatchBufferedAlertsAndErrors();
188 void DispatchAlertOrError(bool is_alert, int line_number,
189 const string16& message);
190
191 void LogEventToCurrentRequestAndGlobally(
192 NetLog::EventType type,
193 const NetLog::ParametersCallback& parameters_callback);
194
195 // The thread which called into ProxyResolverV8Tracing, and on which the
196 // completion callback is expected to run.
197 scoped_refptr<base::MessageLoopProxy> origin_loop_;
198
199 // The ProxyResolverV8Tracing which spawned this Job.
200 // Initialized on origin thread and then accessed from both threads.
201 ProxyResolverV8Tracing* parent_;
202
203 // The callback to run (on the origin thread) when the Job finishes.
204 // Should only be accessed from origin thread.
205 CompletionCallback callback_;
206
207 // Flag to indicate whether the request has been cancelled.
208 base::CancellationFlag cancelled_;
209
210 // The operation that this Job is running.
211 // Initialized on origin thread and then accessed from both threads.
212 Operation operation_;
213
214 // The DNS mode for this Job.
215 // Initialized on origin thread, mutated on worker thread, and accessed
216 // by both the origin thread and worker thread.
217 bool blocking_dns_;
218
219 // Used to block the worker thread on a DNS operation taking place on the
220 // origin thread.
221 base::WaitableEvent event_;
222
223 // Map of DNS operations completed so far. Written into on the origin thread
224 // and read on the worker thread.
225 DnsCache dns_cache_;
226
227 // -------------------------------------------------------
228 // State specific to SET_PAC_SCRIPT.
229 // -------------------------------------------------------
230
231 scoped_refptr<ProxyResolverScriptData> script_data_;
232
233 // -------------------------------------------------------
234 // State specific to GET_PROXY_FOR_URL.
235 // -------------------------------------------------------
236
237 ProxyInfo* user_results_; // Owned by caller, lives on origin thread.
238 GURL url_;
239 ProxyInfo results_;
240 BoundNetLog bound_net_log_;
241
242 // ---------------------------------------------------------------------------
243 // State for ExecuteNonBlocking()
244 // ---------------------------------------------------------------------------
245 // These variables are used exclusively on the worker thread and are only
246 // meaningful when executing inside of ExecuteNonBlocking().
247
248 // Whether this execution was abandoned due to a missing DNS dependency.
249 bool abandoned_;
250
251 // Number of calls made to ResolveDns() by this execution.
252 int num_dns_;
253
254 // Sequence of calls made to Alert() or OnError() by this execution.
255 std::vector<AlertOrError> alerts_and_errors_;
256 size_t alerts_and_errors_byte_cost_; // Approximate byte cost of the above.
257
258 // Number of calls made to ResolveDns() by the PREVIOUS execution.
259 int last_num_dns_;
260
261 // Whether the current execution needs to be restarted in blocking mode.
262 bool should_restart_with_blocking_dns_;
263
264 // ---------------------------------------------------------------------------
265 // State for pending DNS request.
266 // ---------------------------------------------------------------------------
267 // These variables are used exclusively on the origin thread.
268
269 HostResolver::RequestHandle pending_dns_;
270 // Only meaningful when |pending_dns_|:
271 std::string pending_dns_host_;
272 ResolveDnsOperation pending_dns_op_;
273 AddressList pending_dns_addresses_;
274 };
275
276 ProxyResolverV8Tracing::Job::Job(ProxyResolverV8Tracing* parent)
277 : origin_loop_(base::MessageLoopProxy::current()),
278 parent_(parent),
279 event_(true, false),
280 last_num_dns_(0),
281 pending_dns_(NULL) {
282 CheckIsOnOriginThread();
283 }
284
285 void ProxyResolverV8Tracing::Job::StartSetPacScript(
286 const scoped_refptr<ProxyResolverScriptData>& script_data,
287 const CompletionCallback& callback) {
288 CheckIsOnOriginThread();
289
290 script_data_ = script_data;
291
292 // Script initialization uses blocking DNS since there isn't any
293 // advantage to using non-blocking mode here. That is because the
294 // parent ProxyService can't submit any ProxyResolve requests until
295 // initialization has completed successfully!
296 Start(SET_PAC_SCRIPT, true /*blocking*/, callback);
297 }
298
299 void ProxyResolverV8Tracing::Job::StartGetProxyForURL(
300 const GURL& url,
301 ProxyInfo* results,
302 const BoundNetLog& net_log,
303 const CompletionCallback& callback) {
304 CheckIsOnOriginThread();
305
306 url_ = url;
307 user_results_ = results;
308 bound_net_log_ = net_log;
309
310 Start(GET_PROXY_FOR_URL, false /*non-blocking*/, callback);
311 }
312
313 void ProxyResolverV8Tracing::Job::Cancel() {
314 CheckIsOnOriginThread();
315
316 // There are several possibilities to consider for cancellation:
317 // (a) The job has been posted to the worker thread, but not yet started
mmenke 2013/01/29 20:19:08 Suggest "but script execution has not yet started.
eroman 2013/01/29 22:51:23 Done.
318 // (b) The script is executing on the worker thread
319 // (c) The script is blocked on the worker thread waiting for DNS
mmenke 2013/01/29 20:19:08 nit: The above 3 lines should all end in periods.
320 // (d) The script has stopped and there is a pending DNS request.
321 // (e) The script completed, and posted a task to notify caller.
322 //
323 // |cancelled_| is read on both the origin thread and worker thread. The
324 // code that runs on the worker thread is littered with checks on
325 // |cancelled_| to break out early.
326 cancelled_.Set();
327
328 ReleaseCallback();
329
330 if (pending_dns_) {
331 host_resolver()->CancelRequest(pending_dns_);
332 pending_dns_ = NULL;
333 }
334
335 // The worker thread might be blocked waiting for DNS.
336 event_.Signal();
337
338 Release();
mmenke 2013/01/29 20:19:08 Hmm...One alternative that may be marginally bette
eroman 2013/01/29 22:51:23 Can you explain what that would look like? (Since
mmenke 2013/01/29 23:10:31 Replace the two Release() calls with: owned_self_
eroman 2013/01/30 01:25:25 Done.
339 }
340
341 LoadState ProxyResolverV8Tracing::Job::GetLoadState() const {
342 CheckIsOnOriginThread();
343
344 if (pending_dns_)
345 return LOAD_STATE_RESOLVING_HOST_IN_PROXY_SCRIPT;
346
347 return LOAD_STATE_RESOLVING_PROXY_FOR_URL;
348 }
349
350 ProxyResolverV8Tracing::Job::~Job() {
351 DCHECK(!pending_dns_);
mmenke 2013/01/29 20:19:08 DCHECK(callback_.is_null());? (Or is_empty, or em
eroman 2013/01/29 22:51:23 Done.
352 }
353
354 void ProxyResolverV8Tracing::Job::CheckIsOnWorkerThread() const {
355 DCHECK_EQ(MessageLoop::current(), parent_->thread_->message_loop());
356 }
357
358 void ProxyResolverV8Tracing::Job::CheckIsOnOriginThread() const {
359 DCHECK(origin_loop_->BelongsToCurrentThread());
360 }
361
362 void ProxyResolverV8Tracing::Job::SetCallback(
363 const CompletionCallback& callback) {
364 CheckIsOnOriginThread();
mmenke 2013/01/29 20:19:08 DCHECK(callback_.is_null());?
eroman 2013/01/29 22:51:23 Done.
365 parent_->num_outstanding_callbacks_++;
366 callback_ = callback;
367 }
368
369 void ProxyResolverV8Tracing::Job::ReleaseCallback() {
370 CheckIsOnOriginThread();
mmenke 2013/01/29 20:19:08 DCHECK(!callback_.is_null());? Mostly to make sur
eroman 2013/01/29 22:51:23 Done.
371 CHECK_GT(parent_->num_outstanding_callbacks_, 0);
372 parent_->num_outstanding_callbacks_--;
373 callback_ = CompletionCallback();
mmenke 2013/01/29 20:19:08 callback_.Reset()?
eroman 2013/01/29 22:51:23 Done.
374
375 // For good measure, clear this other user-owned pointer.
376 user_results_ = NULL;
377 }
378
379 ProxyResolverV8* ProxyResolverV8Tracing::Job::v8_resolver() {
380 return parent_->v8_resolver_.get();
381 }
382
383 MessageLoop* ProxyResolverV8Tracing::Job::worker_loop() {
384 return parent_->thread_->message_loop();
385 }
386
387 HostResolver* ProxyResolverV8Tracing::Job::host_resolver() {
388 return parent_->host_resolver_;
389 }
390
391 ProxyResolverErrorObserver* ProxyResolverV8Tracing::Job::error_observer() {
392 return parent_->error_observer_.get();
393 }
394
395 NetLog* ProxyResolverV8Tracing::Job::net_log() {
396 return parent_->net_log_;
397 }
398
399 // Post a task to the origin thread, to invoke the user's callback.
mmenke 2013/01/29 20:19:08 nit: Function-level comments should be with funct
eroman 2013/01/29 22:51:23 Done.
400 void ProxyResolverV8Tracing::Job::NotifyCaller(int result) {
401 CheckIsOnWorkerThread();
402
403 origin_loop_->PostTask(
404 FROM_HERE,
405 base::Bind(&Job::NotifyCallerOnOriginLoop, this, result));
406 }
407
408 void ProxyResolverV8Tracing::Job::NotifyCallerOnOriginLoop(int result) {
409 CheckIsOnOriginThread();
410
411 if (cancelled_.IsSet())
412 return;
413
mmenke 2013/01/29 20:19:08 DCHECK(!callback_.is_empty());? (or .empty() or .
eroman 2013/01/29 22:51:23 Done.
414 if (operation_ == GET_PROXY_FOR_URL)
415 *user_results_ = results_;
416
417 // There is only ever 1 outstanding SET_PAC_SCRIPT job. It needs to be
418 // tracked to support cancellation.
419 if (operation_ == SET_PAC_SCRIPT) {
420 DCHECK(parent_->set_pac_script_job_);
mmenke 2013/01/29 20:19:08 Maybe DCHECK_EQ(this, parent_->set_pac_script_job_
eroman 2013/01/29 22:51:23 Done.
421 parent_->set_pac_script_job_ = NULL;
422 }
423
424 CompletionCallback callback = callback_;
425 ReleaseCallback();
426 callback.Run(result);
427
428 Release();
429 }
430
431 void ProxyResolverV8Tracing::Job::Start(Operation op, bool blocking_dns,
432 const CompletionCallback& callback) {
433 CheckIsOnOriginThread();
434
435 operation_ = op;
436 blocking_dns_ = blocking_dns;
437 SetCallback(callback);
438
439 AddRef();
440
441 worker_loop()->PostTask(FROM_HERE,
442 blocking_dns_ ? base::Bind(&Job::ExecuteBlocking, this) :
443 base::Bind(&Job::ExecuteNonBlocking, this));
444 }
445
446 void ProxyResolverV8Tracing::Job::ExecuteBlocking() {
447 CheckIsOnWorkerThread();
448 DCHECK(blocking_dns_);
449
450 if (cancelled_.IsSet())
451 return;
452
mmenke 2013/01/29 20:19:08 Suggest DCHECKing that alerts_and_errors_ is empty
eroman 2013/01/29 22:51:23 I don't reset that state on fallback so I can't cu
mmenke 2013/01/29 23:10:31 Didn't realize that. I don't feel particularly st
453 NotifyCaller(ExecuteProxyResolver());
454 }
455
456 void ProxyResolverV8Tracing::Job::ExecuteNonBlocking() {
457 CheckIsOnWorkerThread();
458 DCHECK(!blocking_dns_);
459
460 if (cancelled_.IsSet())
461 return;
462
463 // Reset state for the current execution.
464 abandoned_ = false;
465 num_dns_ = 0;
466 alerts_and_errors_.clear();
467 alerts_and_errors_byte_cost_ = 0;
468 should_restart_with_blocking_dns_ = false;
mmenke 2013/01/29 20:19:08 Can't you DCHECK on this at the start of this func
eroman 2013/01/29 22:51:23 This is the only place I in initialize it. I favor
mmenke 2013/01/29 23:10:31 Ah, just assumed it was initialized on constructio
469
470 int result = ExecuteProxyResolver();
471
472 if (should_restart_with_blocking_dns_) {
473 DCHECK(!blocking_dns_);
474 DCHECK(abandoned_);
475 blocking_dns_ = true;
476 ExecuteBlocking();
477 return;
478 }
479
480 if (abandoned_)
481 return;
482
483 DispatchBufferedAlertsAndErrors();
484 NotifyCaller(result);
485 }
486
487 int ProxyResolverV8Tracing::Job::ExecuteProxyResolver() {
488 JSBindings* prev_bindings = v8_resolver()->js_bindings();
489 v8_resolver()->set_js_bindings(this);
490
491 int result = ERR_UNEXPECTED; // Initialized to silence warnings.
492
493 switch (operation_) {
494 case SET_PAC_SCRIPT:
495 result = v8_resolver()->SetPacScript(
496 script_data_, CompletionCallback());
497 break;
498 case GET_PROXY_FOR_URL:
499 result = v8_resolver()->GetProxyForURL(
500 url_,
501 // Important: Do not write directly into |user_results_|, since if the
502 // request were to be cancelled from the origin thread, must guarantee
503 // that |user_results_| is not accessed anymore.
504 &results_,
505 CompletionCallback(),
506 NULL,
507 bound_net_log_);
508 break;
509 }
510
511 v8_resolver()->set_js_bindings(prev_bindings);
512 return result;
513 }
514
515 bool ProxyResolverV8Tracing::Job::ResolveDns(const std::string& host,
516 ResolveDnsOperation op,
517 std::string* output) {
518 if (cancelled_.IsSet())
519 return false;
520
521 if ((op == DNS_RESOLVE || op == DNS_RESOLVE_EX) && host.empty()) {
522 // a DNS resolve with an empty hostname is considered an error.
523 return false;
524 }
525
526 return blocking_dns_ ?
527 ResolveDnsBlocking(host, op, output) :
528 ResolveDnsNonBlocking(host, op, output);
529 }
530
531 void ProxyResolverV8Tracing::Job::Alert(const string16& message) {
532 HandleAlertOrError(true, -1, message);
533 }
534
535 void ProxyResolverV8Tracing::Job::OnError(int line_number,
536 const string16& error) {
537 HandleAlertOrError(false, line_number, error);
538 }
539
540 bool ProxyResolverV8Tracing::Job::ResolveDnsBlocking(const std::string& host,
541 ResolveDnsOperation op,
542 std::string* output) {
543 CheckIsOnWorkerThread();
544
545 // Check if the DNS result for this host has already been cached.
546 bool rv;
547 if (GetDnsFromLocalCache(host, op, output, &rv)) {
548 // Yay, cache hit!
549 return rv;
550 }
551
552 // If the host was not in the local cache, this is a new hostname.
553
554 if (dns_cache_.size() >= kMaxUniqueResolveDnsPerExec) {
555 // Safety net for scripts with unexpectedly many DNS calls.
556 // We will continue running to completion, but will fail every
557 // subsequent DNS request.
558 return false;
559 }
560
561 bool unused;
562 origin_loop_->PostTask(
563 FROM_HERE, base::Bind(&Job::DoDnsOperation, this, host, op, &unused));
564
565 // Wait for the DNS operation to be completed by the host resolver on the
566 // origin thread. After waking up, either the request was cancelled, or
567 // the DNS result is now available in the cache.
568 event_.Wait();
569 event_.Reset();
570
571 if (cancelled_.IsSet())
572 return false;
573
574 CHECK(GetDnsFromLocalCache(host, op, output, &rv));
575 return rv;
576 }
577
578 bool ProxyResolverV8Tracing::Job::ResolveDnsNonBlocking(const std::string& host,
579 ResolveDnsOperation op,
580 std::string* output) {
581 CheckIsOnWorkerThread();
582
583 if (abandoned_) {
584 // If this execution was already abandoned can fail right away. Only 1 DNS
585 // dependency will be traced at a time (for more predictable outcomes).
586 return false;
587 }
588
589 num_dns_ += 1;
590
591 // Check if the DNS result for this host has already been cached.
592 bool rv;
593 if (GetDnsFromLocalCache(host, op, output, &rv)) {
594 // Yay, cache hit!
595 return rv;
596 }
597
598 // If the host was not in the local cache, then this is a new hostname.
599
600 if (num_dns_ <= last_num_dns_) {
601 // The sequence of DNS operations is different from last time!
602 ScheduleRestartWithBlockingDns();
603 return false;
604 }
605
606 if (dns_cache_.size() >= kMaxUniqueResolveDnsPerExec) {
607 // Safety net for scripts with unexpectedly many DNS calls.
608 return false;
609 }
610
611 DCHECK(!should_restart_with_blocking_dns_);
612
613 // Post the DNS request to the origin thread.
614 bool resolver_cache_hit = false;
615 origin_loop_->PostTask(
616 FROM_HERE, base::Bind(&Job::DoDnsOperation, this, host, op,
617 &resolver_cache_hit));
618
619 // As an optimization to avoid restarting too often, wait until the
620 // resolver's cache has been inspected on the origin thread.
621 event_.Wait();
622 event_.Reset();
623
624 if (cancelled_.IsSet())
625 return false;
626
627 if (resolver_cache_hit) {
628 CHECK(GetDnsFromLocalCache(host, op, output, &rv));
629 return rv;
630 }
631
632 // Otherwise if the result was not in the cache, then a DNS request has
633 // been started. Abandon this invocation of FindProxyForURL(), it will be
634 // restarted once the DNS request completes.
635 abandoned_ = true;
636 last_num_dns_ = num_dns_;
637 return false;
638 }
639
640 void ProxyResolverV8Tracing::Job::DoDnsOperation(
641 const std::string& host, ResolveDnsOperation op, bool* out_cache_hit) {
642 CheckIsOnOriginThread();
643
644 if (cancelled_.IsSet())
645 return;
646
647 DCHECK(!pending_dns_);
mmenke 2013/01/29 20:19:08 This can go before cancelled_.IsSet(), right? On
eroman 2013/01/29 22:51:23 Done.
eroman 2013/01/29 22:51:23 Done.
648
649 int result = host_resolver()->Resolve(
650 MakeDnsRequestInfo(host, op),
651 &pending_dns_addresses_,
652 base::Bind(&Job::OnDnsOperationComplete, this),
653 &pending_dns_,
654 bound_net_log_);
655
656 bool completed_synchronously = result != ERR_IO_PENDING;
657
658 if (!blocking_dns_) {
659 // Check if the DNS result can be serviced directly from the cache.
660 // (The worker thread is blocked waiting for this information).
661 if (completed_synchronously) {
662 SaveDnsToLocalCache(host, op, result, pending_dns_addresses_);
663 pending_dns_ = NULL;
mmenke 2013/01/29 20:19:08 Is pending_dns_ even written to in this case? If
eroman 2013/01/29 22:51:23 I prefer to explicitly NULL this just in case the
mmenke 2013/01/29 23:10:31 OK
664 }
665
666 // Important: Do not read/write |out_cache_hit| after signalling, since
667 // the memory may no longer be valid.
668 *out_cache_hit = completed_synchronously;
669 event_.Signal();
670
671 if (completed_synchronously)
672 return;
673 }
674
675 pending_dns_host_ = host;
676 pending_dns_op_ = op;
677
678 if (completed_synchronously)
679 OnDnsOperationComplete(result);
680 }
681
682 void ProxyResolverV8Tracing::Job::OnDnsOperationComplete(int result) {
683 CheckIsOnOriginThread();
684
685 DCHECK(pending_dns_);
686 DCHECK(!cancelled_.IsSet());
687
688 SaveDnsToLocalCache(pending_dns_host_, pending_dns_op_, result,
689 pending_dns_addresses_);
690 pending_dns_ = NULL;
691
692 if (!blocking_dns_) {
693 // Restart. This time it should make more progress due to having
694 // cached items.
695 worker_loop()->PostTask(FROM_HERE,
696 base::Bind(&Job::ExecuteNonBlocking, this));
697 } else {
698 // Otherwise wakeup the blocked worker thread.
699 event_.Signal();
700 }
701 }
702
703 void ProxyResolverV8Tracing::Job::ScheduleRestartWithBlockingDns() {
704 CheckIsOnWorkerThread();
705
706 DCHECK(!should_restart_with_blocking_dns_);
707 DCHECK(!abandoned_);
708 DCHECK(!blocking_dns_);
709
710 abandoned_ = true;
711
712 // The restart will happen after ExecuteNonBlocking() finishes.
713 should_restart_with_blocking_dns_ = true;
714 }
715
716 bool ProxyResolverV8Tracing::Job::GetDnsFromLocalCache(
717 const std::string& host,
718 ResolveDnsOperation op,
719 std::string* output,
720 bool* return_value) {
721 CheckIsOnWorkerThread();
722
723 DnsCache::const_iterator it = dns_cache_.find(MakeDnsCacheKey(host, op));
724 if (it == dns_cache_.end())
725 return false;
726
727 *output = it->second;
728 *return_value = !it->second.empty();
729 return true;
730 }
731
732 void ProxyResolverV8Tracing::Job::SaveDnsToLocalCache(
733 const std::string& host,
734 ResolveDnsOperation op,
735 int net_error,
736 const net::AddressList& addresses) {
737 CheckIsOnOriginThread();
738
739 // Serialize the result into a string to save to the cache.
740 std::string cache_value;
741 if (net_error != OK) {
742 cache_value = std::string();
mmenke 2013/01/29 20:19:08 Hmm.... Are compilers smart enough to optimize th
eroman 2013/01/29 22:51:23 I doubt it. I wrote it this way, because I wanted
mmenke 2013/01/29 23:10:31 That's fine. Of course, the only concern with thi
743 } else if (op == DNS_RESOLVE || op == MY_IP_ADDRESS) {
744 // dnsResolve() and myIpAddress() are expected to return a single IP
745 // address.
746 cache_value = addresses.front().ToStringWithoutPort();
747 } else {
748 // The *Ex versions are expected to return a semi-colon separated list.
749 for (AddressList::const_iterator iter = addresses.begin();
750 iter != addresses.end(); ++iter) {
mmenke 2013/01/29 20:19:08 No tests check this code. Can't really do the MY_
mmenke 2013/01/29 20:19:08 Hmm....No test uses multiple IPs, either. Suggest
mmenke 2013/01/29 20:19:08 Should we check IPv6 as well? Just make sure the
eroman 2013/01/29 22:51:23 I was lazy on these because it requires some addit
eroman 2013/01/30 01:25:25 Done -- I have added tests for both dnsResolveEx()
751 if (!cache_value.empty())
752 cache_value += ";";
753 const std::string address_string = iter->ToStringWithoutPort();
754 if (address_string.empty()) {
755 cache_value = std::string();
mmenke 2013/01/29 20:19:08 Does this happen? Doesn't look like it to me. Co
eroman 2013/01/29 22:51:23 Done. This was a carry over from when the string
756 break;
757 }
758 cache_value += address_string;
759 }
760 }
761
762 dns_cache_[MakeDnsCacheKey(host, op)] = cache_value;
763 }
764
765 // static
766 HostResolver::RequestInfo ProxyResolverV8Tracing::Job::MakeDnsRequestInfo(
767 const std::string& host, ResolveDnsOperation op) {
768 HostPortPair host_port = HostPortPair(host, 80);
769 if (op == MY_IP_ADDRESS || op == MY_IP_ADDRESS_EX) {
770 host_port.set_host(GetHostName());
771 }
772
773 HostResolver::RequestInfo info(host_port);
774
775 // The non-ex flavors are limited to IPv4 results.
776 if (op == MY_IP_ADDRESS || op == DNS_RESOLVE) {
777 info.set_address_family(ADDRESS_FAMILY_IPV4);
778 }
779
780 return info;
781 }
782
783 // Make a key for looking up |host, op| in |dns_cache_|. Strings are used for
784 // convenience, to avoid defining custom comparators.
mmenke 2013/01/29 20:19:08 nit: Function-level comments should be at the dec
eroman 2013/01/29 22:51:23 Done.
785 // static
786 std::string ProxyResolverV8Tracing::Job::MakeDnsCacheKey(
787 const std::string& host, ResolveDnsOperation op) {
788 return StringPrintf("%d:%s", op, host.c_str());
789 }
790
791 void ProxyResolverV8Tracing::Job::HandleAlertOrError(bool is_alert,
792 int line_number,
793 const string16& message) {
794 CheckIsOnWorkerThread();
795
796 if (cancelled_.IsSet())
797 return;
798
799 if (blocking_dns_) {
800 // In blocking DNS mode the events can be dispatched immediately.
801 DispatchAlertOrError(is_alert, line_number, message);
802 return;
803 }
804
805 // Otherwise in nonblocking mode, buffer all the messages until
806 // the end.
807
808 if (abandoned_)
809 return;
810
811 alerts_and_errors_byte_cost_ += sizeof(AlertOrError) + message.size() * 2;
812
813 // If there have been lots of messages, enqueing could be expensive on
814 // memory. (consider a script which does megabytes worth of alerts()).
mmenke 2013/01/29 20:19:08 nit: "Consider". Also, think either the parenthe
eroman 2013/01/29 22:51:23 Done.
815 // Avoid this by falling back to blocking mode.
816 if (alerts_and_errors_byte_cost_ > kMaxAlertsAndErrorsBytes) {
817 ScheduleRestartWithBlockingDns();
818 return;
819 }
820
821 AlertOrError entry = {is_alert, line_number, message};
822 alerts_and_errors_.push_back(entry);
823 }
824
825 void ProxyResolverV8Tracing::Job::DispatchBufferedAlertsAndErrors() {
826 CheckIsOnWorkerThread();
827 DCHECK(!blocking_dns_);
828 DCHECK(!abandoned_);
829
830 for (size_t i = 0; i < alerts_and_errors_.size(); ++i) {
831 const AlertOrError& x = alerts_and_errors_[i];
832 DispatchAlertOrError(x.is_alert, x.line_number, x.message);
833 }
834 }
835
836 void ProxyResolverV8Tracing::Job::DispatchAlertOrError(
837 bool is_alert, int line_number, const string16& message) {
838 CheckIsOnWorkerThread();
839
840 // Note that the handling of cancellation is racy with regard to
841 // alerts/errors. The request might get cancelled shortly after this
842 // check! (There is no lock being held to guarantee otherwise).
843 //
844 // If this happens, then some information will get written to the NetLog
845 // needlessly, however the NetLog will still be alive so it shouldn't cause
846 // problems.
847 if (cancelled_.IsSet())
848 return;
849
850 if (is_alert) {
851 // -------------------
852 // alert
853 // -------------------
854 VLOG(1) << "PAC-alert: " << message;
855
856 // Send to the NetLog.
857 LogEventToCurrentRequestAndGlobally(
858 NetLog::TYPE_PAC_JAVASCRIPT_ALERT,
859 NetLog::StringCallback("message", &message));
860 } else {
861 // -------------------
862 // error
863 // -------------------
864 if (line_number == -1)
865 VLOG(1) << "PAC-error: " << message;
866 else
867 VLOG(1) << "PAC-error: " << "line: " << line_number << ": " << message;
868
869 // Send the error to the NetLog.
870 LogEventToCurrentRequestAndGlobally(
871 NetLog::TYPE_PAC_JAVASCRIPT_ERROR,
872 base::Bind(&NetLogErrorCallback, line_number, &message));
873
874 if (error_observer())
875 error_observer()->OnPACScriptError(line_number, message);
876 }
877 }
878
879 void ProxyResolverV8Tracing::Job::LogEventToCurrentRequestAndGlobally(
880 NetLog::EventType type,
881 const NetLog::ParametersCallback& parameters_callback) {
882 CheckIsOnWorkerThread();
883 bound_net_log_.AddEvent(type, parameters_callback);
884
885 // Emit to the global NetLog event stream.
886 if (net_log())
887 net_log()->AddGlobalEntry(type, parameters_callback);
888 }
889
890 ProxyResolverV8Tracing::ProxyResolverV8Tracing(
891 HostResolver* host_resolver,
892 ProxyResolverErrorObserver* error_observer,
893 NetLog* net_log)
894 : ProxyResolver(true /*expects_pac_bytes*/),
895 host_resolver_(host_resolver),
896 error_observer_(error_observer),
897 net_log_(net_log),
898 num_outstanding_callbacks_(0) {
899 DCHECK(host_resolver);
900 // Start up the thread.
901 thread_.reset(new base::Thread("Proxy resolver"));
902 CHECK(thread_->Start());
903
904 v8_resolver_.reset(new ProxyResolverV8);
905 }
906
907 ProxyResolverV8Tracing::~ProxyResolverV8Tracing() {
908 // Note, all requests should have been cancelled.
909 CHECK(!set_pac_script_job_);
910 CHECK_EQ(0, num_outstanding_callbacks_);
911
912 // Join the worker thread.
913 // See http://crbug.com/69710.
914 base::ThreadRestrictions::ScopedAllowIO allow_io;
915 thread_.reset();
916 }
917
918 int ProxyResolverV8Tracing::GetProxyForURL(const GURL& url,
919 ProxyInfo* results,
920 const CompletionCallback& callback,
921 RequestHandle* request,
922 const BoundNetLog& net_log) {
923 DCHECK(CalledOnValidThread());
924 DCHECK(!callback.is_null());
mmenke 2013/01/29 20:19:08 DCHECK(!set_pac_script_job_);?
eroman 2013/01/29 22:51:23 Done.
925
926 scoped_refptr<Job> job = new Job(this);
927
928 if (request)
929 *request = job.get();
930
931 job->StartGetProxyForURL(url, results, net_log, callback);
932 return ERR_IO_PENDING;
933 }
934
935 void ProxyResolverV8Tracing::CancelRequest(RequestHandle request) {
936 Job* job = reinterpret_cast<Job*>(request);
937 job->Cancel();
938 }
939
940 LoadState ProxyResolverV8Tracing::GetLoadState(RequestHandle request) const {
941 Job* job = reinterpret_cast<Job*>(request);
942 return job->GetLoadState();
943 }
944
945 void ProxyResolverV8Tracing::CancelSetPacScript() {
946 DCHECK(set_pac_script_job_);
947 set_pac_script_job_->Cancel();
948 set_pac_script_job_ = NULL;
949 }
950
951 void ProxyResolverV8Tracing::PurgeMemory() {
952 thread_->message_loop()->PostTask(
953 FROM_HERE,
954 base::Bind(&ProxyResolverV8::PurgeMemory,
955 // The use of unretained is safe, since the worker thread
956 // cannot outlive |this|.
957 base::Unretained(v8_resolver_.get())));
958 }
959
960 int ProxyResolverV8Tracing::SetPacScript(
961 const scoped_refptr<ProxyResolverScriptData>& script_data,
962 const CompletionCallback& callback) {
963 DCHECK(CalledOnValidThread());
964 DCHECK(!callback.is_null());
965
966 // Note that there should not be any outstanding (non-cancelled) Jobs when
967 // setting the PAC script (ProxyService should guarantee this). If there are,
968 // then they might complete in strange ways after the new script is set.
969 DCHECK(!set_pac_script_job_);
970 CHECK_EQ(0, num_outstanding_callbacks_);
971
972 set_pac_script_job_ = new Job(this);
973 set_pac_script_job_->StartSetPacScript(script_data, callback);
974
975 return ERR_IO_PENDING;
976 }
977
978 } // namespace net
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698