diff options
-rw-r--r-- | net/socket/client_socket_pool_base.cc | 23 | ||||
-rw-r--r-- | net/socket/client_socket_pool_base.h | 19 | ||||
-rw-r--r-- | net/socket/client_socket_pool_base_unittest.cc | 114 | ||||
-rw-r--r-- | net/socket/tcp_client_socket_pool.cc | 10 | ||||
-rw-r--r-- | net/socket/tcp_client_socket_pool.h | 13 |
5 files changed, 161 insertions, 18 deletions
diff --git a/net/socket/client_socket_pool_base.cc b/net/socket/client_socket_pool_base.cc index db3858d..00b9753 100644 --- a/net/socket/client_socket_pool_base.cc +++ b/net/socket/client_socket_pool_base.cc @@ -34,9 +34,11 @@ bool ClientSocketPoolBase::g_late_binding = false; ConnectJob::ConnectJob(const std::string& group_name, const ClientSocketHandle* key_handle, + base::TimeDelta timeout_duration, Delegate* delegate) : group_name_(group_name), key_handle_(key_handle), + timeout_duration_(timeout_duration), delegate_(delegate), load_state_(LOAD_STATE_IDLE) { DCHECK(!group_name.empty()); @@ -46,6 +48,19 @@ ConnectJob::ConnectJob(const std::string& group_name, ConnectJob::~ConnectJob() {} +int ConnectJob::Connect() { + if (timeout_duration_ != base::TimeDelta()) + timer_.Start(timeout_duration_, this, &ConnectJob::OnTimeout); + return ConnectInternal(); +} + +void ConnectJob::OnTimeout() { + // The delegate will delete |this|. + Delegate *delegate = delegate_; + delegate_ = NULL; + delegate->OnConnectJobComplete(ERR_TIMED_OUT, this); +} + ClientSocketPoolBase::ClientSocketPoolBase( int max_sockets, int max_sockets_per_group, @@ -164,6 +179,12 @@ void ClientSocketPoolBase::CancelRequest(const std::string& group_name, for (; it != group.pending_requests.end(); ++it) { if (it->handle == handle) { group.pending_requests.erase(it); + if (g_late_binding && + group.jobs.size() > group.pending_requests.size() + 1) { + // TODO(willchan): Cancel the job in the earliest LoadState. + RemoveConnectJob(handle, *group.jobs.begin(), &group); + OnAvailableSocketSlot(group_name, &group); + } return; } } @@ -417,7 +438,7 @@ void ClientSocketPoolBase::EnableLateBindingOfSockets(bool enabled) { } void ClientSocketPoolBase::RemoveConnectJob( - const ClientSocketHandle* handle, ConnectJob *job, Group* group) { + const ClientSocketHandle* handle, const ConnectJob *job, Group* group) { CHECK(connecting_socket_count_ > 0); connecting_socket_count_--; diff --git a/net/socket/client_socket_pool_base.h b/net/socket/client_socket_pool_base.h index 21cd642..51b2139 100644 --- a/net/socket/client_socket_pool_base.h +++ b/net/socket/client_socket_pool_base.h @@ -43,8 +43,10 @@ class ConnectJob { DISALLOW_COPY_AND_ASSIGN(Delegate); }; + // A |timeout_duration| of 0 corresponds to no timeout. ConnectJob(const std::string& group_name, const ClientSocketHandle* key_handle, + base::TimeDelta timeout_duration, Delegate* delegate); virtual ~ConnectJob(); @@ -63,7 +65,7 @@ class ConnectJob { // |delegate_| via OnConnectJobComplete. In both asynchronous and synchronous // completion, ReleaseSocket() can be called to acquire the connected socket // if it succeeded. - virtual int Connect() = 0; + int Connect(); protected: void set_load_state(LoadState load_state) { load_state_ = load_state; } @@ -72,10 +74,18 @@ class ConnectJob { Delegate* delegate() { return delegate_; } private: + virtual int ConnectInternal() = 0; + + // Alerts the delegate that the ConnectJob has timed out. + void OnTimeout(); + const std::string group_name_; // Temporarily needed until we switch to late binding. const ClientSocketHandle* const key_handle_; - Delegate* const delegate_; + const base::TimeDelta timeout_duration_; + // Timer to abort jobs that take too long. + base::OneShotTimer<ConnectJob> timer_; + Delegate* delegate_; LoadState load_state_; scoped_ptr<ClientSocket> socket_; @@ -168,6 +178,9 @@ class ClientSocketPoolBase // For testing. bool may_have_stalled_group() const { return may_have_stalled_group_; } + int NumConnectJobsInGroup(const std::string& group_name) const { + return group_map_.find(group_name)->second.jobs.size(); + } private: // Entry for a persistent socket which became idle at time |start_time|. @@ -252,7 +265,7 @@ class ClientSocketPoolBase // binding is enabled. |job| must be non-NULL when late binding is // enabled. Also updates |group| if non-NULL. void RemoveConnectJob(const ClientSocketHandle* handle, - ConnectJob* job, + const ConnectJob* job, Group* group); // Same as OnAvailableSocketSlot except it looks up the Group first to see if diff --git a/net/socket/client_socket_pool_base_unittest.cc b/net/socket/client_socket_pool_base_unittest.cc index dcae067b..6ccc2e4 100644 --- a/net/socket/client_socket_pool_base_unittest.cc +++ b/net/socket/client_socket_pool_base_unittest.cc @@ -6,6 +6,7 @@ #include "base/compiler_specific.h" #include "base/message_loop.h" +#include "base/platform_thread.h" #include "base/scoped_vector.h" #include "net/base/net_errors.h" #include "net/base/test_completion_callback.h" @@ -107,16 +108,22 @@ class TestConnectJob : public ConnectJob { TestConnectJob(JobType job_type, const std::string& group_name, const ClientSocketPoolBase::Request& request, + base::TimeDelta timeout_duration, ConnectJob::Delegate* delegate, MockClientSocketFactory* client_socket_factory) - : ConnectJob(group_name, request.handle, delegate), + : ConnectJob(group_name, request.handle, timeout_duration, delegate), job_type_(job_type), client_socket_factory_(client_socket_factory), method_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)) {} + void Signal() { + DoConnect(waiting_success_, true /* async */); + } + + private: // ConnectJob methods: - virtual int Connect() { + virtual int ConnectInternal() { AddressList ignored; client_socket_factory_->CreateTCPClientSocket(ignored); switch (job_type_) { @@ -158,11 +165,6 @@ class TestConnectJob : public ConnectJob { } } - void Signal() { - DoConnect(waiting_success_, true /* async */); - } - - private: int DoConnect(bool succeed, bool was_async) { int result = ERR_CONNECTION_FAILED; if (succeed) { @@ -207,6 +209,10 @@ class TestConnectJobFactory : public ClientSocketPoolBase::ConnectJobFactory { void set_job_type(TestConnectJob::JobType job_type) { job_type_ = job_type; } + void set_timeout_duration(base::TimeDelta timeout_duration) { + timeout_duration_ = timeout_duration; + } + // ConnectJobFactory methods: virtual ConnectJob* NewConnectJob( @@ -216,12 +222,14 @@ class TestConnectJobFactory : public ClientSocketPoolBase::ConnectJobFactory { return new TestConnectJob(job_type_, group_name, request, + timeout_duration_, delegate, client_socket_factory_); } private: TestConnectJob::JobType job_type_; + base::TimeDelta timeout_duration_; MockClientSocketFactory* const client_socket_factory_; DISALLOW_COPY_AND_ASSIGN(TestConnectJobFactory); @@ -275,6 +283,10 @@ class TestClientSocketPool : public ClientSocketPool { const ClientSocketPoolBase* base() const { return base_.get(); } + int NumConnectJobsInGroup(const std::string& group_name) const { + return base_->NumConnectJobsInGroup(group_name); + } + private: const scoped_refptr<ClientSocketPoolBase> base_; @@ -289,6 +301,37 @@ void MockClientSocketFactory::SignalJobs() { waiting_jobs_.clear(); } +class TestConnectJobDelegate : public ConnectJob::Delegate { + public: + TestConnectJobDelegate() + : have_result_(false), waiting_for_result_(false), result_(OK) {} + virtual ~TestConnectJobDelegate() {} + + virtual void OnConnectJobComplete(int result, ConnectJob* job) { + result_ = result; + delete job; + have_result_ = true; + if (waiting_for_result_) + MessageLoop::current()->Quit(); + } + + int WaitForResult() { + DCHECK(!waiting_for_result_); + while (!have_result_) { + waiting_for_result_ = true; + MessageLoop::current()->Run(); + waiting_for_result_ = false; + } + have_result_ = false; // auto-reset for next callback + return result_; + } + + private: + bool have_result_; + bool waiting_for_result_; + int result_; +}; + class ClientSocketPoolBaseTest : public ClientSocketPoolTest { protected: ClientSocketPoolBaseTest() @@ -324,6 +367,41 @@ class ClientSocketPoolBaseTest : public ClientSocketPoolTest { scoped_refptr<TestClientSocketPool> pool_; }; +// Even though a timeout is specified, it doesn't time out on a synchronous +// completion. +TEST_F(ClientSocketPoolBaseTest, ConnectJob_NoTimeoutOnSynchronousCompletion) { + TestConnectJobDelegate delegate; + ClientSocketPoolBase::Request request; + ClientSocketHandle ignored(pool_.get()); + request.handle = &ignored; + scoped_ptr<TestConnectJob> job( + new TestConnectJob(TestConnectJob::kMockJob, + "a", + request, + base::TimeDelta::FromMicroseconds(1), + &delegate, + &client_socket_factory_)); + EXPECT_EQ(OK, job->Connect()); +} + +TEST_F(ClientSocketPoolBaseTest, ConnectJob_TimedOut) { + TestConnectJobDelegate delegate; + ClientSocketPoolBase::Request request; + ClientSocketHandle ignored(pool_.get()); + request.handle = &ignored; + // Deleted by TestConnectJobDelegate. + TestConnectJob* job = + new TestConnectJob(TestConnectJob::kMockPendingJob, + "a", + request, + base::TimeDelta::FromMicroseconds(1), + &delegate, + &client_socket_factory_); + ASSERT_EQ(ERR_IO_PENDING, job->Connect()); + PlatformThread::Sleep(1); + EXPECT_EQ(ERR_TIMED_OUT, delegate.WaitForResult()); +} + TEST_F(ClientSocketPoolBaseTest, BasicSynchronous) { CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); @@ -1210,6 +1288,28 @@ TEST_F(ClientSocketPoolBaseTest_LateBinding, CancelRequest) { EXPECT_EQ(kIndexOutOfBounds, GetOrderOfRequest(8)); } +TEST_F(ClientSocketPoolBaseTest_LateBinding, CancelRequestLimitsJobs) { + connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob); + + CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); + + EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", 1)); + EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", 2)); + EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", 3)); + EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", 4)); + + EXPECT_EQ(kDefaultMaxSocketsPerGroup, pool_->NumConnectJobsInGroup("a")); + requests_[2]->handle()->Reset(); + requests_[3]->handle()->Reset(); + EXPECT_EQ(kDefaultMaxSocketsPerGroup, pool_->NumConnectJobsInGroup("a")); + + requests_[1]->handle()->Reset(); + EXPECT_EQ(kDefaultMaxSocketsPerGroup, pool_->NumConnectJobsInGroup("a")); + + requests_[0]->handle()->Reset(); + EXPECT_EQ(kDefaultMaxSocketsPerGroup - 1, pool_->NumConnectJobsInGroup("a")); +} + TEST_F(ClientSocketPoolBaseTest_LateBinding, RequestPendingJobTwice) { CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); diff --git a/net/socket/tcp_client_socket_pool.cc b/net/socket/tcp_client_socket_pool.cc index decc23f1..197ccdb 100644 --- a/net/socket/tcp_client_socket_pool.cc +++ b/net/socket/tcp_client_socket_pool.cc @@ -18,14 +18,19 @@ using base::TimeDelta; namespace net { +// TCPConnectJobs will time out after this many seconds. Note this is the total +// time, including both host resolution and TCP connect() times. +static const int kTCPConnectJobTimeoutInSeconds = 60; + TCPConnectJob::TCPConnectJob( const std::string& group_name, const HostResolver::RequestInfo& resolve_info, const ClientSocketHandle* handle, + base::TimeDelta timeout_duration, ClientSocketFactory* client_socket_factory, HostResolver* host_resolver, Delegate* delegate) - : ConnectJob(group_name, handle, delegate), + : ConnectJob(group_name, handle, timeout_duration, delegate), resolve_info_(resolve_info), client_socket_factory_(client_socket_factory), ALLOW_THIS_IN_INITIALIZER_LIST( @@ -38,7 +43,7 @@ TCPConnectJob::~TCPConnectJob() { // ~SingleRequestHostResolver and ~ClientSocket will take care of it. } -int TCPConnectJob::Connect() { +int TCPConnectJob::ConnectInternal() { next_state_ = kStateResolveHost; return DoLoop(OK); } @@ -128,6 +133,7 @@ ConnectJob* TCPClientSocketPool::TCPConnectJobFactory::NewConnectJob( ConnectJob::Delegate* delegate) const { return new TCPConnectJob( group_name, request.resolve_info, request.handle, + base::TimeDelta::FromSeconds(kTCPConnectJobTimeoutInSeconds), client_socket_factory_, host_resolver_, delegate); } diff --git a/net/socket/tcp_client_socket_pool.h b/net/socket/tcp_client_socket_pool.h index 1906dcc..b07ea68 100644 --- a/net/socket/tcp_client_socket_pool.h +++ b/net/socket/tcp_client_socket_pool.h @@ -10,6 +10,8 @@ #include "base/basictypes.h" #include "base/ref_counted.h" #include "base/scoped_ptr.h" +#include "base/time.h" +#include "base/timer.h" #include "net/socket/client_socket_pool_base.h" #include "net/socket/client_socket_pool.h" @@ -24,6 +26,7 @@ class TCPConnectJob : public ConnectJob { TCPConnectJob(const std::string& group_name, const HostResolver::RequestInfo& resolve_info, const ClientSocketHandle* handle, + base::TimeDelta timeout_duration, ClientSocketFactory* client_socket_factory, HostResolver* host_resolver, Delegate* delegate); @@ -31,11 +34,6 @@ class TCPConnectJob : public ConnectJob { // ConnectJob methods. - // Begins the host resolution and the TCP connect. Returns OK on success - // and ERR_IO_PENDING if it cannot immediately service the request. - // Otherwise, it returns a net error code. - virtual int Connect(); - private: enum State { kStateResolveHost, @@ -45,6 +43,11 @@ class TCPConnectJob : public ConnectJob { kStateNone, }; + // Begins the host resolution and the TCP connect. Returns OK on success + // and ERR_IO_PENDING if it cannot immediately service the request. + // Otherwise, it returns a net error code. + virtual int ConnectInternal(); + void OnIOComplete(int result); // Runs the state transition loop. |