diff options
author | rch@chromium.org <rch@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-03-21 03:21:00 +0000 |
---|---|---|
committer | rch@chromium.org <rch@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-03-21 03:21:00 +0000 |
commit | ba0a9317e599cdedf877f12daa4ba02ba19f18a2 (patch) | |
tree | c26d5ed9eae7cbcaa3cb5b0f6860aeb6ea2d1576 /net/socket/client_socket_pool_base_unittest.cc | |
parent | 5d92530396203f7d9f58b3b6fec9063d9fbe139f (diff) | |
download | chromium_src-ba0a9317e599cdedf877f12daa4ba02ba19f18a2.zip chromium_src-ba0a9317e599cdedf877f12daa4ba02ba19f18a2.tar.gz chromium_src-ba0a9317e599cdedf877f12daa4ba02ba19f18a2.tar.bz2 |
Attempting to re-land the feature.
Revert 127730 - Revert 127717 - Revert 118788 - Revert 113405 - Revert 113305 - Revert 113300 - Revert 112134 - Revert 112130 - Close idle connections / SPDY sessions when needed
Due to the idle connection state being held by different socket pools, it's possible for one socket pool to hold an idle socket in a lower layer socket pool. From the lower level socket pool's perspective, the socket is being "actively" used. From the higher socket pool's (including SpdySession, which is more of a connection manager) perspective, the connection is idle and can be closed if we have hit a limit.
Normally this isn't a big deal, except when we have a lot of idle SPDY connections and are connecting via a proxy, so we have low connection limits through the proxy server. We address this problem by allowing lower-level socket pools to tell higher level socket pools to close a socket.
Fixed ASAN test failures by removing .Times(1) and .Times(2) from CloseMultipleIdleSocketsHeldByLayeredPoolWhenNeeded unittest (this removes the tests relying on the order of std::set in CloseOneIdleConnectionInLayeredPool). ASAN is prob
ably causing the memory allocator to allocate the pools differently. The std::set is ordered by LayeredPool* which is the address of the LayeredPool (willchan).
Added NET_EXPORT for layered_pool class defintion to fix windows shared compile.
BUG=62364, 92244, 109876, 110368
TEST=
Committed: http://src.chromium.org/viewvc/chrome?view=rev&revision=127717
Review URL: https://chromiumcodereview.appspot.com/9667016
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@127893 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net/socket/client_socket_pool_base_unittest.cc')
-rw-r--r-- | net/socket/client_socket_pool_base_unittest.cc | 223 |
1 files changed, 219 insertions, 4 deletions
diff --git a/net/socket/client_socket_pool_base_unittest.cc b/net/socket/client_socket_pool_base_unittest.cc index b1c1a99..6c692fe 100644 --- a/net/socket/client_socket_pool_base_unittest.cc +++ b/net/socket/client_socket_pool_base_unittest.cc @@ -4,6 +4,8 @@ #include "net/socket/client_socket_pool_base.h" +#include <vector> + #include "base/bind.h" #include "base/bind_helpers.h" #include "base/callback.h" @@ -28,8 +30,12 @@ #include "net/socket/socket_test_util.h" #include "net/socket/ssl_host_info.h" #include "net/socket/stream_socket.h" +#include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" +using ::testing::Invoke; +using ::testing::Return; + namespace net { namespace { @@ -40,10 +46,18 @@ const net::RequestPriority kDefaultPriority = MEDIUM; class TestSocketParams : public base::RefCounted<TestSocketParams> { public: - bool ignore_limits() { return false; } + TestSocketParams() : ignore_limits_(false) {} + + void set_ignore_limits(bool ignore_limits) { + ignore_limits_ = ignore_limits; + } + bool ignore_limits() { return ignore_limits_; } + private: friend class base::RefCounted<TestSocketParams>; ~TestSocketParams() {} + + bool ignore_limits_; }; typedef ClientSocketPoolBase<TestSocketParams> TestClientSocketPoolBase; @@ -355,12 +369,18 @@ class TestConnectJobFactory public: explicit TestConnectJobFactory(MockClientSocketFactory* client_socket_factory) : job_type_(TestConnectJob::kMockJob), + job_types_(NULL), client_socket_factory_(client_socket_factory) {} virtual ~TestConnectJobFactory() {} void set_job_type(TestConnectJob::JobType job_type) { job_type_ = job_type; } + void set_job_types(std::list<TestConnectJob::JobType>* job_types) { + job_types_ = job_types; + CHECK(!job_types_->empty()); + } + void set_timeout_duration(base::TimeDelta timeout_duration) { timeout_duration_ = timeout_duration; } @@ -371,7 +391,13 @@ class TestConnectJobFactory const std::string& group_name, const TestClientSocketPoolBase::Request& request, ConnectJob::Delegate* delegate) const { - return new TestConnectJob(job_type_, + EXPECT_TRUE(!job_types_ || !job_types_->empty()); + TestConnectJob::JobType job_type = job_type_; + if (job_types_ && !job_types_->empty()) { + job_type = job_types_->front(); + job_types_->pop_front(); + } + return new TestConnectJob(job_type, group_name, request, timeout_duration_, @@ -386,6 +412,7 @@ class TestConnectJobFactory private: TestConnectJob::JobType job_type_; + std::list<TestConnectJob::JobType>* job_types_; base::TimeDelta timeout_duration_; MockClientSocketFactory* const client_socket_factory_; @@ -447,6 +474,10 @@ class TestClientSocketPool : public ClientSocketPool { base_.Flush(); } + virtual bool IsStalled() const OVERRIDE { + return base_.IsStalled(); + } + virtual void CloseIdleSockets() OVERRIDE { base_.CloseIdleSockets(); } @@ -466,6 +497,14 @@ class TestClientSocketPool : public ClientSocketPool { return base_.GetLoadState(group_name, handle); } + virtual void AddLayeredPool(LayeredPool* pool) OVERRIDE { + base_.AddLayeredPool(pool); + } + + virtual void RemoveLayeredPool(LayeredPool* pool) OVERRIDE { + base_.RemoveLayeredPool(pool); + } + virtual DictionaryValue* GetInfoAsValue( const std::string& name, const std::string& type, @@ -499,6 +538,10 @@ class TestClientSocketPool : public ClientSocketPool { void EnableConnectBackupJobs() { base_.EnableConnectBackupJobs(); } + bool CloseOneIdleConnectionInLayeredPool() { + return base_.CloseOneIdleConnectionInLayeredPool(); + } + private: TestClientSocketPoolBase base_; @@ -1164,6 +1207,7 @@ TEST_F(ClientSocketPoolBaseTest, WaitForStalledSocketAtSocketLimit) { ClientSocketHandle stalled_handle; TestCompletionCallback callback; { + EXPECT_FALSE(pool_->IsStalled()); ClientSocketHandle handles[kDefaultMaxSockets]; for (int i = 0; i < kDefaultMaxSockets; ++i) { TestCompletionCallback callback; @@ -1178,6 +1222,7 @@ TEST_F(ClientSocketPoolBaseTest, WaitForStalledSocketAtSocketLimit) { EXPECT_EQ(kDefaultMaxSockets, client_socket_factory_.allocation_count()); EXPECT_EQ(0, pool_->IdleSocketCount()); + EXPECT_FALSE(pool_->IsStalled()); // Now we will hit the socket limit. EXPECT_EQ(ERR_IO_PENDING, stalled_handle.Init("foo", @@ -1186,6 +1231,7 @@ TEST_F(ClientSocketPoolBaseTest, WaitForStalledSocketAtSocketLimit) { callback.callback(), pool_.get(), BoundNetLog())); + EXPECT_TRUE(pool_->IsStalled()); // Dropping out of scope will close all handles and return them to idle. } @@ -2005,9 +2051,9 @@ TEST_F(ClientSocketPoolBaseTest, DisableCleanupTimer) { EXPECT_EQ(1, handle2.socket()->Write(NULL, 1, CompletionCallback())); handle2.Reset(); - // The idle socket timeout value was set to 10 milliseconds. Wait 20 + // The idle socket timeout value was set to 10 milliseconds. Wait 100 // milliseconds so the sockets timeout. - base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(20)); + base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(100)); MessageLoop::current()->RunAllPending(); ASSERT_EQ(2, pool_->IdleSocketCount()); @@ -3039,6 +3085,7 @@ TEST_F(ClientSocketPoolBaseTest, RequestSocketsHitMaxSocketLimit) { ASSERT_TRUE(pool_->HasGroup("a")); EXPECT_EQ(kDefaultMaxSockets - 1, pool_->NumConnectJobsInGroup("a")); + EXPECT_FALSE(pool_->IsStalled()); ASSERT_FALSE(pool_->HasGroup("b")); @@ -3047,6 +3094,7 @@ TEST_F(ClientSocketPoolBaseTest, RequestSocketsHitMaxSocketLimit) { ASSERT_TRUE(pool_->HasGroup("b")); EXPECT_EQ(1, pool_->NumConnectJobsInGroup("b")); + EXPECT_FALSE(pool_->IsStalled()); } TEST_F(ClientSocketPoolBaseTest, RequestSocketsCountIdleSockets) { @@ -3365,6 +3413,173 @@ TEST_F(ClientSocketPoolBaseTest, PreconnectWithBackupJob) { EXPECT_EQ(1, pool_->NumActiveSocketsInGroup("a")); } +class MockLayeredPool : public LayeredPool { + public: + MockLayeredPool(TestClientSocketPool* pool, + const std::string& group_name) + : pool_(pool), + params_(new TestSocketParams), + group_name_(group_name), + can_release_connection_(true) { + pool_->AddLayeredPool(this); + } + + ~MockLayeredPool() { + pool_->RemoveLayeredPool(this); + } + + int RequestSocket(TestClientSocketPool* pool) { + return handle_.Init(group_name_, params_, kDefaultPriority, + callback_.callback(), pool, BoundNetLog()); + } + + int RequestSocketWithoutLimits(TestClientSocketPool* pool) { + params_->set_ignore_limits(true); + return handle_.Init(group_name_, params_, kDefaultPriority, + callback_.callback(), pool, BoundNetLog()); + } + + bool ReleaseOneConnection() { + if (!handle_.is_initialized() || !can_release_connection_) { + return false; + } + handle_.socket()->Disconnect(); + handle_.Reset(); + return true; + } + + void set_can_release_connection(bool can_release_connection) { + can_release_connection_ = can_release_connection; + } + + MOCK_METHOD0(CloseOneIdleConnection, bool()); + + private: + TestClientSocketPool* const pool_; + scoped_refptr<TestSocketParams> params_; + ClientSocketHandle handle_; + TestCompletionCallback callback_; + const std::string group_name_; + bool can_release_connection_; +}; + +TEST_F(ClientSocketPoolBaseTest, FailToCloseIdleSocketsNotHeldByLayeredPool) { + CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); + connect_job_factory_->set_job_type(TestConnectJob::kMockJob); + + MockLayeredPool mock_layered_pool(pool_.get(), "foo"); + EXPECT_EQ(OK, mock_layered_pool.RequestSocket(pool_.get())); + EXPECT_CALL(mock_layered_pool, CloseOneIdleConnection()) + .WillOnce(Return(false)); + EXPECT_FALSE(pool_->CloseOneIdleConnectionInLayeredPool()); +} + +TEST_F(ClientSocketPoolBaseTest, ForciblyCloseIdleSocketsHeldByLayeredPool) { + CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); + connect_job_factory_->set_job_type(TestConnectJob::kMockJob); + + MockLayeredPool mock_layered_pool(pool_.get(), "foo"); + EXPECT_EQ(OK, mock_layered_pool.RequestSocket(pool_.get())); + EXPECT_CALL(mock_layered_pool, CloseOneIdleConnection()) + .WillOnce(Invoke(&mock_layered_pool, + &MockLayeredPool::ReleaseOneConnection)); + EXPECT_TRUE(pool_->CloseOneIdleConnectionInLayeredPool()); +} + +// This test exercises the codepath which caused http://crbug.com/109876 +TEST_F(ClientSocketPoolBaseTest, + CloseIdleSocketsHeldByLayeredPoolInSameGroupWhenNeeded) { + CreatePool(2, 2); + std::list<TestConnectJob::JobType> job_types; + job_types.push_back(TestConnectJob::kMockJob); + job_types.push_back(TestConnectJob::kMockJob); + job_types.push_back(TestConnectJob::kMockFailingJob); + job_types.push_back(TestConnectJob::kMockJob); + connect_job_factory_->set_job_types(&job_types); + + ClientSocketHandle handle1; + TestCompletionCallback callback1; + EXPECT_EQ(OK, handle1.Init("group1", + params_, + kDefaultPriority, + callback1.callback(), + pool_.get(), + BoundNetLog())); + + MockLayeredPool mock_layered_pool(pool_.get(), "group2"); + EXPECT_EQ(OK, mock_layered_pool.RequestSocket(pool_.get())); + EXPECT_CALL(mock_layered_pool, CloseOneIdleConnection()) + .WillRepeatedly(Invoke(&mock_layered_pool, + &MockLayeredPool::ReleaseOneConnection)); + mock_layered_pool.set_can_release_connection(false); + + // This connection attempt will fail when the next request causes the + // MockLayeredPool to delete the socket it's holding. This request is + // needed to trigger the destruction of the "group2" Group. + ClientSocketHandle handle3; + TestCompletionCallback callback3; + EXPECT_EQ(ERR_IO_PENDING, handle3.Init("group2", + params_, + kDefaultPriority, + callback3.callback(), + pool_.get(), + BoundNetLog())); + + mock_layered_pool.set_can_release_connection(true); + ClientSocketHandle handle4; + TestCompletionCallback callback4; + EXPECT_EQ(OK, handle4.Init("group2", + params_, + kDefaultPriority, + callback4.callback(), + pool_.get(), + BoundNetLog())); +} + +TEST_F(ClientSocketPoolBaseTest, CloseIdleSocketsHeldByLayeredPoolWhenNeeded) { + CreatePool(1, 1); + connect_job_factory_->set_job_type(TestConnectJob::kMockJob); + + MockLayeredPool mock_layered_pool(pool_.get(), "foo"); + EXPECT_EQ(OK, mock_layered_pool.RequestSocket(pool_.get())); + EXPECT_CALL(mock_layered_pool, CloseOneIdleConnection()) + .WillOnce(Invoke(&mock_layered_pool, + &MockLayeredPool::ReleaseOneConnection)); + ClientSocketHandle handle; + TestCompletionCallback callback; + EXPECT_EQ(OK, handle.Init("a", + params_, + kDefaultPriority, + callback.callback(), + pool_.get(), + BoundNetLog())); +} + +TEST_F(ClientSocketPoolBaseTest, + CloseMultipleIdleSocketsHeldByLayeredPoolWhenNeeded) { + CreatePool(1, 1); + connect_job_factory_->set_job_type(TestConnectJob::kMockJob); + + MockLayeredPool mock_layered_pool1(pool_.get(), "foo"); + EXPECT_EQ(OK, mock_layered_pool1.RequestSocket(pool_.get())); + EXPECT_CALL(mock_layered_pool1, CloseOneIdleConnection()) + .WillRepeatedly(Invoke(&mock_layered_pool1, + &MockLayeredPool::ReleaseOneConnection)); + MockLayeredPool mock_layered_pool2(pool_.get(), "bar"); + EXPECT_EQ(OK, mock_layered_pool2.RequestSocketWithoutLimits(pool_.get())); + EXPECT_CALL(mock_layered_pool2, CloseOneIdleConnection()) + .WillRepeatedly(Invoke(&mock_layered_pool2, + &MockLayeredPool::ReleaseOneConnection)); + ClientSocketHandle handle; + TestCompletionCallback callback; + EXPECT_EQ(OK, handle.Init("a", + params_, + kDefaultPriority, + callback.callback(), + pool_.get(), + BoundNetLog())); +} + } // namespace } // namespace net |