910e62b5创建于 1月15日历史提交
// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "services/proxy_resolver/proxy_resolver_v8_tracing.h"

#include <map>
#include <memory>
#include <set>
#include <string>
#include <utility>
#include <vector>

#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/strings/stringprintf.h"
#include "base/synchronization/atomic_flag.h"
#include "base/synchronization/waitable_event.h"
#include "base/task/single_thread_task_runner.h"
#include "base/threading/thread.h"
#include "base/threading/thread_checker.h"
#include "base/threading/thread_restrictions.h"
#include "base/trace_event/trace_event.h"
#include "net/base/ip_address.h"
#include "net/base/net_errors.h"
#include "net/base/network_anonymization_key.h"
#include "net/base/network_interfaces.h"
#include "net/base/trace_constants.h"
#include "net/log/net_log_with_source.h"
#include "net/proxy_resolution/proxy_info.h"
#include "net/proxy_resolution/proxy_resolve_dns_operation.h"
#include "net/proxy_resolution/proxy_resolver_error_observer.h"
#include "services/proxy_resolver/proxy_host_resolver.h"
#include "services/proxy_resolver/proxy_resolver_v8.h"

// The intent of this class is explained in the design document:
// https://docs.google.com/a/chromium.org/document/d/16Ij5OcVnR3s0MH4Z5XkhI9VTPoMJdaBn9rKreAmGOdE/edit
//
// In a nutshell, PAC scripts are Javascript programs and may depend on
// network I/O, by calling functions like dnsResolve().
//
// This is problematic since functions such as dnsResolve() will block the
// Javascript execution until the DNS result is availble, thereby stalling the
// PAC thread, which hurts the ability to process parallel proxy resolves.
// An obvious solution is to simply start more PAC threads, however this scales
// poorly, which hurts the ability to process parallel proxy resolves.
//
// The solution in ProxyResolverV8Tracing is to model PAC scripts as being
// deterministic, and depending only on the inputted URL. When the script
// issues a dnsResolve() for a yet unresolved hostname, the Javascript
// execution is "aborted", and then re-started once the DNS result is
// known.
namespace proxy_resolver {

class ScopedAllowThreadJoinForProxyResolverV8Tracing
    : public base::ScopedAllowBaseSyncPrimitivesOutsideBlockingScope {};

namespace {

// Upper bound on how many *unique* DNS resolves a PAC script is allowed
// to make. This is a failsafe both for scripts that do a ridiculous
// number of DNS resolves, as well as scripts which are misbehaving
// under the tracing optimization. It is not expected to hit this normally.
const size_t kMaxUniqueResolveDnsPerExec = 20;

// Approximate number of bytes to use for buffering alerts() and errors.
// This is a failsafe in case repeated executions of the script causes
// too much memory bloat. It is not expected for well behaved scripts to
// hit this. (In fact normal scripts should not even have alerts() or errors).
const size_t kMaxAlertsAndErrorsBytes = 2048;

// The Job class is responsible for executing GetProxyForURL() and
// creating ProxyResolverV8 instances, since both of these operations share
// similar code.
//
// The DNS for these operations can operate in either blocking or
// non-blocking mode. Blocking mode is used as a fallback when the PAC script
// seems to be misbehaving under the tracing optimization.
//
// Note that this class runs on both the origin thread and a worker
// thread. Most methods are expected to be used exclusively on one thread
// or the other.
//
// The lifetime of Jobs does not exceed that of the ProxyResolverV8TracingImpl
// that spawned it. Destruction might happen on either the origin thread or the
// worker thread.
class Job : public base::RefCountedThreadSafe<Job>,
            public ProxyResolverV8::JSBindings {
 public:
  struct Params {
    Params(
        const scoped_refptr<base::SingleThreadTaskRunner>& worker_task_runner,
        int* num_outstanding_callbacks)
        : v8_resolver(nullptr),
          worker_task_runner(worker_task_runner),
          num_outstanding_callbacks(num_outstanding_callbacks) {}

    raw_ptr<ProxyResolverV8, DanglingUntriaged> v8_resolver;
    scoped_refptr<base::SingleThreadTaskRunner> worker_task_runner;
    raw_ptr<int> num_outstanding_callbacks;
  };
  // |params| is non-owned. It contains the parameters for this Job, and must
  // outlive it.
  Job(const Params* params,
      std::unique_ptr<ProxyResolverV8Tracing::Bindings> bindings);

  // Called from origin thread.
  void StartCreateV8Resolver(const scoped_refptr<net::PacFileData>& script_data,
                             std::unique_ptr<ProxyResolverV8>* resolver,
                             net::CompletionOnceCallback callback);

  // Called from origin thread.
  void StartGetProxyForURL(
      const GURL& url,
      const net::NetworkAnonymizationKey& network_anonymization_key,
      net::ProxyInfo* results,
      net::CompletionOnceCallback callback);

  // Called from origin thread.
  void Cancel();

  // Called from origin thread.
  net::LoadState GetLoadState() const;

 private:
  typedef std::map<std::string, std::string> DnsCache;
  friend class base::RefCountedThreadSafe<Job>;

  enum Operation {
    CREATE_V8_RESOLVER,
    GET_PROXY_FOR_URL,
  };

  struct AlertOrError {
    bool is_alert;
    int line_number;
    std::u16string message;
  };

  ~Job() override;

  void CheckIsOnWorkerThread() const;
  void CheckIsOnOriginThread() const;

  void SetCallback(net::CompletionOnceCallback callback);
  void ReleaseCallback();

  ProxyResolverV8* v8_resolver();
  const scoped_refptr<base::SingleThreadTaskRunner>& worker_task_runner();
  ProxyHostResolver* host_resolver();

  // Invokes the user's callback.
  void NotifyCaller(int result);
  void NotifyCallerOnOriginLoop(int result);

  void Start(Operation op,
             bool blocking_dns,
             net::CompletionOnceCallback callback);

  void ExecuteBlocking();
  void ExecuteNonBlocking();
  int ExecuteProxyResolver();

  // Implementation of ProxyResolverv8::JSBindings
  bool ResolveDns(const std::string& host,
                  net::ProxyResolveDnsOperation op,
                  std::string* output,
                  bool* terminate) override;
  void Alert(const std::u16string& message) override;
  void OnError(int line_number, const std::u16string& error) override;

  bool ResolveDnsBlocking(const std::string& host,
                          net::ProxyResolveDnsOperation op,
                          std::string* output);

  bool ResolveDnsNonBlocking(const std::string& host,
                             net::ProxyResolveDnsOperation op,
                             std::string* output,
                             bool* terminate);

  [[nodiscard]] bool PostDnsOperationAndWait(const std::string& host,
                                             net::ProxyResolveDnsOperation op,
                                             bool* completed_synchronously);

  void DoDnsOperation();
  void OnDnsOperationComplete(int result);

  void ScheduleRestartWithBlockingDns();

  bool GetDnsFromLocalCache(const std::string& host,
                            net::ProxyResolveDnsOperation op,
                            std::string* output,
                            bool* return_value);

  void SaveDnsToLocalCache(const std::string& host,
                           net::ProxyResolveDnsOperation op,
                           int net_error,
                           const std::vector<net::IPAddress>& addresses);

  // Makes a key for looking up |host, op| in |dns_cache_|. Strings are used for
  // convenience, to avoid defining custom comparators.
  static std::string MakeDnsCacheKey(const std::string& host,
                                     net::ProxyResolveDnsOperation op);

  void HandleAlertOrError(bool is_alert,
                          int line_number,
                          const std::u16string& message);
  void DispatchBufferedAlertsAndErrors();
  void DispatchAlertOrErrorOnOriginThread(bool is_alert,
                                          int line_number,
                                          const std::u16string& message);

  // The thread which called into ProxyResolverV8TracingImpl, and on which the
  // completion callback is expected to run.
  scoped_refptr<base::SingleThreadTaskRunner> origin_runner_;

  // The Parameters for this Job.
  // Initialized on origin thread and then accessed from both threads.
  const raw_ptr<const Params, AcrossTasksDanglingUntriaged> params_;

  std::unique_ptr<ProxyResolverV8Tracing::Bindings> bindings_;

  // The callback to run (on the origin thread) when the Job finishes.
  // Should only be accessed from origin thread.
  net::CompletionOnceCallback callback_;

  // Flag to indicate whether the request has been cancelled.
  base::AtomicFlag cancelled_;

  // The operation that this Job is running.
  // Initialized on origin thread and then accessed from both threads.
  Operation operation_;

  // The DNS mode for this Job.
  // Initialized on origin thread, mutated on worker thread, and accessed
  // by both the origin thread and worker thread.
  bool blocking_dns_;

  // Used to block the worker thread on a DNS operation taking place on the
  // origin thread.
  base::WaitableEvent event_;

  // Map of DNS operations completed so far. Written into on the origin thread
  // and read on the worker thread.
  DnsCache dns_cache_;

  // The job holds a reference to itself to ensure that it remains alive until
  // either completion or cancellation.
  scoped_refptr<Job> owned_self_reference_;

  // -------------------------------------------------------
  // State specific to CREATE_V8_RESOLVER.
  // -------------------------------------------------------

  scoped_refptr<net::PacFileData> script_data_;
  raw_ptr<std::unique_ptr<ProxyResolverV8>, AcrossTasksDanglingUntriaged>
      resolver_out_;

  // -------------------------------------------------------
  // State specific to GET_PROXY_FOR_URL.
  // -------------------------------------------------------

  raw_ptr<net::ProxyInfo, AcrossTasksDanglingUntriaged>
      user_results_;  // Owned by caller, lives on origin thread.
  GURL url_;
  net::NetworkAnonymizationKey network_anonymization_key_;
  net::ProxyInfo results_;

  // ---------------------------------------------------------------------------
  // State for ExecuteNonBlocking()
  // ---------------------------------------------------------------------------
  // These variables are used exclusively on the worker thread and are only
  // meaningful when executing inside of ExecuteNonBlocking().

  // Whether this execution was abandoned due to a missing DNS dependency.
  bool abandoned_;

  // Number of calls made to ResolveDns() by this execution.
  int num_dns_;

  // Sequence of calls made to Alert() or OnError() by this execution.
  std::vector<AlertOrError> alerts_and_errors_;
  size_t alerts_and_errors_byte_cost_;  // Approximate byte cost of the above.

  // Number of calls made to ResolveDns() by the PREVIOUS execution.
  int last_num_dns_;

  // Whether the current execution needs to be restarted in blocking mode.
  bool should_restart_with_blocking_dns_;

  // ---------------------------------------------------------------------------
  // State for pending DNS request.
  // ---------------------------------------------------------------------------

  // Handle to the outstanding request in the ProxyHostResolver, or NULL.
  // This is mutated and used on the origin thread, however it may be read by
  // the worker thread for some DCHECKS().
  std::unique_ptr<ProxyHostResolver::Request> pending_dns_;

  // Indicates if the outstanding DNS request completed synchronously. Written
  // on the origin thread, and read by the worker thread.
  bool pending_dns_completed_synchronously_;

  // These are the inputs to DoDnsOperation(). Written on the worker thread,
  // read by the origin thread.
  std::string pending_dns_host_;
  net::ProxyResolveDnsOperation pending_dns_op_;
};

class ProxyResolverV8TracingImpl : public ProxyResolverV8Tracing {
 public:
  ProxyResolverV8TracingImpl(std::unique_ptr<base::Thread> thread,
                             std::unique_ptr<ProxyResolverV8> resolver,
                             std::unique_ptr<Job::Params> job_params);

  ProxyResolverV8TracingImpl(const ProxyResolverV8TracingImpl&) = delete;
  ProxyResolverV8TracingImpl& operator=(const ProxyResolverV8TracingImpl&) =
      delete;

  ~ProxyResolverV8TracingImpl() override;

  // ProxyResolverV8Tracing overrides.
  void GetProxyForURL(
      const GURL& url,
      const net::NetworkAnonymizationKey& network_anonymization_key,
      net::ProxyInfo* results,
      net::CompletionOnceCallback callback,
      std::unique_ptr<net::ProxyResolver::Request>* request,
      std::unique_ptr<Bindings> bindings) override;

  class RequestImpl : public net::ProxyResolver::Request {
   public:
    explicit RequestImpl(scoped_refptr<Job> job);
    ~RequestImpl() override;
    net::LoadState GetLoadState() override;

   private:
    scoped_refptr<Job> job_;
  };

 private:
  // The worker thread on which the ProxyResolverV8 will be run.
  std::unique_ptr<base::Thread> thread_;
  std::unique_ptr<ProxyResolverV8> v8_resolver_;

  std::unique_ptr<Job::Params> job_params_;

  // The number of outstanding (non-cancelled) jobs.
  int num_outstanding_callbacks_;

  THREAD_CHECKER(thread_checker_);
};

Job::Job(const Job::Params* params,
         std::unique_ptr<ProxyResolverV8Tracing::Bindings> bindings)
    : origin_runner_(base::SingleThreadTaskRunner::GetCurrentDefault()),
      params_(params),
      bindings_(std::move(bindings)),
      event_(base::WaitableEvent::ResetPolicy::MANUAL,
             base::WaitableEvent::InitialState::NOT_SIGNALED),
      last_num_dns_(0) {
  CheckIsOnOriginThread();
}

void Job::StartCreateV8Resolver(
    const scoped_refptr<net::PacFileData>& script_data,
    std::unique_ptr<ProxyResolverV8>* resolver,
    net::CompletionOnceCallback callback) {
  CheckIsOnOriginThread();

  // |network_anonymization_key_| is not populated, so any resolutions done
  // while loading the PAC script will be done with an empty
  // net::NetworkAnonymizationKey. Since a PAC script is considered trusted, and
  // is loaded once and then handles requests made by multiple
  // NetworkAnonymizationKeys, using an empty key makes sense.
  DCHECK(network_anonymization_key_.IsEmpty());

  resolver_out_ = resolver;
  script_data_ = script_data;

  // Script initialization uses blocking DNS since there isn't any
  // advantage to using non-blocking mode here. That is because the
  // parent ConfiguredProxyResolutionService can't submit any ProxyResolve
  // requests until initialization has completed successfully!
  Start(CREATE_V8_RESOLVER, true /*blocking*/, std::move(callback));
}

void Job::StartGetProxyForURL(
    const GURL& url,
    const net::NetworkAnonymizationKey& network_anonymization_key,
    net::ProxyInfo* results,
    net::CompletionOnceCallback callback) {
  CheckIsOnOriginThread();

  url_ = url;
  network_anonymization_key_ = network_anonymization_key;
  user_results_ = results;

  Start(GET_PROXY_FOR_URL, false /*non-blocking*/, std::move(callback));
}

void Job::Cancel() {
  CheckIsOnOriginThread();

  // There are several possibilities to consider for cancellation:
  // (a) The job has been posted to the worker thread, however script execution
  //     has not yet started.
  // (b) The script is executing on the worker thread.
  // (c) The script is executing on the worker thread, however is blocked inside
  //     of dnsResolve() waiting for a response from the origin thread.
  // (d) Nothing is running on the worker thread, however the host resolver has
  //     a pending DNS request which upon completion will restart the script
  //     execution.
  // (e) The worker thread has a pending task to restart execution, which was
  //     posted after the DNS dependency was resolved and saved to local cache.
  // (f) The script execution completed entirely, and posted a task to the
  //     origin thread to notify the caller.
  // (g) The job is already completed.
  //
  // |cancelled_| is read on both the origin thread and worker thread. The
  // code that runs on the worker thread is littered with checks on
  // |cancelled_| to break out early.

  // If the job already completed, there is nothing to be cancelled.
  if (callback_.is_null())
    return;

  cancelled_.Set();

  ReleaseCallback();

  // Note we only mutate |pending_dns_| if it is non-null. If it is null, the
  // worker thread may be about to request a new DNS resolution. This avoids a
  // race condition with the DCHECK in PostDnsOperationAndWait().
  // See https://crbug.com/699562.
  if (pending_dns_)
    pending_dns_.reset();

  // The worker thread might be blocked waiting for DNS.
  event_.Signal();

  bindings_.reset();
  owned_self_reference_ = nullptr;
}

net::LoadState Job::GetLoadState() const {
  CheckIsOnOriginThread();

  if (pending_dns_)
    return net::LOAD_STATE_RESOLVING_HOST_IN_PAC_FILE;

  return net::LOAD_STATE_RESOLVING_PROXY_FOR_URL;
}

Job::~Job() {
  DCHECK(!pending_dns_);
  DCHECK(callback_.is_null());
  DCHECK(!bindings_);
}

void Job::CheckIsOnWorkerThread() const {
  DCHECK(params_->worker_task_runner->BelongsToCurrentThread());
}

void Job::CheckIsOnOriginThread() const {
  DCHECK(origin_runner_->BelongsToCurrentThread());
}

void Job::SetCallback(net::CompletionOnceCallback callback) {
  CheckIsOnOriginThread();
  DCHECK(callback_.is_null());
  (*params_->num_outstanding_callbacks)++;
  callback_ = std::move(callback);
}

void Job::ReleaseCallback() {
  CheckIsOnOriginThread();
  CHECK_GT(*params_->num_outstanding_callbacks, 0);
  (*params_->num_outstanding_callbacks)--;
  callback_.Reset();

  // For good measure, clear this other user-owned pointer.
  user_results_ = nullptr;
}

ProxyResolverV8* Job::v8_resolver() {
  return params_->v8_resolver;
}

const scoped_refptr<base::SingleThreadTaskRunner>& Job::worker_task_runner() {
  return params_->worker_task_runner;
}

ProxyHostResolver* Job::host_resolver() {
  return bindings_->GetHostResolver();
}

void Job::NotifyCaller(int result) {
  CheckIsOnWorkerThread();

  origin_runner_->PostTask(
      FROM_HERE, base::BindOnce(&Job::NotifyCallerOnOriginLoop, this, result));
}

void Job::NotifyCallerOnOriginLoop(int result) {
  CheckIsOnOriginThread();

  if (cancelled_.IsSet())
    return;

  DispatchBufferedAlertsAndErrors();

  // This isn't the ordinary execution flow, however it is exercised by
  // unit-tests.
  if (cancelled_.IsSet())
    return;

  DCHECK(!callback_.is_null());
  DCHECK(!pending_dns_);

  if (operation_ == GET_PROXY_FOR_URL) {
    *user_results_ = results_;
  }

  net::CompletionOnceCallback callback = std::move(callback_);
  ReleaseCallback();
  std::move(callback).Run(result);

  bindings_.reset();
  owned_self_reference_ = nullptr;
}

void Job::Start(Operation op,
                bool blocking_dns,
                net::CompletionOnceCallback callback) {
  CheckIsOnOriginThread();

  operation_ = op;
  blocking_dns_ = blocking_dns;
  SetCallback(std::move(callback));

  owned_self_reference_ = this;

  worker_task_runner()->PostTask(
      FROM_HERE, blocking_dns_
                     ? base::BindOnce(&Job::ExecuteBlocking, this)
                     : base::BindOnce(&Job::ExecuteNonBlocking, this));
}

void Job::ExecuteBlocking() {
  CheckIsOnWorkerThread();
  DCHECK(blocking_dns_);

  if (cancelled_.IsSet())
    return;

  NotifyCaller(ExecuteProxyResolver());
}

void Job::ExecuteNonBlocking() {
  CheckIsOnWorkerThread();
  DCHECK(!blocking_dns_);

  if (cancelled_.IsSet())
    return;

  // Reset state for the current execution.
  abandoned_ = false;
  num_dns_ = 0;
  alerts_and_errors_.clear();
  alerts_and_errors_byte_cost_ = 0;
  should_restart_with_blocking_dns_ = false;

  int result = ExecuteProxyResolver();

  if (should_restart_with_blocking_dns_) {
    DCHECK(!blocking_dns_);
    DCHECK(abandoned_);
    blocking_dns_ = true;
    ExecuteBlocking();
    return;
  }

  if (abandoned_)
    return;

  NotifyCaller(result);
}

int Job::ExecuteProxyResolver() {
  TRACE_EVENT0(net::NetTracingCategory(), "Job::ExecuteProxyResolver");
  int result = net::ERR_UNEXPECTED;  // Initialized to silence warnings.

  switch (operation_) {
    case CREATE_V8_RESOLVER: {
      std::unique_ptr<ProxyResolverV8> resolver;
      result = ProxyResolverV8::Create(script_data_, this, &resolver);
      if (result == net::OK)
        *resolver_out_ = std::move(resolver);
      break;
    }
    case GET_PROXY_FOR_URL: {
      result = v8_resolver()->GetProxyForURL(
          url_,
          // Important: Do not write directly into |user_results_|, since if the
          // request were to be cancelled from the origin thread, must guarantee
          // that |user_results_| is not accessed anymore.
          &results_, this);
      break;
    }
  }

  return result;
}

bool Job::ResolveDns(const std::string& host,
                     net::ProxyResolveDnsOperation op,
                     std::string* output,
                     bool* terminate) {
  if (cancelled_.IsSet()) {
    *terminate = true;
    return false;
  }

  if ((op == net::ProxyResolveDnsOperation::DNS_RESOLVE ||
       op == net::ProxyResolveDnsOperation::DNS_RESOLVE_EX) &&
      host.empty()) {
    // a DNS resolve with an empty hostname is considered an error.
    return false;
  }

  return blocking_dns_ ? ResolveDnsBlocking(host, op, output)
                       : ResolveDnsNonBlocking(host, op, output, terminate);
}

void Job::Alert(const std::u16string& message) {
  HandleAlertOrError(true, -1, message);
}

void Job::OnError(int line_number, const std::u16string& error) {
  HandleAlertOrError(false, line_number, error);
}

bool Job::ResolveDnsBlocking(const std::string& host,
                             net::ProxyResolveDnsOperation op,
                             std::string* output) {
  CheckIsOnWorkerThread();

  // Check if the DNS result for this host has already been cached.
  bool rv;
  if (GetDnsFromLocalCache(host, op, output, &rv)) {
    // Yay, cache hit!
    return rv;
  }

  if (dns_cache_.size() >= kMaxUniqueResolveDnsPerExec) {
    // Safety net for scripts with unexpectedly many DNS calls.
    // We will continue running to completion, but will fail every
    // subsequent DNS request.
    return false;
  }

  if (!PostDnsOperationAndWait(host, op, nullptr))
    return false;  // Was cancelled.

  CHECK(GetDnsFromLocalCache(host, op, output, &rv));
  return rv;
}

bool Job::ResolveDnsNonBlocking(const std::string& host,
                                net::ProxyResolveDnsOperation op,
                                std::string* output,
                                bool* terminate) {
  CheckIsOnWorkerThread();

  if (abandoned_) {
    // If this execution was already abandoned can fail right away. Only 1 DNS
    // dependency will be traced at a time (for more predictable outcomes).
    return false;
  }

  num_dns_ += 1;

  // Check if the DNS result for this host has already been cached.
  bool rv;
  if (GetDnsFromLocalCache(host, op, output, &rv)) {
    // Yay, cache hit!
    return rv;
  }

  if (num_dns_ <= last_num_dns_) {
    // The sequence of DNS operations is different from last time!
    ScheduleRestartWithBlockingDns();
    *terminate = true;
    return false;
  }

  if (dns_cache_.size() >= kMaxUniqueResolveDnsPerExec) {
    // Safety net for scripts with unexpectedly many DNS calls.
    return false;
  }

  DCHECK(!should_restart_with_blocking_dns_);

  bool completed_synchronously;
  if (!PostDnsOperationAndWait(host, op, &completed_synchronously))
    return false;  // Was cancelled.

  if (completed_synchronously) {
    CHECK(GetDnsFromLocalCache(host, op, output, &rv));
    return rv;
  }

  // Otherwise if the result was not in the cache, then a DNS request has
  // been started. Abandon this invocation of FindProxyForURL(), it will be
  // restarted once the DNS request completes.
  abandoned_ = true;
  *terminate = true;
  last_num_dns_ = num_dns_;
  return false;
}

bool Job::PostDnsOperationAndWait(const std::string& host,
                                  net::ProxyResolveDnsOperation op,
                                  bool* completed_synchronously) {
  // Post the DNS request to the origin thread. It is safe to mutate
  // |pending_dns_host_| and |pending_dns_op_| because there cannot be another
  // DNS operation in progress or scheduled.
  DCHECK(!pending_dns_);
  pending_dns_host_ = host;
  pending_dns_op_ = op;
  origin_runner_->PostTask(FROM_HERE,
                           base::BindOnce(&Job::DoDnsOperation, this));

  event_.Wait();
  event_.Reset();

  if (cancelled_.IsSet())
    return false;

  if (completed_synchronously)
    *completed_synchronously = pending_dns_completed_synchronously_;

  return true;
}

void Job::DoDnsOperation() {
  CheckIsOnOriginThread();
  DCHECK(!pending_dns_);

  if (cancelled_.IsSet())
    return;

  bool is_my_ip_request =
      pending_dns_op_ == net::ProxyResolveDnsOperation::MY_IP_ADDRESS ||
      pending_dns_op_ == net::ProxyResolveDnsOperation::MY_IP_ADDRESS_EX;
  pending_dns_ = host_resolver()->CreateRequest(
      is_my_ip_request ? net::GetHostName() : pending_dns_host_,
      pending_dns_op_,
      is_my_ip_request ? net::NetworkAnonymizationKey()
                       : network_anonymization_key_);
  int result =
      pending_dns_->Start(base::BindOnce(&Job::OnDnsOperationComplete, this));

  pending_dns_completed_synchronously_ = result != net::ERR_IO_PENDING;

  // Check if the request was cancelled as a side-effect of calling into the
  // HostResolver. This isn't the ordinary execution flow, however it is
  // exercised by unit-tests.
  if (cancelled_.IsSet())
    return;

  if (pending_dns_completed_synchronously_) {
    OnDnsOperationComplete(result);
  }
  // Else OnDnsOperationComplete() will be called by host resolver on
  // completion.

  if (!blocking_dns_) {
    // The worker thread always blocks waiting to see if the result can be
    // serviced from cache before restarting.
    event_.Signal();
  }
}

void Job::OnDnsOperationComplete(int result) {
  CheckIsOnOriginThread();

  DCHECK(!cancelled_.IsSet());

  SaveDnsToLocalCache(pending_dns_host_, pending_dns_op_, result,
                      pending_dns_->GetResults());
  pending_dns_.reset();

  if (blocking_dns_) {
    event_.Signal();
    return;
  }

  if (!blocking_dns_ && !pending_dns_completed_synchronously_) {
    // Restart. This time it should make more progress due to having
    // cached items.
    worker_task_runner()->PostTask(
        FROM_HERE, base::BindOnce(&Job::ExecuteNonBlocking, this));
  }
}

void Job::ScheduleRestartWithBlockingDns() {
  CheckIsOnWorkerThread();

  DCHECK(!should_restart_with_blocking_dns_);
  DCHECK(!abandoned_);
  DCHECK(!blocking_dns_);

  abandoned_ = true;

  // The restart will happen after ExecuteNonBlocking() finishes.
  should_restart_with_blocking_dns_ = true;
}

bool Job::GetDnsFromLocalCache(const std::string& host,
                               net::ProxyResolveDnsOperation op,
                               std::string* output,
                               bool* return_value) {
  CheckIsOnWorkerThread();

  DnsCache::const_iterator it = dns_cache_.find(MakeDnsCacheKey(host, op));
  if (it == dns_cache_.end())
    return false;

  *output = it->second;
  *return_value = !it->second.empty();
  return true;
}

void Job::SaveDnsToLocalCache(const std::string& host,
                              net::ProxyResolveDnsOperation op,
                              int net_error,
                              const std::vector<net::IPAddress>& addresses) {
  CheckIsOnOriginThread();

  // Serialize the result into a string to save to the cache.
  std::string cache_value;
  if (net_error != net::OK) {
    cache_value = std::string();
  } else if (op == net::ProxyResolveDnsOperation::DNS_RESOLVE ||
             op == net::ProxyResolveDnsOperation::MY_IP_ADDRESS) {
    // dnsResolve() and myIpAddress() are expected to return a single IP
    // address.
    cache_value = addresses.front().ToString();
  } else {
    // The *Ex versions are expected to return a semi-colon separated list.
    for (auto iter = addresses.begin(); iter != addresses.end(); ++iter) {
      if (!cache_value.empty())
        cache_value += ";";
      cache_value += iter->ToString();
    }
  }

  dns_cache_[MakeDnsCacheKey(host, op)] = cache_value;
}

std::string Job::MakeDnsCacheKey(const std::string& host,
                                 net::ProxyResolveDnsOperation op) {
  return base::StringPrintf("%d:%s", static_cast<int>(op), host.c_str());
}

void Job::HandleAlertOrError(bool is_alert,
                             int line_number,
                             const std::u16string& message) {
  CheckIsOnWorkerThread();

  if (cancelled_.IsSet())
    return;

  if (blocking_dns_) {
    // In blocking DNS mode the events can be dispatched immediately.
    origin_runner_->PostTask(
        FROM_HERE, base::BindOnce(&Job::DispatchAlertOrErrorOnOriginThread,
                                  this, is_alert, line_number, message));
    return;
  }

  // Otherwise in nonblocking mode, buffer all the messages until
  // the end.

  if (abandoned_)
    return;

  alerts_and_errors_byte_cost_ += sizeof(AlertOrError) + message.size() * 2;

  // If there have been lots of messages, enqueing could be expensive on
  // memory. Consider a script which does megabytes worth of alerts().
  // Avoid this by falling back to blocking mode.
  if (alerts_and_errors_byte_cost_ > kMaxAlertsAndErrorsBytes) {
    alerts_and_errors_.clear();
    ScheduleRestartWithBlockingDns();
    return;
  }

  AlertOrError entry = {is_alert, line_number, message};
  alerts_and_errors_.push_back(entry);
}

void Job::DispatchBufferedAlertsAndErrors() {
  CheckIsOnOriginThread();
  for (size_t i = 0; i < alerts_and_errors_.size(); ++i) {
    const AlertOrError& x = alerts_and_errors_[i];
    DispatchAlertOrErrorOnOriginThread(x.is_alert, x.line_number, x.message);
  }
}

void Job::DispatchAlertOrErrorOnOriginThread(bool is_alert,
                                             int line_number,
                                             const std::u16string& message) {
  CheckIsOnOriginThread();

  if (cancelled_.IsSet())
    return;

  if (is_alert) {
    // -------------------
    // alert
    // -------------------
    VLOG(1) << "PAC-alert: " << message;

    bindings_->Alert(message);
  } else {
    // -------------------
    // error
    // -------------------
    if (line_number == -1)
      VLOG(1) << "PAC-error: " << message;
    else
      VLOG(1) << "PAC-error: "
              << "line: " << line_number << ": " << message;

    bindings_->OnError(line_number, message);
  }
}

ProxyResolverV8TracingImpl::ProxyResolverV8TracingImpl(
    std::unique_ptr<base::Thread> thread,
    std::unique_ptr<ProxyResolverV8> resolver,
    std::unique_ptr<Job::Params> job_params)
    : thread_(std::move(thread)),
      v8_resolver_(std::move(resolver)),
      job_params_(std::move(job_params)),
      num_outstanding_callbacks_(0) {
  job_params_->num_outstanding_callbacks = &num_outstanding_callbacks_;
}

ProxyResolverV8TracingImpl::~ProxyResolverV8TracingImpl() {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  // Note, all requests should have been cancelled.
  CHECK_EQ(0, num_outstanding_callbacks_);

  // Join the worker thread. See http://crbug.com/69710.
  ScopedAllowThreadJoinForProxyResolverV8Tracing allow_thread_join;
  thread_.reset();
}

ProxyResolverV8TracingImpl::RequestImpl::RequestImpl(scoped_refptr<Job> job)
    : job_(std::move(job)) {}

ProxyResolverV8TracingImpl::RequestImpl::~RequestImpl() {
  job_->Cancel();
}

net::LoadState ProxyResolverV8TracingImpl::RequestImpl::GetLoadState() {
  return job_->GetLoadState();
}

void ProxyResolverV8TracingImpl::GetProxyForURL(
    const GURL& url,
    const net::NetworkAnonymizationKey& network_anonymization_key,
    net::ProxyInfo* results,
    net::CompletionOnceCallback callback,
    std::unique_ptr<net::ProxyResolver::Request>* request,
    std::unique_ptr<Bindings> bindings) {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  DCHECK(!callback.is_null());

  scoped_refptr<Job> job = new Job(job_params_.get(), std::move(bindings));

  *request = std::make_unique<RequestImpl>(job);

  job->StartGetProxyForURL(url, network_anonymization_key, results,
                           std::move(callback));
}

class ProxyResolverV8TracingFactoryImpl : public ProxyResolverV8TracingFactory {
 public:
  ProxyResolverV8TracingFactoryImpl();

  ProxyResolverV8TracingFactoryImpl(const ProxyResolverV8TracingFactoryImpl&) =
      delete;
  ProxyResolverV8TracingFactoryImpl& operator=(
      const ProxyResolverV8TracingFactoryImpl&) = delete;

  ~ProxyResolverV8TracingFactoryImpl() override;

  void CreateProxyResolverV8Tracing(
      const scoped_refptr<net::PacFileData>& pac_script,
      std::unique_ptr<ProxyResolverV8Tracing::Bindings> bindings,
      std::unique_ptr<ProxyResolverV8Tracing>* resolver,
      net::CompletionOnceCallback callback,
      std::unique_ptr<net::ProxyResolverFactory::Request>* request) override;

 private:
  class CreateJob;

  void RemoveJob(CreateJob* job);

  std::set<raw_ptr<CreateJob, SetExperimental>> jobs_;
};

class ProxyResolverV8TracingFactoryImpl::CreateJob
    : public net::ProxyResolverFactory::Request {
 public:
  CreateJob(ProxyResolverV8TracingFactoryImpl* factory,
            std::unique_ptr<ProxyResolverV8Tracing::Bindings> bindings,
            const scoped_refptr<net::PacFileData>& pac_script,
            std::unique_ptr<ProxyResolverV8Tracing>* resolver_out,
            net::CompletionOnceCallback callback)
      : factory_(factory),
        thread_(new base::Thread("Proxy Resolver")),
        resolver_out_(resolver_out),
        callback_(std::move(callback)),
        num_outstanding_callbacks_(0) {
    // Start up the thread.
    CHECK(thread_->Start());
    job_params_ = std::make_unique<Job::Params>(thread_->task_runner(),
                                                &num_outstanding_callbacks_);
    create_resolver_job_ = new Job(job_params_.get(), std::move(bindings));
    create_resolver_job_->StartCreateV8Resolver(
        pac_script, &v8_resolver_,
        base::BindOnce(
            &ProxyResolverV8TracingFactoryImpl::CreateJob::OnV8ResolverCreated,
            base::Unretained(this)));
  }

  CreateJob(const CreateJob&) = delete;
  CreateJob& operator=(const CreateJob&) = delete;

  ~CreateJob() override {
    if (factory_) {
      factory_->RemoveJob(this);
      DCHECK(create_resolver_job_);
      create_resolver_job_->Cancel();
      StopWorkerThread();
    }
    DCHECK_EQ(0, num_outstanding_callbacks_);
  }

  void FactoryDestroyed() {
    factory_ = nullptr;
    create_resolver_job_->Cancel();
    create_resolver_job_ = nullptr;
    StopWorkerThread();
  }

 private:
  void OnV8ResolverCreated(int error) {
    DCHECK(factory_);
    if (error == net::OK) {
      job_params_->v8_resolver = v8_resolver_.get();
      *resolver_out_ = std::make_unique<ProxyResolverV8TracingImpl>(
          std::move(thread_), std::move(v8_resolver_), std::move(job_params_));
    } else {
      StopWorkerThread();
    }

    factory_->RemoveJob(this);
    factory_ = nullptr;
    create_resolver_job_ = nullptr;
    std::move(callback_).Run(error);
  }

  void StopWorkerThread() {
    // Join the worker thread. See http://crbug.com/69710.
    ScopedAllowThreadJoinForProxyResolverV8Tracing allow_thread_join;
    thread_.reset();
  }

  raw_ptr<ProxyResolverV8TracingFactoryImpl> factory_;
  std::unique_ptr<base::Thread> thread_;
  std::unique_ptr<Job::Params> job_params_;
  scoped_refptr<Job> create_resolver_job_;
  std::unique_ptr<ProxyResolverV8> v8_resolver_;
  raw_ptr<std::unique_ptr<ProxyResolverV8Tracing>> resolver_out_;
  net::CompletionOnceCallback callback_;
  int num_outstanding_callbacks_;
};

ProxyResolverV8TracingFactoryImpl::ProxyResolverV8TracingFactoryImpl() =
    default;

ProxyResolverV8TracingFactoryImpl::~ProxyResolverV8TracingFactoryImpl() {
  for (CreateJob* job : jobs_) {
    job->FactoryDestroyed();
  }
}

void ProxyResolverV8TracingFactoryImpl::CreateProxyResolverV8Tracing(
    const scoped_refptr<net::PacFileData>& pac_script,
    std::unique_ptr<ProxyResolverV8Tracing::Bindings> bindings,
    std::unique_ptr<ProxyResolverV8Tracing>* resolver,
    net::CompletionOnceCallback callback,
    std::unique_ptr<net::ProxyResolverFactory::Request>* request) {
  std::unique_ptr<CreateJob> job(new CreateJob(
      this, std::move(bindings), pac_script, resolver, std::move(callback)));
  jobs_.insert(job.get());
  *request = std::move(job);
}

void ProxyResolverV8TracingFactoryImpl::RemoveJob(
    ProxyResolverV8TracingFactoryImpl::CreateJob* job) {
  size_t erased = jobs_.erase(job);
  DCHECK_EQ(1u, erased);
}

}  // namespace

// static
std::unique_ptr<ProxyResolverV8TracingFactory>
ProxyResolverV8TracingFactory::Create() {
  return std::make_unique<ProxyResolverV8TracingFactoryImpl>();
}

}  // namespace proxy_resolver