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