diff options
author | mmenke@chromium.org <mmenke@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-05-14 01:07:03 +0000 |
---|---|---|
committer | mmenke@chromium.org <mmenke@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-05-14 01:07:03 +0000 |
commit | 483fa202c401443e4abfb4773d2705745364eeed (patch) | |
tree | 621a41ce7028f7a5abf6ca7f0abd25b168006132 /net | |
parent | b0c819a8e60befd294d6de8e2ee32755801e62d5 (diff) | |
download | chromium_src-483fa202c401443e4abfb4773d2705745364eeed.zip chromium_src-483fa202c401443e4abfb4773d2705745364eeed.tar.gz chromium_src-483fa202c401443e4abfb4773d2705745364eeed.tar.bz2 |
Make SpdySessions implement LayeredPool and hook them up
to their parent socket pool as, so they can be closed when
they're idle and the parent pool is stalled.
BUG=92244
Review URL: https://chromiumcodereview.appspot.com/14473012
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@199880 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net')
-rw-r--r-- | net/http/http_network_transaction_spdy2_unittest.cc | 188 | ||||
-rw-r--r-- | net/http/http_network_transaction_spdy3_unittest.cc | 188 | ||||
-rw-r--r-- | net/spdy/spdy_session.cc | 12 | ||||
-rw-r--r-- | net/spdy/spdy_session.h | 7 | ||||
-rw-r--r-- | net/spdy/spdy_session_spdy2_unittest.cc | 222 | ||||
-rw-r--r-- | net/spdy/spdy_session_spdy3_unittest.cc | 220 |
6 files changed, 830 insertions, 7 deletions
diff --git a/net/http/http_network_transaction_spdy2_unittest.cc b/net/http/http_network_transaction_spdy2_unittest.cc index e302f0e..6130b9a 100644 --- a/net/http/http_network_transaction_spdy2_unittest.cc +++ b/net/http/http_network_transaction_spdy2_unittest.cc @@ -48,6 +48,7 @@ #include "net/proxy/proxy_resolver.h" #include "net/proxy/proxy_service.h" #include "net/socket/client_socket_factory.h" +#include "net/socket/client_socket_pool_manager.h" #include "net/socket/mock_client_socket_pool_manager.h" #include "net/socket/next_proto.h" #include "net/socket/socket_test_util.h" @@ -235,8 +236,24 @@ HttpNetworkSession* CreateSession(SpdySessionDependencies* session_deps) { } // namespace class HttpNetworkTransactionSpdy2Test : public PlatformTest { + public: + virtual ~HttpNetworkTransactionSpdy2Test() { + // Important to restore the per-pool limit first, since the pool limit must + // always be greater than group limit, and the tests reduce both limits. + ClientSocketPoolManager::set_max_sockets_per_pool( + HttpNetworkSession::NORMAL_SOCKET_POOL, old_max_pool_sockets_); + ClientSocketPoolManager::set_max_sockets_per_group( + HttpNetworkSession::NORMAL_SOCKET_POOL, old_max_group_sockets_); + } + protected: - HttpNetworkTransactionSpdy2Test() : session_deps_(kProtoSPDY2) {} + HttpNetworkTransactionSpdy2Test() + : session_deps_(kProtoSPDY2), + old_max_group_sockets_(ClientSocketPoolManager::max_sockets_per_group( + HttpNetworkSession::NORMAL_SOCKET_POOL)), + old_max_pool_sockets_(ClientSocketPoolManager::max_sockets_per_pool( + HttpNetworkSession::NORMAL_SOCKET_POOL)) { + } struct SimpleGetHelperResult { int rv; @@ -357,6 +374,11 @@ class HttpNetworkTransactionSpdy2Test : public PlatformTest { void CheckErrorIsPassedBack(int error, IoMode mode); SpdySessionDependencies session_deps_; + + // Original socket limits. Some tests set these. Safest to always restore + // them once each test has been run. + int old_max_group_sockets_; + int old_max_pool_sockets_; }; namespace { @@ -11187,4 +11209,168 @@ TEST_F(HttpNetworkTransactionSpdy2Test, ErrorSocketNotConnected) { EXPECT_TRUE(trans2.GetResponseInfo()->was_fetched_via_spdy); } +TEST_F(HttpNetworkTransactionSpdy2Test, CloseIdleSpdySessionToOpenNewOne) { + HttpStreamFactory::SetNextProtos(SpdyNextProtos()); + ClientSocketPoolManager::set_max_sockets_per_group( + HttpNetworkSession::NORMAL_SOCKET_POOL, 1); + ClientSocketPoolManager::set_max_sockets_per_pool( + HttpNetworkSession::NORMAL_SOCKET_POOL, 1); + + // Use two different hosts with different IPs so they don't get pooled. + session_deps_.host_resolver->rules()->AddRule("www.a.com", "10.0.0.1"); + session_deps_.host_resolver->rules()->AddRule("www.b.com", "10.0.0.2"); + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps_)); + + SSLSocketDataProvider ssl1(ASYNC, OK); + ssl1.SetNextProto(kProtoSPDY2); + SSLSocketDataProvider ssl2(ASYNC, OK); + ssl2.SetNextProto(kProtoSPDY2); + session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl1); + session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl2); + + scoped_ptr<SpdyFrame> host1_req(ConstructSpdyGet( + "https://www.a.com", false, 1, DEFAULT_PRIORITY)); + MockWrite spdy1_writes[] = { + CreateMockWrite(*host1_req, 1), + }; + scoped_ptr<SpdyFrame> host1_resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<SpdyFrame> host1_resp_body(ConstructSpdyBodyFrame(1, true)); + MockRead spdy1_reads[] = { + CreateMockRead(*host1_resp, 2), + CreateMockRead(*host1_resp_body, 3), + MockRead(ASYNC, ERR_IO_PENDING, 4), + }; + + scoped_ptr<OrderedSocketData> spdy1_data( + new OrderedSocketData( + spdy1_reads, arraysize(spdy1_reads), + spdy1_writes, arraysize(spdy1_writes))); + session_deps_.socket_factory->AddSocketDataProvider(spdy1_data.get()); + + scoped_ptr<SpdyFrame> host2_req(ConstructSpdyGet( + "https://www.b.com", false, 1, DEFAULT_PRIORITY)); + MockWrite spdy2_writes[] = { + CreateMockWrite(*host2_req, 1), + }; + scoped_ptr<SpdyFrame> host2_resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<SpdyFrame> host2_resp_body(ConstructSpdyBodyFrame(1, true)); + MockRead spdy2_reads[] = { + CreateMockRead(*host2_resp, 2), + CreateMockRead(*host2_resp_body, 3), + MockRead(ASYNC, ERR_IO_PENDING, 4), + }; + + scoped_ptr<OrderedSocketData> spdy2_data( + new OrderedSocketData( + spdy2_reads, arraysize(spdy2_reads), + spdy2_writes, arraysize(spdy2_writes))); + session_deps_.socket_factory->AddSocketDataProvider(spdy2_data.get()); + + MockWrite http_write[] = { + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.a.com\r\n" + "Connection: keep-alive\r\n\r\n"), + }; + + MockRead http_read[] = { + MockRead("HTTP/1.1 200 OK\r\n"), + MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"), + MockRead("Content-Length: 6\r\n\r\n"), + MockRead("hello!"), + }; + StaticSocketDataProvider http_data(http_read, arraysize(http_read), + http_write, arraysize(http_write)); + session_deps_.socket_factory->AddSocketDataProvider(&http_data); + + HostPortPair host_port_pair_a("www.a.com", 443); + HostPortProxyPair host_port_proxy_pair_a( + host_port_pair_a, ProxyServer::Direct()); + EXPECT_FALSE( + session->spdy_session_pool()->HasSession(host_port_proxy_pair_a)); + + TestCompletionCallback callback; + HttpRequestInfo request1; + request1.method = "GET"; + request1.url = GURL("https://www.a.com/"); + request1.load_flags = 0; + scoped_ptr<HttpNetworkTransaction> trans( + new HttpNetworkTransaction(DEFAULT_PRIORITY, session)); + + int rv = trans->Start(&request1, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + EXPECT_EQ(OK, callback.WaitForResult()); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + ASSERT_TRUE(response->headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine()); + EXPECT_TRUE(response->was_fetched_via_spdy); + EXPECT_TRUE(response->was_npn_negotiated); + + std::string response_data; + ASSERT_EQ(OK, ReadTransaction(trans.get(), &response_data)); + EXPECT_EQ("hello!", response_data); + trans.reset(); + EXPECT_TRUE( + session->spdy_session_pool()->HasSession(host_port_proxy_pair_a)); + + HostPortPair host_port_pair_b("www.b.com", 443); + HostPortProxyPair host_port_proxy_pair_b( + host_port_pair_b, ProxyServer::Direct()); + EXPECT_FALSE( + session->spdy_session_pool()->HasSession(host_port_proxy_pair_b)); + HttpRequestInfo request2; + request2.method = "GET"; + request2.url = GURL("https://www.b.com/"); + request2.load_flags = 0; + trans.reset(new HttpNetworkTransaction(DEFAULT_PRIORITY, session)); + + rv = trans->Start(&request2, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + EXPECT_EQ(OK, callback.WaitForResult()); + + response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + ASSERT_TRUE(response->headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine()); + EXPECT_TRUE(response->was_fetched_via_spdy); + EXPECT_TRUE(response->was_npn_negotiated); + ASSERT_EQ(OK, ReadTransaction(trans.get(), &response_data)); + EXPECT_EQ("hello!", response_data); + EXPECT_FALSE( + session->spdy_session_pool()->HasSession(host_port_proxy_pair_a)); + EXPECT_TRUE( + session->spdy_session_pool()->HasSession(host_port_proxy_pair_b)); + + HostPortPair host_port_pair_a1("www.a.com", 80); + HostPortProxyPair host_port_proxy_pair_a1( + host_port_pair_a1, ProxyServer::Direct()); + EXPECT_FALSE( + session->spdy_session_pool()->HasSession(host_port_proxy_pair_a1)); + HttpRequestInfo request3; + request3.method = "GET"; + request3.url = GURL("http://www.a.com/"); + request3.load_flags = 0; + trans.reset(new HttpNetworkTransaction(DEFAULT_PRIORITY, session)); + + rv = trans->Start(&request3, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + EXPECT_EQ(OK, callback.WaitForResult()); + + response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + ASSERT_TRUE(response->headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine()); + EXPECT_FALSE(response->was_fetched_via_spdy); + EXPECT_FALSE(response->was_npn_negotiated); + ASSERT_EQ(OK, ReadTransaction(trans.get(), &response_data)); + EXPECT_EQ("hello!", response_data); + EXPECT_FALSE( + session->spdy_session_pool()->HasSession(host_port_proxy_pair_a)); + EXPECT_FALSE( + session->spdy_session_pool()->HasSession(host_port_proxy_pair_b)); + + HttpStreamFactory::SetNextProtos(std::vector<std::string>()); +} + } // namespace net diff --git a/net/http/http_network_transaction_spdy3_unittest.cc b/net/http/http_network_transaction_spdy3_unittest.cc index 88e43eb..d766fd2 100644 --- a/net/http/http_network_transaction_spdy3_unittest.cc +++ b/net/http/http_network_transaction_spdy3_unittest.cc @@ -48,6 +48,7 @@ #include "net/proxy/proxy_resolver.h" #include "net/proxy/proxy_service.h" #include "net/socket/client_socket_factory.h" +#include "net/socket/client_socket_pool_manager.h" #include "net/socket/mock_client_socket_pool_manager.h" #include "net/socket/next_proto.h" #include "net/socket/socket_test_util.h" @@ -236,8 +237,24 @@ void TestLoadTimingNotReusedWithPac(const net::LoadTimingInfo& load_timing_info, } // namespace class HttpNetworkTransactionSpdy3Test : public PlatformTest { + public: + virtual ~HttpNetworkTransactionSpdy3Test() { + // Important to restore the per-pool limit first, since the pool limit must + // always be greater than group limit, and the tests reduce both limits. + ClientSocketPoolManager::set_max_sockets_per_pool( + HttpNetworkSession::NORMAL_SOCKET_POOL, old_max_pool_sockets_); + ClientSocketPoolManager::set_max_sockets_per_group( + HttpNetworkSession::NORMAL_SOCKET_POOL, old_max_group_sockets_); + } + protected: - HttpNetworkTransactionSpdy3Test() : session_deps_(kProtoSPDY3) {} + HttpNetworkTransactionSpdy3Test() + : session_deps_(kProtoSPDY3), + old_max_group_sockets_(ClientSocketPoolManager::max_sockets_per_group( + HttpNetworkSession::NORMAL_SOCKET_POOL)), + old_max_pool_sockets_(ClientSocketPoolManager::max_sockets_per_pool( + HttpNetworkSession::NORMAL_SOCKET_POOL)) { + } struct SimpleGetHelperResult { int rv; @@ -358,6 +375,11 @@ class HttpNetworkTransactionSpdy3Test : public PlatformTest { void CheckErrorIsPassedBack(int error, IoMode mode); SpdySessionDependencies session_deps_; + + // Original socket limits. Some tests set these. Safest to always restore + // them once each test has been run. + int old_max_group_sockets_; + int old_max_pool_sockets_; }; namespace { @@ -11132,4 +11154,168 @@ TEST_F(HttpNetworkTransactionSpdy3Test, ErrorSocketNotConnected) { EXPECT_TRUE(trans2.GetResponseInfo()->was_fetched_via_spdy); } +TEST_F(HttpNetworkTransactionSpdy3Test, CloseIdleSpdySessionToOpenNewOne) { + HttpStreamFactory::SetNextProtos(SpdyNextProtos()); + ClientSocketPoolManager::set_max_sockets_per_group( + HttpNetworkSession::NORMAL_SOCKET_POOL, 1); + ClientSocketPoolManager::set_max_sockets_per_pool( + HttpNetworkSession::NORMAL_SOCKET_POOL, 1); + + // Use two different hosts with different IPs so they don't get pooled. + session_deps_.host_resolver->rules()->AddRule("www.a.com", "10.0.0.1"); + session_deps_.host_resolver->rules()->AddRule("www.b.com", "10.0.0.2"); + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps_)); + + SSLSocketDataProvider ssl1(ASYNC, OK); + ssl1.SetNextProto(kProtoSPDY3); + SSLSocketDataProvider ssl2(ASYNC, OK); + ssl2.SetNextProto(kProtoSPDY3); + session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl1); + session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl2); + + scoped_ptr<SpdyFrame> host1_req(ConstructSpdyGet( + "https://www.a.com", false, 1, DEFAULT_PRIORITY)); + MockWrite spdy1_writes[] = { + CreateMockWrite(*host1_req, 1), + }; + scoped_ptr<SpdyFrame> host1_resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<SpdyFrame> host1_resp_body(ConstructSpdyBodyFrame(1, true)); + MockRead spdy1_reads[] = { + CreateMockRead(*host1_resp, 2), + CreateMockRead(*host1_resp_body, 3), + MockRead(ASYNC, ERR_IO_PENDING, 4), + }; + + scoped_ptr<OrderedSocketData> spdy1_data( + new OrderedSocketData( + spdy1_reads, arraysize(spdy1_reads), + spdy1_writes, arraysize(spdy1_writes))); + session_deps_.socket_factory->AddSocketDataProvider(spdy1_data.get()); + + scoped_ptr<SpdyFrame> host2_req(ConstructSpdyGet( + "https://www.b.com", false, 1, DEFAULT_PRIORITY)); + MockWrite spdy2_writes[] = { + CreateMockWrite(*host2_req, 1), + }; + scoped_ptr<SpdyFrame> host2_resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<SpdyFrame> host2_resp_body(ConstructSpdyBodyFrame(1, true)); + MockRead spdy2_reads[] = { + CreateMockRead(*host2_resp, 2), + CreateMockRead(*host2_resp_body, 3), + MockRead(ASYNC, ERR_IO_PENDING, 4), + }; + + scoped_ptr<OrderedSocketData> spdy2_data( + new OrderedSocketData( + spdy2_reads, arraysize(spdy2_reads), + spdy2_writes, arraysize(spdy2_writes))); + session_deps_.socket_factory->AddSocketDataProvider(spdy2_data.get()); + + MockWrite http_write[] = { + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.a.com\r\n" + "Connection: keep-alive\r\n\r\n"), + }; + + MockRead http_read[] = { + MockRead("HTTP/1.1 200 OK\r\n"), + MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"), + MockRead("Content-Length: 6\r\n\r\n"), + MockRead("hello!"), + }; + StaticSocketDataProvider http_data(http_read, arraysize(http_read), + http_write, arraysize(http_write)); + session_deps_.socket_factory->AddSocketDataProvider(&http_data); + + HostPortPair host_port_pair_a("www.a.com", 443); + HostPortProxyPair host_port_proxy_pair_a( + host_port_pair_a, ProxyServer::Direct()); + EXPECT_FALSE( + session->spdy_session_pool()->HasSession(host_port_proxy_pair_a)); + + TestCompletionCallback callback; + HttpRequestInfo request1; + request1.method = "GET"; + request1.url = GURL("https://www.a.com/"); + request1.load_flags = 0; + scoped_ptr<HttpNetworkTransaction> trans( + new HttpNetworkTransaction(DEFAULT_PRIORITY, session)); + + int rv = trans->Start(&request1, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + EXPECT_EQ(OK, callback.WaitForResult()); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + ASSERT_TRUE(response->headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine()); + EXPECT_TRUE(response->was_fetched_via_spdy); + EXPECT_TRUE(response->was_npn_negotiated); + + std::string response_data; + ASSERT_EQ(OK, ReadTransaction(trans.get(), &response_data)); + EXPECT_EQ("hello!", response_data); + trans.reset(); + EXPECT_TRUE( + session->spdy_session_pool()->HasSession(host_port_proxy_pair_a)); + + HostPortPair host_port_pair_b("www.b.com", 443); + HostPortProxyPair host_port_proxy_pair_b( + host_port_pair_b, ProxyServer::Direct()); + EXPECT_FALSE( + session->spdy_session_pool()->HasSession(host_port_proxy_pair_b)); + HttpRequestInfo request2; + request2.method = "GET"; + request2.url = GURL("https://www.b.com/"); + request2.load_flags = 0; + trans.reset(new HttpNetworkTransaction(DEFAULT_PRIORITY, session)); + + rv = trans->Start(&request2, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + EXPECT_EQ(OK, callback.WaitForResult()); + + response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + ASSERT_TRUE(response->headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine()); + EXPECT_TRUE(response->was_fetched_via_spdy); + EXPECT_TRUE(response->was_npn_negotiated); + ASSERT_EQ(OK, ReadTransaction(trans.get(), &response_data)); + EXPECT_EQ("hello!", response_data); + EXPECT_FALSE( + session->spdy_session_pool()->HasSession(host_port_proxy_pair_a)); + EXPECT_TRUE( + session->spdy_session_pool()->HasSession(host_port_proxy_pair_b)); + + HostPortPair host_port_pair_a1("www.a.com", 80); + HostPortProxyPair host_port_proxy_pair_a1( + host_port_pair_a1, ProxyServer::Direct()); + EXPECT_FALSE( + session->spdy_session_pool()->HasSession(host_port_proxy_pair_a1)); + HttpRequestInfo request3; + request3.method = "GET"; + request3.url = GURL("http://www.a.com/"); + request3.load_flags = 0; + trans.reset(new HttpNetworkTransaction(DEFAULT_PRIORITY, session)); + + rv = trans->Start(&request3, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + EXPECT_EQ(OK, callback.WaitForResult()); + + response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + ASSERT_TRUE(response->headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine()); + EXPECT_FALSE(response->was_fetched_via_spdy); + EXPECT_FALSE(response->was_npn_negotiated); + ASSERT_EQ(OK, ReadTransaction(trans.get(), &response_data)); + EXPECT_EQ("hello!", response_data); + EXPECT_FALSE( + session->spdy_session_pool()->HasSession(host_port_proxy_pair_a)); + EXPECT_FALSE( + session->spdy_session_pool()->HasSession(host_port_proxy_pair_b)); + + HttpStreamFactory::SetNextProtos(std::vector<std::string>()); +} + } // namespace net diff --git a/net/spdy/spdy_session.cc b/net/spdy/spdy_session.cc index cf697bf..1b62517 100644 --- a/net/spdy/spdy_session.cc +++ b/net/spdy/spdy_session.cc @@ -425,6 +425,7 @@ Error SpdySession::InitializeWithSocket( state_ = STATE_DO_READ; connection_.reset(connection); + connection_->AddLayeredPool(this); is_secure_ = is_secure; certificate_error_code_ = certificate_error_code; @@ -675,6 +676,17 @@ int SpdySession::GetProtocolVersion() const { return buffered_spdy_framer_->protocol_version(); } +bool SpdySession::CloseOneIdleConnection() { + if (!spdy_session_pool_ || num_active_streams() > 0) + return false; + bool ret = HasOneRef(); + // Will remove a reference to this. + RemoveFromPool(); + // Since the underlying socket is only returned when |this| is destroyed + // we should only return true if RemoveFromPool() removed the last ref. + return ret; +} + void SpdySession::EnqueueStreamWrite( SpdyStream* stream, SpdyFrameType frame_type, diff --git a/net/spdy/spdy_session.h b/net/spdy/spdy_session.h index 5e22074..e838fa6 100644 --- a/net/spdy/spdy_session.h +++ b/net/spdy/spdy_session.h @@ -22,6 +22,7 @@ #include "net/base/net_export.h" #include "net/base/request_priority.h" #include "net/socket/client_socket_handle.h" +#include "net/socket/client_socket_pool.h" #include "net/socket/next_proto.h" #include "net/socket/ssl_client_socket.h" #include "net/socket/stream_socket.h" @@ -174,7 +175,8 @@ class NET_EXPORT_PRIVATE SpdyStreamRequest { }; class NET_EXPORT SpdySession : public base::RefCounted<SpdySession>, - public BufferedSpdyFramerVisitorInterface { + public BufferedSpdyFramerVisitorInterface, + public LayeredPool { public: // TODO(akalin): Use base::TickClock when it becomes available. typedef base::TimeTicks (*TimeFunc)(void); @@ -453,6 +455,9 @@ class NET_EXPORT SpdySession : public base::RefCounted<SpdySession>, return buffered_spdy_framer_->GetDataFrameMaximumPayload(); } + // LayeredPool implementation: + virtual bool CloseOneIdleConnection() OVERRIDE; + private: friend class base::RefCounted<SpdySession>; friend class SpdyStreamRequest; diff --git a/net/spdy/spdy_session_spdy2_unittest.cc b/net/spdy/spdy_session_spdy2_unittest.cc index 686949b..0a36e6a 100644 --- a/net/spdy/spdy_session_spdy2_unittest.cc +++ b/net/spdy/spdy_session_spdy2_unittest.cc @@ -5,6 +5,7 @@ #include "net/spdy/spdy_session.h" #include "base/memory/scoped_ptr.h" +#include "base/run_loop.h" #include "net/base/io_buffer.h" #include "net/base/ip_endpoint.h" #include "net/base/net_log_unittest.h" @@ -12,6 +13,7 @@ #include "net/base/test_data_directory.h" #include "net/base/test_data_stream.h" #include "net/dns/host_cache.h" +#include "net/socket/client_socket_pool_manager.h" #include "net/socket/next_proto.h" #include "net/spdy/spdy_session_pool.h" #include "net/spdy/spdy_session_test_util.h" @@ -43,14 +45,28 @@ base::TimeTicks TheNearFuture() { class SpdySessionSpdy2Test : public PlatformTest { protected: SpdySessionSpdy2Test() - : session_deps_(kProtoSPDY2), + : old_max_group_sockets_(ClientSocketPoolManager::max_sockets_per_group( + HttpNetworkSession::NORMAL_SOCKET_POOL)), + old_max_pool_sockets_(ClientSocketPoolManager::max_sockets_per_pool( + HttpNetworkSession::NORMAL_SOCKET_POOL)), + spdy_util_(kProtoSPDY2), + session_deps_(kProtoSPDY2), spdy_session_pool_(NULL), test_url_(kTestUrl), test_host_port_pair_(kTestHost, kTestPort), pair_(test_host_port_pair_, ProxyServer::Direct()) { } - virtual void SetUp() { + virtual ~SpdySessionSpdy2Test() { + // Important to restore the per-pool limit first, since the pool limit must + // always be greater than group limit, and the tests reduce both limits. + ClientSocketPoolManager::set_max_sockets_per_pool( + HttpNetworkSession::NORMAL_SOCKET_POOL, old_max_pool_sockets_); + ClientSocketPoolManager::set_max_sockets_per_group( + HttpNetworkSession::NORMAL_SOCKET_POOL, old_max_group_sockets_); + } + + virtual void SetUp() OVERRIDE { g_delta_seconds = 0; } @@ -100,6 +116,12 @@ class SpdySessionSpdy2Test : public PlatformTest { return session->InitializeWithSocket(connection.release(), false, OK); } + // Original socket limits. Some tests set these. Safest to always restore + // them once each test has been run. + int old_max_group_sockets_; + int old_max_pool_sockets_; + + SpdyTestUtil spdy_util_; scoped_refptr<TransportSocketParams> transport_params_; SpdySessionDependencies session_deps_; scoped_refptr<HttpNetworkSession> http_session_; @@ -2135,4 +2157,200 @@ TEST_F(SpdySessionSpdy2Test, ProtocolNegotiation) { EXPECT_EQ(0, session->session_unacked_recv_window_bytes_); } +// Tests the case of a non-SPDY request closing an idle SPDY session when no +// pointers to the idle session are currently held. +TEST_F(SpdySessionSpdy2Test, CloseOneIdleConnection) { + ClientSocketPoolManager::set_max_sockets_per_group( + HttpNetworkSession::NORMAL_SOCKET_POOL, 1); + ClientSocketPoolManager::set_max_sockets_per_pool( + HttpNetworkSession::NORMAL_SOCKET_POOL, 1); + + MockConnect connect_data(SYNCHRONOUS, OK); + MockRead reads[] = { + MockRead(SYNCHRONOUS, ERR_IO_PENDING) // Stall forever. + }; + StaticSocketDataProvider data(reads, arraysize(reads), NULL, 0); + data.set_connect_data(connect_data); + session_deps_.socket_factory->AddSocketDataProvider(&data); + session_deps_.socket_factory->AddSocketDataProvider(&data); + + CreateNetworkSession(); + + TransportClientSocketPool* pool = + http_session_->GetTransportSocketPool( + HttpNetworkSession::NORMAL_SOCKET_POOL); + + // Create an idle SPDY session. + HostPortProxyPair pair1(HostPortPair("1.com", 80), ProxyServer::Direct()); + scoped_refptr<SpdySession> session1 = GetSession(pair1); + EXPECT_EQ( + OK, + InitializeSession(http_session_.get(), session1.get(), pair1.first)); + EXPECT_FALSE(pool->IsStalled()); + // Release the pointer to the session so it can be closed. + session1 = NULL; + + // Trying to create a new connection should cause the pool to be stalled, and + // post a task asynchronously to try and close the session. + TestCompletionCallback callback2; + HostPortPair host_port2("2.com", 80); + scoped_refptr<TransportSocketParams> params2( + new TransportSocketParams(host_port2, DEFAULT_PRIORITY, false, false, + OnHostResolutionCallback())); + scoped_ptr<ClientSocketHandle> connection2(new ClientSocketHandle); + EXPECT_EQ(ERR_IO_PENDING, + connection2->Init(host_port2.ToString(), params2, DEFAULT_PRIORITY, + callback2.callback(), pool, BoundNetLog())); + EXPECT_TRUE(pool->IsStalled()); + + // The socket pool should close the connection asynchronously and establish a + // new connection. + EXPECT_EQ(OK, callback2.WaitForResult()); + EXPECT_FALSE(pool->IsStalled()); +} + +// Tests the case of a non-SPDY request closing an idle SPDY session when a +// pointer to the idle session is still held. +TEST_F(SpdySessionSpdy2Test, CloseOneIdleConnectionSessionStillHeld) { + ClientSocketPoolManager::set_max_sockets_per_group( + HttpNetworkSession::NORMAL_SOCKET_POOL, 1); + ClientSocketPoolManager::set_max_sockets_per_pool( + HttpNetworkSession::NORMAL_SOCKET_POOL, 1); + + MockConnect connect_data(SYNCHRONOUS, OK); + MockRead reads[] = { + MockRead(SYNCHRONOUS, ERR_IO_PENDING) // Stall forever. + }; + StaticSocketDataProvider data(reads, arraysize(reads), NULL, 0); + data.set_connect_data(connect_data); + session_deps_.socket_factory->AddSocketDataProvider(&data); + session_deps_.socket_factory->AddSocketDataProvider(&data); + + CreateNetworkSession(); + + TransportClientSocketPool* pool = + http_session_->GetTransportSocketPool( + HttpNetworkSession::NORMAL_SOCKET_POOL); + + // Create an idle SPDY session. + HostPortProxyPair pair1(HostPortPair("1.com", 80), ProxyServer::Direct()); + scoped_refptr<SpdySession> session1 = GetSession(pair1); + EXPECT_EQ( + OK, + InitializeSession(http_session_.get(), session1.get(), pair1.first)); + EXPECT_FALSE(pool->IsStalled()); + + // Trying to create a new connection should cause the pool to be stalled, and + // post a task asynchronously to try and close the session. + TestCompletionCallback callback2; + HostPortPair host_port2("2.com", 80); + scoped_refptr<TransportSocketParams> params2( + new TransportSocketParams(host_port2, DEFAULT_PRIORITY, false, false, + OnHostResolutionCallback())); + scoped_ptr<ClientSocketHandle> connection2(new ClientSocketHandle); + EXPECT_EQ(ERR_IO_PENDING, + connection2->Init(host_port2.ToString(), params2, DEFAULT_PRIORITY, + callback2.callback(), pool, BoundNetLog())); + EXPECT_TRUE(pool->IsStalled()); + + // Running the message loop should cause the session to prepare to be closed, + // but since there's still an outstanding reference, it should not be closed + // yet. + base::RunLoop().RunUntilIdle(); + EXPECT_TRUE(pool->IsStalled()); + EXPECT_FALSE(callback2.have_result()); + + // Release the pointer to the session so it can be closed. + session1 = NULL; + EXPECT_EQ(OK, callback2.WaitForResult()); + EXPECT_FALSE(pool->IsStalled()); +} + +// Tests that a non-SPDY request can't close a SPDY session that's currently in +// use. +TEST_F(SpdySessionSpdy2Test, CloseOneIdleConnectionFailsWhenSessionInUse) { + ClientSocketPoolManager::set_max_sockets_per_group( + HttpNetworkSession::NORMAL_SOCKET_POOL, 1); + ClientSocketPoolManager::set_max_sockets_per_pool( + HttpNetworkSession::NORMAL_SOCKET_POOL, 1); + + MockConnect connect_data(SYNCHRONOUS, OK); + MockRead reads[] = { + MockRead(SYNCHRONOUS, ERR_IO_PENDING) // Stall forever. + }; + scoped_ptr<SpdyFrame> req1(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + scoped_ptr<SpdyFrame> cancel1(ConstructSpdyRstStream(1, RST_STREAM_CANCEL)); + MockWrite writes[] = { + CreateMockWrite(*req1, 1), + CreateMockWrite(*cancel1, 1), + }; + StaticSocketDataProvider data(reads, arraysize(reads), + writes, arraysize(writes)); + data.set_connect_data(connect_data); + session_deps_.socket_factory->AddSocketDataProvider(&data); + + CreateNetworkSession(); + + TransportClientSocketPool* pool = + http_session_->GetTransportSocketPool( + HttpNetworkSession::NORMAL_SOCKET_POOL); + + // Create a SPDY session. + GURL url1("http://www.google.com"); + HostPortProxyPair pair1(HostPortPair(url1.host(), 80), + ProxyServer::Direct()); + scoped_refptr<SpdySession> session1 = GetSession(pair1); + EXPECT_EQ( + OK, + InitializeSession(http_session_.get(), session1.get(), pair1.first)); + EXPECT_FALSE(pool->IsStalled()); + + // Create a stream using the session, and send a request. + + TestCompletionCallback callback1; + base::WeakPtr<SpdyStream> spdy_stream1 = + CreateStreamSynchronously(session1, url1, DEFAULT_PRIORITY, + BoundNetLog()); + ASSERT_TRUE(spdy_stream1.get()); + + spdy_stream1->set_spdy_headers( + spdy_util_.ConstructGetHeaderBlock(url1.spec())); + EXPECT_TRUE(spdy_stream1->HasUrl()); + + spdy_stream1->SendRequest(false); + MessageLoop::current()->RunUntilIdle(); + + // Release the session, so holding onto a pointer here does not affect + // anything. + session1 = NULL; + + // Trying to create a new connection should cause the pool to be stalled, and + // post a task asynchronously to try and close the session. + TestCompletionCallback callback2; + HostPortPair host_port2("2.com", 80); + scoped_refptr<TransportSocketParams> params2( + new TransportSocketParams(host_port2, DEFAULT_PRIORITY, false, false, + OnHostResolutionCallback())); + scoped_ptr<ClientSocketHandle> connection2(new ClientSocketHandle); + EXPECT_EQ(ERR_IO_PENDING, + connection2->Init(host_port2.ToString(), params2, DEFAULT_PRIORITY, + callback2.callback(), pool, BoundNetLog())); + EXPECT_TRUE(pool->IsStalled()); + + // Running the message loop should cause the socket pool to ask the SPDY + // session to close an idle socket, but since the socket is in use, nothing + // happens. + base::RunLoop().RunUntilIdle(); + EXPECT_TRUE(pool->IsStalled()); + EXPECT_FALSE(callback2.have_result()); + + // Cancelling the request should still not release the session's socket, + // since the session is still kept alive by the SpdySessionPool. + ASSERT_TRUE(spdy_stream1.get()); + spdy_stream1->Cancel(); + base::RunLoop().RunUntilIdle(); + EXPECT_TRUE(pool->IsStalled()); + EXPECT_FALSE(callback2.have_result()); +} + } // namespace net diff --git a/net/spdy/spdy_session_spdy3_unittest.cc b/net/spdy/spdy_session_spdy3_unittest.cc index 6c7313a..f906b2f 100644 --- a/net/spdy/spdy_session_spdy3_unittest.cc +++ b/net/spdy/spdy_session_spdy3_unittest.cc @@ -7,6 +7,7 @@ #include "base/bind.h" #include "base/callback.h" #include "base/memory/scoped_ptr.h" +#include "base/run_loop.h" #include "net/base/io_buffer.h" #include "net/base/ip_endpoint.h" #include "net/base/net_log_unittest.h" @@ -14,6 +15,7 @@ #include "net/base/test_data_directory.h" #include "net/base/test_data_stream.h" #include "net/dns/host_cache.h" +#include "net/socket/client_socket_pool_manager.h" #include "net/socket/next_proto.h" #include "net/spdy/spdy_http_utils.h" #include "net/spdy/spdy_session_pool.h" @@ -97,7 +99,11 @@ class SpdySessionSpdy3Test : public PlatformTest { protected: SpdySessionSpdy3Test() - : spdy_util_(kProtoSPDY3), + : old_max_group_sockets_(ClientSocketPoolManager::max_sockets_per_group( + HttpNetworkSession::NORMAL_SOCKET_POOL)), + old_max_pool_sockets_(ClientSocketPoolManager::max_sockets_per_pool( + HttpNetworkSession::NORMAL_SOCKET_POOL)), + spdy_util_(kProtoSPDY3), session_deps_(kProtoSPDY3), spdy_session_pool_(NULL), test_url_(kTestUrl), @@ -105,7 +111,16 @@ class SpdySessionSpdy3Test : public PlatformTest { pair_(test_host_port_pair_, ProxyServer::Direct()) { } - virtual void SetUp() { + virtual ~SpdySessionSpdy3Test() { + // Important to restore the per-pool limit first, since the pool limit must + // always be greater than group limit, and the tests reduce both limits. + ClientSocketPoolManager::set_max_sockets_per_pool( + HttpNetworkSession::NORMAL_SOCKET_POOL, old_max_pool_sockets_); + ClientSocketPoolManager::set_max_sockets_per_group( + HttpNetworkSession::NORMAL_SOCKET_POOL, old_max_group_sockets_); + } + + virtual void SetUp() OVERRIDE { g_delta_seconds = 0; } @@ -183,6 +198,11 @@ class SpdySessionSpdy3Test : public PlatformTest { const base::Callback<void(SpdySession*, SpdyStream*)>& stall_fn, const base::Callback<void(SpdySession*, SpdyStream*, int32)>& unstall_fn); + // Original socket limits. Some tests set these. Safest to always restore + // them once each test has been run. + int old_max_group_sockets_; + int old_max_pool_sockets_; + SpdyTestUtil spdy_util_; scoped_refptr<TransportSocketParams> transport_params_; SpdySessionDependencies session_deps_; @@ -3516,4 +3536,200 @@ TEST_F(SpdySessionSpdy3Test, SendWindowSizeIncreaseWithDeletedSession31) { EXPECT_EQ(0, delegate2.body_data_sent()); } +// Tests the case of a non-SPDY request closing an idle SPDY session when no +// pointers to the idle session are currently held. +TEST_F(SpdySessionSpdy3Test, CloseOneIdleConnection) { + ClientSocketPoolManager::set_max_sockets_per_group( + HttpNetworkSession::NORMAL_SOCKET_POOL, 1); + ClientSocketPoolManager::set_max_sockets_per_pool( + HttpNetworkSession::NORMAL_SOCKET_POOL, 1); + + MockConnect connect_data(SYNCHRONOUS, OK); + MockRead reads[] = { + MockRead(SYNCHRONOUS, ERR_IO_PENDING) // Stall forever. + }; + StaticSocketDataProvider data(reads, arraysize(reads), NULL, 0); + data.set_connect_data(connect_data); + session_deps_.socket_factory->AddSocketDataProvider(&data); + session_deps_.socket_factory->AddSocketDataProvider(&data); + + CreateNetworkSession(); + + TransportClientSocketPool* pool = + http_session_->GetTransportSocketPool( + HttpNetworkSession::NORMAL_SOCKET_POOL); + + // Create an idle SPDY session. + HostPortProxyPair pair1(HostPortPair("1.com", 80), ProxyServer::Direct()); + scoped_refptr<SpdySession> session1 = GetSession(pair1); + EXPECT_EQ( + OK, + InitializeSession(http_session_.get(), session1.get(), pair1.first)); + EXPECT_FALSE(pool->IsStalled()); + // Release the pointer to the session so it can be closed. + session1 = NULL; + + // Trying to create a new connection should cause the pool to be stalled, and + // post a task asynchronously to try and close the session. + TestCompletionCallback callback2; + HostPortPair host_port2("2.com", 80); + scoped_refptr<TransportSocketParams> params2( + new TransportSocketParams(host_port2, DEFAULT_PRIORITY, false, false, + OnHostResolutionCallback())); + scoped_ptr<ClientSocketHandle> connection2(new ClientSocketHandle); + EXPECT_EQ(ERR_IO_PENDING, + connection2->Init(host_port2.ToString(), params2, DEFAULT_PRIORITY, + callback2.callback(), pool, BoundNetLog())); + EXPECT_TRUE(pool->IsStalled()); + + // The socket pool should close the connection asynchronously and establish a + // new connection. + EXPECT_EQ(OK, callback2.WaitForResult()); + EXPECT_FALSE(pool->IsStalled()); +} + +// Tests the case of a non-SPDY request closing an idle SPDY session when a +// pointer to the idle session is still held. +TEST_F(SpdySessionSpdy3Test, CloseOneIdleConnectionSessionStillHeld) { + ClientSocketPoolManager::set_max_sockets_per_group( + HttpNetworkSession::NORMAL_SOCKET_POOL, 1); + ClientSocketPoolManager::set_max_sockets_per_pool( + HttpNetworkSession::NORMAL_SOCKET_POOL, 1); + + MockConnect connect_data(SYNCHRONOUS, OK); + MockRead reads[] = { + MockRead(SYNCHRONOUS, ERR_IO_PENDING) // Stall forever. + }; + StaticSocketDataProvider data(reads, arraysize(reads), NULL, 0); + data.set_connect_data(connect_data); + session_deps_.socket_factory->AddSocketDataProvider(&data); + session_deps_.socket_factory->AddSocketDataProvider(&data); + + CreateNetworkSession(); + + TransportClientSocketPool* pool = + http_session_->GetTransportSocketPool( + HttpNetworkSession::NORMAL_SOCKET_POOL); + + // Create an idle SPDY session. + HostPortProxyPair pair1(HostPortPair("1.com", 80), ProxyServer::Direct()); + scoped_refptr<SpdySession> session1 = GetSession(pair1); + EXPECT_EQ( + OK, + InitializeSession(http_session_.get(), session1.get(), pair1.first)); + EXPECT_FALSE(pool->IsStalled()); + + // Trying to create a new connection should cause the pool to be stalled, and + // post a task asynchronously to try and close the session. + TestCompletionCallback callback2; + HostPortPair host_port2("2.com", 80); + scoped_refptr<TransportSocketParams> params2( + new TransportSocketParams(host_port2, DEFAULT_PRIORITY, false, false, + OnHostResolutionCallback())); + scoped_ptr<ClientSocketHandle> connection2(new ClientSocketHandle); + EXPECT_EQ(ERR_IO_PENDING, + connection2->Init(host_port2.ToString(), params2, DEFAULT_PRIORITY, + callback2.callback(), pool, BoundNetLog())); + EXPECT_TRUE(pool->IsStalled()); + + // Running the message loop should cause the session to prepare to be closed, + // but since there's still an outstanding reference, it should not be closed + // yet. + base::RunLoop().RunUntilIdle(); + EXPECT_TRUE(pool->IsStalled()); + EXPECT_FALSE(callback2.have_result()); + + // Release the pointer to the session so it can be closed. + session1 = NULL; + EXPECT_EQ(OK, callback2.WaitForResult()); + EXPECT_FALSE(pool->IsStalled()); +} + +// Tests that a non-SPDY request can't close a SPDY session that's currently in +// use. +TEST_F(SpdySessionSpdy3Test, CloseOneIdleConnectionFailsWhenSessionInUse) { + ClientSocketPoolManager::set_max_sockets_per_group( + HttpNetworkSession::NORMAL_SOCKET_POOL, 1); + ClientSocketPoolManager::set_max_sockets_per_pool( + HttpNetworkSession::NORMAL_SOCKET_POOL, 1); + + MockConnect connect_data(SYNCHRONOUS, OK); + MockRead reads[] = { + MockRead(SYNCHRONOUS, ERR_IO_PENDING) // Stall forever. + }; + scoped_ptr<SpdyFrame> req1(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + scoped_ptr<SpdyFrame> cancel1(ConstructSpdyRstStream(1, RST_STREAM_CANCEL)); + MockWrite writes[] = { + CreateMockWrite(*req1, 1), + CreateMockWrite(*cancel1, 1), + }; + StaticSocketDataProvider data(reads, arraysize(reads), + writes, arraysize(writes)); + data.set_connect_data(connect_data); + session_deps_.socket_factory->AddSocketDataProvider(&data); + + CreateNetworkSession(); + + TransportClientSocketPool* pool = + http_session_->GetTransportSocketPool( + HttpNetworkSession::NORMAL_SOCKET_POOL); + + // Create a SPDY session. + GURL url1("http://www.google.com"); + HostPortProxyPair pair1(HostPortPair(url1.host(), 80), + ProxyServer::Direct()); + scoped_refptr<SpdySession> session1 = GetSession(pair1); + EXPECT_EQ( + OK, + InitializeSession(http_session_.get(), session1.get(), pair1.first)); + EXPECT_FALSE(pool->IsStalled()); + + // Create a stream using the session, and send a request. + + TestCompletionCallback callback1; + base::WeakPtr<SpdyStream> spdy_stream1 = + CreateStreamSynchronously(session1, url1, DEFAULT_PRIORITY, + BoundNetLog()); + ASSERT_TRUE(spdy_stream1.get()); + + spdy_stream1->set_spdy_headers( + spdy_util_.ConstructGetHeaderBlock(url1.spec())); + EXPECT_TRUE(spdy_stream1->HasUrl()); + + spdy_stream1->SendRequest(false); + MessageLoop::current()->RunUntilIdle(); + + // Release the session, so holding onto a pointer here does not affect + // anything. + session1 = NULL; + + // Trying to create a new connection should cause the pool to be stalled, and + // post a task asynchronously to try and close the session. + TestCompletionCallback callback2; + HostPortPair host_port2("2.com", 80); + scoped_refptr<TransportSocketParams> params2( + new TransportSocketParams(host_port2, DEFAULT_PRIORITY, false, false, + OnHostResolutionCallback())); + scoped_ptr<ClientSocketHandle> connection2(new ClientSocketHandle); + EXPECT_EQ(ERR_IO_PENDING, + connection2->Init(host_port2.ToString(), params2, DEFAULT_PRIORITY, + callback2.callback(), pool, BoundNetLog())); + EXPECT_TRUE(pool->IsStalled()); + + // Running the message loop should cause the socket pool to ask the SPDY + // session to close an idle socket, but since the socket is in use, nothing + // happens. + base::RunLoop().RunUntilIdle(); + EXPECT_TRUE(pool->IsStalled()); + EXPECT_FALSE(callback2.have_result()); + + // Cancelling the request should still not release the session's socket, + // since the session is still kept alive by the SpdySessionPool. + ASSERT_TRUE(spdy_stream1.get()); + spdy_stream1->Cancel(); + base::RunLoop().RunUntilIdle(); + EXPECT_TRUE(pool->IsStalled()); + EXPECT_FALSE(callback2.have_result()); +} + } // namespace net |