// 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(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(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& 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 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 async_resolver_; scoped_ptr 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