blob: 755313a910a55c21071599168ba221079554cc52 [file] [log] [blame]
// 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