Updating trunk VERSION from 2139.0 to 2140.0
[chromium-blink-merge.git] / net / proxy / proxy_resolver_v8_tracing.cc
blob26c60e061e45153e30ac85814fca3af39165a009
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.
5 #include "net/proxy/proxy_resolver_v8_tracing.h"
7 #include "base/bind.h"
8 #include "base/message_loop/message_loop_proxy.h"
9 #include "base/metrics/histogram.h"
10 #include "base/strings/stringprintf.h"
11 #include "base/synchronization/cancellation_flag.h"
12 #include "base/synchronization/waitable_event.h"
13 #include "base/threading/thread.h"
14 #include "base/threading/thread_restrictions.h"
15 #include "base/values.h"
16 #include "net/base/address_list.h"
17 #include "net/base/net_errors.h"
18 #include "net/base/net_log.h"
19 #include "net/dns/host_resolver.h"
20 #include "net/proxy/proxy_info.h"
21 #include "net/proxy/proxy_resolver_error_observer.h"
22 #include "net/proxy/proxy_resolver_v8.h"
24 // The intent of this class is explained in the design document:
25 // https://docs.google.com/a/chromium.org/document/d/16Ij5OcVnR3s0MH4Z5XkhI9VTPoMJdaBn9rKreAmGOdE/edit
27 // In a nutshell, PAC scripts are Javascript programs and may depend on
28 // network I/O, by calling functions like dnsResolve().
30 // This is problematic since functions such as dnsResolve() will block the
31 // Javascript execution until the DNS result is availble, thereby stalling the
32 // PAC thread, which hurts the ability to process parallel proxy resolves.
33 // An obvious solution is to simply start more PAC threads, however this scales
34 // poorly, which hurts the ability to process parallel proxy resolves.
36 // The solution in ProxyResolverV8Tracing is to model PAC scripts as being
37 // deterministic, and depending only on the inputted URL. When the script
38 // issues a dnsResolve() for a yet unresolved hostname, the Javascript
39 // execution is "aborted", and then re-started once the DNS result is
40 // known.
41 namespace net {
43 namespace {
45 // Upper bound on how many *unique* DNS resolves a PAC script is allowed
46 // to make. This is a failsafe both for scripts that do a ridiculous
47 // number of DNS resolves, as well as scripts which are misbehaving
48 // under the tracing optimization. It is not expected to hit this normally.
49 const size_t kMaxUniqueResolveDnsPerExec = 20;
51 // Approximate number of bytes to use for buffering alerts() and errors.
52 // This is a failsafe in case repeated executions of the script causes
53 // too much memory bloat. It is not expected for well behaved scripts to
54 // hit this. (In fact normal scripts should not even have alerts() or errors).
55 const size_t kMaxAlertsAndErrorsBytes = 2048;
57 // Returns event parameters for a PAC error message (line number + message).
58 base::Value* NetLogErrorCallback(int line_number,
59 const base::string16* message,
60 NetLog::LogLevel /* log_level */) {
61 base::DictionaryValue* dict = new base::DictionaryValue();
62 dict->SetInteger("line_number", line_number);
63 dict->SetString("message", *message);
64 return dict;
67 void IncrementWithoutOverflow(uint8* x) {
68 if (*x != 0xFF)
69 *x += 1;
72 } // namespace
74 // The Job class is responsible for executing GetProxyForURL() and
75 // SetPacScript(), since both of these operations share similar code.
77 // The DNS for these operations can operate in either blocking or
78 // non-blocking mode. Blocking mode is used as a fallback when the PAC script
79 // seems to be misbehaving under the tracing optimization.
81 // Note that this class runs on both the origin thread and a worker
82 // thread. Most methods are expected to be used exclusively on one thread
83 // or the other.
85 // The lifetime of Jobs does not exceed that of the ProxyResolverV8Tracing that
86 // spawned it. Destruction might happen on either the origin thread or the
87 // worker thread.
88 class ProxyResolverV8Tracing::Job
89 : public base::RefCountedThreadSafe<ProxyResolverV8Tracing::Job>,
90 public ProxyResolverV8::JSBindings {
91 public:
92 // |parent| is non-owned. It is the ProxyResolverV8Tracing that spawned this
93 // Job, and must oulive it.
94 explicit Job(ProxyResolverV8Tracing* parent);
96 // Called from origin thread.
97 void StartSetPacScript(
98 const scoped_refptr<ProxyResolverScriptData>& script_data,
99 const CompletionCallback& callback);
101 // Called from origin thread.
102 void StartGetProxyForURL(const GURL& url,
103 ProxyInfo* results,
104 const BoundNetLog& net_log,
105 const CompletionCallback& callback);
107 // Called from origin thread.
108 void Cancel();
110 // Called from origin thread.
111 LoadState GetLoadState() const;
113 private:
114 typedef std::map<std::string, std::string> DnsCache;
115 friend class base::RefCountedThreadSafe<ProxyResolverV8Tracing::Job>;
117 enum Operation {
118 SET_PAC_SCRIPT,
119 GET_PROXY_FOR_URL,
122 struct AlertOrError {
123 bool is_alert;
124 int line_number;
125 base::string16 message;
128 virtual ~Job();
130 void CheckIsOnWorkerThread() const;
131 void CheckIsOnOriginThread() const;
133 void SetCallback(const CompletionCallback& callback);
134 void ReleaseCallback();
136 ProxyResolverV8* v8_resolver();
137 base::MessageLoop* worker_loop();
138 HostResolver* host_resolver();
139 ProxyResolverErrorObserver* error_observer();
140 NetLog* net_log();
142 // Invokes the user's callback.
143 void NotifyCaller(int result);
144 void NotifyCallerOnOriginLoop(int result);
146 void RecordMetrics() const;
148 void Start(Operation op, bool blocking_dns,
149 const CompletionCallback& callback);
151 void ExecuteBlocking();
152 void ExecuteNonBlocking();
153 int ExecuteProxyResolver();
155 // Implementation of ProxyResolverv8::JSBindings
156 virtual bool ResolveDns(const std::string& host,
157 ResolveDnsOperation op,
158 std::string* output,
159 bool* terminate) OVERRIDE;
160 virtual void Alert(const base::string16& message) OVERRIDE;
161 virtual void OnError(int line_number, const base::string16& error) OVERRIDE;
163 bool ResolveDnsBlocking(const std::string& host,
164 ResolveDnsOperation op,
165 std::string* output);
167 bool ResolveDnsNonBlocking(const std::string& host,
168 ResolveDnsOperation op,
169 std::string* output,
170 bool* terminate);
172 bool PostDnsOperationAndWait(const std::string& host,
173 ResolveDnsOperation op,
174 bool* completed_synchronously)
175 WARN_UNUSED_RESULT;
177 void DoDnsOperation();
178 void OnDnsOperationComplete(int result);
180 void ScheduleRestartWithBlockingDns();
182 bool GetDnsFromLocalCache(const std::string& host, ResolveDnsOperation op,
183 std::string* output, bool* return_value);
185 void SaveDnsToLocalCache(const std::string& host, ResolveDnsOperation op,
186 int net_error, const net::AddressList& addresses);
188 // Builds a RequestInfo to service the specified PAC DNS operation.
189 static HostResolver::RequestInfo MakeDnsRequestInfo(const std::string& host,
190 ResolveDnsOperation op);
192 // Makes a key for looking up |host, op| in |dns_cache_|. Strings are used for
193 // convenience, to avoid defining custom comparators.
194 static std::string MakeDnsCacheKey(const std::string& host,
195 ResolveDnsOperation op);
197 void HandleAlertOrError(bool is_alert, int line_number,
198 const base::string16& message);
199 void DispatchBufferedAlertsAndErrors();
200 void DispatchAlertOrError(bool is_alert, int line_number,
201 const base::string16& message);
203 void LogEventToCurrentRequestAndGlobally(
204 NetLog::EventType type,
205 const NetLog::ParametersCallback& parameters_callback);
207 // The thread which called into ProxyResolverV8Tracing, and on which the
208 // completion callback is expected to run.
209 scoped_refptr<base::MessageLoopProxy> origin_loop_;
211 // The ProxyResolverV8Tracing which spawned this Job.
212 // Initialized on origin thread and then accessed from both threads.
213 ProxyResolverV8Tracing* parent_;
215 // The callback to run (on the origin thread) when the Job finishes.
216 // Should only be accessed from origin thread.
217 CompletionCallback callback_;
219 // Flag to indicate whether the request has been cancelled.
220 base::CancellationFlag cancelled_;
222 // The operation that this Job is running.
223 // Initialized on origin thread and then accessed from both threads.
224 Operation operation_;
226 // The DNS mode for this Job.
227 // Initialized on origin thread, mutated on worker thread, and accessed
228 // by both the origin thread and worker thread.
229 bool blocking_dns_;
231 // Used to block the worker thread on a DNS operation taking place on the
232 // origin thread.
233 base::WaitableEvent event_;
235 // Map of DNS operations completed so far. Written into on the origin thread
236 // and read on the worker thread.
237 DnsCache dns_cache_;
239 // The job holds a reference to itself to ensure that it remains alive until
240 // either completion or cancellation.
241 scoped_refptr<Job> owned_self_reference_;
243 // -------------------------------------------------------
244 // State specific to SET_PAC_SCRIPT.
245 // -------------------------------------------------------
247 scoped_refptr<ProxyResolverScriptData> script_data_;
249 // -------------------------------------------------------
250 // State specific to GET_PROXY_FOR_URL.
251 // -------------------------------------------------------
253 ProxyInfo* user_results_; // Owned by caller, lives on origin thread.
254 GURL url_;
255 ProxyInfo results_;
256 BoundNetLog bound_net_log_;
258 // ---------------------------------------------------------------------------
259 // State for ExecuteNonBlocking()
260 // ---------------------------------------------------------------------------
261 // These variables are used exclusively on the worker thread and are only
262 // meaningful when executing inside of ExecuteNonBlocking().
264 // Whether this execution was abandoned due to a missing DNS dependency.
265 bool abandoned_;
267 // Number of calls made to ResolveDns() by this execution.
268 int num_dns_;
270 // Sequence of calls made to Alert() or OnError() by this execution.
271 std::vector<AlertOrError> alerts_and_errors_;
272 size_t alerts_and_errors_byte_cost_; // Approximate byte cost of the above.
274 // Number of calls made to ResolveDns() by the PREVIOUS execution.
275 int last_num_dns_;
277 // Whether the current execution needs to be restarted in blocking mode.
278 bool should_restart_with_blocking_dns_;
280 // ---------------------------------------------------------------------------
281 // State for pending DNS request.
282 // ---------------------------------------------------------------------------
284 // Handle to the outstanding request in the HostResolver, or NULL.
285 // This is mutated and used on the origin thread, however it may be read by
286 // the worker thread for some DCHECKS().
287 HostResolver::RequestHandle pending_dns_;
289 // Indicates if the outstanding DNS request completed synchronously. Written
290 // on the origin thread, and read by the worker thread.
291 bool pending_dns_completed_synchronously_;
293 // These are the inputs to DoDnsOperation(). Written on the worker thread,
294 // read by the origin thread.
295 std::string pending_dns_host_;
296 ResolveDnsOperation pending_dns_op_;
298 // This contains the resolved address list that DoDnsOperation() fills in.
299 // Used exclusively on the origin thread.
300 AddressList pending_dns_addresses_;
302 // ---------------------------------------------------------------------------
303 // Metrics for histograms
304 // ---------------------------------------------------------------------------
305 // These values are used solely for logging histograms. They do not affect
306 // the execution flow of requests.
308 // The time when the proxy resolve request started. Used exclusively on the
309 // origin thread.
310 base::TimeTicks metrics_start_time_;
312 // The time when the proxy resolve request completes on the worker thread.
313 // Written on the worker thread, read on the origin thread.
314 base::TimeTicks metrics_end_time_;
316 // The time when PostDnsOperationAndWait() was called. Written on the worker
317 // thread, read by the origin thread.
318 base::TimeTicks metrics_pending_dns_start_;
320 // The total amount of time that has been spent by the script waiting for
321 // DNS dependencies. This includes the time spent posting the task to
322 // the origin thread, up until the DNS result is found on the origin
323 // thread. It does not include any time spent waiting in the message loop
324 // for the worker thread, nor any time restarting or executing the
325 // script. Used exclusively on the origin thread.
326 base::TimeDelta metrics_dns_total_time_;
328 // The following variables are initialized on the origin thread,
329 // incremented on the worker thread, and then read upon completion on the
330 // origin thread. The values are not expected to exceed the range of a uint8.
331 // If they do, then they will be clamped to 0xFF.
332 uint8 metrics_num_executions_;
333 uint8 metrics_num_unique_dns_;
334 uint8 metrics_num_alerts_;
335 uint8 metrics_num_errors_;
337 // The time that the latest execution took (time spent inside of
338 // ExecuteProxyResolver(), which includes time spent in bindings too).
339 // Written on the worker thread, read on the origin thread.
340 base::TimeDelta metrics_execution_time_;
342 // The cumulative time spent in ExecuteProxyResolver() that was ultimately
343 // discarded work.
344 // Written on the worker thread, read on the origin thread.
345 base::TimeDelta metrics_abandoned_execution_total_time_;
347 // The duration that the worker thread was blocked waiting on DNS results from
348 // the origin thread, when operating in nonblocking mode.
349 // Written on the worker thread, read on the origin thread.
350 base::TimeDelta metrics_nonblocking_dns_wait_total_time_;
353 ProxyResolverV8Tracing::Job::Job(ProxyResolverV8Tracing* parent)
354 : origin_loop_(base::MessageLoopProxy::current()),
355 parent_(parent),
356 event_(true, false),
357 last_num_dns_(0),
358 pending_dns_(NULL),
359 metrics_num_executions_(0),
360 metrics_num_unique_dns_(0),
361 metrics_num_alerts_(0),
362 metrics_num_errors_(0) {
363 CheckIsOnOriginThread();
366 void ProxyResolverV8Tracing::Job::StartSetPacScript(
367 const scoped_refptr<ProxyResolverScriptData>& script_data,
368 const CompletionCallback& callback) {
369 CheckIsOnOriginThread();
371 script_data_ = script_data;
373 // Script initialization uses blocking DNS since there isn't any
374 // advantage to using non-blocking mode here. That is because the
375 // parent ProxyService can't submit any ProxyResolve requests until
376 // initialization has completed successfully!
377 Start(SET_PAC_SCRIPT, true /*blocking*/, callback);
380 void ProxyResolverV8Tracing::Job::StartGetProxyForURL(
381 const GURL& url,
382 ProxyInfo* results,
383 const BoundNetLog& net_log,
384 const CompletionCallback& callback) {
385 CheckIsOnOriginThread();
387 url_ = url;
388 user_results_ = results;
389 bound_net_log_ = net_log;
391 Start(GET_PROXY_FOR_URL, false /*non-blocking*/, callback);
394 void ProxyResolverV8Tracing::Job::Cancel() {
395 CheckIsOnOriginThread();
397 // There are several possibilities to consider for cancellation:
398 // (a) The job has been posted to the worker thread, however script execution
399 // has not yet started.
400 // (b) The script is executing on the worker thread.
401 // (c) The script is executing on the worker thread, however is blocked inside
402 // of dnsResolve() waiting for a response from the origin thread.
403 // (d) Nothing is running on the worker thread, however the host resolver has
404 // a pending DNS request which upon completion will restart the script
405 // execution.
406 // (e) The worker thread has a pending task to restart execution, which was
407 // posted after the DNS dependency was resolved and saved to local cache.
408 // (f) The script execution completed entirely, and posted a task to the
409 // origin thread to notify the caller.
411 // |cancelled_| is read on both the origin thread and worker thread. The
412 // code that runs on the worker thread is littered with checks on
413 // |cancelled_| to break out early.
414 cancelled_.Set();
416 ReleaseCallback();
418 if (pending_dns_) {
419 host_resolver()->CancelRequest(pending_dns_);
420 pending_dns_ = NULL;
423 // The worker thread might be blocked waiting for DNS.
424 event_.Signal();
426 owned_self_reference_ = NULL;
429 LoadState ProxyResolverV8Tracing::Job::GetLoadState() const {
430 CheckIsOnOriginThread();
432 if (pending_dns_)
433 return LOAD_STATE_RESOLVING_HOST_IN_PROXY_SCRIPT;
435 return LOAD_STATE_RESOLVING_PROXY_FOR_URL;
438 ProxyResolverV8Tracing::Job::~Job() {
439 DCHECK(!pending_dns_);
440 DCHECK(callback_.is_null());
443 void ProxyResolverV8Tracing::Job::CheckIsOnWorkerThread() const {
444 DCHECK_EQ(base::MessageLoop::current(), parent_->thread_->message_loop());
447 void ProxyResolverV8Tracing::Job::CheckIsOnOriginThread() const {
448 DCHECK(origin_loop_->BelongsToCurrentThread());
451 void ProxyResolverV8Tracing::Job::SetCallback(
452 const CompletionCallback& callback) {
453 CheckIsOnOriginThread();
454 DCHECK(callback_.is_null());
455 parent_->num_outstanding_callbacks_++;
456 callback_ = callback;
459 void ProxyResolverV8Tracing::Job::ReleaseCallback() {
460 CheckIsOnOriginThread();
461 DCHECK(!callback_.is_null());
462 CHECK_GT(parent_->num_outstanding_callbacks_, 0);
463 parent_->num_outstanding_callbacks_--;
464 callback_.Reset();
466 // For good measure, clear this other user-owned pointer.
467 user_results_ = NULL;
470 ProxyResolverV8* ProxyResolverV8Tracing::Job::v8_resolver() {
471 return parent_->v8_resolver_.get();
474 base::MessageLoop* ProxyResolverV8Tracing::Job::worker_loop() {
475 return parent_->thread_->message_loop();
478 HostResolver* ProxyResolverV8Tracing::Job::host_resolver() {
479 return parent_->host_resolver_;
482 ProxyResolverErrorObserver* ProxyResolverV8Tracing::Job::error_observer() {
483 return parent_->error_observer_.get();
486 NetLog* ProxyResolverV8Tracing::Job::net_log() {
487 return parent_->net_log_;
490 void ProxyResolverV8Tracing::Job::NotifyCaller(int result) {
491 CheckIsOnWorkerThread();
493 metrics_end_time_ = base::TimeTicks::Now();
495 origin_loop_->PostTask(
496 FROM_HERE,
497 base::Bind(&Job::NotifyCallerOnOriginLoop, this, result));
500 void ProxyResolverV8Tracing::Job::NotifyCallerOnOriginLoop(int result) {
501 CheckIsOnOriginThread();
503 if (cancelled_.IsSet())
504 return;
506 DCHECK(!callback_.is_null());
507 DCHECK(!pending_dns_);
509 if (operation_ == GET_PROXY_FOR_URL) {
510 RecordMetrics();
511 *user_results_ = results_;
514 // There is only ever 1 outstanding SET_PAC_SCRIPT job. It needs to be
515 // tracked to support cancellation.
516 if (operation_ == SET_PAC_SCRIPT) {
517 DCHECK_EQ(parent_->set_pac_script_job_.get(), this);
518 parent_->set_pac_script_job_ = NULL;
521 CompletionCallback callback = callback_;
522 ReleaseCallback();
523 callback.Run(result);
525 owned_self_reference_ = NULL;
528 void ProxyResolverV8Tracing::Job::RecordMetrics() const {
529 CheckIsOnOriginThread();
530 DCHECK_EQ(GET_PROXY_FOR_URL, operation_);
532 base::TimeTicks now = base::TimeTicks::Now();
534 // Metrics are output for each completed request to GetProxyForURL()).
536 // Note that a different set of histograms is used to record the metrics for
537 // requests that completed in non-blocking mode versus blocking mode. The
538 // expectation is for requests to complete in non-blocking mode each time.
539 // If they don't then something strange is happening, and the purpose of the
540 // seprate statistics is to better understand that trend.
541 #define UPDATE_HISTOGRAMS(base_name) \
542 do {\
543 UMA_HISTOGRAM_MEDIUM_TIMES(base_name "TotalTime", now - metrics_start_time_);\
544 UMA_HISTOGRAM_MEDIUM_TIMES(base_name "TotalTimeWorkerThread",\
545 metrics_end_time_ - metrics_start_time_);\
546 UMA_HISTOGRAM_TIMES(base_name "OriginThreadLatency",\
547 now - metrics_end_time_);\
548 UMA_HISTOGRAM_MEDIUM_TIMES(base_name "TotalTimeDNS",\
549 metrics_dns_total_time_);\
550 UMA_HISTOGRAM_MEDIUM_TIMES(base_name "ExecutionTime",\
551 metrics_execution_time_);\
552 UMA_HISTOGRAM_MEDIUM_TIMES(base_name "AbandonedExecutionTotalTime",\
553 metrics_abandoned_execution_total_time_);\
554 UMA_HISTOGRAM_MEDIUM_TIMES(base_name "DnsWaitTotalTime",\
555 metrics_nonblocking_dns_wait_total_time_);\
556 UMA_HISTOGRAM_CUSTOM_COUNTS(\
557 base_name "NumRestarts", metrics_num_executions_ - 1,\
558 1, kMaxUniqueResolveDnsPerExec, kMaxUniqueResolveDnsPerExec);\
559 UMA_HISTOGRAM_CUSTOM_COUNTS(\
560 base_name "UniqueDNS", metrics_num_unique_dns_,\
561 1, kMaxUniqueResolveDnsPerExec, kMaxUniqueResolveDnsPerExec);\
562 UMA_HISTOGRAM_COUNTS_100(base_name "NumAlerts", metrics_num_alerts_);\
563 UMA_HISTOGRAM_CUSTOM_COUNTS(\
564 base_name "NumErrors", metrics_num_errors_, 1, 10, 10);\
565 } while (false)
567 if (!blocking_dns_)
568 UPDATE_HISTOGRAMS("Net.ProxyResolver.");
569 else
570 UPDATE_HISTOGRAMS("Net.ProxyResolver.BlockingDNSMode.");
572 #undef UPDATE_HISTOGRAMS
574 // Histograms to better understand http://crbug.com/240536 -- long
575 // URLs can cause a significant slowdown in PAC execution. Figure out how
576 // severe this is in the wild.
577 if (!blocking_dns_) {
578 size_t url_size = url_.spec().size();
580 UMA_HISTOGRAM_CUSTOM_COUNTS(
581 "Net.ProxyResolver.URLSize", url_size, 1, 200000, 50);
583 if (url_size > 2048) {
584 UMA_HISTOGRAM_MEDIUM_TIMES("Net.ProxyResolver.ExecutionTime_UrlOver2K",
585 metrics_execution_time_);
588 if (url_size > 4096) {
589 UMA_HISTOGRAM_MEDIUM_TIMES("Net.ProxyResolver.ExecutionTime_UrlOver4K",
590 metrics_execution_time_);
593 if (url_size > 8192) {
594 UMA_HISTOGRAM_MEDIUM_TIMES("Net.ProxyResolver.ExecutionTime_UrlOver8K",
595 metrics_execution_time_);
598 if (url_size > 131072) {
599 UMA_HISTOGRAM_MEDIUM_TIMES("Net.ProxyResolver.ExecutionTime_UrlOver128K",
600 metrics_execution_time_);
606 void ProxyResolverV8Tracing::Job::Start(Operation op, bool blocking_dns,
607 const CompletionCallback& callback) {
608 CheckIsOnOriginThread();
610 metrics_start_time_ = base::TimeTicks::Now();
611 operation_ = op;
612 blocking_dns_ = blocking_dns;
613 SetCallback(callback);
615 owned_self_reference_ = this;
617 worker_loop()->PostTask(FROM_HERE,
618 blocking_dns_ ? base::Bind(&Job::ExecuteBlocking, this) :
619 base::Bind(&Job::ExecuteNonBlocking, this));
622 void ProxyResolverV8Tracing::Job::ExecuteBlocking() {
623 CheckIsOnWorkerThread();
624 DCHECK(blocking_dns_);
626 if (cancelled_.IsSet())
627 return;
629 NotifyCaller(ExecuteProxyResolver());
632 void ProxyResolverV8Tracing::Job::ExecuteNonBlocking() {
633 CheckIsOnWorkerThread();
634 DCHECK(!blocking_dns_);
636 if (cancelled_.IsSet())
637 return;
639 // Reset state for the current execution.
640 abandoned_ = false;
641 num_dns_ = 0;
642 alerts_and_errors_.clear();
643 alerts_and_errors_byte_cost_ = 0;
644 should_restart_with_blocking_dns_ = false;
646 int result = ExecuteProxyResolver();
648 if (abandoned_)
649 metrics_abandoned_execution_total_time_ += metrics_execution_time_;
651 if (should_restart_with_blocking_dns_) {
652 DCHECK(!blocking_dns_);
653 DCHECK(abandoned_);
654 blocking_dns_ = true;
655 ExecuteBlocking();
656 return;
659 if (abandoned_)
660 return;
662 DispatchBufferedAlertsAndErrors();
663 NotifyCaller(result);
666 int ProxyResolverV8Tracing::Job::ExecuteProxyResolver() {
667 IncrementWithoutOverflow(&metrics_num_executions_);
669 base::TimeTicks start = base::TimeTicks::Now();
671 JSBindings* prev_bindings = v8_resolver()->js_bindings();
672 v8_resolver()->set_js_bindings(this);
674 int result = ERR_UNEXPECTED; // Initialized to silence warnings.
676 switch (operation_) {
677 case SET_PAC_SCRIPT:
678 result = v8_resolver()->SetPacScript(
679 script_data_, CompletionCallback());
680 break;
681 case GET_PROXY_FOR_URL:
682 result = v8_resolver()->GetProxyForURL(
683 url_,
684 // Important: Do not write directly into |user_results_|, since if the
685 // request were to be cancelled from the origin thread, must guarantee
686 // that |user_results_| is not accessed anymore.
687 &results_,
688 CompletionCallback(),
689 NULL,
690 bound_net_log_);
691 break;
694 v8_resolver()->set_js_bindings(prev_bindings);
696 metrics_execution_time_ = base::TimeTicks::Now() - start;
698 return result;
701 bool ProxyResolverV8Tracing::Job::ResolveDns(const std::string& host,
702 ResolveDnsOperation op,
703 std::string* output,
704 bool* terminate) {
705 if (cancelled_.IsSet()) {
706 *terminate = true;
707 return false;
710 if ((op == DNS_RESOLVE || op == DNS_RESOLVE_EX) && host.empty()) {
711 // a DNS resolve with an empty hostname is considered an error.
712 return false;
715 return blocking_dns_ ?
716 ResolveDnsBlocking(host, op, output) :
717 ResolveDnsNonBlocking(host, op, output, terminate);
720 void ProxyResolverV8Tracing::Job::Alert(const base::string16& message) {
721 HandleAlertOrError(true, -1, message);
724 void ProxyResolverV8Tracing::Job::OnError(int line_number,
725 const base::string16& error) {
726 HandleAlertOrError(false, line_number, error);
729 bool ProxyResolverV8Tracing::Job::ResolveDnsBlocking(const std::string& host,
730 ResolveDnsOperation op,
731 std::string* output) {
732 CheckIsOnWorkerThread();
734 // Check if the DNS result for this host has already been cached.
735 bool rv;
736 if (GetDnsFromLocalCache(host, op, output, &rv)) {
737 // Yay, cache hit!
738 return rv;
741 // If the host was not in the local cache, this is a new hostname.
742 IncrementWithoutOverflow(&metrics_num_unique_dns_);
744 if (dns_cache_.size() >= kMaxUniqueResolveDnsPerExec) {
745 // Safety net for scripts with unexpectedly many DNS calls.
746 // We will continue running to completion, but will fail every
747 // subsequent DNS request.
748 return false;
751 if (!PostDnsOperationAndWait(host, op, NULL))
752 return false; // Was cancelled.
754 CHECK(GetDnsFromLocalCache(host, op, output, &rv));
755 return rv;
758 bool ProxyResolverV8Tracing::Job::ResolveDnsNonBlocking(const std::string& host,
759 ResolveDnsOperation op,
760 std::string* output,
761 bool* terminate) {
762 CheckIsOnWorkerThread();
764 if (abandoned_) {
765 // If this execution was already abandoned can fail right away. Only 1 DNS
766 // dependency will be traced at a time (for more predictable outcomes).
767 return false;
770 num_dns_ += 1;
772 // Check if the DNS result for this host has already been cached.
773 bool rv;
774 if (GetDnsFromLocalCache(host, op, output, &rv)) {
775 // Yay, cache hit!
776 return rv;
779 // If the host was not in the local cache, then this is a new hostname.
780 IncrementWithoutOverflow(&metrics_num_unique_dns_);
782 if (num_dns_ <= last_num_dns_) {
783 // The sequence of DNS operations is different from last time!
784 ScheduleRestartWithBlockingDns();
785 *terminate = true;
786 return false;
789 if (dns_cache_.size() >= kMaxUniqueResolveDnsPerExec) {
790 // Safety net for scripts with unexpectedly many DNS calls.
791 return false;
794 DCHECK(!should_restart_with_blocking_dns_);
796 bool completed_synchronously;
797 if (!PostDnsOperationAndWait(host, op, &completed_synchronously))
798 return false; // Was cancelled.
800 if (completed_synchronously) {
801 CHECK(GetDnsFromLocalCache(host, op, output, &rv));
802 return rv;
805 // Otherwise if the result was not in the cache, then a DNS request has
806 // been started. Abandon this invocation of FindProxyForURL(), it will be
807 // restarted once the DNS request completes.
808 abandoned_ = true;
809 *terminate = true;
810 last_num_dns_ = num_dns_;
811 return false;
814 bool ProxyResolverV8Tracing::Job::PostDnsOperationAndWait(
815 const std::string& host, ResolveDnsOperation op,
816 bool* completed_synchronously) {
818 base::TimeTicks start = base::TimeTicks::Now();
820 // Post the DNS request to the origin thread.
821 DCHECK(!pending_dns_);
822 metrics_pending_dns_start_ = base::TimeTicks::Now();
823 pending_dns_host_ = host;
824 pending_dns_op_ = op;
825 origin_loop_->PostTask(FROM_HERE, base::Bind(&Job::DoDnsOperation, this));
827 event_.Wait();
828 event_.Reset();
830 if (cancelled_.IsSet())
831 return false;
833 if (completed_synchronously)
834 *completed_synchronously = pending_dns_completed_synchronously_;
836 if (!blocking_dns_)
837 metrics_nonblocking_dns_wait_total_time_ += base::TimeTicks::Now() - start;
839 return true;
842 void ProxyResolverV8Tracing::Job::DoDnsOperation() {
843 CheckIsOnOriginThread();
844 DCHECK(!pending_dns_);
846 if (cancelled_.IsSet())
847 return;
849 HostResolver::RequestHandle dns_request = NULL;
850 int result = host_resolver()->Resolve(
851 MakeDnsRequestInfo(pending_dns_host_, pending_dns_op_),
852 DEFAULT_PRIORITY,
853 &pending_dns_addresses_,
854 base::Bind(&Job::OnDnsOperationComplete, this),
855 &dns_request,
856 bound_net_log_);
858 pending_dns_completed_synchronously_ = result != ERR_IO_PENDING;
860 // Check if the request was cancelled as a side-effect of calling into the
861 // HostResolver. This isn't the ordinary execution flow, however it is
862 // exercised by unit-tests.
863 if (cancelled_.IsSet()) {
864 if (!pending_dns_completed_synchronously_)
865 host_resolver()->CancelRequest(dns_request);
866 return;
869 if (pending_dns_completed_synchronously_) {
870 OnDnsOperationComplete(result);
871 } else {
872 DCHECK(dns_request);
873 pending_dns_ = dns_request;
874 // OnDnsOperationComplete() will be called by host resolver on completion.
877 if (!blocking_dns_) {
878 // The worker thread always blocks waiting to see if the result can be
879 // serviced from cache before restarting.
880 event_.Signal();
884 void ProxyResolverV8Tracing::Job::OnDnsOperationComplete(int result) {
885 CheckIsOnOriginThread();
887 DCHECK(!cancelled_.IsSet());
888 DCHECK(pending_dns_completed_synchronously_ == (pending_dns_ == NULL));
890 SaveDnsToLocalCache(pending_dns_host_, pending_dns_op_, result,
891 pending_dns_addresses_);
892 pending_dns_ = NULL;
894 metrics_dns_total_time_ +=
895 base::TimeTicks::Now() - metrics_pending_dns_start_;
897 if (blocking_dns_) {
898 event_.Signal();
899 return;
902 if (!blocking_dns_ && !pending_dns_completed_synchronously_) {
903 // Restart. This time it should make more progress due to having
904 // cached items.
905 worker_loop()->PostTask(FROM_HERE,
906 base::Bind(&Job::ExecuteNonBlocking, this));
910 void ProxyResolverV8Tracing::Job::ScheduleRestartWithBlockingDns() {
911 CheckIsOnWorkerThread();
913 DCHECK(!should_restart_with_blocking_dns_);
914 DCHECK(!abandoned_);
915 DCHECK(!blocking_dns_);
917 abandoned_ = true;
919 // The restart will happen after ExecuteNonBlocking() finishes.
920 should_restart_with_blocking_dns_ = true;
923 bool ProxyResolverV8Tracing::Job::GetDnsFromLocalCache(
924 const std::string& host,
925 ResolveDnsOperation op,
926 std::string* output,
927 bool* return_value) {
928 CheckIsOnWorkerThread();
930 DnsCache::const_iterator it = dns_cache_.find(MakeDnsCacheKey(host, op));
931 if (it == dns_cache_.end())
932 return false;
934 *output = it->second;
935 *return_value = !it->second.empty();
936 return true;
939 void ProxyResolverV8Tracing::Job::SaveDnsToLocalCache(
940 const std::string& host,
941 ResolveDnsOperation op,
942 int net_error,
943 const net::AddressList& addresses) {
944 CheckIsOnOriginThread();
946 // Serialize the result into a string to save to the cache.
947 std::string cache_value;
948 if (net_error != OK) {
949 cache_value = std::string();
950 } else if (op == DNS_RESOLVE || op == MY_IP_ADDRESS) {
951 // dnsResolve() and myIpAddress() are expected to return a single IP
952 // address.
953 cache_value = addresses.front().ToStringWithoutPort();
954 } else {
955 // The *Ex versions are expected to return a semi-colon separated list.
956 for (AddressList::const_iterator iter = addresses.begin();
957 iter != addresses.end(); ++iter) {
958 if (!cache_value.empty())
959 cache_value += ";";
960 cache_value += iter->ToStringWithoutPort();
964 dns_cache_[MakeDnsCacheKey(host, op)] = cache_value;
967 // static
968 HostResolver::RequestInfo ProxyResolverV8Tracing::Job::MakeDnsRequestInfo(
969 const std::string& host, ResolveDnsOperation op) {
970 HostPortPair host_port = HostPortPair(host, 80);
971 if (op == MY_IP_ADDRESS || op == MY_IP_ADDRESS_EX) {
972 host_port.set_host(GetHostName());
975 HostResolver::RequestInfo info(host_port);
976 // Flag myIpAddress requests.
977 if (op == MY_IP_ADDRESS || op == MY_IP_ADDRESS_EX) {
978 // TODO: Provide a RequestInfo construction mechanism that does not
979 // require a hostname and sets is_my_ip_address to true instead of this.
980 info.set_is_my_ip_address(true);
982 // The non-ex flavors are limited to IPv4 results.
983 if (op == MY_IP_ADDRESS || op == DNS_RESOLVE) {
984 info.set_address_family(ADDRESS_FAMILY_IPV4);
987 return info;
990 std::string ProxyResolverV8Tracing::Job::MakeDnsCacheKey(
991 const std::string& host, ResolveDnsOperation op) {
992 return base::StringPrintf("%d:%s", op, host.c_str());
995 void ProxyResolverV8Tracing::Job::HandleAlertOrError(
996 bool is_alert,
997 int line_number,
998 const base::string16& message) {
999 CheckIsOnWorkerThread();
1001 if (cancelled_.IsSet())
1002 return;
1004 if (blocking_dns_) {
1005 // In blocking DNS mode the events can be dispatched immediately.
1006 DispatchAlertOrError(is_alert, line_number, message);
1007 return;
1010 // Otherwise in nonblocking mode, buffer all the messages until
1011 // the end.
1013 if (abandoned_)
1014 return;
1016 alerts_and_errors_byte_cost_ += sizeof(AlertOrError) + message.size() * 2;
1018 // If there have been lots of messages, enqueing could be expensive on
1019 // memory. Consider a script which does megabytes worth of alerts().
1020 // Avoid this by falling back to blocking mode.
1021 if (alerts_and_errors_byte_cost_ > kMaxAlertsAndErrorsBytes) {
1022 ScheduleRestartWithBlockingDns();
1023 return;
1026 AlertOrError entry = {is_alert, line_number, message};
1027 alerts_and_errors_.push_back(entry);
1030 void ProxyResolverV8Tracing::Job::DispatchBufferedAlertsAndErrors() {
1031 CheckIsOnWorkerThread();
1032 DCHECK(!blocking_dns_);
1033 DCHECK(!abandoned_);
1035 for (size_t i = 0; i < alerts_and_errors_.size(); ++i) {
1036 const AlertOrError& x = alerts_and_errors_[i];
1037 DispatchAlertOrError(x.is_alert, x.line_number, x.message);
1041 void ProxyResolverV8Tracing::Job::DispatchAlertOrError(
1042 bool is_alert, int line_number, const base::string16& message) {
1043 CheckIsOnWorkerThread();
1045 // Note that the handling of cancellation is racy with regard to
1046 // alerts/errors. The request might get cancelled shortly after this
1047 // check! (There is no lock being held to guarantee otherwise).
1049 // If this happens, then some information will get written to the NetLog
1050 // needlessly, however the NetLog will still be alive so it shouldn't cause
1051 // problems.
1052 if (cancelled_.IsSet())
1053 return;
1055 if (is_alert) {
1056 // -------------------
1057 // alert
1058 // -------------------
1059 IncrementWithoutOverflow(&metrics_num_alerts_);
1060 VLOG(1) << "PAC-alert: " << message;
1062 // Send to the NetLog.
1063 LogEventToCurrentRequestAndGlobally(
1064 NetLog::TYPE_PAC_JAVASCRIPT_ALERT,
1065 NetLog::StringCallback("message", &message));
1066 } else {
1067 // -------------------
1068 // error
1069 // -------------------
1070 IncrementWithoutOverflow(&metrics_num_errors_);
1071 if (line_number == -1)
1072 VLOG(1) << "PAC-error: " << message;
1073 else
1074 VLOG(1) << "PAC-error: " << "line: " << line_number << ": " << message;
1076 // Send the error to the NetLog.
1077 LogEventToCurrentRequestAndGlobally(
1078 NetLog::TYPE_PAC_JAVASCRIPT_ERROR,
1079 base::Bind(&NetLogErrorCallback, line_number, &message));
1081 if (error_observer())
1082 error_observer()->OnPACScriptError(line_number, message);
1086 void ProxyResolverV8Tracing::Job::LogEventToCurrentRequestAndGlobally(
1087 NetLog::EventType type,
1088 const NetLog::ParametersCallback& parameters_callback) {
1089 CheckIsOnWorkerThread();
1090 bound_net_log_.AddEvent(type, parameters_callback);
1092 // Emit to the global NetLog event stream.
1093 if (net_log())
1094 net_log()->AddGlobalEntry(type, parameters_callback);
1097 ProxyResolverV8Tracing::ProxyResolverV8Tracing(
1098 HostResolver* host_resolver,
1099 ProxyResolverErrorObserver* error_observer,
1100 NetLog* net_log)
1101 : ProxyResolver(true /*expects_pac_bytes*/),
1102 host_resolver_(host_resolver),
1103 error_observer_(error_observer),
1104 net_log_(net_log),
1105 num_outstanding_callbacks_(0) {
1106 DCHECK(host_resolver);
1107 // Start up the thread.
1108 thread_.reset(new base::Thread("Proxy resolver"));
1109 base::Thread::Options options;
1110 options.timer_slack = base::TIMER_SLACK_MAXIMUM;
1111 CHECK(thread_->StartWithOptions(options));
1113 v8_resolver_.reset(new ProxyResolverV8);
1116 ProxyResolverV8Tracing::~ProxyResolverV8Tracing() {
1117 // Note, all requests should have been cancelled.
1118 CHECK(!set_pac_script_job_.get());
1119 CHECK_EQ(0, num_outstanding_callbacks_);
1121 // Join the worker thread. See http://crbug.com/69710. Note that we call
1122 // Stop() here instead of simply clearing thread_ since there may be pending
1123 // callbacks on the worker thread which want to dereference thread_.
1124 base::ThreadRestrictions::ScopedAllowIO allow_io;
1125 thread_->Stop();
1128 int ProxyResolverV8Tracing::GetProxyForURL(const GURL& url,
1129 ProxyInfo* results,
1130 const CompletionCallback& callback,
1131 RequestHandle* request,
1132 const BoundNetLog& net_log) {
1133 DCHECK(CalledOnValidThread());
1134 DCHECK(!callback.is_null());
1135 DCHECK(!set_pac_script_job_.get());
1137 scoped_refptr<Job> job = new Job(this);
1139 if (request)
1140 *request = job.get();
1142 job->StartGetProxyForURL(url, results, net_log, callback);
1143 return ERR_IO_PENDING;
1146 void ProxyResolverV8Tracing::CancelRequest(RequestHandle request) {
1147 Job* job = reinterpret_cast<Job*>(request);
1148 job->Cancel();
1151 LoadState ProxyResolverV8Tracing::GetLoadState(RequestHandle request) const {
1152 Job* job = reinterpret_cast<Job*>(request);
1153 return job->GetLoadState();
1156 void ProxyResolverV8Tracing::CancelSetPacScript() {
1157 DCHECK(set_pac_script_job_.get());
1158 set_pac_script_job_->Cancel();
1159 set_pac_script_job_ = NULL;
1162 int ProxyResolverV8Tracing::SetPacScript(
1163 const scoped_refptr<ProxyResolverScriptData>& script_data,
1164 const CompletionCallback& callback) {
1165 DCHECK(CalledOnValidThread());
1166 DCHECK(!callback.is_null());
1168 // Note that there should not be any outstanding (non-cancelled) Jobs when
1169 // setting the PAC script (ProxyService should guarantee this). If there are,
1170 // then they might complete in strange ways after the new script is set.
1171 DCHECK(!set_pac_script_job_.get());
1172 CHECK_EQ(0, num_outstanding_callbacks_);
1174 set_pac_script_job_ = new Job(this);
1175 set_pac_script_job_->StartSetPacScript(script_data, callback);
1177 return ERR_IO_PENDING;
1180 } // namespace net