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

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: whitespace changes 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
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 blocking or
89 // non-blocking mode. Blocking mode is used as a fallback when the PAC script
90 // seems to be misbehaving under the tracing optimization.
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 lifetime of Jobs does not exceed that of the ProxyResolverV8Tracing that
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.
mmenke 2013/01/24 21:06:33 Suggest separating into two sentences, or making i
eroman 2013/01/25 03:02:01 Done.
105 explicit Job(ProxyResolverV8Tracing* parent);
106
107 void StartSetPacScript(
mmenke 2013/01/24 21:06:33 Suggest a comment either here or in the class desc
eroman 2013/01/25 03:02:01 Done.
108 const scoped_refptr<ProxyResolverScriptData>& script_data,
109 const CompletionCallback& callback);
110
111 void StartGetProxyForURL(const GURL& url,
112 ProxyInfo* results,
113 const BoundNetLog& net_log,
114 const CompletionCallback& callback);
115
116 void Cancel();
117 LoadState GetLoadState() const;
118
119 private:
120 typedef std::map<std::string, std::string> DnsCache;
121 friend class base::RefCountedThreadSafe<ProxyResolverV8Tracing::Job>;
122
123 enum Operation {
124 SET_PAC_SCRIPT,
125 GET_PROXY_FOR_URL,
126 };
127
128 struct AlertOrError {
129 bool is_alert;
130 int line_number;
131 string16 message;
132 };
133
134 ~Job();
135
136 void CheckIsOnWorkerThread() const;
137 void CheckIsOnOriginThread() const;
138
139 ProxyResolverV8* v8_resolver();
140 MessageLoop* worker_loop();
141 HostResolver* host_resolver();
142 ProxyResolverErrorObserver* error_observer();
143 NetLog* net_log();
144
145 // Invokes the user's callback.
146 void NotifyCaller(int result);
147 void NotifyCallerOnOriginLoop(int result);
148
149 void Start(Operation op, bool blocking_dns, const CompletionCallback& callback );
mmenke 2013/01/24 21:06:33 nit: Line too long.
eroman 2013/01/25 03:02:01 Done.
150
151 void ExecuteBlocking();
152 void ExecuteNonBlocking();
153 int ExecuteProxyResolver();
154
155 // Implementation of ProxyResolverv8::JSBindings
156 virtual bool ResolveDns(const std::string& host,
157 ResolveDnsOperation op,
158 std::string* output) OVERRIDE;
159 virtual void Alert(const string16& message) OVERRIDE;
160 virtual void OnError(int line_number, const string16& error) OVERRIDE;
161
162 bool ResolveDnsBlocking(const std::string& host,
163 ResolveDnsOperation op,
164 std::string* output);
165
166 bool ResolveDnsNonBlocking(const std::string& host,
167 ResolveDnsOperation op,
168 std::string* output);
169
170 void DoDnsOperation(const std::string& host, ResolveDnsOperation op,
171 bool* out_cache_hit);
172 void OnDnsOperationComplete(int result);
173
174 void ScheduleRestartWithBlockingDns();
175 void RestartWithBlockingDns();
176
177 bool GetDnsFromLocalCache(const std::string& host, ResolveDnsOperation op,
178 std::string* output, bool* return_value);
179
180 void SaveDnsToLocalCache(const std::string& host, ResolveDnsOperation op,
181 int net_error, const net::AddressList& addresses);
182
183 // Builds a RequestInfo to service the specified PAC DNS operation.
184 static HostResolver::RequestInfo MakeDnsRequestInfo(const std::string& host,
185 ResolveDnsOperation op);
186
187 // Makes a key for looking up |host, op| in |dns_cache_|. Strings are used for
188 // convenience, to avoid defining custom comparators.
189 static std::string MakeDnsCacheKey(const std::string& host,
190 ResolveDnsOperation op);
191
192 void HandleAlertOrError(bool is_alert, int line_number,
193 const string16& message);
194 void DispatchBufferedAlertsAndErrors();
195 void DispatchAlertOrError(bool is_alert, int line_number,
196 const string16& message);
197
198 void LogEventToCurrentRequestAndGlobally(
199 NetLog::EventType type,
200 const NetLog::ParametersCallback& parameters_callback);
201
202 // The thread which called into ProxyResolverV8Tracing, and on which the
203 // completion callback is expected to run.
204 scoped_refptr<base::MessageLoopProxy> origin_loop_;
205
206 // The ProxyResolverV8Tracing which spawned this Job.
207 // Initialized on origin thread and then accessed from both threads.
208 ProxyResolverV8Tracing* parent_;
209
210 // The callback to run (on the origin thread) when the Job finishes.
211 // Should only be accessed from origin thread.
212 CompletionCallback callback_;
213
214 // Flag to indicate whether the request has been cancelled.
215 base::CancellationFlag cancelled_;
216
217 // The operation that this Job is running.
218 // Initialized on origin thread and then accessed from both threads.
219 Operation operation_;
220
221 // The DNS mode for this Job.
222 // Initialized on origin thread and then accessed from both threads.
223 bool blocking_dns_;
224
225 // Used to block the worker thread on a DNS operation taking place on the
226 // origin thread.
227 base::WaitableEvent event_;
228
229 // Map of DNS operations completed so far. Written into on the origin thread
230 // and read on the worker thread.
231 DnsCache dns_cache_;
232
233 // -------------------------------------------------------
234 // State specific to SET_PAC_SCRIPT.
235 // -------------------------------------------------------
236
237 scoped_refptr<ProxyResolverScriptData> script_data_;
238
239 // -------------------------------------------------------
240 // State specific to GET_PROXY_FOR_URL.
241 // -------------------------------------------------------
242
243 ProxyInfo* user_results_; // Owned by caller, lives on origin thread.
244 GURL url_;
245 ProxyInfo results_;
246 BoundNetLog bound_net_log_;
247
248 // ---------------------------------------------------------------------------
249 // State for ExecuteNonBlocking()
250 // ---------------------------------------------------------------------------
251 // These variables are used exclusively on the worker thread and are only
252 // meaningful when executing inside of ExecuteNonBlocking().
253
254 // Whether this execution was abandoned due to a missing DNS dependency.
255 bool abandoned_;
256
257 // Sequence of calls made to ResolveDns() by this execution.
258 int num_dns_; // Count.
259 base::MD5Context dns_digest_; // MD5 digest of the parameters.
260
261 // Sequence of calls made to Alert() or OnError() by this execution.
262 std::vector<AlertOrError> alerts_and_errors_;
263 size_t alerts_and_errors_byte_cost_; // Approximate byte cost of the above.
264
265 // Sequence of calls made to ResolveDns() by the PREVIOUS execution.
266 int last_num_dns_;
267 base::MD5Digest last_dns_digest_;
268
269 // ---------------------------------------------------------------------------
270 // State for pending DNS request.
271 // ---------------------------------------------------------------------------
272 // These variables are used exclusively on the origin thread.
273
274 HostResolver::RequestHandle pending_dns_;
275 // Only meaningful when |pending_dns_|:
276 std::string pending_dns_host_;
277 ResolveDnsOperation pending_dns_op_;
278 AddressList pending_dns_addresses_;
279 };
280
281 ProxyResolverV8Tracing::Job::Job(ProxyResolverV8Tracing* parent)
282 : origin_loop_(base::MessageLoopProxy::current()),
283 parent_(parent),
284 event_(true, false),
285 last_num_dns_(0),
286 pending_dns_(NULL) {
287 }
288
289 void ProxyResolverV8Tracing::Job::StartSetPacScript(
290 const scoped_refptr<ProxyResolverScriptData>& script_data,
291 const CompletionCallback& callback) {
292 CheckIsOnOriginThread();
293
294 script_data_ = script_data;
295
296 // Script initialization uses blocking DNS since there isn't any
297 // advantage to using non-blocking mode here. That is because the
298 // parent ProxyService can't submit any ProxyResolve requests until
299 // initialization has completed successfully!
300 Start(SET_PAC_SCRIPT, true /*blocking*/, callback);
mmenke 2013/01/24 21:06:33 If the PAC script changes to a new PAC script, wha
eroman 2013/01/25 03:02:01 There shouldn't be any outstanding PAC requests wh
mmenke 2013/01/25 05:03:28 Thought that might be the case, but looks like the
eroman 2013/01/25 21:42:25 MutliThreadedProxyResolver asserts that there are
301 }
302
303 void ProxyResolverV8Tracing::Job::StartGetProxyForURL(
304 const GURL& url,
305 ProxyInfo* results,
306 const BoundNetLog& net_log,
307 const CompletionCallback& callback) {
308 CheckIsOnOriginThread();
309
310 url_ = url;
311 user_results_ = results;
312 bound_net_log_ = net_log;
313
314 Start(GET_PROXY_FOR_URL, false /*non-blocking*/, callback);
315 }
316
317 void ProxyResolverV8Tracing::Job::Cancel() {
318 CheckIsOnOriginThread();
319
320 // There are three main possibility to consider for cancellation:
mmenke 2013/01/24 21:06:33 five?
eroman 2013/01/25 03:02:01 Done.
321 // (a) The job has been posted to the worker thread, but not yet started
mmenke 2013/01/24 21:06:33 nit: started->[re]started
eroman 2013/01/25 03:02:01 What i meant here was the first execution, hence "
mmenke 2013/01/25 05:03:28 I think it falls under a), actually. We run a scr
322 // (b) The script is executing on the worker thread
323 // (c) The script is blocked waiting for DNS
324 // (c) The script has stopped and there is a pending DNS request.
mmenke 2013/01/24 21:06:33 nit: (d)
mmenke 2013/01/24 21:06:33 (e) The script has a pending NotifyCallerOnOriginL
eroman 2013/01/25 03:02:01 This can probabilistic-ally happen in the CancelSo
mmenke 2013/01/25 05:03:28 I was thinking this: Start a lookup with a synchr
325 //
326 // |cancelled_| is read on both the origin thread and worker thread. The
327 // code that runs on the worker thread is littered with checks on
328 // |cancelled_| to break out early.
329 cancelled_.Set();
330
331 // These two assignments are not necessary for correctness. Just defensive
332 // programming to make sure the user-provided data is not used beyond this
333 // point.
334 user_results_ = NULL;
335 callback_ = CompletionCallback();
336
337 if (pending_dns_) {
338 host_resolver()->CancelRequest(pending_dns_);
339 pending_dns_ = NULL;
340 }
341
342 // The worker thread might be blocked waiting for DNS.
343 event_.Signal();
344
345 Release();
346 }
347
348 LoadState ProxyResolverV8Tracing::Job::GetLoadState() const {
349 CheckIsOnOriginThread();
350
351 if (pending_dns_)
352 return LOAD_STATE_RESOLVING_HOST_IN_PROXY_SCRIPT;
353
354 return LOAD_STATE_RESOLVING_PROXY_FOR_URL;
355 }
356
357 ProxyResolverV8Tracing::Job::~Job() {
358 DCHECK(!pending_dns_);
359 }
360
361 void ProxyResolverV8Tracing::Job::CheckIsOnWorkerThread() const {
362 DCHECK_EQ(MessageLoop::current(), parent_->thread_->message_loop());
363 }
364
365 void ProxyResolverV8Tracing::Job::CheckIsOnOriginThread() const {
366 DCHECK(origin_loop_->BelongsToCurrentThread());
367 }
368
369 ProxyResolverV8* ProxyResolverV8Tracing::Job::v8_resolver() {
370 return parent_->v8_resolver_.get();
371 }
372
373 MessageLoop* ProxyResolverV8Tracing::Job::worker_loop() {
374 return parent_->thread_->message_loop();
375 }
376
377 HostResolver* ProxyResolverV8Tracing::Job::host_resolver() {
378 return parent_->host_resolver_;
379 }
380
381 ProxyResolverErrorObserver* ProxyResolverV8Tracing::Job::error_observer() {
382 return parent_->error_observer_.get();
383 }
384
385 NetLog* ProxyResolverV8Tracing::Job::net_log() {
386 return parent_->net_log_;
387 }
388
389 // Post a task to the origin thread, to invoke the user's callback.
390 void ProxyResolverV8Tracing::Job::NotifyCaller(int result) {
391 CheckIsOnWorkerThread();
392
393 origin_loop_->PostTask(
394 FROM_HERE,
395 base::Bind(&Job::NotifyCallerOnOriginLoop, this, result));
396 }
397
398 void ProxyResolverV8Tracing::Job::NotifyCallerOnOriginLoop(int result) {
399 CheckIsOnOriginThread();
400
401 if (cancelled_.IsSet())
402 return;
403
404 if (operation_ == GET_PROXY_FOR_URL)
405 *user_results_ = results_;
406
407 // There is only ever 1 outstanding SET_PAC_SCRIPT job. It needs to be
408 // tracked to support cancellation.
409 if (operation_ == SET_PAC_SCRIPT) {
410 DCHECK(parent_->set_pac_script_job_);
411 parent_->set_pac_script_job_ = NULL;
412 }
413
414 callback_.Run(result);
415 callback_.Reset();
416
417 Release();
418 }
419
420 void ProxyResolverV8Tracing::Job::Start(Operation op, bool blocking_dns,
421 const CompletionCallback& callback) {
422 operation_ = op;
423 blocking_dns_ = blocking_dns;
424 callback_ = callback;
425
426 AddRef();
427
428 worker_loop()->PostTask(FROM_HERE,
429 blocking_dns_ ? base::Bind(&Job::ExecuteBlocking, this) :
430 base::Bind(&Job::ExecuteNonBlocking, this));
431 }
432
433 void ProxyResolverV8Tracing::Job::ExecuteBlocking() {
434 CheckIsOnWorkerThread();
435 DCHECK(blocking_dns_);
436
437 if (cancelled_.IsSet())
438 return;
439
440 NotifyCaller(ExecuteProxyResolver());
441 }
442
443 void ProxyResolverV8Tracing::Job::ExecuteNonBlocking() {
444 CheckIsOnWorkerThread();
445 DCHECK(!blocking_dns_);
446
447 if (cancelled_.IsSet())
448 return;
449
450 // Reset state for the current execution.
451 abandoned_ = false;
452 num_dns_ = 0;
453 base::MD5Init(&dns_digest_);
454 alerts_and_errors_.clear();
455 alerts_and_errors_byte_cost_ = 0;
456
457 int result = ExecuteProxyResolver();
458
459 if (abandoned_)
460 return;
461
462 DispatchBufferedAlertsAndErrors();
463 NotifyCaller(result);
464 }
465
466 int ProxyResolverV8Tracing::Job::ExecuteProxyResolver() {
467 JSBindings* prev_bindings = v8_resolver()->js_bindings();
468 v8_resolver()->set_js_bindings(this);
469
470 int result = ERR_UNEXPECTED; // Initialized to silence warnings.
471
472 switch (operation_) {
473 case SET_PAC_SCRIPT:
474 result = v8_resolver()->SetPacScript(
475 script_data_, CompletionCallback());
476 break;
477 case GET_PROXY_FOR_URL:
478 result = v8_resolver()->GetProxyForURL(
479 url_,
480 // Important: Do not write directly into |user_results_|, since if the
481 // request were to be cancelled from the origin thread, must guarantee
482 // that |user_results_| is not accessed anymore.
483 &results_,
484 CompletionCallback(),
485 NULL,
486 bound_net_log_);
487 break;
488 }
489
490 v8_resolver()->set_js_bindings(prev_bindings);
491 return result;
492 }
493
494 bool ProxyResolverV8Tracing::Job::ResolveDns(const std::string& host,
495 ResolveDnsOperation op,
496 std::string* output) {
497 if (cancelled_.IsSet())
498 return false;
499
500 if ((op == DNS_RESOLVE || op == DNS_RESOLVE_EX) && host.empty()) {
501 // a DNS resolve with an empty hostname is considered an error.
502 return false;
503 }
504
505 return blocking_dns_ ?
506 ResolveDnsBlocking(host, op, output) :
507 ResolveDnsNonBlocking(host, op, output);
508 }
509
510 void ProxyResolverV8Tracing::Job::Alert(const string16& message) {
511 HandleAlertOrError(true, -1, message);
512 }
513
514 void ProxyResolverV8Tracing::Job::OnError(int line_number,
515 const string16& error) {
516 HandleAlertOrError(false, line_number, error);
517 }
518
519 bool ProxyResolverV8Tracing::Job::ResolveDnsBlocking(const std::string& host,
520 ResolveDnsOperation op,
521 std::string* output) {
522 CheckIsOnWorkerThread();
523
524 // Check if the DNS result for this host has already been cached.
525 bool rv;
526 if (GetDnsFromLocalCache(host, op, output, &rv)) {
527 // Yay, cache hit!
528 return rv;
529 }
530
531 // If the host was not in the local cache, this is a new hostname.
532
533 if (dns_cache_.size() > kMaxUniqueResolveDnsPerExec) {
534 // Safety net for scripts with unexpectedly many DNS calls.
535 // We will continue running to completion, but will fail every
536 // subsequent DNS request.
537 return false;
538 }
539
540 bool unused;
541 origin_loop_->PostTask(
542 FROM_HERE, base::Bind(&Job::DoDnsOperation, this, host, op, &unused));
543
544 // Wait for the DNS operation to be completed by the host resolver on the
545 // origin thread. After waking up, either the request was cancelled, or
546 // the DNS result is now available in the cache.
547 event_.Wait();
548 event_.Reset();
549
550 if (cancelled_.IsSet())
551 return false;
552
553 CHECK(GetDnsFromLocalCache(host, op, output, &rv));
554 return rv;
555 }
556
557 bool ProxyResolverV8Tracing::Job::ResolveDnsNonBlocking(const std::string& host,
558 ResolveDnsOperation op,
559 std::string* output) {
560 CheckIsOnWorkerThread();
561
562 if (abandoned_) {
563 // If this execution was already abandoned can fail right away. Only 1 DNS
564 // dependency will be traced at a time (for more predictable outcomes).
565 return false;
566 }
567
568 num_dns_ += 1;
569
570 // Keep a digest of the sequence of DNS requests this execution has seen so
571 // far. This is cheaper than keeping a full list of the history, and good
572 // enough for our purposes (we want to detect when things have gone wrong in
573 // order to fallback to blocking mode).
574 base::MD5Update(&dns_digest_, host);
575 base::MD5Update(&dns_digest_, base::IntToString(op));
576
577 // Upon reaching the point of execution from the last run, make sure
578 // that the sequence of resolves has matched so far.
579 if (num_dns_ == last_num_dns_ &&
580 !MD5DigestEquals(last_dns_digest_, dns_digest_)) {
581 // The sequence of DNS operations is different from last time!
582 ScheduleRestartWithBlockingDns();
583 abandoned_ = true;
584 return false;
585 }
586
587 // Check if the DNS result for this host has already been cached.
588 bool rv;
589 if (GetDnsFromLocalCache(host, op, output, &rv)) {
590 // Yay, cache hit!
591 return rv;
592 }
593
594 // If the host was not in the local cache, then this is a new hostname.
595
596 if (num_dns_ <= last_num_dns_) {
597 // The sequence of DNS operations is different from last time!
598 ScheduleRestartWithBlockingDns();
599 abandoned_ = true;
600 return false;
601 }
602
603 if (dns_cache_.size() > kMaxUniqueResolveDnsPerExec) {
mmenke 2013/01/24 21:06:33 No test goes through here, I believe. This code i
eroman 2013/01/25 03:02:01 Thanks. Now covered by the InfiniteDNSSequence* te
604 // Safety net for scripts with unexpectedly many DNS calls.
605 return false;
606 }
607
608 // Post the DNS request to the origin thread.
609 bool resolver_cache_hit = false;
610 origin_loop_->PostTask(
611 FROM_HERE, base::Bind(&Job::DoDnsOperation, this, host, op,
612 &resolver_cache_hit));
613
614 // As an optimization to avoid restarting too often, wait until the
615 // resolver's cache has been inspected on the origin thread.
616 event_.Wait();
617 event_.Reset();
618
619 if (cancelled_.IsSet())
620 return false;
621
622 if (resolver_cache_hit) {
623 CHECK(GetDnsFromLocalCache(host, op, output, &rv));
624 return rv;
625 }
626
627 // Otherwise if the result was not in the cache, then a DNS request has
628 // been started. Abandon this invocation of FindProxyForURL(), it will be
629 // restarted once the DNS request completes.
630 abandoned_ = true;
631 last_num_dns_ = num_dns_;
632 base::MD5Final(&last_dns_digest_, &dns_digest_);
633 return false;
634 }
635
636 void ProxyResolverV8Tracing::Job::DoDnsOperation(
637 const std::string& host, ResolveDnsOperation op, bool* out_cache_hit) {
638 CheckIsOnOriginThread();
639
640 if (cancelled_.IsSet())
641 return;
642
643 DCHECK(!pending_dns_);
644
645 HostResolver::RequestInfo info = MakeDnsRequestInfo(host, op);
646
647 if (!blocking_dns_) {
mmenke 2013/01/24 21:06:33 Rather than have all this cruft here, suggest movi
eroman 2013/01/25 03:02:01 Done -- Good idea, I have removed the duplicate ca
648 // Check if the DNS result can be serviced directly from the cache.
649 // (The worker thread is blocked waiting for this information).
650 net::AddressList addresses;
651 int result = host_resolver()->ResolveFromCache(
652 info, &addresses, bound_net_log_);
653
654 bool cache_hit = result != ERR_DNS_CACHE_MISS;
655 if (cache_hit) {
656 // Cool, it was in the host resolver's cache. Copy it to the
657 // per-request cache.
658 SaveDnsToLocalCache(host, op, result, addresses);
mmenke 2013/01/24 21:06:33 HostResolver cache hit case doesn't seem to be tes
eroman 2013/01/25 03:02:01 Thanks! Added a test to exercise this: DnsChecksCa
659 }
660
661 // Important: Do not read/write |out_cache_hit| after signalling, since
662 // the memory may no longer be valid.
663 *out_cache_hit = cache_hit;
664 event_.Signal();
665
666 if (cache_hit)
667 return;
668 }
669
670 pending_dns_host_ = host;
671 pending_dns_op_ = op;
672
673 int result = host_resolver()->Resolve(
674 info,
675 &pending_dns_addresses_, base::Bind(&Job::OnDnsOperationComplete, this),
676 &pending_dns_, bound_net_log_);
677
678 if (result != ERR_IO_PENDING)
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, base::Bind(&Job::ExecuteNonBlocking, this ));
mmenke 2013/01/24 21:06:33 nit: Line too long
eroman 2013/01/25 03:02:01 Done.
696 } else {
697 // Otherwise wakeup the blocked worker thread.
698 event_.Signal();
699 }
700 }
701
702 void ProxyResolverV8Tracing::Job::ScheduleRestartWithBlockingDns() {
mmenke 2013/01/24 21:06:33 In the interest of failing early, suggest: DCHECK
mmenke 2013/01/24 21:06:33 Any reason not to just set abandoned_ to true here
mmenke 2013/01/24 21:06:33 CheckIsOnWorkerThread(); for documentation purpose
eroman 2013/01/25 03:02:01 Done.
eroman 2013/01/25 03:02:01 I can't set blocking_dns_ to true, because we may
703 MessageLoop::current()->PostTask(
704 FROM_HERE, base::Bind(&Job::RestartWithBlockingDns, this));
705 }
706
707 void ProxyResolverV8Tracing::Job::RestartWithBlockingDns() {
mmenke 2013/01/24 21:06:33 CheckIsOnWorkerThread();
eroman 2013/01/25 03:02:01 Done.
708 DCHECK(!blocking_dns_);
709 blocking_dns_ = true;
710 ExecuteBlocking();
711 }
712
713 bool ProxyResolverV8Tracing::Job::GetDnsFromLocalCache(
714 const std::string& host,
715 ResolveDnsOperation op,
716 std::string* output,
717 bool* return_value) {
mmenke 2013/01/24 21:06:33 CheckIsOnWorkerThread();
eroman 2013/01/25 03:02:01 Done.
718 DnsCache::const_iterator it = dns_cache_.find(MakeDnsCacheKey(host, op));
719 if (it == dns_cache_.end())
720 return false;
721
722 *output = it->second;
723 *return_value = !it->second.empty();
724 return true;
725 }
726
727 void ProxyResolverV8Tracing::Job::SaveDnsToLocalCache(
728 const std::string& host,
729 ResolveDnsOperation op,
730 int net_error,
731 const net::AddressList& addresses) {
mmenke 2013/01/24 21:06:33 "CheckIsOnOriginThread();" for documentation purpo
eroman 2013/01/25 03:02:01 Done.
732
733 // Serialize the result into a string to save to the cache.
734 std::string cache_value;
735 if (net_error != OK) {
736 cache_value = std::string();
737 } else if (op == DNS_RESOLVE || op == MY_IP_ADDRESS) {
738 // dnsResolve() and myIpAddress() are expected to return a single IP
739 // address.
740 cache_value = addresses.front().ToStringWithoutPort();
741 } else {
742 // The *Ex versions are expected to return a semi-colon separated list.
743 for (AddressList::const_iterator iter = addresses.begin();
744 iter != addresses.end(); ++iter) {
745 if (!cache_value.empty())
746 cache_value += ";";
747 const std::string address_string = iter->ToStringWithoutPort();
748 if (address_string.empty()) {
749 cache_value = std::string();
750 break;
751 }
752 cache_value += address_string;
753 }
754 }
755
756 dns_cache_[MakeDnsCacheKey(host, op)] = cache_value;
757 }
758
759 HostResolver::RequestInfo ProxyResolverV8Tracing::Job::MakeDnsRequestInfo(
760 const std::string& host, ResolveDnsOperation op) {
761 HostPortPair host_port = HostPortPair(host, 80);
762 if (op == MY_IP_ADDRESS || op == MY_IP_ADDRESS_EX) {
763 host_port.set_host(GetHostName());
764 }
765
766 HostResolver::RequestInfo info(host_port);
767
768 // The non-ex flavors are limited to IPv4 results.
769 if (op == MY_IP_ADDRESS || op == DNS_RESOLVE) {
770 info.set_address_family(ADDRESS_FAMILY_IPV4);
771 }
772
773 return info;
774 }
775
776 // Make a key for looking up |host, op| in |dns_cache_|. Strings are used for
777 // convenience, to avoid defining custom comparators.
778 std::string ProxyResolverV8Tracing::Job::MakeDnsCacheKey(
779 const std::string& host, ResolveDnsOperation op) {
780 return StringPrintf("%d:%s", op, host.c_str());
781 }
782
783 void ProxyResolverV8Tracing::Job::HandleAlertOrError(bool is_alert,
784 int line_number,
785 const string16& message) {
786 if (cancelled_.IsSet())
787 return;
788
789 if (blocking_dns_) {
790 // In blocking DNS mode the events can be dispatched immediately.
791 DispatchAlertOrError(is_alert, line_number, message);
792 return;
793 }
794
795 // Otherwise in nonblocking mode, buffer all the messages until
796 // the end.
797
798 if (abandoned_)
799 return;
800
801 alerts_and_errors_byte_cost_ += message.size() * 2;
mmenke 2013/01/24 21:06:33 Should we toss in a sizeof(AlertOrError)? Otherwi
eroman 2013/01/25 03:02:01 Excellent point. Done.
802
803 // If there have been lots of messages, enqueuing could be expensive on
804 // memory. (consider a script which does megabytes worth of alerts()).
805 // Avoid this by falling back to blocking mode.
806 if (alerts_and_errors_byte_cost_ > kMaxAlertsAndErrorsBytes) {
807 ScheduleRestartWithBlockingDns();
808 abandoned_ = true;
809 return;
810 }
811
812 AlertOrError entry = {is_alert, line_number, message};
813 alerts_and_errors_.push_back(entry);
814 }
815
816 void ProxyResolverV8Tracing::Job::DispatchBufferedAlertsAndErrors() {
817 DCHECK(!blocking_dns_);
818 DCHECK(!abandoned_);
819
820 for (size_t i = 0; i < alerts_and_errors_.size(); ++i) {
821 const AlertOrError& x = alerts_and_errors_[i];
822 DispatchAlertOrError(x.is_alert, x.line_number, x.message);
823 }
824 }
825
826 void ProxyResolverV8Tracing::Job::DispatchAlertOrError(
827 bool is_alert, int line_number, const string16& message) {
mmenke 2013/01/24 21:06:33 Suggest a CheckIsOnWorkerThread() for documentatio
eroman 2013/01/25 03:02:01 Done.
828 // Note that the handling of cancellation is racy with regard to
829 // alerts/errors. The request might get cancelled shortly after this
830 // check! (There is no lock being held to guarantee otherwise).
831 //
832 // If this happens, then some information will get written to the NetLog
833 // needlessly, however the NetLog will still be alive so it shouldn't cause
834 // problems.
835 if (cancelled_.IsSet())
836 return;
837
838 if (is_alert) {
839 // -------------------
840 // alert
841 // -------------------
842 VLOG(1) << "PAC-alert: " << message;
843
844 // Send to the NetLog.
845 LogEventToCurrentRequestAndGlobally(
846 NetLog::TYPE_PAC_JAVASCRIPT_ALERT,
847 NetLog::StringCallback("message", &message));
mmenke 2013/01/24 21:06:33 So we basically just silently consume PAC script a
eroman 2013/01/25 03:02:01 Correct. Actually popping up an alert() is silly,
mmenke 2013/01/25 05:03:28 In an ideal world, we'd have some mechanism to sur
eroman 2013/01/25 21:42:25 Developers actually do use alerts(), so it is good
848 } else {
849 // -------------------
850 // error
851 // -------------------
852 if (line_number == -1)
853 VLOG(1) << "PAC-error: " << message;
854 else
855 VLOG(1) << "PAC-error: " << "line: " << line_number << ": " << message;
856
857 // Send the error to the NetLog.
858 LogEventToCurrentRequestAndGlobally(
859 NetLog::TYPE_PAC_JAVASCRIPT_ERROR,
860 base::Bind(&NetLogErrorCallback, line_number, &message));
861
862 if (error_observer())
863 error_observer()->OnPACScriptError(line_number, message);
864 }
865 }
866
867 void ProxyResolverV8Tracing::Job::LogEventToCurrentRequestAndGlobally(
868 NetLog::EventType type,
869 const NetLog::ParametersCallback& parameters_callback) {
870 bound_net_log_.AddEvent(type, parameters_callback);
871
872 // Emit to the global NetLog event stream.
873 if (net_log())
874 net_log()->AddGlobalEntry(type, parameters_callback);
875 }
876
877 ProxyResolverV8Tracing::ProxyResolverV8Tracing(
878 HostResolver* host_resolver,
879 ProxyResolverErrorObserver* error_observer,
880 NetLog* net_log)
881 : ProxyResolver(true /*expects_pac_bytes*/),
882 host_resolver_(host_resolver),
883 error_observer_(error_observer),
884 net_log_(net_log) {
885 DCHECK(host_resolver);
886 // Start up the thread.
887 thread_.reset(new base::Thread("Proxy resolver"));
888 CHECK(thread_->Start());
889
890 v8_resolver_.reset(new ProxyResolverV8);
mmenke 2013/01/24 21:06:33 Any compelling reason not to include this and thre
eroman 2013/01/25 03:02:01 It is so I can use base::ThreadRestrictions::Scope
891 }
892
893 ProxyResolverV8Tracing::~ProxyResolverV8Tracing() {
894 // Note, all requests should have been cancelled.
895 DCHECK(!set_pac_script_job_);
896
897 // Join the worker thread.
898 // See http://crbug.com/69710.
899 base::ThreadRestrictions::ScopedAllowIO allow_io;
900 thread_.reset();
901 }
902
903 int ProxyResolverV8Tracing::GetProxyForURL(const GURL& url,
904 ProxyInfo* results,
905 const CompletionCallback& callback,
906 RequestHandle* request,
907 const BoundNetLog& net_log) {
908 DCHECK(CalledOnValidThread());
909 DCHECK(!callback.is_null());
910
911 scoped_refptr<Job> job = new Job(this);
912
913 if (request)
914 *request = job.get();
915
916 job->StartGetProxyForURL(url, results, net_log, callback);
917 return ERR_IO_PENDING;
918 }
919
920 void ProxyResolverV8Tracing::CancelRequest(RequestHandle request) {
921 Job* job = reinterpret_cast<Job*>(request);
922 job->Cancel();
923 }
924
925 LoadState ProxyResolverV8Tracing::GetLoadState(RequestHandle request) const {
926 Job* job = reinterpret_cast<Job*>(request);
927 return job->GetLoadState();
928 }
929
930 void ProxyResolverV8Tracing::CancelSetPacScript() {
931 set_pac_script_job_->Cancel();
932 set_pac_script_job_ = NULL;
933 }
934
935 void ProxyResolverV8Tracing::PurgeMemory() {
936 thread_->message_loop()->PostTask(
937 FROM_HERE,
938 base::Bind(&ProxyResolverV8::PurgeMemory,
939 // The use of unretained is safe, since the worker thread
940 // cannot outlive |this|.
941 base::Unretained(v8_resolver_.get())));
942 }
943
944 int ProxyResolverV8Tracing::SetPacScript(
945 const scoped_refptr<ProxyResolverScriptData>& script_data,
946 const CompletionCallback& callback) {
947 DCHECK(CalledOnValidThread());
948 DCHECK(!callback.is_null());
949
950 set_pac_script_job_ = new Job(this);
951 set_pac_script_job_->StartSetPacScript(script_data, callback);
952
953 return ERR_IO_PENDING;
954 }
955
956 } // namespace net
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698