#include "net/proxy_resolution/multi_threaded_proxy_resolver.h"
#include <memory>
#include <utility>
#include <vector>
#include "base/containers/circular_deque.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/location.h"
#include "base/memory/raw_ptr.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.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 "net/base/net_errors.h"
#include "net/base/network_anonymization_key.h"
#include "net/log/net_log.h"
#include "net/log/net_log_event_type.h"
#include "net/log/net_log_with_source.h"
#include "net/proxy_resolution/proxy_info.h"
#include "net/proxy_resolution/proxy_resolver.h"
namespace net {
class NetworkAnonymizationKey;
class MultiThreadedProxyResolverScopedAllowJoinOnIO
: public base::ScopedAllowBaseSyncPrimitivesOutsideBlockingScope {};
namespace {
class Job;
class Executor : public base::RefCountedThreadSafe<Executor> {
public:
class Coordinator {
public:
virtual void OnExecutorReady(Executor* executor) = 0;
protected:
virtual ~Coordinator() = default;
};
Executor(Coordinator* coordinator, int thread_number);
void StartJob(scoped_refptr<Job> job);
void OnJobCompleted(Job* job);
void Destroy();
Job* outstanding_job() const { return outstanding_job_.get(); }
ProxyResolver* resolver() { return resolver_.get(); }
int thread_number() const { return thread_number_; }
void set_resolver(std::unique_ptr<ProxyResolver> resolver) {
resolver_ = std::move(resolver);
}
void set_coordinator(Coordinator* coordinator) {
DCHECK(coordinator);
DCHECK(coordinator_);
coordinator_ = coordinator;
}
private:
friend class base::RefCountedThreadSafe<Executor>;
~Executor();
raw_ptr<Coordinator> coordinator_;
const int thread_number_;
scoped_refptr<Job> outstanding_job_;
std::unique_ptr<ProxyResolver> resolver_;
std::unique_ptr<base::Thread> thread_;
};
class MultiThreadedProxyResolver : public ProxyResolver,
public Executor::Coordinator {
public:
MultiThreadedProxyResolver(
std::unique_ptr<ProxyResolverFactory> resolver_factory,
size_t max_num_threads,
const scoped_refptr<PacFileData>& script_data,
scoped_refptr<Executor> executor);
~MultiThreadedProxyResolver() override;
int GetProxyForURL(const GURL& url,
const NetworkAnonymizationKey& network_anonymization_key,
ProxyInfo* results,
CompletionOnceCallback callback,
std::unique_ptr<Request>* request,
const NetLogWithSource& net_log) override;
private:
class GetProxyForURLJob;
class RequestImpl;
using PendingJobsQueue = base::circular_deque<scoped_refptr<Job>>;
using ExecutorList = std::vector<scoped_refptr<Executor>>;
Executor* FindIdleExecutor();
void AddNewExecutor();
void OnExecutorReady(Executor* executor) override;
const std::unique_ptr<ProxyResolverFactory> resolver_factory_;
const size_t max_num_threads_;
PendingJobsQueue pending_jobs_;
ExecutorList executors_;
scoped_refptr<PacFileData> script_data_;
THREAD_CHECKER(thread_checker_);
};
class Job : public base::RefCountedThreadSafe<Job> {
public:
Job() = default;
void set_executor(Executor* executor) {
executor_ = executor;
}
Executor* executor() {
return executor_;
}
virtual void Cancel() { was_cancelled_ = true; }
bool was_cancelled() const { return was_cancelled_; }
virtual void WaitingForThread() {}
virtual void FinishedWaitingForThread() {}
virtual void Run(
scoped_refptr<base::SingleThreadTaskRunner> origin_runner) = 0;
protected:
void OnJobCompleted() {
if (executor_)
executor_->OnJobCompleted(this);
}
friend class base::RefCountedThreadSafe<Job>;
virtual ~Job() = default;
private:
raw_ptr<Executor> executor_ = nullptr;
bool was_cancelled_ = false;
};
class MultiThreadedProxyResolver::RequestImpl : public ProxyResolver::Request {
public:
explicit RequestImpl(scoped_refptr<Job> job) : job_(std::move(job)) {}
~RequestImpl() override { job_->Cancel(); }
LoadState GetLoadState() override {
return LOAD_STATE_RESOLVING_PROXY_FOR_URL;
}
private:
scoped_refptr<Job> job_;
};
class CreateResolverJob : public Job {
public:
CreateResolverJob(const scoped_refptr<PacFileData>& script_data,
ProxyResolverFactory* factory)
: script_data_(script_data), factory_(factory) {}
void Run(scoped_refptr<base::SingleThreadTaskRunner> origin_runner) override {
std::unique_ptr<ProxyResolverFactory::Request> request;
int rv = factory_->CreateProxyResolver(script_data_, &resolver_,
CompletionOnceCallback(), &request);
DCHECK_NE(rv, ERR_IO_PENDING);
origin_runner->PostTask(
FROM_HERE,
base::BindOnce(&CreateResolverJob::RequestComplete, this, rv));
}
protected:
~CreateResolverJob() override = default;
void Cancel() override {
factory_ = nullptr;
Job::Cancel();
}
private:
void RequestComplete(int result_code) {
if (!was_cancelled()) {
DCHECK(executor());
executor()->set_resolver(std::move(resolver_));
}
OnJobCompleted();
}
const scoped_refptr<PacFileData> script_data_;
raw_ptr<ProxyResolverFactory> factory_;
std::unique_ptr<ProxyResolver> resolver_;
};
class MultiThreadedProxyResolver::GetProxyForURLJob : public Job {
public:
GetProxyForURLJob(const GURL& url,
const NetworkAnonymizationKey& network_anonymization_key,
ProxyInfo* results,
CompletionOnceCallback callback,
const NetLogWithSource& net_log)
: callback_(std::move(callback)),
results_(results),
net_log_(net_log),
url_(url),
network_anonymization_key_(network_anonymization_key) {
DCHECK(callback_);
}
NetLogWithSource* net_log() { return &net_log_; }
void WaitingForThread() override {
was_waiting_for_thread_ = true;
net_log_.BeginEvent(NetLogEventType::WAITING_FOR_PROXY_RESOLVER_THREAD);
}
void FinishedWaitingForThread() override {
DCHECK(executor());
if (was_waiting_for_thread_) {
net_log_.EndEvent(NetLogEventType::WAITING_FOR_PROXY_RESOLVER_THREAD);
}
net_log_.AddEventWithIntParams(
NetLogEventType::SUBMITTED_TO_RESOLVER_THREAD, "thread_number",
executor()->thread_number());
}
void Run(scoped_refptr<base::SingleThreadTaskRunner> origin_runner) override {
ProxyResolver* resolver = executor()->resolver();
DCHECK(resolver);
int rv = resolver->GetProxyForURL(url_, network_anonymization_key_,
&results_buf_, CompletionOnceCallback(),
nullptr, net_log_);
DCHECK_NE(rv, ERR_IO_PENDING);
origin_runner->PostTask(
FROM_HERE, base::BindOnce(&GetProxyForURLJob::QueryComplete, this, rv));
}
void Cancel() override {
results_ = nullptr;
Job::Cancel();
}
protected:
~GetProxyForURLJob() override = default;
private:
void QueryComplete(int result_code) {
if (!was_cancelled()) {
if (result_code >= OK) {
results_->Use(results_buf_);
}
std::move(callback_).Run(result_code);
}
OnJobCompleted();
}
CompletionOnceCallback callback_;
raw_ptr<ProxyInfo> results_;
NetLogWithSource net_log_;
const GURL url_;
const NetworkAnonymizationKey network_anonymization_key_;
ProxyInfo results_buf_;
bool was_waiting_for_thread_ = false;
};
Executor::Executor(Executor::Coordinator* coordinator, int thread_number)
: coordinator_(coordinator), thread_number_(thread_number) {
DCHECK(coordinator);
thread_ = std::make_unique<base::Thread>(
base::StringPrintf("PAC thread #%d", thread_number));
CHECK(thread_->Start());
}
void Executor::StartJob(scoped_refptr<Job> job) {
DCHECK(!outstanding_job_.get());
outstanding_job_ = job;
job->set_executor(this);
job->FinishedWaitingForThread();
thread_->task_runner()->PostTask(
FROM_HERE,
base::BindOnce(&Job::Run, job,
base::SingleThreadTaskRunner::GetCurrentDefault()));
}
void Executor::OnJobCompleted(Job* job) {
DCHECK_EQ(job, outstanding_job_.get());
outstanding_job_ = nullptr;
coordinator_->OnExecutorReady(this);
}
void Executor::Destroy() {
DCHECK(coordinator_);
{
MultiThreadedProxyResolverScopedAllowJoinOnIO allow_thread_join;
thread_.reset();
}
if (outstanding_job_.get()) {
outstanding_job_->Cancel();
outstanding_job_->set_executor(nullptr);
}
resolver_.reset();
coordinator_ = nullptr;
outstanding_job_ = nullptr;
}
Executor::~Executor() {
DCHECK(!coordinator_) << "Destroy() was not called";
DCHECK(!thread_.get());
DCHECK(!resolver_.get());
DCHECK(!outstanding_job_.get());
}
MultiThreadedProxyResolver::MultiThreadedProxyResolver(
std::unique_ptr<ProxyResolverFactory> resolver_factory,
size_t max_num_threads,
const scoped_refptr<PacFileData>& script_data,
scoped_refptr<Executor> executor)
: resolver_factory_(std::move(resolver_factory)),
max_num_threads_(max_num_threads),
script_data_(script_data) {
DCHECK(script_data_);
executor->set_coordinator(this);
executors_.push_back(executor);
}
MultiThreadedProxyResolver::~MultiThreadedProxyResolver() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
pending_jobs_.clear();
for (auto& executor : executors_) {
executor->Destroy();
}
}
int MultiThreadedProxyResolver::GetProxyForURL(
const GURL& url,
const NetworkAnonymizationKey& network_anonymization_key,
ProxyInfo* results,
CompletionOnceCallback callback,
std::unique_ptr<Request>* request,
const NetLogWithSource& net_log) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(!callback.is_null());
auto job = base::MakeRefCounted<GetProxyForURLJob>(
url, network_anonymization_key, results, std::move(callback), net_log);
if (request)
*request = std::make_unique<RequestImpl>(job);
Executor* executor = FindIdleExecutor();
if (executor) {
DCHECK_EQ(0u, pending_jobs_.size());
executor->StartJob(job);
return ERR_IO_PENDING;
}
job->WaitingForThread();
pending_jobs_.push_back(job);
if (executors_.size() < max_num_threads_)
AddNewExecutor();
return ERR_IO_PENDING;
}
Executor* MultiThreadedProxyResolver::FindIdleExecutor() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
for (auto& executor : executors_) {
if (!executor->outstanding_job())
return executor.get();
}
return nullptr;
}
void MultiThreadedProxyResolver::AddNewExecutor() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK_LT(executors_.size(), max_num_threads_);
int thread_number = executors_.size();
auto executor = base::MakeRefCounted<Executor>(this, thread_number);
executor->StartJob(base::MakeRefCounted<CreateResolverJob>(
script_data_, resolver_factory_.get()));
executors_.push_back(std::move(executor));
}
void MultiThreadedProxyResolver::OnExecutorReady(Executor* executor) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
while (!pending_jobs_.empty()) {
scoped_refptr<Job> job = pending_jobs_.front();
pending_jobs_.pop_front();
if (!job->was_cancelled()) {
executor->StartJob(std::move(job));
return;
}
}
}
}
class MultiThreadedProxyResolverFactory::Job
: public ProxyResolverFactory::Request,
public Executor::Coordinator {
public:
Job(MultiThreadedProxyResolverFactory* factory,
const scoped_refptr<PacFileData>& script_data,
std::unique_ptr<ProxyResolver>* resolver,
std::unique_ptr<ProxyResolverFactory> resolver_factory,
size_t max_num_threads,
CompletionOnceCallback callback)
: factory_(factory),
resolver_out_(resolver),
resolver_factory_(std::move(resolver_factory)),
max_num_threads_(max_num_threads),
script_data_(script_data),
executor_(base::MakeRefCounted<Executor>(this, 0)),
callback_(std::move(callback)) {
executor_->StartJob(base::MakeRefCounted<CreateResolverJob>(
script_data_, resolver_factory_.get()));
}
~Job() override {
if (factory_) {
executor_->Destroy();
factory_->RemoveJob(this);
}
}
void FactoryDestroyed() {
executor_->Destroy();
executor_ = nullptr;
factory_ = nullptr;
resolver_out_ = nullptr;
}
private:
void OnExecutorReady(Executor* executor) override {
int error = OK;
if (executor->resolver()) {
*resolver_out_ = std::make_unique<MultiThreadedProxyResolver>(
std::move(resolver_factory_), max_num_threads_,
std::move(script_data_), executor_);
} else {
error = ERR_PAC_SCRIPT_FAILED;
executor_->Destroy();
}
factory_->RemoveJob(this);
factory_ = nullptr;
std::move(callback_).Run(error);
}
raw_ptr<MultiThreadedProxyResolverFactory> factory_;
raw_ptr<std::unique_ptr<ProxyResolver>> resolver_out_;
std::unique_ptr<ProxyResolverFactory> resolver_factory_;
const size_t max_num_threads_;
scoped_refptr<PacFileData> script_data_;
scoped_refptr<Executor> executor_;
CompletionOnceCallback callback_;
};
MultiThreadedProxyResolverFactory::MultiThreadedProxyResolverFactory(
size_t max_num_threads,
bool factory_expects_bytes)
: ProxyResolverFactory(factory_expects_bytes),
max_num_threads_(max_num_threads) {
DCHECK_GE(max_num_threads, 1u);
}
MultiThreadedProxyResolverFactory::~MultiThreadedProxyResolverFactory() {
for (Job* job : jobs_) {
job->FactoryDestroyed();
}
}
int MultiThreadedProxyResolverFactory::CreateProxyResolver(
const scoped_refptr<PacFileData>& pac_script,
std::unique_ptr<ProxyResolver>* resolver,
CompletionOnceCallback callback,
std::unique_ptr<Request>* request) {
auto job = std::make_unique<Job>(this, pac_script, resolver,
CreateProxyResolverFactory(),
max_num_threads_, std::move(callback));
jobs_.insert(job.get());
*request = std::move(job);
return ERR_IO_PENDING;
}
void MultiThreadedProxyResolverFactory::RemoveJob(
MultiThreadedProxyResolverFactory::Job* job) {
size_t erased = jobs_.erase(job);
DCHECK_EQ(1u, erased);
}
}