diff options
author | eroman@chromium.org <eroman@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-07-08 21:10:27 +0000 |
---|---|---|
committer | eroman@chromium.org <eroman@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-07-08 21:10:27 +0000 |
commit | 0007f1434eafa4e955ff916d50a7a39fd850d435 (patch) | |
tree | 278dca04911fbefee519ac7ab449d208f4938a3d /net/proxy | |
parent | 41c9921ad0ffa548263262477777e865cd85b144 (diff) | |
download | chromium_src-0007f1434eafa4e955ff916d50a7a39fd850d435.zip chromium_src-0007f1434eafa4e955ff916d50a7a39fd850d435.tar.gz chromium_src-0007f1434eafa4e955ff916d50a7a39fd850d435.tar.bz2 |
Revert 51877, since SpdyNetworkTransactionTest.CorruptFrameSessionError started failing after this check-in (but only on vista modules builder).
BUG=48588
Original CL description:
Add the capability to run multiple proxy PAC scripts in parallel.
Refactors SingleThreadedProxyResolver into MultiThreadedProxyResolver.
New threads are created lazily on demand, up to a fixed maximum.
Note that this CL does NOT change the policy in Chrome -- it will continue to use a single thread for proxy resolving, but using the new code to do so.
BUG=11079
Review URL: http://codereview.chromium.org/2822043
TBR=eroman@chromium.org
Review URL: http://codereview.chromium.org/2945004
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@51893 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net/proxy')
-rw-r--r-- | net/proxy/multi_threaded_proxy_resolver.cc | 588 | ||||
-rw-r--r-- | net/proxy/multi_threaded_proxy_resolver.h | 144 | ||||
-rw-r--r-- | net/proxy/proxy_resolver.h | 4 | ||||
-rw-r--r-- | net/proxy/proxy_resolver_js_bindings.cc | 6 | ||||
-rw-r--r-- | net/proxy/proxy_resolver_js_bindings.h | 3 | ||||
-rw-r--r-- | net/proxy/proxy_resolver_v8.cc | 4 | ||||
-rw-r--r-- | net/proxy/proxy_resolver_v8.h | 1 | ||||
-rw-r--r-- | net/proxy/proxy_resolver_v8_unittest.cc | 2 | ||||
-rw-r--r-- | net/proxy/proxy_service.cc | 96 | ||||
-rw-r--r-- | net/proxy/proxy_service.h | 4 | ||||
-rw-r--r-- | net/proxy/single_threaded_proxy_resolver.cc | 353 | ||||
-rw-r--r-- | net/proxy/single_threaded_proxy_resolver.h | 94 | ||||
-rw-r--r-- | net/proxy/single_threaded_proxy_resolver_unittest.cc (renamed from net/proxy/multi_threaded_proxy_resolver_unittest.cc) | 388 | ||||
-rw-r--r-- | net/proxy/sync_host_resolver_bridge.cc | 15 | ||||
-rw-r--r-- | net/proxy/sync_host_resolver_bridge.h | 19 | ||||
-rw-r--r-- | net/proxy/sync_host_resolver_bridge_unittest.cc | 41 |
16 files changed, 588 insertions, 1174 deletions
diff --git a/net/proxy/multi_threaded_proxy_resolver.cc b/net/proxy/multi_threaded_proxy_resolver.cc deleted file mode 100644 index a55fef9..0000000 --- a/net/proxy/multi_threaded_proxy_resolver.cc +++ /dev/null @@ -1,588 +0,0 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "net/proxy/multi_threaded_proxy_resolver.h" - -#include "base/message_loop.h" -#include "base/string_util.h" -#include "base/thread.h" -#include "net/base/capturing_net_log.h" -#include "net/base/net_errors.h" -#include "net/proxy/proxy_info.h" - -// TODO(eroman): Have the MultiThreadedProxyResolver clear its PAC script -// data when SetPacScript fails. That will reclaim memory when -// testing bogus scripts. - -namespace net { - -namespace { - -class PurgeMemoryTask : public base::RefCountedThreadSafe<PurgeMemoryTask> { - public: - explicit PurgeMemoryTask(ProxyResolver* resolver) : resolver_(resolver) {} - void PurgeMemory() { resolver_->PurgeMemory(); } - private: - friend class base::RefCountedThreadSafe<PurgeMemoryTask>; - ~PurgeMemoryTask() {} - ProxyResolver* resolver_; -}; - -} // namespace - -// 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 MultiThreadedProxyResolver::Executor - : public base::RefCountedThreadSafe<MultiThreadedProxyResolver::Executor > { - public: - // |coordinator| must remain valid throughout our lifetime. It is used to - // signal when the executor is ready to receive work by calling - // |coordinator->OnExecutorReady()|. - // The constructor takes ownership of |resolver|. - // |thread_number| is an identifier used when naming the worker thread. - Executor(MultiThreadedProxyResolver* coordinator, - ProxyResolver* resolver, - int thread_number); - - // Submit a job to this executor. - void StartJob(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(); - - void PurgeMemory(); - - // 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_; } - - private: - friend class base::RefCountedThreadSafe<Executor>; - ~Executor(); - - MultiThreadedProxyResolver* coordinator_; - const int thread_number_; - - // The currently active job for this executor (either a SetPacScript or - // GetProxyForURL task). - scoped_refptr<Job> outstanding_job_; - - // The synchronous resolver implementation. - scoped_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_|. - scoped_ptr<base::Thread> thread_; -}; - -// MultiThreadedProxyResolver::Job --------------------------------------------- - -class MultiThreadedProxyResolver::Job - : public base::RefCountedThreadSafe<MultiThreadedProxyResolver::Job> { - public: - // Identifies the subclass of Job (only being used for debugging purposes). - enum Type { - TYPE_GET_PROXY_FOR_URL, - TYPE_SET_PAC_SCRIPT, - TYPE_SET_PAC_SCRIPT_INTERNAL, - }; - - Job(Type type, CompletionCallback* user_callback) - : type_(type), - user_callback_(user_callback), - executor_(NULL), - was_cancelled_(false) { - } - - 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_; } - - Type type() const { return type_; } - - // Returns true if this job still has a user callback. Some jobs - // do not have a user callback, because they were helper jobs - // scheduled internally (for example TYPE_SET_PAC_SCRIPT_INTERNAL). - // - // Otherwise jobs that correspond with user-initiated work will - // have a non-NULL callback up until the callback is run. - bool has_user_callback() const { return user_callback_ != NULL; } - - // 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_loop|. - virtual void Run(MessageLoop* origin_loop) = 0; - - protected: - void OnJobCompleted() { - // |executor_| will be NULL if the executor has already been deleted. - if (executor_) - executor_->OnJobCompleted(this); - } - - void RunUserCallback(int result) { - DCHECK(has_user_callback()); - CompletionCallback* callback = user_callback_; - // Null the callback so has_user_callback() will now return false. - user_callback_ = NULL; - callback->Run(result); - } - - friend class base::RefCountedThreadSafe<MultiThreadedProxyResolver::Job>; - - virtual ~Job() {} - - private: - const Type type_; - CompletionCallback* user_callback_; - Executor* executor_; - bool was_cancelled_; -}; - -// MultiThreadedProxyResolver::SetPacScriptJob --------------------------------- - -// Runs on the worker thread to call ProxyResolver::SetPacScript. -class MultiThreadedProxyResolver::SetPacScriptJob - : public MultiThreadedProxyResolver::Job { - public: - SetPacScriptJob(const GURL& pac_url, - const string16& pac_script, - CompletionCallback* callback) - : Job(callback ? TYPE_SET_PAC_SCRIPT : TYPE_SET_PAC_SCRIPT_INTERNAL, - callback), - pac_url_(pac_url), - pac_script_(pac_script) { - } - - // Runs on the worker thread. - virtual void Run(MessageLoop* origin_loop) { - ProxyResolver* resolver = executor()->resolver(); - int rv = resolver->expects_pac_bytes() ? - resolver->SetPacScriptByData(pac_script_, NULL) : - resolver->SetPacScriptByUrl(pac_url_, NULL); - - DCHECK_NE(rv, ERR_IO_PENDING); - origin_loop->PostTask( - FROM_HERE, - NewRunnableMethod(this, &SetPacScriptJob::RequestComplete, rv)); - } - - 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() && has_user_callback()) { - RunUserCallback(result_code); - } - OnJobCompleted(); - } - - const GURL pac_url_; - const string16 pac_script_; -}; - -// MultiThreadedProxyResolver::GetProxyForURLJob ------------------------------ - -class MultiThreadedProxyResolver::GetProxyForURLJob - : public MultiThreadedProxyResolver::Job { - public: - // |url| -- the URL of the query. - // |results| -- the structure to fill with proxy resolve results. - GetProxyForURLJob(const GURL& url, - ProxyInfo* results, - CompletionCallback* callback, - const BoundNetLog& net_log) - : Job(TYPE_GET_PROXY_FOR_URL, callback), - results_(results), - net_log_(net_log), - url_(url), - was_waiting_for_thread_(false) { - DCHECK(callback); - } - - BoundNetLog* net_log() { return &net_log_; } - - virtual void WaitingForThread() { - was_waiting_for_thread_ = true; - net_log_.BeginEvent( - NetLog::TYPE_WAITING_FOR_PROXY_RESOLVER_THREAD, NULL); - } - - virtual void FinishedWaitingForThread() { - DCHECK(executor()); - - if (was_waiting_for_thread_) { - net_log_.EndEvent( - NetLog::TYPE_WAITING_FOR_PROXY_RESOLVER_THREAD, NULL); - } - - net_log_.AddEvent( - NetLog::TYPE_SUBMITTED_TO_RESOLVER_THREAD, - new NetLogIntegerParameter( - "thread_number", executor()->thread_number())); - } - - // Runs on the worker thread. - virtual void Run(MessageLoop* origin_loop) { - const size_t kNetLogBound = 50u; - worker_log_.reset(new CapturingNetLog(kNetLogBound)); - BoundNetLog bound_worker_log(NetLog::Source(), worker_log_.get()); - - ProxyResolver* resolver = executor()->resolver(); - int rv = resolver->GetProxyForURL( - url_, &results_buf_, NULL, NULL, bound_worker_log); - DCHECK_NE(rv, ERR_IO_PENDING); - - origin_loop->PostTask( - FROM_HERE, - NewRunnableMethod(this, &GetProxyForURLJob::QueryComplete, rv)); - } - - 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()) { - // Merge the load log that was generated on the worker thread, into the - // main log. - CapturingBoundNetLog bound_worker_log(NetLog::Source(), - worker_log_.release()); - bound_worker_log.AppendTo(net_log_); - - if (result_code >= OK) { // Note: unit-tests use values > 0. - results_->Use(results_buf_); - } - RunUserCallback(result_code); - } - OnJobCompleted(); - } - - // Must only be used on the "origin" thread. - ProxyInfo* results_; - BoundNetLog net_log_; - const GURL url_; - - // Usable from within DoQuery on the worker thread. - ProxyInfo results_buf_; - - // Used to pass the captured events between DoQuery [worker thread] and - // QueryComplete [origin thread]. - scoped_ptr<CapturingNetLog> worker_log_; - - bool was_waiting_for_thread_; -}; - -// MultiThreadedProxyResolver::Executor ---------------------------------------- - -MultiThreadedProxyResolver::Executor::Executor( - MultiThreadedProxyResolver* coordinator, - ProxyResolver* resolver, - int thread_number) - : coordinator_(coordinator), - thread_number_(thread_number), - resolver_(resolver) { - DCHECK(coordinator); - DCHECK(resolver); - // Start up the thread. - // Note that it is safe to pass a temporary C-String to Thread(), as it will - // make a copy. - std::string thread_name = - StringPrintf("PAC thread #%d", thread_number); - thread_.reset(new base::Thread(thread_name.c_str())); - thread_->Start(); -} - -void MultiThreadedProxyResolver::Executor::StartJob(Job* job) { - DCHECK(!outstanding_job_); - 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_->message_loop()->PostTask( - FROM_HERE, - NewRunnableMethod(job, &Job::Run, MessageLoop::current())); -} - -void MultiThreadedProxyResolver::Executor::OnJobCompleted(Job* job) { - DCHECK_EQ(job, outstanding_job_.get()); - outstanding_job_ = NULL; - coordinator_->OnExecutorReady(this); -} - -void MultiThreadedProxyResolver::Executor::Destroy() { - DCHECK(coordinator_); - - // Give the resolver an opportunity to shutdown from THIS THREAD before - // joining on the resolver thread. This allows certain implementations - // to avoid deadlocks. - resolver_->Shutdown(); - - // Join the worker thread. - thread_.reset(); - - // Cancel any outstanding job. - if (outstanding_job_) { - outstanding_job_->Cancel(); - // Orphan the job (since this executor may be deleted soon). - outstanding_job_->set_executor(NULL); - } - - // 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_ = NULL; - outstanding_job_ = NULL; -} - -void MultiThreadedProxyResolver::Executor::PurgeMemory() { - scoped_refptr<PurgeMemoryTask> helper(new PurgeMemoryTask(resolver_.get())); - thread_->message_loop()->PostTask( - FROM_HERE, - NewRunnableMethod(helper.get(), &PurgeMemoryTask::PurgeMemory)); -} - -MultiThreadedProxyResolver::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_); -} - -// MultiThreadedProxyResolver -------------------------------------------------- - -MultiThreadedProxyResolver::MultiThreadedProxyResolver( - ProxyResolverFactory* resolver_factory, - size_t max_num_threads) - : ProxyResolver(resolver_factory->resolvers_expect_pac_bytes()), - resolver_factory_(resolver_factory), - max_num_threads_(max_num_threads), - was_set_pac_script_called_(false) { - DCHECK_GE(max_num_threads, 1u); -} - -MultiThreadedProxyResolver::~MultiThreadedProxyResolver() { - // We will cancel all outstanding requests. - pending_jobs_.clear(); - ReleaseAllExecutors(); -} - -int MultiThreadedProxyResolver::GetProxyForURL(const GURL& url, - ProxyInfo* results, - CompletionCallback* callback, - RequestHandle* request, - const BoundNetLog& net_log) { - DCHECK(CalledOnValidThread()); - DCHECK(callback); - DCHECK(was_set_pac_script_called_) - << "Resolver is un-initialized. Must call SetPacScript() first!"; - - scoped_refptr<GetProxyForURLJob> job = - new GetProxyForURLJob(url, results, callback, net_log); - - // Completion will be notified through |callback|, unless the caller cancels - // the request using |request|. - if (request) - *request = reinterpret_cast<RequestHandle>(job.get()); - - // 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_) { - executor = AddNewExecutor(); - executor->StartJob( - new SetPacScriptJob(current_pac_url_, current_pac_script_, NULL)); - } - - return ERR_IO_PENDING; -} - -void MultiThreadedProxyResolver::CancelRequest(RequestHandle req) { - DCHECK(CalledOnValidThread()); - DCHECK(req); - - Job* job = reinterpret_cast<Job*>(req); - DCHECK_EQ(Job::TYPE_GET_PROXY_FOR_URL, job->type()); - - if (job->executor()) { - // If the job was already submitted to the executor, just mark it - // as cancelled so the user callback isn't run on completion. - job->Cancel(); - } else { - // Otherwise the job is just sitting in a queue. - PendingJobsQueue::iterator it = - std::find(pending_jobs_.begin(), pending_jobs_.end(), job); - DCHECK(it != pending_jobs_.end()); - pending_jobs_.erase(it); - } -} - -void MultiThreadedProxyResolver::CancelSetPacScript() { - DCHECK(CalledOnValidThread()); - DCHECK_EQ(0u, pending_jobs_.size()); - DCHECK_EQ(1u, executors_.size()); - DCHECK_EQ(Job::TYPE_SET_PAC_SCRIPT, - executors_[0]->outstanding_job()->type()); - - // Defensively clear some data which shouldn't be getting used - // anymore. - was_set_pac_script_called_ = false; - current_pac_url_ = GURL(); - current_pac_script_ = string16(); - - ReleaseAllExecutors(); -} - -void MultiThreadedProxyResolver::PurgeMemory() { - DCHECK(CalledOnValidThread()); - for (ExecutorList::iterator it = executors_.begin(); - it != executors_.end(); ++it) { - Executor* executor = *it; - executor->PurgeMemory(); - } -} - -int MultiThreadedProxyResolver::SetPacScript( - const GURL& pac_url, - const string16& pac_script, - CompletionCallback* callback) { - DCHECK(CalledOnValidThread()); - DCHECK(callback); - - // Save the script details, so we can provision new executors later. - // (We rely on internal reference counting of strings to avoid this memory - // being duplicated by each of the resolver threads). - was_set_pac_script_called_ = true; - current_pac_url_ = pac_url; - current_pac_script_ = pac_script; - - // The user should not have any outstanding requests when they call - // SetPacScript(). - CheckNoOutstandingUserRequests(); - - // Destroy all of the current threads and their proxy resolvers. - ReleaseAllExecutors(); - - // Provision a new executor, and run the SetPacScript request. On completion - // notification will be sent through |callback|. - Executor* executor = AddNewExecutor(); - executor->StartJob(new SetPacScriptJob(pac_url, pac_script, callback)); - return ERR_IO_PENDING; -} - -void MultiThreadedProxyResolver::CheckNoOutstandingUserRequests() const { - DCHECK(CalledOnValidThread()); - CHECK_EQ(0u, pending_jobs_.size()); - - for (ExecutorList::const_iterator it = executors_.begin(); - it != executors_.end(); ++it) { - const Executor* executor = *it; - Job* job = executor->outstanding_job(); - // The "has_user_callback()" is to exclude jobs for which the callback - // has already been invoked, or was not user-initiated (as in the case of - // lazy thread provisions). User-initiated jobs may !has_user_callback() - // when the callback has already been run. (Since we only clear the - // outstanding job AFTER the callback has been invoked, it is possible - // for a new request to be started from within the callback). - CHECK(!job || job->was_cancelled() || !job->has_user_callback()); - } -} - -void MultiThreadedProxyResolver::ReleaseAllExecutors() { - DCHECK(CalledOnValidThread()); - for (ExecutorList::iterator it = executors_.begin(); - it != executors_.end(); ++it) { - Executor* executor = *it; - executor->Destroy(); - } - executors_.clear(); -} - -MultiThreadedProxyResolver::Executor* -MultiThreadedProxyResolver::FindIdleExecutor() { - DCHECK(CalledOnValidThread()); - for (ExecutorList::iterator it = executors_.begin(); - it != executors_.end(); ++it) { - Executor* executor = *it; - if (!executor->outstanding_job()) - return executor; - } - return NULL; -} - -MultiThreadedProxyResolver::Executor* -MultiThreadedProxyResolver::AddNewExecutor() { - DCHECK(CalledOnValidThread()); - DCHECK_LT(executors_.size(), max_num_threads_); - // The "thread number" is used to give the thread a unique name. - int thread_number = executors_.size(); - ProxyResolver* resolver = resolver_factory_->CreateProxyResolver(); - Executor* executor = new Executor( - this, resolver, thread_number); - executors_.push_back(executor); - return executor; -} - -void MultiThreadedProxyResolver::OnExecutorReady(Executor* executor) { - DCHECK(CalledOnValidThread()); - if (pending_jobs_.empty()) - return; - - // Get the next job to process (FIFO). Transfer it from the pending queue - // to the executor. - scoped_refptr<Job> job = pending_jobs_.front(); - pending_jobs_.pop_front(); - executor->StartJob(job); -} - -} // namespace net diff --git a/net/proxy/multi_threaded_proxy_resolver.h b/net/proxy/multi_threaded_proxy_resolver.h deleted file mode 100644 index c192251..0000000 --- a/net/proxy/multi_threaded_proxy_resolver.h +++ /dev/null @@ -1,144 +0,0 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef NET_PROXY_MULTI_THREADED_PROXY_RESOLVER_H_ -#define NET_PROXY_MULTI_THREADED_PROXY_RESOLVER_H_ - -#include <deque> -#include <string> -#include <vector> - -#include "base/basictypes.h" -#include "base/non_thread_safe.h" -#include "base/ref_counted.h" -#include "base/scoped_ptr.h" -#include "net/proxy/proxy_resolver.h" - -namespace base { -class Thread; -} // namespace base - -namespace net { - -// ProxyResolverFactory is an interface for creating ProxyResolver instances. -class ProxyResolverFactory { - public: - explicit ProxyResolverFactory(bool resolvers_expect_pac_bytes) - : resolvers_expect_pac_bytes_(resolvers_expect_pac_bytes) {} - - virtual ~ProxyResolverFactory() {} - - // Creates a new ProxyResolver. The caller is responsible for freeing this - // object. - virtual ProxyResolver* CreateProxyResolver() = 0; - - bool resolvers_expect_pac_bytes() const { - return resolvers_expect_pac_bytes_; - } - - private: - bool resolvers_expect_pac_bytes_; - DISALLOW_COPY_AND_ASSIGN(ProxyResolverFactory); -}; - -// MultiThreadedProxyResolver is a ProxyResolver implementation that runs -// synchronous ProxyResolver implementations on worker threads. -// -// Threads are created lazily on demand, up to a maximum total. The advantage -// of having a pool of threads, is faster performance. In particular, being -// able to keep servicing PAC requests even if one blocks its execution. -// -// During initialization (SetPacScript), a single thread is spun up to test -// the script. If this succeeds, we cache the input script, and will re-use -// this to lazily provision any new threads as needed. -// -// For each new thread that we spawn, a corresponding new ProxyResolver is -// created using ProxyResolverFactory. -// -// Because we are creating multiple ProxyResolver instances, this means we -// are duplicating script contexts for what is ordinarily seen as being a -// single script. This can affect compatibility on some classes of PAC -// script: -// -// (a) Scripts whose initialization has external dependencies on network or -// time may end up successfully initializing on some threads, but not -// others. So depending on what thread services the request, the result -// may jump between several possibilities. -// -// (b) Scripts whose FindProxyForURL() depends on side-effects may now -// work differently. For example, a PAC script which was incrementing -// a global counter and using that to make a decision. In the -// multi-threaded model, each thread may have a different value for this -// counter, so it won't globally be seen as monotonically increasing! -class MultiThreadedProxyResolver : public ProxyResolver, public NonThreadSafe { - 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, with the exception of - // ProxyResolver::Shutdown() which will be called from the origin thread - // prior to destruction. - // - // The constructor takes ownership of |resolver_factory|. - MultiThreadedProxyResolver(ProxyResolverFactory* resolver_factory, - size_t max_num_threads); - - virtual ~MultiThreadedProxyResolver(); - - // ProxyResolver implementation: - virtual int GetProxyForURL(const GURL& url, - ProxyInfo* results, - CompletionCallback* callback, - RequestHandle* request, - const BoundNetLog& net_log); - virtual void CancelRequest(RequestHandle request); - virtual void CancelSetPacScript(); - virtual void PurgeMemory(); - - private: - class Executor; - class Job; - class SetPacScriptJob; - class GetProxyForURLJob; - // FIFO queue of pending jobs waiting to be started. - // TODO(eroman): Make this priority queue. - typedef std::deque<scoped_refptr<Job> > PendingJobsQueue; - typedef std::vector<scoped_refptr<Executor> > ExecutorList; - - // ProxyResolver implementation: - virtual int SetPacScript(const GURL& pac_url, - const string16& pac_script, - CompletionCallback* callback); - - // Asserts that there are no outstanding user-initiated jobs on any of the - // worker threads. - void CheckNoOutstandingUserRequests() const; - - // Stops and deletes all of the worker threads. - void ReleaseAllExecutors(); - - // 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_|. - Executor* AddNewExecutor(); - - // Starts the next job from |pending_jobs_| if possible. - void OnExecutorReady(Executor* executor); - - const scoped_ptr<ProxyResolverFactory> resolver_factory_; - const size_t max_num_threads_; - PendingJobsQueue pending_jobs_; - ExecutorList executors_; - bool was_set_pac_script_called_; - GURL current_pac_url_; - string16 current_pac_script_; -}; - -} // namespace net - -#endif // NET_PROXY_MULTI_THREADED_PROXY_RESOLVER_H_ diff --git a/net/proxy/proxy_resolver.h b/net/proxy/proxy_resolver.h index 37b1a3a..b200206 100644 --- a/net/proxy/proxy_resolver.h +++ b/net/proxy/proxy_resolver.h @@ -74,10 +74,6 @@ class ProxyResolver { // no-op implementation. virtual void PurgeMemory() {} - // Optional shutdown code to be run before destruction. This is only used - // by the multithreaded runner to signal cleanup from origin thread - virtual void Shutdown() {} - private: // Called to set the PAC script backend to use. If |pac_url| is invalid, // this is a request to use WPAD (auto detect). |pac_script| may be empty if diff --git a/net/proxy/proxy_resolver_js_bindings.cc b/net/proxy/proxy_resolver_js_bindings.cc index 08a7fe7..3bf1bad 100644 --- a/net/proxy/proxy_resolver_js_bindings.cc +++ b/net/proxy/proxy_resolver_js_bindings.cc @@ -110,12 +110,8 @@ class DefaultJSBindings : public ProxyResolverJSBindings { LOG(INFO) << "PAC-error: " << "line: " << line_number << ": " << message; } - virtual void Shutdown() { - host_resolver_->Shutdown(); - } - private: - // Helper to execute a synchronous DNS resolve, using the per-request + // Helper to execute a syncrhonous DNS resolve, using the per-request // DNS cache if there is one. int DnsResolveHelper(const HostResolver::RequestInfo& info, AddressList* address_list) { diff --git a/net/proxy/proxy_resolver_js_bindings.h b/net/proxy/proxy_resolver_js_bindings.h index b5912c4..834ea73 100644 --- a/net/proxy/proxy_resolver_js_bindings.h +++ b/net/proxy/proxy_resolver_js_bindings.h @@ -54,9 +54,6 @@ class ProxyResolverJSBindings { // if a line number is not applicable to this error. virtual void OnError(int line_number, const string16& error) = 0; - // Called before the thread running the proxy resolver is stopped. - virtual void Shutdown() = 0; - // Creates a default javascript bindings implementation that will: // - Send script error messages to LOG(INFO) // - Send script alert()s to LOG(INFO) diff --git a/net/proxy/proxy_resolver_v8.cc b/net/proxy/proxy_resolver_v8.cc index 4a47b345..5860872 100644 --- a/net/proxy/proxy_resolver_v8.cc +++ b/net/proxy/proxy_resolver_v8.cc @@ -640,10 +640,6 @@ void ProxyResolverV8::PurgeMemory() { context_->PurgeMemory(); } -void ProxyResolverV8::Shutdown() { - js_bindings_->Shutdown(); -} - int ProxyResolverV8::SetPacScript(const GURL& /*url*/, const string16& pac_script, CompletionCallback* /*callback*/) { diff --git a/net/proxy/proxy_resolver_v8.h b/net/proxy/proxy_resolver_v8.h index 20f31fc..bc4d731 100644 --- a/net/proxy/proxy_resolver_v8.h +++ b/net/proxy/proxy_resolver_v8.h @@ -50,7 +50,6 @@ class ProxyResolverV8 : public ProxyResolver { const BoundNetLog& net_log); virtual void CancelRequest(RequestHandle request); virtual void PurgeMemory(); - virtual void Shutdown(); ProxyResolverJSBindings* js_bindings() const { return js_bindings_.get(); } diff --git a/net/proxy/proxy_resolver_v8_unittest.cc b/net/proxy/proxy_resolver_v8_unittest.cc index ae664b2..2e09e4a 100644 --- a/net/proxy/proxy_resolver_v8_unittest.cc +++ b/net/proxy/proxy_resolver_v8_unittest.cc @@ -62,8 +62,6 @@ class MockJSBindings : public ProxyResolverJSBindings { errors_line_number.push_back(line_number); } - virtual void Shutdown() {} - // Mock values to return. std::string my_ip_address_result; std::string my_ip_address_ex_result; diff --git a/net/proxy/proxy_service.cc b/net/proxy/proxy_service.cc index 060745b..26cb1b6 100644 --- a/net/proxy/proxy_service.cc +++ b/net/proxy/proxy_service.cc @@ -15,7 +15,6 @@ #include "net/base/net_errors.h" #include "net/base/net_util.h" #include "net/proxy/init_proxy_resolver.h" -#include "net/proxy/multi_threaded_proxy_resolver.h" #include "net/proxy/proxy_config_service_fixed.h" #include "net/proxy/proxy_script_fetcher.h" #if defined(OS_WIN) @@ -30,6 +29,7 @@ #include "net/proxy/proxy_resolver.h" #include "net/proxy/proxy_resolver_js_bindings.h" #include "net/proxy/proxy_resolver_v8.h" +#include "net/proxy/single_threaded_proxy_resolver.h" #include "net/proxy/sync_host_resolver_bridge.h" #include "net/url_request/url_request_context.h" @@ -75,55 +75,6 @@ class ProxyResolverNull : public ProxyResolver { } }; -// This factory creates V8ProxyResolvers with appropriate javascript bindings. -class ProxyResolverFactoryForV8 : public ProxyResolverFactory { - public: - // Both |async_host_resolver| and |host_resolver_loop| must remain valid for - // duration of our lifetime. - ProxyResolverFactoryForV8(HostResolver* async_host_resolver, - MessageLoop* host_resolver_loop) - : ProxyResolverFactory(true /*expects_pac_bytes*/), - async_host_resolver_(async_host_resolver), - host_resolver_loop_(host_resolver_loop) { - } - - virtual ProxyResolver* CreateProxyResolver() { - // Create a synchronous host resolver wrapper that operates - // |async_host_resolver_| on |host_resolver_loop_|. - SyncHostResolverBridge* sync_host_resolver = - new SyncHostResolverBridge(async_host_resolver_, host_resolver_loop_); - - ProxyResolverJSBindings* js_bindings = - ProxyResolverJSBindings::CreateDefault(sync_host_resolver); - - // ProxyResolverV8 takes ownership of |js_bindings|. - return new ProxyResolverV8(js_bindings); - } - - private: - scoped_refptr<HostResolver> async_host_resolver_; - MessageLoop* host_resolver_loop_; -}; - -// Creates ProxyResolvers using a non-V8 implementation. -class ProxyResolverFactoryForNonV8 : public ProxyResolverFactory { - public: - ProxyResolverFactoryForNonV8() - : ProxyResolverFactory(false /*expects_pac_bytes*/) {} - - virtual ProxyResolver* CreateProxyResolver() { -#if defined(OS_WIN) - return new ProxyResolverWinHttp(); -#elif defined(OS_MACOSX) - return new ProxyResolverMac(); -#else - LOG(WARNING) << "PAC support disabled because there is no fallback " - "non-V8 implementation"; - return new ProxyResolverNull(); -#endif - } -}; - // ProxyService::PacRequest --------------------------------------------------- class ProxyService::PacRequest @@ -267,22 +218,32 @@ ProxyService* ProxyService::Create( URLRequestContext* url_request_context, NetLog* net_log, MessageLoop* io_loop) { + ProxyResolver* proxy_resolver = NULL; - ProxyResolverFactory* sync_resolver_factory; if (use_v8_resolver) { - sync_resolver_factory = - new ProxyResolverFactoryForV8( - url_request_context->host_resolver(), - io_loop); + // Use the IO thread's host resolver (but since it is not threadsafe, + // bridge requests from the PAC thread over to the IO thread). + SyncHostResolverBridge* sync_host_resolver = + new SyncHostResolverBridge(url_request_context->host_resolver(), + io_loop); + + // Send javascript errors and alerts to LOG(INFO). + ProxyResolverJSBindings* js_bindings = + ProxyResolverJSBindings::CreateDefault(sync_host_resolver); + + // Wrap the (synchronous) ProxyResolver implementation in a single-threaded + // asynchronous resolver. This version of SingleThreadedProxyResolver + // additionally aborts any synchronous host resolves to avoid deadlock + // during shutdown. + proxy_resolver = + new SingleThreadedProxyResolverUsingBridgedHostResolver( + new ProxyResolverV8(js_bindings), + sync_host_resolver); } else { - sync_resolver_factory = new ProxyResolverFactoryForNonV8(); + proxy_resolver = + new SingleThreadedProxyResolver(CreateNonV8ProxyResolver()); } - const size_t kMaxNumResolverThreads = 1u; - ProxyResolver* proxy_resolver = - new MultiThreadedProxyResolver(sync_resolver_factory, - kMaxNumResolverThreads); - ProxyService* proxy_service = new ProxyService(proxy_config_service, proxy_resolver, net_log); @@ -598,6 +559,19 @@ ProxyConfigService* ProxyService::CreateSystemProxyConfigService( #endif } +// static +ProxyResolver* ProxyService::CreateNonV8ProxyResolver() { +#if defined(OS_WIN) + return new ProxyResolverWinHttp(); +#elif defined(OS_MACOSX) + return new ProxyResolverMac(); +#else + LOG(WARNING) << "PAC support disabled because there is no fallback " + "non-V8 implementation"; + return new ProxyResolverNull(); +#endif +} + void ProxyService::UpdateConfig(const BoundNetLog& net_log) { bool is_first_update = !config_has_been_initialized(); diff --git a/net/proxy/proxy_service.h b/net/proxy/proxy_service.h index 5c981f8..22435e4 100644 --- a/net/proxy/proxy_service.h +++ b/net/proxy/proxy_service.h @@ -181,6 +181,10 @@ class ProxyService : public base::RefCountedThreadSafe<ProxyService>, ~ProxyService(); + // Creates a proxy resolver appropriate for this platform that doesn't rely + // on V8. + static ProxyResolver* CreateNonV8ProxyResolver(); + // Identifies the proxy configuration. ProxyConfig::ID config_id() const { return config_.id(); } diff --git a/net/proxy/single_threaded_proxy_resolver.cc b/net/proxy/single_threaded_proxy_resolver.cc new file mode 100644 index 0000000..2fdc3ac --- /dev/null +++ b/net/proxy/single_threaded_proxy_resolver.cc @@ -0,0 +1,353 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/proxy/single_threaded_proxy_resolver.h" + +#include "base/message_loop.h" +#include "base/thread.h" +#include "net/base/capturing_net_log.h" +#include "net/base/net_errors.h" +#include "net/proxy/proxy_info.h" + +namespace net { + +namespace { + +class PurgeMemoryTask : public base::RefCountedThreadSafe<PurgeMemoryTask> { + public: + explicit PurgeMemoryTask(ProxyResolver* resolver) : resolver_(resolver) {} + void PurgeMemory() { resolver_->PurgeMemory(); } + private: + friend class base::RefCountedThreadSafe<PurgeMemoryTask>; + ~PurgeMemoryTask() {} + ProxyResolver* resolver_; +}; + +} // namespace + +// SingleThreadedProxyResolver::SetPacScriptTask ------------------------------ + +// Runs on the worker thread to call ProxyResolver::SetPacScript. +class SingleThreadedProxyResolver::SetPacScriptTask + : public base::RefCountedThreadSafe< + SingleThreadedProxyResolver::SetPacScriptTask> { + public: + SetPacScriptTask(SingleThreadedProxyResolver* coordinator, + const GURL& pac_url, + const string16& pac_script, + CompletionCallback* callback) + : coordinator_(coordinator), + callback_(callback), + pac_script_(pac_script), + pac_url_(pac_url), + origin_loop_(MessageLoop::current()) { + DCHECK(callback); + } + + // Start the SetPacScript request on the worker thread. + void Start() { + coordinator_->thread()->message_loop()->PostTask( + FROM_HERE, NewRunnableMethod(this, &SetPacScriptTask::DoRequest, + coordinator_->resolver_.get())); + } + + void Cancel() { + // Clear these to inform RequestComplete that it should not try to + // access them. + coordinator_ = NULL; + callback_ = NULL; + } + + // Returns true if Cancel() has been called. + bool was_cancelled() const { return callback_ == NULL; } + + private: + friend class base::RefCountedThreadSafe< + SingleThreadedProxyResolver::SetPacScriptTask>; + + ~SetPacScriptTask() {} + + // Runs on the worker thread. + void DoRequest(ProxyResolver* resolver) { + int rv = resolver->expects_pac_bytes() ? + resolver->SetPacScriptByData(pac_script_, NULL) : + resolver->SetPacScriptByUrl(pac_url_, NULL); + + DCHECK_NE(rv, ERR_IO_PENDING); + origin_loop_->PostTask(FROM_HERE, + NewRunnableMethod(this, &SetPacScriptTask::RequestComplete, rv)); + } + + // 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()) { + CompletionCallback* callback = callback_; + coordinator_->RemoveOutstandingSetPacScriptTask(this); + callback->Run(result_code); + } + } + + // Must only be used on the "origin" thread. + SingleThreadedProxyResolver* coordinator_; + CompletionCallback* callback_; + string16 pac_script_; + GURL pac_url_; + + // Usable from within DoQuery on the worker thread. + MessageLoop* origin_loop_; +}; + +// SingleThreadedProxyResolver::Job ------------------------------------------- + +class SingleThreadedProxyResolver::Job + : public base::RefCountedThreadSafe<SingleThreadedProxyResolver::Job> { + public: + // |coordinator| -- the SingleThreadedProxyResolver that owns this job. + // |url| -- the URL of the query. + // |results| -- the structure to fill with proxy resolve results. + Job(SingleThreadedProxyResolver* coordinator, + const GURL& url, + ProxyInfo* results, + CompletionCallback* callback, + const BoundNetLog& net_log) + : coordinator_(coordinator), + callback_(callback), + results_(results), + net_log_(net_log), + url_(url), + is_started_(false), + origin_loop_(MessageLoop::current()) { + DCHECK(callback); + } + + // Start the resolve proxy request on the worker thread. + void Start() { + is_started_ = true; + + size_t load_log_bound = 100; + + coordinator_->thread()->message_loop()->PostTask( + FROM_HERE, NewRunnableMethod(this, &Job::DoQuery, + coordinator_->resolver_.get(), + load_log_bound)); + } + + bool is_started() const { return is_started_; } + + void Cancel() { + // Clear these to inform QueryComplete that it should not try to + // access them. + coordinator_ = NULL; + callback_ = NULL; + results_ = NULL; + } + + // Returns true if Cancel() has been called. + bool was_cancelled() const { return callback_ == NULL; } + + BoundNetLog* net_log() { return &net_log_; } + + private: + friend class base::RefCountedThreadSafe<SingleThreadedProxyResolver::Job>; + + ~Job() {} + + // Runs on the worker thread. + void DoQuery(ProxyResolver* resolver, size_t load_log_bound) { + worker_log_.reset(new CapturingNetLog(load_log_bound)); + BoundNetLog bound_worker_log(NetLog::Source(), worker_log_.get()); + + int rv = resolver->GetProxyForURL(url_, &results_buf_, NULL, NULL, + bound_worker_log); + DCHECK_NE(rv, ERR_IO_PENDING); + + origin_loop_->PostTask(FROM_HERE, + NewRunnableMethod(this, &Job::QueryComplete, rv)); + } + + // Runs the completion callback on the origin thread. + void QueryComplete(int result_code) { + // Merge the load log that was generated on the worker thread, into the + // main log. + CapturingBoundNetLog bound_worker_log(NetLog::Source(), + worker_log_.release()); + bound_worker_log.AppendTo(net_log_); + + // 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_); + } + callback_->Run(result_code); + + // We check for cancellation once again, in case the callback deleted + // the owning ProxyService (whose destructor will in turn cancel us). + if (!was_cancelled()) + coordinator_->RemoveFrontOfJobsQueueAndStartNext(this); + } + } + + // Must only be used on the "origin" thread. + SingleThreadedProxyResolver* coordinator_; + CompletionCallback* callback_; + ProxyInfo* results_; + BoundNetLog net_log_; + GURL url_; + bool is_started_; + + // Usable from within DoQuery on the worker thread. + ProxyInfo results_buf_; + MessageLoop* origin_loop_; + + // Used to pass the captured events between DoQuery [worker thread] and + // QueryComplete [origin thread]. + scoped_ptr<CapturingNetLog> worker_log_; +}; + +// SingleThreadedProxyResolver ------------------------------------------------ + +SingleThreadedProxyResolver::SingleThreadedProxyResolver( + ProxyResolver* resolver) + : ProxyResolver(resolver->expects_pac_bytes()), + resolver_(resolver) { +} + +SingleThreadedProxyResolver::~SingleThreadedProxyResolver() { + // Cancel the inprogress job (if any), and free the rest. + for (PendingJobsQueue::iterator it = pending_jobs_.begin(); + it != pending_jobs_.end(); + ++it) { + (*it)->Cancel(); + } + + if (outstanding_set_pac_script_task_) + outstanding_set_pac_script_task_->Cancel(); + + // Note that |thread_| is destroyed before |resolver_|. This is important + // since |resolver_| could be running on |thread_|. +} + +int SingleThreadedProxyResolver::GetProxyForURL(const GURL& url, + ProxyInfo* results, + CompletionCallback* callback, + RequestHandle* request, + const BoundNetLog& net_log) { + DCHECK(callback); + + scoped_refptr<Job> job = new Job(this, url, results, callback, net_log); + bool is_first_job = pending_jobs_.empty(); + pending_jobs_.push_back(job); // Jobs can never finish synchronously. + + if (is_first_job) { + // If there is nothing already running, start the job now. + EnsureThreadStarted(); + job->Start(); + } else { + // Otherwise the job will get started eventually by ProcessPendingJobs(). + job->net_log()->BeginEvent( + NetLog::TYPE_WAITING_FOR_SINGLE_PROXY_RESOLVER_THREAD, NULL); + } + + // Completion will be notified through |callback|, unless the caller cancels + // the request using |request|. + if (request) + *request = reinterpret_cast<RequestHandle>(job.get()); + + return ERR_IO_PENDING; +} + +// There are three states of the request we need to handle: +// (1) Not started (just sitting in the queue). +// (2) Executing Job::DoQuery in the worker thread. +// (3) Waiting for Job::QueryComplete to be run on the origin thread. +void SingleThreadedProxyResolver::CancelRequest(RequestHandle req) { + DCHECK(req); + + Job* job = reinterpret_cast<Job*>(req); + + bool is_active_job = job->is_started() && !pending_jobs_.empty() && + pending_jobs_.front().get() == job; + + job->Cancel(); + + if (is_active_job) { + RemoveFrontOfJobsQueueAndStartNext(job); + return; + } + + // Otherwise just delete the job from the queue. + PendingJobsQueue::iterator it = std::find( + pending_jobs_.begin(), pending_jobs_.end(), job); + DCHECK(it != pending_jobs_.end()); + pending_jobs_.erase(it); +} + +void SingleThreadedProxyResolver::CancelSetPacScript() { + DCHECK(outstanding_set_pac_script_task_); + outstanding_set_pac_script_task_->Cancel(); + outstanding_set_pac_script_task_ = NULL; +} + +void SingleThreadedProxyResolver::PurgeMemory() { + if (thread_.get()) { + scoped_refptr<PurgeMemoryTask> helper(new PurgeMemoryTask(resolver_.get())); + thread_->message_loop()->PostTask(FROM_HERE, + NewRunnableMethod(helper.get(), &PurgeMemoryTask::PurgeMemory)); + } +} + +int SingleThreadedProxyResolver::SetPacScript( + const GURL& pac_url, + const string16& pac_script, + CompletionCallback* callback) { + EnsureThreadStarted(); + DCHECK(!outstanding_set_pac_script_task_); + + SetPacScriptTask* task = new SetPacScriptTask( + this, pac_url, pac_script, callback); + outstanding_set_pac_script_task_ = task; + task->Start(); + return ERR_IO_PENDING; +} + +void SingleThreadedProxyResolver::EnsureThreadStarted() { + if (!thread_.get()) { + thread_.reset(new base::Thread("pac-thread")); + thread_->Start(); + } +} + +void SingleThreadedProxyResolver::ProcessPendingJobs() { + if (pending_jobs_.empty()) + return; + + // Get the next job to process (FIFO). + Job* job = pending_jobs_.front().get(); + if (job->is_started()) + return; + + job->net_log()->EndEvent( + NetLog::TYPE_WAITING_FOR_SINGLE_PROXY_RESOLVER_THREAD, NULL); + + EnsureThreadStarted(); + job->Start(); +} + +void SingleThreadedProxyResolver::RemoveFrontOfJobsQueueAndStartNext( + Job* expected_job) { + DCHECK_EQ(expected_job, pending_jobs_.front().get()); + pending_jobs_.pop_front(); + + // Start next work item. + ProcessPendingJobs(); +} + +void SingleThreadedProxyResolver::RemoveOutstandingSetPacScriptTask( + SetPacScriptTask* task) { + DCHECK_EQ(outstanding_set_pac_script_task_.get(), task); + outstanding_set_pac_script_task_ = NULL; +} + +} // namespace net diff --git a/net/proxy/single_threaded_proxy_resolver.h b/net/proxy/single_threaded_proxy_resolver.h new file mode 100644 index 0000000..1c9582c --- /dev/null +++ b/net/proxy/single_threaded_proxy_resolver.h @@ -0,0 +1,94 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef NET_PROXY_SINGLE_THREADED_PROXY_RESOLVER_H_ +#define NET_PROXY_SINGLE_THREADED_PROXY_RESOLVER_H_ + +#include <deque> +#include <string> + +#include "base/ref_counted.h" +#include "base/scoped_ptr.h" +#include "net/proxy/proxy_resolver.h" + +namespace base { +class Thread; +} // namespace base + +namespace net { + +// ProxyResolver implementation that wraps a synchronous ProxyResolver, and +// runs it on a single worker thread. If multiple requests accumulate, they +// are serviced in FIFO order. +class SingleThreadedProxyResolver : public ProxyResolver { + public: + // |resolver| is a synchronous ProxyResolver implementation. It doesn't + // have to be thread-safe, since it is run on exactly one thread. The + // constructor takes ownership of |resolver|. + explicit SingleThreadedProxyResolver(ProxyResolver* resolver); + + virtual ~SingleThreadedProxyResolver(); + + // ProxyResolver implementation: + virtual int GetProxyForURL(const GURL& url, + ProxyInfo* results, + CompletionCallback* callback, + RequestHandle* request, + const BoundNetLog& net_log); + virtual void CancelRequest(RequestHandle request); + virtual void CancelSetPacScript(); + virtual void PurgeMemory(); + + protected: + // The wrapped (synchronous) ProxyResolver. + ProxyResolver* resolver() { return resolver_.get(); } + + private: + // Refcounted helper class that bridges between origin thread and worker + // thread. + class Job; + friend class Job; + class SetPacScriptTask; + friend class SetPacScriptTask; + // FIFO queue that contains the in-progress job, and any pending jobs. + typedef std::deque<scoped_refptr<Job> > PendingJobsQueue; + + base::Thread* thread() { return thread_.get(); } + + // ProxyResolver implementation: + virtual int SetPacScript(const GURL& pac_url, + const string16& pac_script, + CompletionCallback* callback); + + // Starts the worker thread if it isn't already running. + void EnsureThreadStarted(); + + // Starts the next job from |pending_jobs_| if possible. + void ProcessPendingJobs(); + + // Removes the front entry of the jobs queue. |expected_job| is our + // expectation of what the front of the job queue is; it is only used by + // DCHECK for verification purposes. + void RemoveFrontOfJobsQueueAndStartNext(Job* expected_job); + + // Clears |outstanding_set_pac_script_task_|. + // Called when |task| has just finished. + void RemoveOutstandingSetPacScriptTask(SetPacScriptTask* task); + + // The synchronous resolver implementation. + scoped_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_|. + scoped_ptr<base::Thread> thread_; + + PendingJobsQueue pending_jobs_; + scoped_refptr<SetPacScriptTask> outstanding_set_pac_script_task_; +}; + +} // namespace net + +#endif // NET_PROXY_SINGLE_THREADED_PROXY_RESOLVER_H_ diff --git a/net/proxy/multi_threaded_proxy_resolver_unittest.cc b/net/proxy/single_threaded_proxy_resolver_unittest.cc index f451c3a..a84253c 100644 --- a/net/proxy/multi_threaded_proxy_resolver_unittest.cc +++ b/net/proxy/single_threaded_proxy_resolver_unittest.cc @@ -2,9 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "net/proxy/multi_threaded_proxy_resolver.h" - -#include "base/stl_util-inl.h" #include "base/string_util.h" #include "base/waitable_event.h" #include "googleurl/src/gurl.h" @@ -13,14 +10,14 @@ #include "net/base/net_errors.h" #include "net/base/test_completion_callback.h" #include "net/proxy/proxy_info.h" +#include "net/proxy/single_threaded_proxy_resolver.h" #include "testing/gtest/include/gtest/gtest.h" namespace net { - namespace { // A synchronous mock ProxyResolver implementation, which can be used in -// conjunction with MultiThreadedProxyResolver. +// conjunction with SingleThreadedProxyResolver. // - returns a single-item proxy list with the query's host. class MockProxyResolver : public ProxyResolver { public: @@ -72,7 +69,6 @@ class MockProxyResolver : public ProxyResolver { } int purge_count() const { return purge_count_; } - int request_count() const { return request_count_; } const string16& last_pac_script() const { return last_pac_script_; } @@ -83,7 +79,7 @@ class MockProxyResolver : public ProxyResolver { private: void CheckIsOnWorkerThread() { // We should be running on the worker thread -- while we don't know the - // message loop of MultiThreadedProxyResolver's worker thread, we do + // message loop of SingleThreadedProxyResolver's worker thread, we do // know that it is going to be distinct from the loop running the // test, so at least make sure it isn't the main loop. EXPECT_NE(MessageLoop::current(), wrong_loop_); @@ -145,87 +141,9 @@ class BlockableProxyResolver : public MockProxyResolver { base::WaitableEvent blocked_; }; -// ForwardingProxyResolver forwards all requests to |impl|. -class ForwardingProxyResolver : public ProxyResolver { - public: - explicit ForwardingProxyResolver(ProxyResolver* impl) - : ProxyResolver(impl->expects_pac_bytes()), - impl_(impl) {} - - virtual int GetProxyForURL(const GURL& query_url, - ProxyInfo* results, - CompletionCallback* callback, - RequestHandle* request, - const BoundNetLog& net_log) { - return impl_->GetProxyForURL( - query_url, results, callback, request, net_log); - } - - virtual void CancelRequest(RequestHandle request) { - impl_->CancelRequest(request); - } - - virtual int SetPacScript(const GURL& pac_url, - const string16& script, - CompletionCallback* callback) { - if (impl_->expects_pac_bytes()) - return impl_->SetPacScriptByData(script, callback); - else - return impl_->SetPacScriptByUrl(pac_url, callback); - } - - virtual void PurgeMemory() { - impl_->PurgeMemory(); - } - - private: - ProxyResolver* impl_; -}; - -// This factory returns ProxyResolvers that forward all requests to -// |resolver|. -class ForwardingProxyResolverFactory : public ProxyResolverFactory { - public: - explicit ForwardingProxyResolverFactory(ProxyResolver* resolver) - : ProxyResolverFactory(resolver->expects_pac_bytes()), - resolver_(resolver) {} - - virtual ProxyResolver* CreateProxyResolver() { - return new ForwardingProxyResolver(resolver_); - } - - private: - ProxyResolver* resolver_; -}; - -// This factory returns new instances of BlockableProxyResolver. -class BlockableProxyResolverFactory : public ProxyResolverFactory { - public: - BlockableProxyResolverFactory() : ProxyResolverFactory(true) {} - - ~BlockableProxyResolverFactory() { - STLDeleteElements(&resolvers_); - } - - virtual ProxyResolver* CreateProxyResolver() { - BlockableProxyResolver* resolver = new BlockableProxyResolver; - resolvers_.push_back(resolver); - return new ForwardingProxyResolver(resolver); - } - - std::vector<BlockableProxyResolver*> resolvers() { - return resolvers_; - } - - private: - std::vector<BlockableProxyResolver*> resolvers_; -}; - -TEST(MultiThreadedProxyResolverTest, SingleThread_Basic) { - const size_t kNumThreads = 1u; - scoped_ptr<MockProxyResolver> mock(new MockProxyResolver); - MultiThreadedProxyResolver resolver( - new ForwardingProxyResolverFactory(mock.get()), kNumThreads); +TEST(SingleThreadedProxyResolverTest, Basic) { + MockProxyResolver* mock = new MockProxyResolver; + SingleThreadedProxyResolver resolver(mock); int rv; @@ -255,11 +173,7 @@ TEST(MultiThreadedProxyResolverTest, SingleThread_Basic) { // The mock proxy resolver should have written 1 log entry. And // on completion, this should have been copied into |log0|. - // We also have 1 log entry that was emitted by the - // MultiThreadedProxyResolver. - ASSERT_EQ(2u, log0.entries().size()); - EXPECT_EQ(NetLog::TYPE_SUBMITTED_TO_RESOLVER_THREAD, - log0.entries()[0].type); + EXPECT_EQ(1u, log0.entries().size()); // Start 3 more requests (request1 to request3). @@ -311,20 +225,12 @@ TEST(MultiThreadedProxyResolverTest, SingleThread_Basic) { // Tests that the NetLog is updated to include the time the request was waiting // to be scheduled to a thread. -TEST(MultiThreadedProxyResolverTest, - SingleThread_UpdatesNetLogWithThreadWait) { - const size_t kNumThreads = 1u; - scoped_ptr<BlockableProxyResolver> mock(new BlockableProxyResolver); - MultiThreadedProxyResolver resolver( - new ForwardingProxyResolverFactory(mock.get()), kNumThreads); +TEST(SingleThreadedProxyResolverTest, UpdatesNetLogWithThreadWait) { + BlockableProxyResolver* mock = new BlockableProxyResolver; + SingleThreadedProxyResolver resolver(mock); int rv; - // Initialize the resolver. - TestCompletionCallback init_callback; - rv = resolver.SetPacScriptByData(ASCIIToUTF16("foo"), &init_callback); - EXPECT_EQ(OK, init_callback.WaitForResult()); - // Block the proxy resolver, so no request can complete. mock->Block(); @@ -359,53 +265,42 @@ TEST(MultiThreadedProxyResolverTest, mock->Unblock(); // Check that request 0 completed as expected. - // The NetLog has 1 entry that came from the MultiThreadedProxyResolver, and - // 1 entry from the mock proxy resolver. + // The NetLog only has 1 entry (that came from the mock proxy resolver.) EXPECT_EQ(0, callback0.WaitForResult()); EXPECT_EQ("PROXY request0:80", results0.ToPacString()); - ASSERT_EQ(2u, log0.entries().size()); - EXPECT_EQ(NetLog::TYPE_SUBMITTED_TO_RESOLVER_THREAD, - log0.entries()[0].type); + ASSERT_EQ(1u, log0.entries().size()); // Check that request 1 completed as expected. EXPECT_EQ(1, callback1.WaitForResult()); EXPECT_EQ("PROXY request1:80", results1.ToPacString()); - ASSERT_EQ(4u, log1.entries().size()); + ASSERT_EQ(3u, log1.entries().size()); EXPECT_TRUE(LogContainsBeginEvent( log1.entries(), 0, - NetLog::TYPE_WAITING_FOR_PROXY_RESOLVER_THREAD)); + NetLog::TYPE_WAITING_FOR_SINGLE_PROXY_RESOLVER_THREAD)); EXPECT_TRUE(LogContainsEndEvent( log1.entries(), 1, - NetLog::TYPE_WAITING_FOR_PROXY_RESOLVER_THREAD)); + NetLog::TYPE_WAITING_FOR_SINGLE_PROXY_RESOLVER_THREAD)); // Check that request 2 completed as expected. EXPECT_EQ(2, callback2.WaitForResult()); EXPECT_EQ("PROXY request2:80", results2.ToPacString()); - ASSERT_EQ(4u, log2.entries().size()); + ASSERT_EQ(3u, log2.entries().size()); EXPECT_TRUE(LogContainsBeginEvent( log2.entries(), 0, - NetLog::TYPE_WAITING_FOR_PROXY_RESOLVER_THREAD)); + NetLog::TYPE_WAITING_FOR_SINGLE_PROXY_RESOLVER_THREAD)); EXPECT_TRUE(LogContainsEndEvent( log2.entries(), 1, - NetLog::TYPE_WAITING_FOR_PROXY_RESOLVER_THREAD)); + NetLog::TYPE_WAITING_FOR_SINGLE_PROXY_RESOLVER_THREAD)); } // Cancel a request which is in progress, and then cancel a request which // is pending. -TEST(MultiThreadedProxyResolverTest, SingleThread_CancelRequest) { - const size_t kNumThreads = 1u; - scoped_ptr<BlockableProxyResolver> mock(new BlockableProxyResolver); - MultiThreadedProxyResolver resolver( - new ForwardingProxyResolverFactory(mock.get()), - kNumThreads); +TEST(SingleThreadedProxyResolverTest, CancelRequest) { + BlockableProxyResolver* mock = new BlockableProxyResolver; + SingleThreadedProxyResolver resolver(mock); int rv; - // Initialize the resolver. - TestCompletionCallback init_callback; - rv = resolver.SetPacScriptByData(ASCIIToUTF16("foo"), &init_callback); - EXPECT_EQ(OK, init_callback.WaitForResult()); - // Block the proxy resolver, so no request can complete. mock->Block(); @@ -466,22 +361,15 @@ TEST(MultiThreadedProxyResolverTest, SingleThread_CancelRequest) { EXPECT_FALSE(callback2.have_result()); } -// Test that deleting MultiThreadedProxyResolver while requests are +// Test that deleting SingleThreadedProxyResolver while requests are // outstanding cancels them (and doesn't leak anything). -TEST(MultiThreadedProxyResolverTest, SingleThread_CancelRequestByDeleting) { - const size_t kNumThreads = 1u; - scoped_ptr<BlockableProxyResolver> mock(new BlockableProxyResolver); - scoped_ptr<MultiThreadedProxyResolver> resolver( - new MultiThreadedProxyResolver( - new ForwardingProxyResolverFactory(mock.get()), kNumThreads)); +TEST(SingleThreadedProxyResolverTest, CancelRequestByDeleting) { + BlockableProxyResolver* mock = new BlockableProxyResolver; + scoped_ptr<SingleThreadedProxyResolver> resolver( + new SingleThreadedProxyResolver(mock)); int rv; - // Initialize the resolver. - TestCompletionCallback init_callback; - rv = resolver->SetPacScriptByData(ASCIIToUTF16("foo"), &init_callback); - EXPECT_EQ(OK, init_callback.WaitForResult()); - // Block the proxy resolver, so no request can complete. mock->Block(); @@ -509,14 +397,14 @@ TEST(MultiThreadedProxyResolverTest, SingleThread_CancelRequestByDeleting) { mock->WaitUntilBlocked(); // Add some latency, to improve the chance that when - // MultiThreadedProxyResolver is deleted below we are still running inside + // SingleThreadedProxyResolver is deleted below we are still running inside // of the worker thread. The test will pass regardless, so this race doesn't // cause flakiness. However the destruction during execution is a more // interesting case to test. mock->SetResolveLatency(100); // Unblock the worker thread and delete the underlying - // MultiThreadedProxyResolver immediately. + // SingleThreadedProxyResolver immediately. mock->Unblock(); resolver.reset(); @@ -530,213 +418,59 @@ TEST(MultiThreadedProxyResolverTest, SingleThread_CancelRequestByDeleting) { } // Cancel an outstanding call to SetPacScriptByData(). -TEST(MultiThreadedProxyResolverTest, SingleThread_CancelSetPacScript) { - const size_t kNumThreads = 1u; - scoped_ptr<BlockableProxyResolver> mock(new BlockableProxyResolver); - MultiThreadedProxyResolver resolver( - new ForwardingProxyResolverFactory(mock.get()), kNumThreads); - - int rv; - - TestCompletionCallback set_pac_script_callback; - rv = resolver.SetPacScriptByData(ASCIIToUTF16("data"), - &set_pac_script_callback); - EXPECT_EQ(ERR_IO_PENDING, rv); - - // Cancel the SetPacScriptByData request. - resolver.CancelSetPacScript(); - - // Start another SetPacScript request - TestCompletionCallback set_pac_script_callback2; - rv = resolver.SetPacScriptByData(ASCIIToUTF16("data2"), - &set_pac_script_callback2); - EXPECT_EQ(ERR_IO_PENDING, rv); - - // Wait for the initialization to complete. - - rv = set_pac_script_callback2.WaitForResult(); - EXPECT_EQ(0, rv); - EXPECT_EQ(ASCIIToUTF16("data2"), mock->last_pac_script()); - - // The first SetPacScript callback should never have been completed. - EXPECT_FALSE(set_pac_script_callback.have_result()); -} - -// Tests setting the PAC script once, lazily creating new threads, and -// cancelling requests. -TEST(MultiThreadedProxyResolverTest, ThreeThreads_Basic) { - const size_t kNumThreads = 3u; - BlockableProxyResolverFactory* factory = new BlockableProxyResolverFactory; - MultiThreadedProxyResolver resolver(factory, kNumThreads); +TEST(SingleThreadedProxyResolverTest, CancelSetPacScript) { + BlockableProxyResolver* mock = new BlockableProxyResolver; + SingleThreadedProxyResolver resolver(mock); int rv; - EXPECT_TRUE(resolver.expects_pac_bytes()); + // Block the proxy resolver, so no request can complete. + mock->Block(); - // Call SetPacScriptByData() -- verify that it reaches the synchronous - // resolver. - TestCompletionCallback set_script_callback; - rv = resolver.SetPacScriptByData(ASCIIToUTF16("pac script bytes"), - &set_script_callback); - EXPECT_EQ(ERR_IO_PENDING, rv); - EXPECT_EQ(OK, set_script_callback.WaitForResult()); - // One thread has been provisioned (i.e. one ProxyResolver was created). - ASSERT_EQ(1u, factory->resolvers().size()); - EXPECT_EQ(ASCIIToUTF16("pac script bytes"), - factory->resolvers()[0]->last_pac_script()); - - const int kNumRequests = 9; - TestCompletionCallback callback[kNumRequests]; - ProxyInfo results[kNumRequests]; - ProxyResolver::RequestHandle request[kNumRequests]; - - // Start request 0 -- this should run on thread 0 as there is nothing else - // going on right now. + // Start request 0. + ProxyResolver::RequestHandle request0; + TestCompletionCallback callback0; + ProxyInfo results0; rv = resolver.GetProxyForURL( - GURL("http://request0"), &results[0], &callback[0], &request[0], - BoundNetLog()); - EXPECT_EQ(ERR_IO_PENDING, rv); - - // Wait for request 0 to finish. - rv = callback[0].WaitForResult(); - EXPECT_EQ(0, rv); - EXPECT_EQ("PROXY request0:80", results[0].ToPacString()); - ASSERT_EQ(1u, factory->resolvers().size()); - EXPECT_EQ(1, factory->resolvers()[0]->request_count()); - - MessageLoop::current()->RunAllPending(); - - // We now start 8 requests in parallel -- this will cause the maximum of - // three threads to be provisioned (an additional two from what we already - // have). - - for (int i = 1; i < kNumRequests; ++i) { - rv = resolver.GetProxyForURL( - GURL(StringPrintf("http://request%d", i)), &results[i], &callback[i], - &request[i], BoundNetLog()); - EXPECT_EQ(ERR_IO_PENDING, rv); - } - - // We should now have a total of 3 threads, each with its own ProxyResolver - // that will get initialized with the same data. (We check this later since - // the assignment happens on the worker threads and may not have occurred - // yet.) - ASSERT_EQ(3u, factory->resolvers().size()); - - // Cancel 3 of the 8 oustanding requests. - resolver.CancelRequest(request[1]); - resolver.CancelRequest(request[3]); - resolver.CancelRequest(request[6]); - - // Wait for the remaining requests to complete. - int kNonCancelledRequests[] = {2, 4, 5, 7, 8}; - for (size_t i = 0; i < arraysize(kNonCancelledRequests); ++i) { - int request_index = kNonCancelledRequests[i]; - EXPECT_GE(callback[request_index].WaitForResult(), 0); - } - - // Check that the cancelled requests never invoked their callback. - EXPECT_FALSE(callback[1].have_result()); - EXPECT_FALSE(callback[3].have_result()); - EXPECT_FALSE(callback[6].have_result()); - - // We call SetPacScript again, solely to stop the current worker threads. - // (That way we can test to see the values observed by the synchronous - // resolvers in a non-racy manner). - TestCompletionCallback set_script_callback2; - rv = resolver.SetPacScriptByData(ASCIIToUTF16("xyz"), &set_script_callback2); + GURL("http://request0"), &results0, &callback0, &request0, BoundNetLog()); EXPECT_EQ(ERR_IO_PENDING, rv); - EXPECT_EQ(OK, set_script_callback2.WaitForResult()); - ASSERT_EQ(4u, factory->resolvers().size()); - - for (int i = 0; i < 3; ++i) { - EXPECT_EQ(ASCIIToUTF16("pac script bytes"), - factory->resolvers()[i]->last_pac_script()) << "i=" << i; - } - - EXPECT_EQ(ASCIIToUTF16("xyz"), - factory->resolvers()[3]->last_pac_script()); - - // We don't know the exact ordering that requests ran on threads with, - // but we do know the total count that should have reached the threads. - // 8 total were submitted, and three were cancelled. Of the three that - // were cancelled, one of them (request 1) was cancelled after it had - // already been posted to the worker thread. So the resolvers will - // have seen 6 total (and 1 from the run prior). - ASSERT_EQ(4u, factory->resolvers().size()); - int total_count = 0; - for (int i = 0; i < 3; ++i) { - total_count += factory->resolvers()[i]->request_count(); - } - EXPECT_EQ(7, total_count); -} - -// Tests using two threads. The first request hangs the first thread. Checks -// that other requests are able to complete while this first request remains -// stalled. -TEST(MultiThreadedProxyResolverTest, OneThreadBlocked) { - const size_t kNumThreads = 2u; - BlockableProxyResolverFactory* factory = new BlockableProxyResolverFactory; - MultiThreadedProxyResolver resolver(factory, kNumThreads); - - int rv; - EXPECT_TRUE(resolver.expects_pac_bytes()); + // Wait until requests 0 reaches the worker thread. + mock->WaitUntilBlocked(); - // Initialize the resolver. - TestCompletionCallback set_script_callback; - rv = resolver.SetPacScriptByData(ASCIIToUTF16("pac script bytes"), - &set_script_callback); + TestCompletionCallback set_pac_script_callback; + rv = resolver.SetPacScriptByData(ASCIIToUTF16("data"), + &set_pac_script_callback); EXPECT_EQ(ERR_IO_PENDING, rv); - EXPECT_EQ(OK, set_script_callback.WaitForResult()); - // One thread has been provisioned (i.e. one ProxyResolver was created). - ASSERT_EQ(1u, factory->resolvers().size()); - EXPECT_EQ(ASCIIToUTF16("pac script bytes"), - factory->resolvers()[0]->last_pac_script()); - const int kNumRequests = 4; - TestCompletionCallback callback[kNumRequests]; - ProxyInfo results[kNumRequests]; - ProxyResolver::RequestHandle request[kNumRequests]; - - // Start a request that will block the first thread. + // Cancel the SetPacScriptByData request (it can't have finished yet, + // since the single-thread is currently blocked). + resolver.CancelSetPacScript(); - factory->resolvers()[0]->Block(); + // Start 1 more request. + TestCompletionCallback callback1; + ProxyInfo results1; rv = resolver.GetProxyForURL( - GURL("http://request0"), &results[0], &callback[0], &request[0], - BoundNetLog()); - + GURL("http://request1"), &results1, &callback1, NULL, BoundNetLog()); EXPECT_EQ(ERR_IO_PENDING, rv); - factory->resolvers()[0]->WaitUntilBlocked(); - // Start 3 more requests -- they should all be serviced by thread #2 - // since thread #1 is blocked. + // Unblock the worker thread so the requests can continue running. + mock->Unblock(); - for (int i = 1; i < kNumRequests; ++i) { - rv = resolver.GetProxyForURL( - GURL(StringPrintf("http://request%d", i)), - &results[i], &callback[i], &request[i], BoundNetLog()); - EXPECT_EQ(ERR_IO_PENDING, rv); - } + // Wait for requests 0 and 1 to finish. - // Wait for the three requests to complete (they should complete in FIFO - // order). - for (int i = 1; i < kNumRequests; ++i) { - EXPECT_EQ(i - 1, callback[i].WaitForResult()); - } + rv = callback0.WaitForResult(); + EXPECT_EQ(0, rv); + EXPECT_EQ("PROXY request0:80", results0.ToPacString()); - // Unblock the first thread. - factory->resolvers()[0]->Unblock(); - EXPECT_EQ(0, callback[0].WaitForResult()); + rv = callback1.WaitForResult(); + EXPECT_EQ(1, rv); + EXPECT_EQ("PROXY request1:80", results1.ToPacString()); - // All in all, the first thread should have seen just 1 request. And the - // second thread 3 requests. - ASSERT_EQ(2u, factory->resolvers().size()); - EXPECT_EQ(1, factory->resolvers()[0]->request_count()); - EXPECT_EQ(3, factory->resolvers()[1]->request_count()); + // The SetPacScript callback should never have been completed. + EXPECT_FALSE(set_pac_script_callback.have_result()); } } // namespace - } // namespace net diff --git a/net/proxy/sync_host_resolver_bridge.cc b/net/proxy/sync_host_resolver_bridge.cc index 6c62c7d..caf4549 100644 --- a/net/proxy/sync_host_resolver_bridge.cc +++ b/net/proxy/sync_host_resolver_bridge.cc @@ -188,4 +188,19 @@ void SyncHostResolverBridge::Shutdown() { core_->Shutdown(); } +// SingleThreadedProxyResolverUsingBridgedHostResolver ----------------------- + +SingleThreadedProxyResolverUsingBridgedHostResolver:: +SingleThreadedProxyResolverUsingBridgedHostResolver( + ProxyResolver* proxy_resolver, + SyncHostResolverBridge* bridged_host_resolver) + : SingleThreadedProxyResolver(proxy_resolver), + bridged_host_resolver_(bridged_host_resolver) { +} + +SingleThreadedProxyResolverUsingBridgedHostResolver:: +~SingleThreadedProxyResolverUsingBridgedHostResolver() { + bridged_host_resolver_->Shutdown(); +} + } // namespace net diff --git a/net/proxy/sync_host_resolver_bridge.h b/net/proxy/sync_host_resolver_bridge.h index b02d496..4d25b4d 100644 --- a/net/proxy/sync_host_resolver_bridge.h +++ b/net/proxy/sync_host_resolver_bridge.h @@ -7,6 +7,7 @@ #include "base/scoped_ptr.h" #include "net/base/host_resolver.h" +#include "net/proxy/single_threaded_proxy_resolver.h" class MessageLoop; @@ -34,7 +35,7 @@ class SyncHostResolverBridge : public HostResolver { // The Shutdown() method should be called prior to destruction, from // |host_resolver_loop_|. It aborts any in progress synchronous resolves, to // prevent deadlocks from happening. - virtual void Shutdown(); + void Shutdown(); private: class Core; @@ -43,6 +44,22 @@ class SyncHostResolverBridge : public HostResolver { scoped_refptr<Core> core_; }; +// Subclass of SingleThreadedProxyResolver that additionally calls +// |bridged_host_resolver_->Shutdown()| during its destructor. +class SingleThreadedProxyResolverUsingBridgedHostResolver + : public SingleThreadedProxyResolver { + public: + SingleThreadedProxyResolverUsingBridgedHostResolver( + ProxyResolver* proxy_resolver, + SyncHostResolverBridge* bridged_host_resolver); + + virtual ~SingleThreadedProxyResolverUsingBridgedHostResolver(); + + private: + scoped_refptr<SyncHostResolverBridge> bridged_host_resolver_; +}; + + } // namespace net #endif // NET_PROXY_SYNC_HOST_RESOLVER_BRIDGE_H_ diff --git a/net/proxy/sync_host_resolver_bridge_unittest.cc b/net/proxy/sync_host_resolver_bridge_unittest.cc index 5f02213..62c662a 100644 --- a/net/proxy/sync_host_resolver_bridge_unittest.cc +++ b/net/proxy/sync_host_resolver_bridge_unittest.cc @@ -9,14 +9,10 @@ #include "net/base/address_list.h" #include "net/base/net_errors.h" #include "net/base/net_log.h" -#include "net/proxy/multi_threaded_proxy_resolver.h" #include "net/base/test_completion_callback.h" #include "net/proxy/proxy_info.h" #include "testing/gtest/include/gtest/gtest.h" -// TODO(eroman): This test should be moved into -// multi_threaded_proxy_resolver_unittest.cc. - namespace net { namespace { @@ -79,7 +75,7 @@ class BlockableHostResolver : public HostResolver { // on |host_resolver| in response to GetProxyForURL(). class SyncProxyResolver : public ProxyResolver { public: - explicit SyncProxyResolver(SyncHostResolverBridge* host_resolver) + explicit SyncProxyResolver(HostResolver* host_resolver) : ProxyResolver(false), host_resolver_(host_resolver) {} virtual int GetProxyForURL(const GURL& url, @@ -105,33 +101,15 @@ class SyncProxyResolver : public ProxyResolver { NOTREACHED(); } - virtual void Shutdown() { - host_resolver_->Shutdown(); - } - private: virtual int SetPacScript(const GURL& pac_url, const string16& pac_script, CompletionCallback* callback) { + NOTREACHED(); return OK; } - scoped_refptr<SyncHostResolverBridge> host_resolver_; -}; - -class SyncProxyResolverFactory : public ProxyResolverFactory { - public: - explicit SyncProxyResolverFactory(SyncHostResolverBridge* sync_host_resolver) - : ProxyResolverFactory(false), - sync_host_resolver_(sync_host_resolver) { - } - - virtual ProxyResolver* CreateProxyResolver() { - return new SyncProxyResolver(sync_host_resolver_); - } - - private: - scoped_refptr<SyncHostResolverBridge> sync_host_resolver_; + scoped_refptr<HostResolver> host_resolver_; }; // This helper thread is used to create the circumstances for the deadlock. @@ -159,14 +137,9 @@ class IOThread : public base::Thread { new SyncHostResolverBridge(async_resolver_, message_loop()); proxy_resolver_.reset( - new MultiThreadedProxyResolver( - new SyncProxyResolverFactory(sync_resolver), - 1u)); - - // Initialize the resolver. - TestCompletionCallback callback; - proxy_resolver_->SetPacScriptByUrl(GURL(), &callback); - EXPECT_EQ(OK, callback.WaitForResult()); + new SingleThreadedProxyResolverUsingBridgedHostResolver( + new SyncProxyResolver(sync_resolver), + sync_resolver)); // Start an asynchronous request to the proxy resolver // (note that it will never complete). @@ -205,7 +178,7 @@ class IOThread : public base::Thread { // Test that a deadlock does not happen during shutdown when a host resolve // is outstanding on the SyncHostResolverBridge. // This is a regression test for http://crbug.com/41244. -TEST(MultiThreadedProxyResolverTest, ShutdownIsCalledBeforeThreadJoin) { +TEST(SingleThreadedProxyResolverWithBridgedHostResolverTest, ShutdownDeadlock) { IOThread io_thread; base::Thread::Options options; options.message_loop_type = MessageLoop::TYPE_IO; |