diff options
author | eroman@chromium.org <eroman@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-04-17 00:07:04 +0000 |
---|---|---|
committer | eroman@chromium.org <eroman@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-04-17 00:07:04 +0000 |
commit | 1ca2b45a6e0b6490d0ab1ff940e2d7769660682b (patch) | |
tree | f6740d35ae7bf87007f6794a49f53f2858ead7b7 /net | |
parent | 54ce726e1af62939cb12b09dc687737b114d426c (diff) | |
download | chromium_src-1ca2b45a6e0b6490d0ab1ff940e2d7769660682b.zip chromium_src-1ca2b45a6e0b6490d0ab1ff940e2d7769660682b.tar.gz chromium_src-1ca2b45a6e0b6490d0ab1ff940e2d7769660682b.tar.bz2 |
Fix a deadlock that could happen during shutdown if a host resolve request was outstanding by a PAC script.
The solution is to abort the oustanding host resolver request during shutdown, and wake-up the blocked PAC thread.
BUG=41244
TEST=SingleThreadedProxyResolverWithBridgedHostResolverTest.ShutdownDeadlock
Review URL: http://codereview.chromium.org/1527037
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@44864 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net')
-rw-r--r-- | net/net.gyp | 3 | ||||
-rw-r--r-- | net/proxy/proxy_resolver_js_bindings.cc | 100 | ||||
-rw-r--r-- | net/proxy/proxy_resolver_js_bindings.h | 7 | ||||
-rw-r--r-- | net/proxy/proxy_resolver_js_bindings_unittest.cc | 10 | ||||
-rw-r--r-- | net/proxy/proxy_resolver_perftest.cc | 3 | ||||
-rw-r--r-- | net/proxy/proxy_service.cc | 30 | ||||
-rw-r--r-- | net/proxy/single_threaded_proxy_resolver.cc | 15 | ||||
-rw-r--r-- | net/proxy/sync_host_resolver_bridge.cc | 123 | ||||
-rw-r--r-- | net/proxy/sync_host_resolver_bridge.h | 97 | ||||
-rw-r--r-- | net/proxy/sync_host_resolver_bridge_unittest.cc | 199 |
10 files changed, 471 insertions, 116 deletions
diff --git a/net/net.gyp b/net/net.gyp index a0602e1..1a41553 100644 --- a/net/net.gyp +++ b/net/net.gyp @@ -422,6 +422,8 @@ 'proxy/proxy_service.h', 'proxy/single_threaded_proxy_resolver.cc', 'proxy/single_threaded_proxy_resolver.h', + 'proxy/sync_host_resolver_bridge.cc', + 'proxy/sync_host_resolver_bridge.h', 'socket/client_socket.h', 'socket/client_socket_factory.cc', 'socket/client_socket_factory.h', @@ -699,6 +701,7 @@ 'proxy/proxy_server_unittest.cc', 'proxy/proxy_service_unittest.cc', 'proxy/single_threaded_proxy_resolver_unittest.cc', + 'proxy/sync_host_resolver_bridge_unittest.cc', 'socket/client_socket_pool_base_unittest.cc', 'socket/socks5_client_socket_unittest.cc', 'socket/socks_client_socket_pool_unittest.cc', diff --git a/net/proxy/proxy_resolver_js_bindings.cc b/net/proxy/proxy_resolver_js_bindings.cc index 0906e7a..4703f2e 100644 --- a/net/proxy/proxy_resolver_js_bindings.cc +++ b/net/proxy/proxy_resolver_js_bindings.cc @@ -4,10 +4,7 @@ #include "net/proxy/proxy_resolver_js_bindings.h" -#include "base/compiler_specific.h" #include "base/logging.h" -#include "base/message_loop.h" -#include "base/waitable_event.h" #include "net/base/address_list.h" #include "net/base/host_resolver.h" #include "net/base/net_errors.h" @@ -18,85 +15,11 @@ namespace net { namespace { -// Wrapper around HostResolver to give a sync API while running the resolve -// in async mode on |host_resolver_loop|. If |host_resolver_loop| is NULL, -// runs sync on the current thread (this mode is just used by testing). -class SyncHostResolverBridge - : public base::RefCountedThreadSafe<SyncHostResolverBridge> { - public: - SyncHostResolverBridge(HostResolver* host_resolver, - MessageLoop* host_resolver_loop) - : host_resolver_(host_resolver), - host_resolver_loop_(host_resolver_loop), - event_(false, false), - ALLOW_THIS_IN_INITIALIZER_LIST( - callback_(this, &SyncHostResolverBridge::OnResolveCompletion)) { - } - - // Run the resolve on host_resolver_loop, and wait for result. - int Resolve(const std::string& hostname, - AddressFamily address_family, - net::AddressList* addresses) { - // Port number doesn't matter. - HostResolver::RequestInfo info(hostname, 80); - info.set_address_family(address_family); - - // Hack for tests -- run synchronously on current thread. - if (!host_resolver_loop_) - return host_resolver_->Resolve(info, addresses, NULL, NULL, - BoundNetLog()); - - // Otherwise start an async resolve on the resolver's thread. - host_resolver_loop_->PostTask(FROM_HERE, NewRunnableMethod(this, - &SyncHostResolverBridge::StartResolve, info, addresses)); - - // Wait for the resolve to complete in the resolver's thread. - event_.Wait(); - return err_; - } - - private: - friend class base::RefCountedThreadSafe<SyncHostResolverBridge>; - - ~SyncHostResolverBridge() {} - - // Called on host_resolver_loop_. - void StartResolve(const HostResolver::RequestInfo& info, - net::AddressList* addresses) { - DCHECK_EQ(host_resolver_loop_, MessageLoop::current()); - int error = host_resolver_->Resolve( - info, addresses, &callback_, NULL, BoundNetLog()); - if (error != ERR_IO_PENDING) - OnResolveCompletion(error); // Completed synchronously. - } - - // Called on host_resolver_loop_. - void OnResolveCompletion(int result) { - DCHECK_EQ(host_resolver_loop_, MessageLoop::current()); - err_ = result; - event_.Signal(); - } - - scoped_refptr<HostResolver> host_resolver_; - MessageLoop* host_resolver_loop_; - - // Event to notify completion of resolve request. - base::WaitableEvent event_; - - // Callback for when the resolve completes on host_resolver_loop_. - net::CompletionCallbackImpl<SyncHostResolverBridge> callback_; - - // The result from the result request (set by in host_resolver_loop_). - int err_; -}; - // ProxyResolverJSBindings implementation. class DefaultJSBindings : public ProxyResolverJSBindings { public: - DefaultJSBindings(HostResolver* host_resolver, - MessageLoop* host_resolver_loop) - : host_resolver_(new SyncHostResolverBridge( - host_resolver, host_resolver_loop)) {} + explicit DefaultJSBindings(HostResolver* host_resolver) + : host_resolver_(host_resolver) {} // Handler for "alert(message)". virtual void Alert(const std::string& message) { @@ -124,10 +47,11 @@ class DefaultJSBindings : public ProxyResolverJSBindings { // Consequently a lot of existing PAC scripts assume they will only get // IPv4 results, and will misbehave if they get an IPv6 result. // See http://crbug.com/24641 for more details. + HostResolver::RequestInfo info(host, 80); // Port doesn't matter. + info.set_address_family(ADDRESS_FAMILY_IPV4); net::AddressList address_list; - int result = host_resolver_->Resolve(host, - ADDRESS_FAMILY_IPV4, - &address_list); + int result = host_resolver_->Resolve(info, &address_list, NULL, NULL, + BoundNetLog()); if (result != OK) return std::string(); // Failed. @@ -143,10 +67,10 @@ class DefaultJSBindings : public ProxyResolverJSBindings { // Handler for "dnsResolveEx(host)". Returns empty string on failure. virtual std::string DnsResolveEx(const std::string& host) { // Do a sync resolve of the hostname. + HostResolver::RequestInfo info(host, 80); // Port doesn't matter. net::AddressList address_list; - int result = host_resolver_->Resolve(host, - ADDRESS_FAMILY_UNSPECIFIED, - &address_list); + int result = host_resolver_->Resolve(info, &address_list, NULL, NULL, + BoundNetLog()); if (result != OK) return std::string(); // Failed. @@ -174,15 +98,15 @@ class DefaultJSBindings : public ProxyResolverJSBindings { } private: - scoped_refptr<SyncHostResolverBridge> host_resolver_; + scoped_refptr<HostResolver> host_resolver_; }; } // namespace // static ProxyResolverJSBindings* ProxyResolverJSBindings::CreateDefault( - HostResolver* host_resolver, MessageLoop* host_resolver_loop) { - return new DefaultJSBindings(host_resolver, host_resolver_loop); + HostResolver* host_resolver) { + return new DefaultJSBindings(host_resolver); } } // namespace net diff --git a/net/proxy/proxy_resolver_js_bindings.h b/net/proxy/proxy_resolver_js_bindings.h index 03ad61a..09e6e1c 100644 --- a/net/proxy/proxy_resolver_js_bindings.h +++ b/net/proxy/proxy_resolver_js_bindings.h @@ -48,11 +48,8 @@ class ProxyResolverJSBindings { // - Send script alert()s to LOG(INFO) // - Use the provided host resolver to service dnsResolve(). // - // |host_resolver| will be used in async mode on |host_resolver_loop|. If - // |host_resolver_loop| is NULL, then |host_resolver| will be used in sync - // mode on the PAC thread. - static ProxyResolverJSBindings* CreateDefault( - HostResolver* host_resolver, MessageLoop* host_resolver_loop); + // Note that |host_resolver| will be used in sync mode mode. + static ProxyResolverJSBindings* CreateDefault(HostResolver* host_resolver); }; } // namespace net diff --git a/net/proxy/proxy_resolver_js_bindings_unittest.cc b/net/proxy/proxy_resolver_js_bindings_unittest.cc index 2d525f7..5bf9c3d 100644 --- a/net/proxy/proxy_resolver_js_bindings_unittest.cc +++ b/net/proxy/proxy_resolver_js_bindings_unittest.cc @@ -83,7 +83,7 @@ TEST(ProxyResolverJSBindingsTest, DnsResolve) { // Get a hold of a DefaultJSBindings* (it is a hidden impl class). scoped_ptr<ProxyResolverJSBindings> bindings( - ProxyResolverJSBindings::CreateDefault(host_resolver, NULL)); + ProxyResolverJSBindings::CreateDefault(host_resolver)); // Empty string is not considered a valid host (even though on some systems // requesting this will resolve to localhost). @@ -105,8 +105,7 @@ TEST(ProxyResolverJSBindingsTest, DnsResolve) { TEST(ProxyResolverJSBindingsTest, MyIpAddress) { // Get a hold of a DefaultJSBindings* (it is a hidden impl class). scoped_ptr<ProxyResolverJSBindings> bindings( - ProxyResolverJSBindings::CreateDefault( - new MockHostResolver, NULL)); + ProxyResolverJSBindings::CreateDefault(new MockHostResolver)); // Our IP address is always going to be 127.0.0.1, since we are using a // mock host resolver. @@ -132,8 +131,7 @@ TEST(ProxyResolverJSBindingsTest, RestrictAddressFamily) { // Get a hold of a DefaultJSBindings* (it is a hidden impl class). scoped_ptr<ProxyResolverJSBindings> bindings( - ProxyResolverJSBindings::CreateDefault( - host_resolver, NULL)); + ProxyResolverJSBindings::CreateDefault(host_resolver)); // Make it so requests resolve to particular address patterns based on family: // IPV4_ONLY --> 192.168.1.* @@ -179,7 +177,7 @@ TEST(ProxyResolverJSBindingsTest, ExFunctionsReturnList) { // Get a hold of a DefaultJSBindings* (it is a hidden impl class). scoped_ptr<ProxyResolverJSBindings> bindings( - ProxyResolverJSBindings::CreateDefault(host_resolver, NULL)); + ProxyResolverJSBindings::CreateDefault(host_resolver)); EXPECT_EQ("192.168.1.1;172.22.34.1;200.100.1.2", bindings->MyIpAddressEx()); diff --git a/net/proxy/proxy_resolver_perftest.cc b/net/proxy/proxy_resolver_perftest.cc index 6e336e9..7650be4 100644 --- a/net/proxy/proxy_resolver_perftest.cc +++ b/net/proxy/proxy_resolver_perftest.cc @@ -189,8 +189,7 @@ TEST(ProxyResolverPerfTest, ProxyResolverMac) { TEST(ProxyResolverPerfTest, ProxyResolverV8) { net::ProxyResolverJSBindings* js_bindings = - net::ProxyResolverJSBindings::CreateDefault( - new net::MockHostResolver, NULL); + net::ProxyResolverJSBindings::CreateDefault(new net::MockHostResolver); net::ProxyResolverV8 resolver(js_bindings); PacPerfSuiteRunner runner(&resolver, "ProxyResolverV8"); diff --git a/net/proxy/proxy_service.cc b/net/proxy/proxy_service.cc index 668a659..e3778b9 100644 --- a/net/proxy/proxy_service.cc +++ b/net/proxy/proxy_service.cc @@ -30,6 +30,7 @@ #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" using base::TimeDelta; @@ -222,23 +223,32 @@ ProxyService* ProxyService::Create( NetworkChangeNotifier* network_change_notifier, NetLog* net_log, MessageLoop* io_loop) { - ProxyResolver* proxy_resolver; + ProxyResolver* proxy_resolver = NULL; if (use_v8_resolver) { + // 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). - HostResolver* host_resolver = url_request_context->host_resolver(); ProxyResolverJSBindings* js_bindings = - ProxyResolverJSBindings::CreateDefault(host_resolver, io_loop); - - proxy_resolver = new ProxyResolverV8(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 { - proxy_resolver = CreateNonV8ProxyResolver(); + proxy_resolver = + new SingleThreadedProxyResolver(CreateNonV8ProxyResolver()); } - // Wrap the (synchronous) ProxyResolver implementation in a single-threaded - // runner. This will dispatch requests to a threadpool of size 1. - proxy_resolver = new SingleThreadedProxyResolver(proxy_resolver); - ProxyService* proxy_service = new ProxyService( proxy_config_service, proxy_resolver, network_change_notifier, BoundNetLog::Make(net_log, NetLog::SOURCE_INIT_PROXY_RESOLVER)); diff --git a/net/proxy/single_threaded_proxy_resolver.cc b/net/proxy/single_threaded_proxy_resolver.cc index 4009e2a..b0fd4a3 100644 --- a/net/proxy/single_threaded_proxy_resolver.cc +++ b/net/proxy/single_threaded_proxy_resolver.cc @@ -156,22 +156,23 @@ class SingleThreadedProxyResolver::Job // Runs on the worker thread. void DoQuery(ProxyResolver* resolver, size_t load_log_bound) { - scoped_ptr<CapturingNetLog> worker_log(new CapturingNetLog(load_log_bound)); - BoundNetLog bound_worker_log(NetLog::Source(), worker_log.get()); + 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, worker_log.release())); + NewRunnableMethod(this, &Job::QueryComplete, rv)); } // Runs the completion callback on the origin thread. - void QueryComplete(int result_code, CapturingNetLog* worker_log) { + 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); + 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. @@ -199,6 +200,10 @@ class SingleThreadedProxyResolver::Job // 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 ------------------------------------------------ diff --git a/net/proxy/sync_host_resolver_bridge.cc b/net/proxy/sync_host_resolver_bridge.cc new file mode 100644 index 0000000..31a326d --- /dev/null +++ b/net/proxy/sync_host_resolver_bridge.cc @@ -0,0 +1,123 @@ +// 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/sync_host_resolver_bridge.h" + +#include "base/compiler_specific.h" +#include "base/logging.h" +#include "base/message_loop.h" +#include "net/base/net_errors.h" +#include "net/base/net_log.h" + +namespace net { + +// SyncHostResolverBridge ----------------------------------------------------- + +SyncHostResolverBridge::SyncHostResolverBridge(HostResolver* host_resolver, + MessageLoop* host_resolver_loop) + : host_resolver_(host_resolver), + host_resolver_loop_(host_resolver_loop), + event_(true, false), + ALLOW_THIS_IN_INITIALIZER_LIST( + callback_(this, &SyncHostResolverBridge::OnResolveCompletion)), + outstanding_request_(NULL), + has_shutdown_(false) { + DCHECK(host_resolver_loop_); +} + +SyncHostResolverBridge::~SyncHostResolverBridge() { + DCHECK(HasShutdown()); +} + +int SyncHostResolverBridge::Resolve(const RequestInfo& info, + AddressList* addresses, + CompletionCallback* callback, + RequestHandle* out_req, + const BoundNetLog& net_log) { + DCHECK(!callback); + DCHECK(!out_req); + + // Otherwise start an async resolve on the resolver's thread. + host_resolver_loop_->PostTask( + FROM_HERE, + NewRunnableMethod(this, &SyncHostResolverBridge::StartResolve, + info, addresses)); + + // Wait for the resolve to complete in the resolver's thread. + event_.Wait(); + + { + AutoLock l(lock_); + if (has_shutdown_) + return ERR_ABORTED; + event_.Reset(); + } + + return err_; +} + +void SyncHostResolverBridge::CancelRequest(RequestHandle req) { + NOTREACHED(); +} + +void SyncHostResolverBridge::AddObserver(Observer* observer) { + NOTREACHED(); +} + +void SyncHostResolverBridge::RemoveObserver(Observer* observer) { + NOTREACHED(); +} + +void SyncHostResolverBridge::Shutdown() { + DCHECK_EQ(MessageLoop::current(), host_resolver_loop_); + + if (outstanding_request_) { + host_resolver_->CancelRequest(outstanding_request_); + outstanding_request_ = NULL; + } + + AutoLock l(lock_); + has_shutdown_ = true; + + // Wake up the PAC thread in case it was waiting for resolve completion. + event_.Signal(); +} + +void SyncHostResolverBridge::StartResolve(const HostResolver::RequestInfo& info, + net::AddressList* addresses) { + DCHECK_EQ(host_resolver_loop_, MessageLoop::current()); + DCHECK(!outstanding_request_); + + if (HasShutdown()) + return; + + int error = host_resolver_->Resolve( + info, addresses, &callback_, &outstanding_request_, BoundNetLog()); + if (error != ERR_IO_PENDING) + OnResolveCompletion(error); // Completed synchronously. +} + +void SyncHostResolverBridge::OnResolveCompletion(int result) { + DCHECK_EQ(host_resolver_loop_, MessageLoop::current()); + err_ = result; + outstanding_request_ = NULL; + event_.Signal(); +} + +// 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 new file mode 100644 index 0000000..d082043 --- /dev/null +++ b/net/proxy/sync_host_resolver_bridge.h @@ -0,0 +1,97 @@ +// 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. + +#ifndef NET_PROXY_SYNC_HOST_RESOLVER_BRIDGE_H_ +#define NET_PROXY_SYNC_HOST_RESOLVER_BRIDGE_H_ + +#include "base/lock.h" +#include "base/scoped_ptr.h" +#include "base/waitable_event.h" +#include "net/base/host_resolver.h" +#include "net/proxy/single_threaded_proxy_resolver.h" + +class MessageLoop; + +namespace net { + +// Wrapper around HostResolver to give a sync API while running the resolver +// in async mode on |host_resolver_loop|. +class SyncHostResolverBridge : public HostResolver { + public: + SyncHostResolverBridge(HostResolver* host_resolver, + MessageLoop* host_resolver_loop); + + virtual ~SyncHostResolverBridge(); + + // HostResolver methods: + virtual int Resolve(const RequestInfo& info, + AddressList* addresses, + CompletionCallback* callback, + RequestHandle* out_req, + const BoundNetLog& net_log); + virtual void CancelRequest(RequestHandle req); + virtual void AddObserver(Observer* observer); + virtual void RemoveObserver(Observer* observer); + + // 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. + void Shutdown(); + + private: + // Called on |host_resolver_loop_|. + void StartResolve(const HostResolver::RequestInfo& info, + net::AddressList* addresses); + + // Called on |host_resolver_loop_|. + void OnResolveCompletion(int result); + + // Returns true if Shutdown() has been called. + bool HasShutdown() const { + AutoLock l(lock_); + return has_shutdown_; + } + + scoped_refptr<HostResolver> host_resolver_; + MessageLoop* host_resolver_loop_; + + // Event to notify completion of resolve request. + base::WaitableEvent event_; + + // Callback for when the resolve completes on host_resolver_loop_. + net::CompletionCallbackImpl<SyncHostResolverBridge> callback_; + + // The result from the current request (set on |host_resolver_loop_|). + int err_; + + // The currently outstanding request to |host_resolver_|, or NULL. + HostResolver::RequestHandle outstanding_request_; + + // True if Shutdown() has been called. Must hold |lock_| to access it. + bool has_shutdown_; + + // Mutex to guard accesses to |has_shutdown_|. + mutable Lock lock_; +}; + +// 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 new file mode 100644 index 0000000..916252b --- /dev/null +++ b/net/proxy/sync_host_resolver_bridge_unittest.cc @@ -0,0 +1,199 @@ +// 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/sync_host_resolver_bridge.h" + +#include "base/thread.h" +#include "net/base/address_list.h" +#include "net/base/net_errors.h" +#include "net/base/net_log.h" +#include "net/base/test_completion_callback.h" +#include "net/proxy/proxy_info.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { + +namespace { + +// This implementation of HostResolver allows blocking until a resolve request +// has been received. The resolve requests it receives will never be completed. +class BlockableHostResolver : public HostResolver { + public: + BlockableHostResolver() + : event_(true, false), + was_request_cancelled_(false) { + } + + virtual int Resolve(const RequestInfo& info, + AddressList* addresses, + CompletionCallback* callback, + RequestHandle* out_req, + const BoundNetLog& net_log) { + EXPECT_TRUE(callback); + EXPECT_TRUE(out_req); + *out_req = reinterpret_cast<RequestHandle*>(1); // Magic value. + + // Indicate to the caller that a request was received. + event_.Signal(); + + // We return ERR_IO_PENDING, as this request will NEVER be completed. + // Expectation is for the caller to later cancel the request. + return ERR_IO_PENDING; + } + + virtual void CancelRequest(RequestHandle req) { + EXPECT_EQ(reinterpret_cast<RequestHandle*>(1), req); + was_request_cancelled_ = true; + } + + virtual void AddObserver(Observer* observer) { + NOTREACHED(); + } + + virtual void RemoveObserver(Observer* observer) { + NOTREACHED(); + } + + // Waits until Resolve() has been called. + void WaitUntilRequestIsReceived() { + event_.Wait(); + } + + bool was_request_cancelled() const { + return was_request_cancelled_; + } + + private: + // Event to notify when a resolve request was received. + base::WaitableEvent event_; + bool was_request_cancelled_; +}; + +// This implementation of ProxyResolver simply does a synchronous resolve +// on |host_resolver| in response to GetProxyForURL(). +class SyncProxyResolver : public ProxyResolver { + public: + explicit SyncProxyResolver(HostResolver* host_resolver) + : ProxyResolver(false), host_resolver_(host_resolver) {} + + virtual int GetProxyForURL(const GURL& url, + ProxyInfo* results, + CompletionCallback* callback, + RequestHandle* request, + const BoundNetLog& net_log) { + EXPECT_FALSE(callback); + EXPECT_FALSE(request); + + // Do a synchronous host resolve. + HostResolver::RequestInfo info(url.host(), 80); + AddressList addresses; + int rv = + host_resolver_->Resolve(info, &addresses, NULL, NULL, BoundNetLog()); + + EXPECT_EQ(ERR_ABORTED, rv); + + return rv; + } + + virtual void CancelRequest(RequestHandle request) { + NOTREACHED(); + } + + private: + virtual int SetPacScript(const GURL& pac_url, + const std::string& bytes_utf8, + CompletionCallback* callback) { + NOTREACHED(); + return OK; + } + + scoped_refptr<HostResolver> host_resolver_; +}; + +// This helper thread is used to create the circumstances for the deadlock. +// It is analagous to the "IO thread" which would be main thread running the +// network stack. +class IOThread : public base::Thread { + public: + explicit IOThread(HostResolver* async_resolver) + : base::Thread("IO-thread"), async_resolver_(async_resolver) { + } + + virtual ~IOThread() { + Stop(); + } + + protected: + virtual void Init() { + // Create a synchronous host resolver that operates the async host + // resolver on THIS thread. + scoped_refptr<SyncHostResolverBridge> sync_resolver = + new SyncHostResolverBridge(async_resolver_, message_loop()); + + proxy_resolver_.reset( + new SingleThreadedProxyResolverUsingBridgedHostResolver( + new SyncProxyResolver(sync_resolver), + sync_resolver)); + + // Start an asynchronous request to the proxy resolver + // (note that it will never complete). + proxy_resolver_->GetProxyForURL(GURL("http://test/"), &results_, + &callback_, &request_, BoundNetLog()); + } + + virtual void CleanUp() { + // Cancel the outstanding request (note however that this will not + // unblock the PAC thread though). + proxy_resolver_->CancelRequest(request_); + + // Delete the single threaded proxy resolver. + proxy_resolver_.reset(); + } + + private: + HostResolver* async_resolver_; + scoped_ptr<ProxyResolver> proxy_resolver_; + + // Data for the outstanding request to the single threaded proxy resolver. + TestCompletionCallback callback_; + ProxyInfo results_; + ProxyResolver::RequestHandle request_; +}; + +// 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(SingleThreadedProxyResolverWithBridgedHostResolverTest, ShutdownDeadlock) { + // This (async) host resolver will outlive the thread that is operating it + // synchronously. + scoped_refptr<BlockableHostResolver> host_resolver = + new BlockableHostResolver(); + + { + IOThread io_thread(host_resolver.get()); + base::Thread::Options options; + options.message_loop_type = MessageLoop::TYPE_IO; + ASSERT_TRUE(io_thread.StartWithOptions(options)); + + // Wait until the host resolver receives a request (this means that the + // PAC thread is now blocked, waiting for the async response from the + // host resolver. + host_resolver->WaitUntilRequestIsReceived(); + + // Now upon exitting this scope, the IOThread is destroyed -- this will + // stop the IOThread, which will in turn delete the + // SingleThreadedProxyResolver, which in turn will stop its internal + // PAC thread (which is currently blocked waiting on the host resolve which + // is running on IOThread). + } + + // During the teardown sequence of the single threaded proxy resolver, + // the outstanding host resolve should have been cancelled. + EXPECT_TRUE(host_resolver->was_request_cancelled()); +} + +} // namespace + +} // namespace net + |