// 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