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.
40namespace net {
41
42namespace {
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.
48const 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).
54const size_t kMaxAlertsAndErrorsBytes = 2048;
55
56// Returns event parameters for a PAC error message (line number + message).
57base::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.
82class 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
295ProxyResolverV8Tracing::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
304void 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
318void 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
332void 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
367LoadState 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
376ProxyResolverV8Tracing::Job::~Job() {
377  DCHECK(!pending_dns_);
378  DCHECK(callback_.is_null());
379}
380
381void ProxyResolverV8Tracing::Job::CheckIsOnWorkerThread() const {
382  DCHECK_EQ(base::MessageLoop::current(), parent_->thread_->message_loop());
383}
384
385void ProxyResolverV8Tracing::Job::CheckIsOnOriginThread() const {
386  DCHECK(origin_loop_->BelongsToCurrentThread());
387}
388
389void ProxyResolverV8Tracing::Job::SetCallback(
390    const CompletionCallback& callback) {
391  CheckIsOnOriginThread();
392  DCHECK(callback_.is_null());
393  parent_->num_outstanding_callbacks_++;
394  callback_ = callback;
395}
396
397void 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
408ProxyResolverV8* ProxyResolverV8Tracing::Job::v8_resolver() {
409  return parent_->v8_resolver_.get();
410}
411
412base::MessageLoop* ProxyResolverV8Tracing::Job::worker_loop() {
413  return parent_->thread_->message_loop();
414}
415
416HostResolver* ProxyResolverV8Tracing::Job::host_resolver() {
417  return parent_->host_resolver_;
418}
419
420ProxyResolverErrorObserver* ProxyResolverV8Tracing::Job::error_observer() {
421  return parent_->error_observer_.get();
422}
423
424NetLog* ProxyResolverV8Tracing::Job::net_log() {
425  return parent_->net_log_;
426}
427
428void ProxyResolverV8Tracing::Job::NotifyCaller(int result) {
429  CheckIsOnWorkerThread();
430
431  origin_loop_->PostTask(
432      FROM_HERE,
433      base::Bind(&Job::NotifyCallerOnOriginLoop, this, result));
434}
435
436void 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
463void 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
478void ProxyResolverV8Tracing::Job::ExecuteBlocking() {
479  CheckIsOnWorkerThread();
480  DCHECK(blocking_dns_);
481
482  if (cancelled_.IsSet())
483    return;
484
485  NotifyCaller(ExecuteProxyResolver());
486}
487
488void 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
519int 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
548bool 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
567void ProxyResolverV8Tracing::Job::Alert(const base::string16& message) {
568  HandleAlertOrError(true, -1, message);
569}
570
571void ProxyResolverV8Tracing::Job::OnError(int line_number,
572                                          const base::string16& error) {
573  HandleAlertOrError(false, line_number, error);
574}
575
576bool 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
602bool 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
655bool 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
677void 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
719void 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
742void 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
755bool 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
771void 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
800HostResolver::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
822std::string ProxyResolverV8Tracing::Job::MakeDnsCacheKey(
823    const std::string& host, ResolveDnsOperation op) {
824  return base::StringPrintf("%d:%s", op, host.c_str());
825}
826
827void 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
862void 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
873void 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
916void 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
927ProxyResolverV8Tracing::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
946ProxyResolverV8Tracing::~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
958int 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
976void ProxyResolverV8Tracing::CancelRequest(RequestHandle request) {
977  Job* job = reinterpret_cast<Job*>(request);
978  job->Cancel();
979}
980
981LoadState ProxyResolverV8Tracing::GetLoadState(RequestHandle request) const {
982  Job* job = reinterpret_cast<Job*>(request);
983  return job->GetLoadState();
984}
985
986void ProxyResolverV8Tracing::CancelSetPacScript() {
987  DCHECK(set_pac_script_job_.get());
988  set_pac_script_job_->Cancel();
989  set_pac_script_job_ = NULL;
990}
991
992int 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