| // 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/threading/thread.h" |
| #include "base/synchronization/waitable_event.h" |
| #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 { |
| |
| // 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(SyncHostResolverBridge* 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(HostPortPair::FromURL(url)); |
| 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(); |
| } |
| |
| virtual void Shutdown() { |
| host_resolver_->Shutdown(); |
| } |
| |
| virtual void CancelSetPacScript() { |
| NOTREACHED(); |
| } |
| |
| virtual int SetPacScript( |
| const scoped_refptr<ProxyResolverScriptData>& script_data, |
| CompletionCallback* callback) { |
| return OK; |
| } |
| |
| private: |
| SyncHostResolverBridge* const host_resolver_; |
| }; |
| |
| class SyncProxyResolverFactory : public ProxyResolverFactory { |
| public: |
| // Takes ownership of |sync_host_resolver|. |
| explicit SyncProxyResolverFactory(SyncHostResolverBridge* sync_host_resolver) |
| : ProxyResolverFactory(false), |
| sync_host_resolver_(sync_host_resolver) { |
| } |
| |
| virtual ProxyResolver* CreateProxyResolver() { |
| return new SyncProxyResolver(sync_host_resolver_.get()); |
| } |
| |
| private: |
| const scoped_ptr<SyncHostResolverBridge> sync_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: |
| IOThread() : base::Thread("IO-thread") {} |
| |
| virtual ~IOThread() { |
| Stop(); |
| } |
| |
| BlockableHostResolver* async_resolver() { |
| return async_resolver_.get(); |
| } |
| |
| protected: |
| virtual void Init() { |
| async_resolver_.reset(new BlockableHostResolver()); |
| |
| // Create a synchronous host resolver that operates the async host |
| // resolver on THIS thread. |
| SyncHostResolverBridge* sync_resolver = |
| new SyncHostResolverBridge(async_resolver_.get(), message_loop()); |
| |
| proxy_resolver_.reset( |
| new MultiThreadedProxyResolver( |
| new SyncProxyResolverFactory(sync_resolver), |
| 1u)); |
| |
| // Initialize the resolver. |
| TestCompletionCallback callback; |
| proxy_resolver_->SetPacScript(ProxyResolverScriptData::FromURL(GURL()), |
| &callback); |
| EXPECT_EQ(OK, callback.WaitForResult()); |
| |
| // 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(); |
| |
| // (There may have been a completion posted back to origin thread, avoid |
| // leaking it by running). |
| MessageLoop::current()->RunAllPending(); |
| |
| // During the teardown sequence of the single threaded proxy resolver, |
| // the outstanding host resolve should have been cancelled. |
| EXPECT_TRUE(async_resolver_->was_request_cancelled()); |
| } |
| |
| private: |
| // This (async) host resolver will outlive the thread that is operating it |
| // synchronously. |
| scoped_ptr<BlockableHostResolver> 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(MultiThreadedProxyResolverTest, ShutdownIsCalledBeforeThreadJoin) { |
| IOThread io_thread; |
| base::Thread::Options options; |
| options.message_loop_type = MessageLoop::TYPE_IO; |
| ASSERT_TRUE(io_thread.StartWithOptions(options)); |
| |
| io_thread.async_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). The IOThread::Cleanup() will verify that after |
| // the PAC thread is stopped, it cancels the request on the HostResolver. |
| } |
| |
| } // namespace |
| |
| } // namespace net |