blob: 10b99a34edeedee4b99acae0b58dd5558ebfb357 [file] [log] [blame]
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#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;
// http://crbug.com/69710
class MultiThreadedProxyResolverScopedAllowJoinOnIO
: public base::ScopedAllowBaseSyncPrimitivesOutsideBlockingScope {};
namespace {
class Job;
// An "executor" is a job-runner for PAC requests. It encapsulates a worker
// thread and a synchronous ProxyResolver (which will be operated on said
// thread.)
class Executor : public base::RefCountedThreadSafe<Executor> {
public:
class Coordinator {
public:
virtual void OnExecutorReady(Executor* executor) = 0;
protected:
virtual ~Coordinator() = default;
};
// |coordinator| must remain valid throughout our lifetime. It is used to
// signal when the executor is ready to receive work by calling
// |coordinator->OnExecutorReady()|.
// |thread_number| is an identifier used when naming the worker thread.
Executor(Coordinator* coordinator, int thread_number);
// Submit a job to this executor.
void StartJob(scoped_refptr<Job> job);
// Callback for when a job has completed running on the executor's thread.
void OnJobCompleted(Job* job);
// Cleanup the executor. Cancels all outstanding work, and frees the thread
// and resolver.
void Destroy();
// Returns the outstanding job, or NULL.
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_;
// The currently active job for this executor (either a CreateProxyResolver or
// GetProxyForURL task).
scoped_refptr<Job> outstanding_job_;
// The synchronous resolver implementation.
std::unique_ptr<ProxyResolver> resolver_;
// The thread where |resolver_| is run on.
// Note that declaration ordering is important here. |thread_| needs to be
// destroyed *before* |resolver_|, in case |resolver_| is currently
// executing on |thread_|.
std::unique_ptr<base::Thread> thread_;
};
class MultiThreadedProxyResolver : public ProxyResolver,
public Executor::Coordinator {
public:
// Creates an asynchronous ProxyResolver that runs requests on up to
// |max_num_threads|.
//
// For each thread that is created, an accompanying synchronous ProxyResolver
// will be provisioned using |resolver_factory|. All methods on these
// ProxyResolvers will be called on the one thread.
MultiThreadedProxyResolver(
std::unique_ptr<ProxyResolverFactory> resolver_factory,
size_t max_num_threads,
const scoped_refptr<PacFileData>& script_data,
scoped_refptr<Executor> executor);
~MultiThreadedProxyResolver() override;
// ProxyResolver implementation:
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;
// FIFO queue of pending jobs waiting to be started.
// TODO(eroman): Make this priority queue.
using PendingJobsQueue = base::circular_deque<scoped_refptr<Job>>;
using ExecutorList = std::vector<scoped_refptr<Executor>>;
// Returns an idle worker thread which is ready to receive GetProxyForURL()
// requests. If all threads are occupied, returns NULL.
Executor* FindIdleExecutor();
// Creates a new worker thread, and appends it to |executors_|.
void AddNewExecutor();
// Starts the next job from |pending_jobs_| if possible.
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_);
};
// Job ---------------------------------------------
class Job : public base::RefCountedThreadSafe<Job> {
public:
Job() = default;
void set_executor(Executor* executor) {
executor_ = executor;
}
// The "executor" is the job runner that is scheduling this job. If
// this job has not been submitted to an executor yet, this will be
// NULL (and we know it hasn't started yet).
Executor* executor() {
return executor_;
}
// Mark the job as having been cancelled.
void Cancel() {
was_cancelled_ = true;
}
// Returns true if Cancel() has been called.
bool was_cancelled() const { return was_cancelled_; }
// This method is called when the job is inserted into a wait queue
// because no executors were ready to accept it.
virtual void WaitingForThread() {}
// This method is called just before the job is posted to the work thread.
virtual void FinishedWaitingForThread() {}
// This method is called on the worker thread to do the job's work. On
// completion, implementors are expected to call OnJobCompleted() on
// |origin_runner|.
virtual void Run(
scoped_refptr<base::SingleThreadTaskRunner> origin_runner) = 0;
protected:
void OnJobCompleted() {
// |executor_| will be NULL if the executor has already been deleted.
if (executor_)
executor_->OnJobCompleted(this);
}
friend class base::RefCountedThreadSafe<Job>;
virtual ~Job() = default;
private:
raw_ptr<Executor, DanglingUntriaged> 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_;
};
// CreateResolverJob -----------------------------------------------------------
// Runs on the worker thread to call ProxyResolverFactory::CreateProxyResolver.
class CreateResolverJob : public Job {
public:
CreateResolverJob(const scoped_refptr<PacFileData>& script_data,
ProxyResolverFactory* factory)
: script_data_(script_data), factory_(factory) {}
// Runs on the worker thread.
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;
private:
// Runs the completion callback on the origin thread.
void RequestComplete(int result_code) {
// The task may have been cancelled after it was started.
if (!was_cancelled()) {
DCHECK(executor());
executor()->set_resolver(std::move(resolver_));
}
OnJobCompleted();
}
const scoped_refptr<PacFileData> script_data_;
raw_ptr<ProxyResolverFactory, DanglingUntriaged> factory_;
std::unique_ptr<ProxyResolver> resolver_;
};
// MultiThreadedProxyResolver::GetProxyForURLJob ------------------------------
class MultiThreadedProxyResolver::GetProxyForURLJob : public Job {
public:
// |url| -- the URL of the query.
// |results| -- the structure to fill with proxy resolve results.
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());
}
// Runs on the worker thread.
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));
}
protected:
~GetProxyForURLJob() override = default;
private:
// Runs the completion callback on the origin thread.
void QueryComplete(int result_code) {
// The Job may have been cancelled after it was started.
if (!was_cancelled()) {
if (result_code >= OK) { // Note: unit-tests use values > 0.
results_->Use(results_buf_);
}
std::move(callback_).Run(result_code);
}
OnJobCompleted();
}
CompletionOnceCallback callback_;
// Must only be used on the "origin" thread.
raw_ptr<ProxyInfo, DanglingUntriaged> results_;
// Can be used on either "origin" or worker thread.
NetLogWithSource net_log_;
const GURL url_;
const NetworkAnonymizationKey network_anonymization_key_;
// Usable from within DoQuery on the worker thread.
ProxyInfo results_buf_;
bool was_waiting_for_thread_ = false;
};
// Executor ----------------------------------------
Executor::Executor(Executor::Coordinator* coordinator, int thread_number)
: coordinator_(coordinator), thread_number_(thread_number) {
DCHECK(coordinator);
// Start up the thread.
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;
// Run the job. Once it has completed (regardless of whether it was
// cancelled), it will invoke OnJobCompleted() on this thread.
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_);
{
// TODO(http://crbug.com/69710): Use ThreadPool instead of creating a
// base::Thread.
MultiThreadedProxyResolverScopedAllowJoinOnIO allow_thread_join;
// Join the worker thread.
thread_.reset();
}
// Cancel any outstanding job.
if (outstanding_job_.get()) {
outstanding_job_->Cancel();
// Orphan the job (since this executor may be deleted soon).
outstanding_job_->set_executor(nullptr);
}
// It is now safe to free the ProxyResolver, since all the tasks that
// were using it on the resolver thread have completed.
resolver_.reset();
// Null some stuff as a precaution.
coordinator_ = nullptr;
outstanding_job_ = nullptr;
}
Executor::~Executor() {
// The important cleanup happens as part of Destroy(), which should always be
// called first.
DCHECK(!coordinator_) << "Destroy() was not called";
DCHECK(!thread_.get());
DCHECK(!resolver_.get());
DCHECK(!outstanding_job_.get());
}
// MultiThreadedProxyResolver --------------------------------------------------
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_);
// We will cancel all outstanding requests.
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);
// Completion will be notified through |callback|, unless the caller cancels
// the request using |request|.
if (request)
*request = std::make_unique<RequestImpl>(job);
// If there is an executor that is ready to run this request, submit it!
Executor* executor = FindIdleExecutor();
if (executor) {
DCHECK_EQ(0u, pending_jobs_.size());
executor->StartJob(job);
return ERR_IO_PENDING;
}
// Otherwise queue this request. (We will schedule it to a thread once one
// becomes available).
job->WaitingForThread();
pending_jobs_.push_back(job);
// If we haven't already reached the thread limit, provision a new thread to
// drain the requests more quickly.
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_);
// The "thread number" is used to give the thread a unique name.
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;
}
}
}
} // namespace
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;
}
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, DanglingUntriaged> factory_;
const raw_ptr<std::unique_ptr<ProxyResolver>, DanglingUntriaged>
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 (auto* 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);
}
} // namespace net