Include all dupe types (event when value is zero) in scan stats.
[chromium-blink-merge.git] / net / proxy / proxy_resolver_v8_tracing.cc
blob2f528e65edb1a24265767ec5c19947b10ed8f2fa
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 <map>
8 #include <string>
9 #include <vector>
11 #include "base/bind.h"
12 #include "base/single_thread_task_runner.h"
13 #include "base/strings/stringprintf.h"
14 #include "base/synchronization/cancellation_flag.h"
15 #include "base/synchronization/waitable_event.h"
16 #include "base/thread_task_runner_handle.h"
17 #include "base/threading/thread.h"
18 #include "base/threading/thread_restrictions.h"
19 #include "base/values.h"
20 #include "net/base/address_list.h"
21 #include "net/base/net_errors.h"
22 #include "net/dns/host_resolver.h"
23 #include "net/log/net_log.h"
24 #include "net/proxy/proxy_info.h"
25 #include "net/proxy/proxy_resolver_error_observer.h"
26 #include "net/proxy/proxy_resolver_v8.h"
28 // The intent of this class is explained in the design document:
29 // https://docs.google.com/a/chromium.org/document/d/16Ij5OcVnR3s0MH4Z5XkhI9VTPoMJdaBn9rKreAmGOdE/edit
31 // In a nutshell, PAC scripts are Javascript programs and may depend on
32 // network I/O, by calling functions like dnsResolve().
34 // This is problematic since functions such as dnsResolve() will block the
35 // Javascript execution until the DNS result is availble, thereby stalling the
36 // PAC thread, which hurts the ability to process parallel proxy resolves.
37 // An obvious solution is to simply start more PAC threads, however this scales
38 // poorly, which hurts the ability to process parallel proxy resolves.
40 // The solution in ProxyResolverV8Tracing is to model PAC scripts as being
41 // deterministic, and depending only on the inputted URL. When the script
42 // issues a dnsResolve() for a yet unresolved hostname, the Javascript
43 // execution is "aborted", and then re-started once the DNS result is
44 // known.
45 namespace net {
47 namespace {
49 // Upper bound on how many *unique* DNS resolves a PAC script is allowed
50 // to make. This is a failsafe both for scripts that do a ridiculous
51 // number of DNS resolves, as well as scripts which are misbehaving
52 // under the tracing optimization. It is not expected to hit this normally.
53 const size_t kMaxUniqueResolveDnsPerExec = 20;
55 // Approximate number of bytes to use for buffering alerts() and errors.
56 // This is a failsafe in case repeated executions of the script causes
57 // too much memory bloat. It is not expected for well behaved scripts to
58 // hit this. (In fact normal scripts should not even have alerts() or errors).
59 const size_t kMaxAlertsAndErrorsBytes = 2048;
61 // Returns event parameters for a PAC error message (line number + message).
62 scoped_ptr<base::Value> NetLogErrorCallback(
63 int line_number,
64 const base::string16* message,
65 NetLogCaptureMode /* capture_mode */) {
66 scoped_ptr<base::DictionaryValue> dict(new base::DictionaryValue());
67 dict->SetInteger("line_number", line_number);
68 dict->SetString("message", *message);
69 return dict.Pass();
72 // The Job class is responsible for executing GetProxyForURL() and
73 // creating ProxyResolverV8 instances, since both of these operations share
74 // similar code.
76 // The DNS for these operations can operate in either blocking or
77 // non-blocking mode. Blocking mode is used as a fallback when the PAC script
78 // seems to be misbehaving under the tracing optimization.
80 // Note that this class runs on both the origin thread and a worker
81 // thread. Most methods are expected to be used exclusively on one thread
82 // or the other.
84 // The lifetime of Jobs does not exceed that of the ProxyResolverV8Tracing that
85 // spawned it. Destruction might happen on either the origin thread or the
86 // worker thread.
87 class Job : public base::RefCountedThreadSafe<Job>,
88 public ProxyResolverV8::JSBindings {
89 public:
90 struct Params {
91 Params(
92 const scoped_refptr<base::SingleThreadTaskRunner>& worker_task_runner,
93 HostResolver* host_resolver,
94 ProxyResolverErrorObserver* error_observer,
95 NetLog* net_log,
96 ProxyResolver::LoadStateChangedCallback on_load_state_changed,
97 int* num_outstanding_callbacks)
98 : v8_resolver(nullptr),
99 worker_task_runner(worker_task_runner),
100 host_resolver(host_resolver),
101 error_observer(error_observer),
102 net_log(net_log),
103 on_load_state_changed(on_load_state_changed),
104 num_outstanding_callbacks(num_outstanding_callbacks) {}
106 ProxyResolverV8* v8_resolver;
107 scoped_refptr<base::SingleThreadTaskRunner> worker_task_runner;
108 HostResolver* host_resolver;
109 ProxyResolverErrorObserver* error_observer;
110 NetLog* net_log;
111 ProxyResolver::LoadStateChangedCallback on_load_state_changed;
112 int* num_outstanding_callbacks;
114 // |params| is non-owned. It contains the parameters for this Job, and must
115 // outlive it.
116 explicit Job(const Params* params);
118 // Called from origin thread.
119 void StartCreateV8Resolver(
120 const scoped_refptr<ProxyResolverScriptData>& script_data,
121 scoped_ptr<ProxyResolverV8>* resolver,
122 const CompletionCallback& callback);
124 // Called from origin thread.
125 void StartGetProxyForURL(const GURL& url,
126 ProxyInfo* results,
127 const BoundNetLog& net_log,
128 const CompletionCallback& callback);
130 // Called from origin thread.
131 void Cancel();
133 // Called from origin thread.
134 LoadState GetLoadState() const;
136 private:
137 typedef std::map<std::string, std::string> DnsCache;
138 friend class base::RefCountedThreadSafe<Job>;
140 enum Operation {
141 CREATE_V8_RESOLVER,
142 GET_PROXY_FOR_URL,
145 struct AlertOrError {
146 bool is_alert;
147 int line_number;
148 base::string16 message;
151 ~Job() override;
153 void CheckIsOnWorkerThread() const;
154 void CheckIsOnOriginThread() const;
156 void SetCallback(const CompletionCallback& callback);
157 void ReleaseCallback();
159 ProxyResolverV8* v8_resolver();
160 const scoped_refptr<base::SingleThreadTaskRunner>& worker_task_runner();
161 HostResolver* host_resolver();
162 ProxyResolverErrorObserver* error_observer();
163 NetLog* net_log();
165 // Invokes the user's callback.
166 void NotifyCaller(int result);
167 void NotifyCallerOnOriginLoop(int result);
169 void Start(Operation op, bool blocking_dns,
170 const CompletionCallback& callback);
172 void ExecuteBlocking();
173 void ExecuteNonBlocking();
174 int ExecuteProxyResolver();
176 // Implementation of ProxyResolverv8::JSBindings
177 bool ResolveDns(const std::string& host,
178 ResolveDnsOperation op,
179 std::string* output,
180 bool* terminate) override;
181 void Alert(const base::string16& message) override;
182 void OnError(int line_number, const base::string16& error) override;
184 bool ResolveDnsBlocking(const std::string& host,
185 ResolveDnsOperation op,
186 std::string* output);
188 bool ResolveDnsNonBlocking(const std::string& host,
189 ResolveDnsOperation op,
190 std::string* output,
191 bool* terminate);
193 bool PostDnsOperationAndWait(const std::string& host,
194 ResolveDnsOperation op,
195 bool* completed_synchronously)
196 WARN_UNUSED_RESULT;
198 void DoDnsOperation();
199 void OnDnsOperationComplete(int result);
201 void ScheduleRestartWithBlockingDns();
203 bool GetDnsFromLocalCache(const std::string& host, ResolveDnsOperation op,
204 std::string* output, bool* return_value);
206 void SaveDnsToLocalCache(const std::string& host,
207 ResolveDnsOperation op,
208 int net_error,
209 const AddressList& addresses);
211 // Builds a RequestInfo to service the specified PAC DNS operation.
212 static HostResolver::RequestInfo MakeDnsRequestInfo(const std::string& host,
213 ResolveDnsOperation op);
215 // Makes a key for looking up |host, op| in |dns_cache_|. Strings are used for
216 // convenience, to avoid defining custom comparators.
217 static std::string MakeDnsCacheKey(const std::string& host,
218 ResolveDnsOperation op);
220 void HandleAlertOrError(bool is_alert, int line_number,
221 const base::string16& message);
222 void DispatchBufferedAlertsAndErrors();
223 void DispatchAlertOrError(bool is_alert, int line_number,
224 const base::string16& message);
226 void LogEventToCurrentRequestAndGlobally(
227 NetLog::EventType type,
228 const NetLog::ParametersCallback& parameters_callback);
230 // The thread which called into ProxyResolverV8Tracing, and on which the
231 // completion callback is expected to run.
232 scoped_refptr<base::SingleThreadTaskRunner> origin_runner_;
234 // The Parameters for this Job.
235 // Initialized on origin thread and then accessed from both threads.
236 const Params* const params_;
238 // The callback to run (on the origin thread) when the Job finishes.
239 // Should only be accessed from origin thread.
240 CompletionCallback callback_;
242 // Flag to indicate whether the request has been cancelled.
243 base::CancellationFlag cancelled_;
245 // The operation that this Job is running.
246 // Initialized on origin thread and then accessed from both threads.
247 Operation operation_;
249 // The DNS mode for this Job.
250 // Initialized on origin thread, mutated on worker thread, and accessed
251 // by both the origin thread and worker thread.
252 bool blocking_dns_;
254 // Used to block the worker thread on a DNS operation taking place on the
255 // origin thread.
256 base::WaitableEvent event_;
258 // Map of DNS operations completed so far. Written into on the origin thread
259 // and read on the worker thread.
260 DnsCache dns_cache_;
262 // The job holds a reference to itself to ensure that it remains alive until
263 // either completion or cancellation.
264 scoped_refptr<Job> owned_self_reference_;
266 // -------------------------------------------------------
267 // State specific to CREATE_V8_RESOLVER.
268 // -------------------------------------------------------
270 scoped_refptr<ProxyResolverScriptData> script_data_;
271 scoped_ptr<ProxyResolverV8>* resolver_out_;
273 // -------------------------------------------------------
274 // State specific to GET_PROXY_FOR_URL.
275 // -------------------------------------------------------
277 ProxyInfo* user_results_; // Owned by caller, lives on origin thread.
278 GURL url_;
279 ProxyInfo results_;
280 BoundNetLog bound_net_log_;
282 // ---------------------------------------------------------------------------
283 // State for ExecuteNonBlocking()
284 // ---------------------------------------------------------------------------
285 // These variables are used exclusively on the worker thread and are only
286 // meaningful when executing inside of ExecuteNonBlocking().
288 // Whether this execution was abandoned due to a missing DNS dependency.
289 bool abandoned_;
291 // Number of calls made to ResolveDns() by this execution.
292 int num_dns_;
294 // Sequence of calls made to Alert() or OnError() by this execution.
295 std::vector<AlertOrError> alerts_and_errors_;
296 size_t alerts_and_errors_byte_cost_; // Approximate byte cost of the above.
298 // Number of calls made to ResolveDns() by the PREVIOUS execution.
299 int last_num_dns_;
301 // Whether the current execution needs to be restarted in blocking mode.
302 bool should_restart_with_blocking_dns_;
304 // ---------------------------------------------------------------------------
305 // State for pending DNS request.
306 // ---------------------------------------------------------------------------
308 // Handle to the outstanding request in the HostResolver, or NULL.
309 // This is mutated and used on the origin thread, however it may be read by
310 // the worker thread for some DCHECKS().
311 HostResolver::RequestHandle pending_dns_;
313 // Indicates if the outstanding DNS request completed synchronously. Written
314 // on the origin thread, and read by the worker thread.
315 bool pending_dns_completed_synchronously_;
317 // These are the inputs to DoDnsOperation(). Written on the worker thread,
318 // read by the origin thread.
319 std::string pending_dns_host_;
320 ResolveDnsOperation pending_dns_op_;
322 // This contains the resolved address list that DoDnsOperation() fills in.
323 // Used exclusively on the origin thread.
324 AddressList pending_dns_addresses_;
327 class ProxyResolverV8Tracing : public ProxyResolver,
328 public base::NonThreadSafe {
329 public:
330 // Constructs a ProxyResolver that will issue DNS requests through
331 // |job_params->host_resolver|, forward Javascript errors through
332 // |error_observer|, and log Javascript errors and alerts to
333 // |job_params->net_log|. When the LoadState for a request changes,
334 // |job_params->on_load_state_changed| will be invoked with the RequestHandle
335 // for that request with the new LoadState.
337 // Note that the constructor takes ownership of |error_observer|, whereas
338 // |job_params->host_resolver| and |job_params->net_log| are expected to
339 // outlive |this|.
340 ProxyResolverV8Tracing(scoped_ptr<ProxyResolverErrorObserver> error_observer,
341 scoped_ptr<base::Thread> thread,
342 scoped_ptr<ProxyResolverV8> resolver,
343 scoped_ptr<Job::Params> job_params);
345 ~ProxyResolverV8Tracing() override;
347 // ProxyResolver implementation:
348 int GetProxyForURL(const GURL& url,
349 ProxyInfo* results,
350 const CompletionCallback& callback,
351 RequestHandle* request,
352 const BoundNetLog& net_log) override;
353 void CancelRequest(RequestHandle request) override;
354 LoadState GetLoadState(RequestHandle request) const override;
356 private:
357 // The worker thread on which the ProxyResolverV8 will be run.
358 scoped_ptr<base::Thread> thread_;
359 scoped_ptr<ProxyResolverV8> v8_resolver_;
361 scoped_ptr<ProxyResolverErrorObserver> error_observer_;
363 scoped_ptr<Job::Params> job_params_;
365 // The number of outstanding (non-cancelled) jobs.
366 int num_outstanding_callbacks_;
368 DISALLOW_COPY_AND_ASSIGN(ProxyResolverV8Tracing);
371 Job::Job(const Job::Params* params)
372 : origin_runner_(base::ThreadTaskRunnerHandle::Get()),
373 params_(params),
374 event_(true, false),
375 last_num_dns_(0),
376 pending_dns_(NULL) {
377 CheckIsOnOriginThread();
380 void Job::StartCreateV8Resolver(
381 const scoped_refptr<ProxyResolverScriptData>& script_data,
382 scoped_ptr<ProxyResolverV8>* resolver,
383 const CompletionCallback& callback) {
384 CheckIsOnOriginThread();
386 resolver_out_ = resolver;
387 script_data_ = script_data;
389 // Script initialization uses blocking DNS since there isn't any
390 // advantage to using non-blocking mode here. That is because the
391 // parent ProxyService can't submit any ProxyResolve requests until
392 // initialization has completed successfully!
393 Start(CREATE_V8_RESOLVER, true /*blocking*/, callback);
396 void Job::StartGetProxyForURL(const GURL& url,
397 ProxyInfo* results,
398 const BoundNetLog& net_log,
399 const CompletionCallback& callback) {
400 CheckIsOnOriginThread();
402 url_ = url;
403 user_results_ = results;
404 bound_net_log_ = net_log;
406 Start(GET_PROXY_FOR_URL, false /*non-blocking*/, callback);
409 void Job::Cancel() {
410 CheckIsOnOriginThread();
412 // There are several possibilities to consider for cancellation:
413 // (a) The job has been posted to the worker thread, however script execution
414 // has not yet started.
415 // (b) The script is executing on the worker thread.
416 // (c) The script is executing on the worker thread, however is blocked inside
417 // of dnsResolve() waiting for a response from the origin thread.
418 // (d) Nothing is running on the worker thread, however the host resolver has
419 // a pending DNS request which upon completion will restart the script
420 // execution.
421 // (e) The worker thread has a pending task to restart execution, which was
422 // posted after the DNS dependency was resolved and saved to local cache.
423 // (f) The script execution completed entirely, and posted a task to the
424 // origin thread to notify the caller.
426 // |cancelled_| is read on both the origin thread and worker thread. The
427 // code that runs on the worker thread is littered with checks on
428 // |cancelled_| to break out early.
429 cancelled_.Set();
431 ReleaseCallback();
433 if (pending_dns_) {
434 host_resolver()->CancelRequest(pending_dns_);
435 pending_dns_ = NULL;
438 // The worker thread might be blocked waiting for DNS.
439 event_.Signal();
441 owned_self_reference_ = NULL;
444 LoadState Job::GetLoadState() const {
445 CheckIsOnOriginThread();
447 if (pending_dns_)
448 return LOAD_STATE_RESOLVING_HOST_IN_PROXY_SCRIPT;
450 return LOAD_STATE_RESOLVING_PROXY_FOR_URL;
453 Job::~Job() {
454 DCHECK(!pending_dns_);
455 DCHECK(callback_.is_null());
458 void Job::CheckIsOnWorkerThread() const {
459 DCHECK(params_->worker_task_runner->BelongsToCurrentThread());
462 void Job::CheckIsOnOriginThread() const {
463 DCHECK(origin_runner_->BelongsToCurrentThread());
466 void Job::SetCallback(const CompletionCallback& callback) {
467 CheckIsOnOriginThread();
468 DCHECK(callback_.is_null());
469 (*params_->num_outstanding_callbacks)++;
470 callback_ = callback;
473 void Job::ReleaseCallback() {
474 CheckIsOnOriginThread();
475 DCHECK(!callback_.is_null());
476 CHECK_GT(*params_->num_outstanding_callbacks, 0);
477 (*params_->num_outstanding_callbacks)--;
478 callback_.Reset();
480 // For good measure, clear this other user-owned pointer.
481 user_results_ = NULL;
484 ProxyResolverV8* Job::v8_resolver() {
485 return params_->v8_resolver;
488 const scoped_refptr<base::SingleThreadTaskRunner>& Job::worker_task_runner() {
489 return params_->worker_task_runner;
492 HostResolver* Job::host_resolver() {
493 return params_->host_resolver;
496 ProxyResolverErrorObserver* Job::error_observer() {
497 return params_->error_observer;
500 NetLog* Job::net_log() {
501 return params_->net_log;
504 void Job::NotifyCaller(int result) {
505 CheckIsOnWorkerThread();
507 origin_runner_->PostTask(
508 FROM_HERE, base::Bind(&Job::NotifyCallerOnOriginLoop, this, result));
511 void Job::NotifyCallerOnOriginLoop(int result) {
512 CheckIsOnOriginThread();
514 if (cancelled_.IsSet())
515 return;
517 DCHECK(!callback_.is_null());
518 DCHECK(!pending_dns_);
520 if (operation_ == GET_PROXY_FOR_URL) {
521 *user_results_ = results_;
524 CompletionCallback callback = callback_;
525 ReleaseCallback();
526 callback.Run(result);
528 owned_self_reference_ = NULL;
531 void Job::Start(Operation op,
532 bool blocking_dns,
533 const CompletionCallback& callback) {
534 CheckIsOnOriginThread();
536 operation_ = op;
537 blocking_dns_ = blocking_dns;
538 SetCallback(callback);
540 owned_self_reference_ = this;
542 worker_task_runner()->PostTask(
543 FROM_HERE, blocking_dns_ ? base::Bind(&Job::ExecuteBlocking, this)
544 : base::Bind(&Job::ExecuteNonBlocking, this));
547 void Job::ExecuteBlocking() {
548 CheckIsOnWorkerThread();
549 DCHECK(blocking_dns_);
551 if (cancelled_.IsSet())
552 return;
554 NotifyCaller(ExecuteProxyResolver());
557 void Job::ExecuteNonBlocking() {
558 CheckIsOnWorkerThread();
559 DCHECK(!blocking_dns_);
561 if (cancelled_.IsSet())
562 return;
564 // Reset state for the current execution.
565 abandoned_ = false;
566 num_dns_ = 0;
567 alerts_and_errors_.clear();
568 alerts_and_errors_byte_cost_ = 0;
569 should_restart_with_blocking_dns_ = false;
571 int result = ExecuteProxyResolver();
573 if (should_restart_with_blocking_dns_) {
574 DCHECK(!blocking_dns_);
575 DCHECK(abandoned_);
576 blocking_dns_ = true;
577 ExecuteBlocking();
578 return;
581 if (abandoned_)
582 return;
584 DispatchBufferedAlertsAndErrors();
585 NotifyCaller(result);
588 int Job::ExecuteProxyResolver() {
589 int result = ERR_UNEXPECTED; // Initialized to silence warnings.
591 switch (operation_) {
592 case CREATE_V8_RESOLVER: {
593 scoped_ptr<ProxyResolverV8> resolver;
594 result = ProxyResolverV8::Create(script_data_, this, &resolver);
595 if (result == OK)
596 *resolver_out_ = resolver.Pass();
597 break;
599 case GET_PROXY_FOR_URL: {
600 result = v8_resolver()->GetProxyForURL(
601 url_,
602 // Important: Do not write directly into |user_results_|, since if the
603 // request were to be cancelled from the origin thread, must guarantee
604 // that |user_results_| is not accessed anymore.
605 &results_, this);
606 break;
610 return result;
613 bool Job::ResolveDns(const std::string& host,
614 ResolveDnsOperation op,
615 std::string* output,
616 bool* terminate) {
617 if (cancelled_.IsSet()) {
618 *terminate = true;
619 return false;
622 if ((op == DNS_RESOLVE || op == DNS_RESOLVE_EX) && host.empty()) {
623 // a DNS resolve with an empty hostname is considered an error.
624 return false;
627 return blocking_dns_ ?
628 ResolveDnsBlocking(host, op, output) :
629 ResolveDnsNonBlocking(host, op, output, terminate);
632 void Job::Alert(const base::string16& message) {
633 HandleAlertOrError(true, -1, message);
636 void Job::OnError(int line_number, const base::string16& error) {
637 HandleAlertOrError(false, line_number, error);
640 bool Job::ResolveDnsBlocking(const std::string& host,
641 ResolveDnsOperation op,
642 std::string* output) {
643 CheckIsOnWorkerThread();
645 // Check if the DNS result for this host has already been cached.
646 bool rv;
647 if (GetDnsFromLocalCache(host, op, output, &rv)) {
648 // Yay, cache hit!
649 return rv;
652 if (dns_cache_.size() >= kMaxUniqueResolveDnsPerExec) {
653 // Safety net for scripts with unexpectedly many DNS calls.
654 // We will continue running to completion, but will fail every
655 // subsequent DNS request.
656 return false;
659 if (!PostDnsOperationAndWait(host, op, NULL))
660 return false; // Was cancelled.
662 CHECK(GetDnsFromLocalCache(host, op, output, &rv));
663 return rv;
666 bool Job::ResolveDnsNonBlocking(const std::string& host,
667 ResolveDnsOperation op,
668 std::string* output,
669 bool* terminate) {
670 CheckIsOnWorkerThread();
672 if (abandoned_) {
673 // If this execution was already abandoned can fail right away. Only 1 DNS
674 // dependency will be traced at a time (for more predictable outcomes).
675 return false;
678 num_dns_ += 1;
680 // Check if the DNS result for this host has already been cached.
681 bool rv;
682 if (GetDnsFromLocalCache(host, op, output, &rv)) {
683 // Yay, cache hit!
684 return rv;
687 if (num_dns_ <= last_num_dns_) {
688 // The sequence of DNS operations is different from last time!
689 ScheduleRestartWithBlockingDns();
690 *terminate = true;
691 return false;
694 if (dns_cache_.size() >= kMaxUniqueResolveDnsPerExec) {
695 // Safety net for scripts with unexpectedly many DNS calls.
696 return false;
699 DCHECK(!should_restart_with_blocking_dns_);
701 bool completed_synchronously;
702 if (!PostDnsOperationAndWait(host, op, &completed_synchronously))
703 return false; // Was cancelled.
705 if (completed_synchronously) {
706 CHECK(GetDnsFromLocalCache(host, op, output, &rv));
707 return rv;
710 // Otherwise if the result was not in the cache, then a DNS request has
711 // been started. Abandon this invocation of FindProxyForURL(), it will be
712 // restarted once the DNS request completes.
713 abandoned_ = true;
714 *terminate = true;
715 last_num_dns_ = num_dns_;
716 return false;
719 bool Job::PostDnsOperationAndWait(const std::string& host,
720 ResolveDnsOperation op,
721 bool* completed_synchronously) {
722 // Post the DNS request to the origin thread.
723 DCHECK(!pending_dns_);
724 pending_dns_host_ = host;
725 pending_dns_op_ = op;
726 origin_runner_->PostTask(FROM_HERE, base::Bind(&Job::DoDnsOperation, this));
728 event_.Wait();
729 event_.Reset();
731 if (cancelled_.IsSet())
732 return false;
734 if (completed_synchronously)
735 *completed_synchronously = pending_dns_completed_synchronously_;
737 return true;
740 void Job::DoDnsOperation() {
741 CheckIsOnOriginThread();
742 DCHECK(!pending_dns_);
744 if (cancelled_.IsSet())
745 return;
747 HostResolver::RequestHandle dns_request = NULL;
748 int result = host_resolver()->Resolve(
749 MakeDnsRequestInfo(pending_dns_host_, pending_dns_op_),
750 DEFAULT_PRIORITY,
751 &pending_dns_addresses_,
752 base::Bind(&Job::OnDnsOperationComplete, this),
753 &dns_request,
754 bound_net_log_);
756 pending_dns_completed_synchronously_ = result != ERR_IO_PENDING;
758 // Check if the request was cancelled as a side-effect of calling into the
759 // HostResolver. This isn't the ordinary execution flow, however it is
760 // exercised by unit-tests.
761 if (cancelled_.IsSet()) {
762 if (!pending_dns_completed_synchronously_)
763 host_resolver()->CancelRequest(dns_request);
764 return;
767 if (pending_dns_completed_synchronously_) {
768 OnDnsOperationComplete(result);
769 } else {
770 DCHECK(dns_request);
771 pending_dns_ = dns_request;
772 if (!params_->on_load_state_changed.is_null()) {
773 params_->on_load_state_changed.Run(
774 this, LOAD_STATE_RESOLVING_HOST_IN_PROXY_SCRIPT);
776 // OnDnsOperationComplete() will be called by host resolver on completion.
779 if (!blocking_dns_) {
780 // The worker thread always blocks waiting to see if the result can be
781 // serviced from cache before restarting.
782 event_.Signal();
786 void Job::OnDnsOperationComplete(int result) {
787 CheckIsOnOriginThread();
789 DCHECK(!cancelled_.IsSet());
790 DCHECK(pending_dns_completed_synchronously_ == (pending_dns_ == NULL));
792 SaveDnsToLocalCache(pending_dns_host_, pending_dns_op_, result,
793 pending_dns_addresses_);
794 pending_dns_ = NULL;
796 if (!params_->on_load_state_changed.is_null() &&
797 !pending_dns_completed_synchronously_ && !cancelled_.IsSet()) {
798 params_->on_load_state_changed.Run(this,
799 LOAD_STATE_RESOLVING_PROXY_FOR_URL);
802 if (blocking_dns_) {
803 event_.Signal();
804 return;
807 if (!blocking_dns_ && !pending_dns_completed_synchronously_) {
808 // Restart. This time it should make more progress due to having
809 // cached items.
810 worker_task_runner()->PostTask(FROM_HERE,
811 base::Bind(&Job::ExecuteNonBlocking, this));
815 void Job::ScheduleRestartWithBlockingDns() {
816 CheckIsOnWorkerThread();
818 DCHECK(!should_restart_with_blocking_dns_);
819 DCHECK(!abandoned_);
820 DCHECK(!blocking_dns_);
822 abandoned_ = true;
824 // The restart will happen after ExecuteNonBlocking() finishes.
825 should_restart_with_blocking_dns_ = true;
828 bool Job::GetDnsFromLocalCache(const std::string& host,
829 ResolveDnsOperation op,
830 std::string* output,
831 bool* return_value) {
832 CheckIsOnWorkerThread();
834 DnsCache::const_iterator it = dns_cache_.find(MakeDnsCacheKey(host, op));
835 if (it == dns_cache_.end())
836 return false;
838 *output = it->second;
839 *return_value = !it->second.empty();
840 return true;
843 void Job::SaveDnsToLocalCache(const std::string& host,
844 ResolveDnsOperation op,
845 int net_error,
846 const AddressList& addresses) {
847 CheckIsOnOriginThread();
849 // Serialize the result into a string to save to the cache.
850 std::string cache_value;
851 if (net_error != OK) {
852 cache_value = std::string();
853 } else if (op == DNS_RESOLVE || op == MY_IP_ADDRESS) {
854 // dnsResolve() and myIpAddress() are expected to return a single IP
855 // address.
856 cache_value = addresses.front().ToStringWithoutPort();
857 } else {
858 // The *Ex versions are expected to return a semi-colon separated list.
859 for (AddressList::const_iterator iter = addresses.begin();
860 iter != addresses.end(); ++iter) {
861 if (!cache_value.empty())
862 cache_value += ";";
863 cache_value += iter->ToStringWithoutPort();
867 dns_cache_[MakeDnsCacheKey(host, op)] = cache_value;
870 // static
871 HostResolver::RequestInfo Job::MakeDnsRequestInfo(const std::string& host,
872 ResolveDnsOperation op) {
873 HostPortPair host_port = HostPortPair(host, 80);
874 if (op == MY_IP_ADDRESS || op == MY_IP_ADDRESS_EX) {
875 host_port.set_host(GetHostName());
878 HostResolver::RequestInfo info(host_port);
879 // Flag myIpAddress requests.
880 if (op == MY_IP_ADDRESS || op == MY_IP_ADDRESS_EX) {
881 // TODO: Provide a RequestInfo construction mechanism that does not
882 // require a hostname and sets is_my_ip_address to true instead of this.
883 info.set_is_my_ip_address(true);
885 // The non-ex flavors are limited to IPv4 results.
886 if (op == MY_IP_ADDRESS || op == DNS_RESOLVE) {
887 info.set_address_family(ADDRESS_FAMILY_IPV4);
890 return info;
893 std::string Job::MakeDnsCacheKey(const std::string& host,
894 ResolveDnsOperation op) {
895 return base::StringPrintf("%d:%s", op, host.c_str());
898 void Job::HandleAlertOrError(bool is_alert,
899 int line_number,
900 const base::string16& message) {
901 CheckIsOnWorkerThread();
903 if (cancelled_.IsSet())
904 return;
906 if (blocking_dns_) {
907 // In blocking DNS mode the events can be dispatched immediately.
908 DispatchAlertOrError(is_alert, line_number, message);
909 return;
912 // Otherwise in nonblocking mode, buffer all the messages until
913 // the end.
915 if (abandoned_)
916 return;
918 alerts_and_errors_byte_cost_ += sizeof(AlertOrError) + message.size() * 2;
920 // If there have been lots of messages, enqueing could be expensive on
921 // memory. Consider a script which does megabytes worth of alerts().
922 // Avoid this by falling back to blocking mode.
923 if (alerts_and_errors_byte_cost_ > kMaxAlertsAndErrorsBytes) {
924 ScheduleRestartWithBlockingDns();
925 return;
928 AlertOrError entry = {is_alert, line_number, message};
929 alerts_and_errors_.push_back(entry);
932 void Job::DispatchBufferedAlertsAndErrors() {
933 CheckIsOnWorkerThread();
934 DCHECK(!blocking_dns_);
935 DCHECK(!abandoned_);
937 for (size_t i = 0; i < alerts_and_errors_.size(); ++i) {
938 const AlertOrError& x = alerts_and_errors_[i];
939 DispatchAlertOrError(x.is_alert, x.line_number, x.message);
943 void Job::DispatchAlertOrError(bool is_alert,
944 int line_number,
945 const base::string16& message) {
946 CheckIsOnWorkerThread();
948 // Note that the handling of cancellation is racy with regard to
949 // alerts/errors. The request might get cancelled shortly after this
950 // check! (There is no lock being held to guarantee otherwise).
952 // If this happens, then some information will get written to the NetLog
953 // needlessly, however the NetLog will still be alive so it shouldn't cause
954 // problems.
955 if (cancelled_.IsSet())
956 return;
958 if (is_alert) {
959 // -------------------
960 // alert
961 // -------------------
962 VLOG(1) << "PAC-alert: " << message;
964 // Send to the NetLog.
965 LogEventToCurrentRequestAndGlobally(
966 NetLog::TYPE_PAC_JAVASCRIPT_ALERT,
967 NetLog::StringCallback("message", &message));
968 } else {
969 // -------------------
970 // error
971 // -------------------
972 if (line_number == -1)
973 VLOG(1) << "PAC-error: " << message;
974 else
975 VLOG(1) << "PAC-error: " << "line: " << line_number << ": " << message;
977 // Send the error to the NetLog.
978 LogEventToCurrentRequestAndGlobally(
979 NetLog::TYPE_PAC_JAVASCRIPT_ERROR,
980 base::Bind(&NetLogErrorCallback, line_number, &message));
982 if (error_observer())
983 error_observer()->OnPACScriptError(line_number, message);
987 void Job::LogEventToCurrentRequestAndGlobally(
988 NetLog::EventType type,
989 const NetLog::ParametersCallback& parameters_callback) {
990 CheckIsOnWorkerThread();
991 bound_net_log_.AddEvent(type, parameters_callback);
993 // Emit to the global NetLog event stream.
994 if (net_log())
995 net_log()->AddGlobalEntry(type, parameters_callback);
998 ProxyResolverV8Tracing::ProxyResolverV8Tracing(
999 scoped_ptr<ProxyResolverErrorObserver> error_observer,
1000 scoped_ptr<base::Thread> thread,
1001 scoped_ptr<ProxyResolverV8> resolver,
1002 scoped_ptr<Job::Params> job_params)
1003 : thread_(thread.Pass()),
1004 v8_resolver_(resolver.Pass()),
1005 error_observer_(error_observer.Pass()),
1006 job_params_(job_params.Pass()),
1007 num_outstanding_callbacks_(0) {
1008 job_params_->num_outstanding_callbacks = &num_outstanding_callbacks_;
1011 ProxyResolverV8Tracing::~ProxyResolverV8Tracing() {
1012 // Note, all requests should have been cancelled.
1013 CHECK_EQ(0, num_outstanding_callbacks_);
1015 // Join the worker thread. See http://crbug.com/69710.
1016 base::ThreadRestrictions::ScopedAllowIO allow_io;
1017 thread_.reset();
1020 int ProxyResolverV8Tracing::GetProxyForURL(const GURL& url,
1021 ProxyInfo* results,
1022 const CompletionCallback& callback,
1023 RequestHandle* request,
1024 const BoundNetLog& net_log) {
1025 DCHECK(CalledOnValidThread());
1026 DCHECK(!callback.is_null());
1028 scoped_refptr<Job> job = new Job(job_params_.get());
1030 if (request)
1031 *request = job.get();
1033 job->StartGetProxyForURL(url, results, net_log, callback);
1034 return ERR_IO_PENDING;
1037 void ProxyResolverV8Tracing::CancelRequest(RequestHandle request) {
1038 Job* job = reinterpret_cast<Job*>(request);
1039 job->Cancel();
1042 LoadState ProxyResolverV8Tracing::GetLoadState(RequestHandle request) const {
1043 Job* job = reinterpret_cast<Job*>(request);
1044 return job->GetLoadState();
1047 } // namespace
1049 class ProxyResolverFactoryV8Tracing::CreateJob
1050 : public ProxyResolverFactory::Request {
1051 public:
1052 CreateJob(ProxyResolverFactoryV8Tracing* factory,
1053 HostResolver* host_resolver,
1054 scoped_ptr<ProxyResolverErrorObserver> error_observer,
1055 NetLog* net_log,
1056 const ProxyResolver::LoadStateChangedCallback&
1057 load_state_changed_callback,
1058 const scoped_refptr<ProxyResolverScriptData>& pac_script,
1059 scoped_ptr<ProxyResolver>* resolver_out,
1060 const CompletionCallback& callback)
1061 : factory_(factory),
1062 thread_(new base::Thread("Proxy Resolver")),
1063 error_observer_(error_observer.Pass()),
1064 resolver_out_(resolver_out),
1065 callback_(callback),
1066 num_outstanding_callbacks_(0) {
1067 // Start up the thread.
1068 base::Thread::Options options;
1069 options.timer_slack = base::TIMER_SLACK_MAXIMUM;
1070 CHECK(thread_->StartWithOptions(options));
1071 job_params_.reset(new Job::Params(
1072 thread_->task_runner(), host_resolver, error_observer_.get(), net_log,
1073 load_state_changed_callback, &num_outstanding_callbacks_));
1074 create_resolver_job_ = new Job(job_params_.get());
1075 create_resolver_job_->StartCreateV8Resolver(
1076 pac_script, &v8_resolver_,
1077 base::Bind(
1078 &ProxyResolverFactoryV8Tracing::CreateJob::OnV8ResolverCreated,
1079 base::Unretained(this)));
1082 ~CreateJob() override {
1083 if (factory_) {
1084 factory_->RemoveJob(this);
1085 DCHECK(create_resolver_job_);
1086 create_resolver_job_->Cancel();
1087 StopWorkerThread();
1089 DCHECK_EQ(0, num_outstanding_callbacks_);
1092 void FactoryDestroyed() {
1093 factory_ = nullptr;
1094 create_resolver_job_->Cancel();
1095 create_resolver_job_ = nullptr;
1096 StopWorkerThread();
1099 private:
1100 void OnV8ResolverCreated(int error) {
1101 DCHECK(factory_);
1102 if (error == OK) {
1103 job_params_->v8_resolver = v8_resolver_.get();
1104 resolver_out_->reset(
1105 new ProxyResolverV8Tracing(error_observer_.Pass(), thread_.Pass(),
1106 v8_resolver_.Pass(), job_params_.Pass()));
1107 } else {
1108 StopWorkerThread();
1111 factory_->RemoveJob(this);
1112 factory_ = nullptr;
1113 create_resolver_job_ = nullptr;
1114 callback_.Run(error);
1117 void StopWorkerThread() {
1118 // Join the worker thread. See http://crbug.com/69710.
1119 base::ThreadRestrictions::ScopedAllowIO allow_io;
1120 thread_.reset();
1123 ProxyResolverFactoryV8Tracing* factory_;
1124 scoped_ptr<base::Thread> thread_;
1125 scoped_ptr<ProxyResolverErrorObserver> error_observer_;
1126 scoped_ptr<Job::Params> job_params_;
1127 scoped_refptr<Job> create_resolver_job_;
1128 scoped_ptr<ProxyResolverV8> v8_resolver_;
1129 scoped_ptr<ProxyResolver>* resolver_out_;
1130 const CompletionCallback callback_;
1131 int num_outstanding_callbacks_;
1133 DISALLOW_COPY_AND_ASSIGN(CreateJob);
1136 ProxyResolverFactoryV8Tracing::ProxyResolverFactoryV8Tracing(
1137 HostResolver* host_resolver,
1138 NetLog* net_log,
1139 const ProxyResolver::LoadStateChangedCallback& callback,
1140 const base::Callback<scoped_ptr<ProxyResolverErrorObserver>()>&
1141 error_observer_factory)
1142 : ProxyResolverFactory(true),
1143 host_resolver_(host_resolver),
1144 net_log_(net_log),
1145 load_state_changed_callback_(callback),
1146 error_observer_factory_(error_observer_factory) {
1149 ProxyResolverFactoryV8Tracing::~ProxyResolverFactoryV8Tracing() {
1150 for (auto job : jobs_) {
1151 job->FactoryDestroyed();
1155 // ProxyResolverFactory override.
1156 int ProxyResolverFactoryV8Tracing::CreateProxyResolver(
1157 const scoped_refptr<ProxyResolverScriptData>& pac_script,
1158 scoped_ptr<ProxyResolver>* resolver,
1159 const CompletionCallback& callback,
1160 scoped_ptr<Request>* request) {
1161 scoped_ptr<CreateJob> job(new CreateJob(
1162 this, host_resolver_,
1163 error_observer_factory_.is_null() ? nullptr
1164 : error_observer_factory_.Run(),
1165 net_log_, load_state_changed_callback_, pac_script, resolver, callback));
1166 jobs_.insert(job.get());
1167 *request = job.Pass();
1168 return ERR_IO_PENDING;
1171 void ProxyResolverFactoryV8Tracing::RemoveJob(
1172 ProxyResolverFactoryV8Tracing::CreateJob* job) {
1173 size_t erased = jobs_.erase(job);
1174 DCHECK_EQ(1u, erased);
1177 } // namespace net