summaryrefslogtreecommitdiffstats
path: root/net
diff options
context:
space:
mode:
authormmenke@chromium.org <mmenke@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-05-14 01:07:03 +0000
committermmenke@chromium.org <mmenke@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-05-14 01:07:03 +0000
commit483fa202c401443e4abfb4773d2705745364eeed (patch)
tree621a41ce7028f7a5abf6ca7f0abd25b168006132 /net
parentb0c819a8e60befd294d6de8e2ee32755801e62d5 (diff)
downloadchromium_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.cc188
-rw-r--r--net/http/http_network_transaction_spdy3_unittest.cc188
-rw-r--r--net/spdy/spdy_session.cc12
-rw-r--r--net/spdy/spdy_session.h7
-rw-r--r--net/spdy/spdy_session_spdy2_unittest.cc222
-rw-r--r--net/spdy/spdy_session_spdy3_unittest.cc220
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