diff options
author | simonjam@chromium.org <simonjam@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-03-01 21:31:31 +0000 |
---|---|---|
committer | simonjam@chromium.org <simonjam@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-03-01 21:31:31 +0000 |
commit | 5477d890353c732bbd0c16d8a3c5ce56a52ee2fe (patch) | |
tree | b225cdf9c0dba2d2c77cc76b094551a8be9ba5cf /net | |
parent | 271398aa3ff859d3deace9ff3ca575f14d263964 (diff) | |
download | chromium_src-5477d890353c732bbd0c16d8a3c5ce56a52ee2fe.zip chromium_src-5477d890353c732bbd0c16d8a3c5ce56a52ee2fe.tar.gz chromium_src-5477d890353c732bbd0c16d8a3c5ce56a52ee2fe.tar.bz2 |
Add a force pipelining option to load flags.
Details:
- Add a HttpPipelinedHostForced class for connections with forced requests.
+ Forced requests get their own pipeline and there's only one per host.
+ They always try to pipeline and won't retry if evicted.
+ Only one HttpStreamFactoryImpl::Job runs for all requests to the same
origin with forced pipelining. All requests will fail if that Job fails.
- Track HttpPipelinedHosts with a Key. Right now that's origin and
force-pipelining, but it might be expanded to include content type.
- Add a BufferedWriteStreamSocket that wraps a normal socket. It buffers Write()
calls until a task fires to dispatch the buffer to the underlying socket.
BUG=110794
TEST=net_unittests and unit_tests
Review URL: http://codereview.chromium.org/9433015
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@124487 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net')
28 files changed, 1264 insertions, 256 deletions
diff --git a/net/http/http_network_session.cc b/net/http/http_network_session.cc index 1a59c9f..6c10c1a 100644 --- a/net/http/http_network_session.cc +++ b/net/http/http_network_session.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -29,6 +29,7 @@ HttpNetworkSession::HttpNetworkSession(const Params& params) http_server_properties_(params.http_server_properties), cert_verifier_(params.cert_verifier), http_auth_handler_factory_(params.http_auth_handler_factory), + force_http_pipelining_(params.force_http_pipelining), proxy_service_(params.proxy_service), ssl_config_service_(params.ssl_config_service), socket_pool_manager_( @@ -49,7 +50,8 @@ HttpNetworkSession::HttpNetworkSession(const Params& params) params.ssl_config_service, params.http_server_properties), ALLOW_THIS_IN_INITIALIZER_LIST(http_stream_factory_( - new HttpStreamFactoryImpl(this))) { + new HttpStreamFactoryImpl(this))), + params_(params) { DCHECK(proxy_service_); DCHECK(ssl_config_service_); CHECK(http_server_properties_); diff --git a/net/http/http_network_session.h b/net/http/http_network_session.h index 2ccec50..cb58cb0 100644 --- a/net/http/http_network_session.h +++ b/net/http/http_network_session.h @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -62,7 +62,8 @@ class NET_EXPORT HttpNetworkSession http_auth_handler_factory(NULL), network_delegate(NULL), http_server_properties(NULL), - net_log(NULL) {} + net_log(NULL), + force_http_pipelining(false) {} ClientSocketFactory* client_socket_factory; HostResolver* host_resolver; @@ -77,6 +78,7 @@ class NET_EXPORT HttpNetworkSession NetworkDelegate* network_delegate; HttpServerProperties* http_server_properties; NetLog* net_log; + bool force_http_pipelining; }; explicit HttpNetworkSession(const Params& params); @@ -138,6 +140,11 @@ class NET_EXPORT HttpNetworkSession void CloseAllConnections(); void CloseIdleConnections(); + bool force_http_pipelining() const { return force_http_pipelining_; } + + // Returns the original Params used to construct this session. + const Params& params() const { return params_; } + private: friend class base::RefCounted<HttpNetworkSession>; friend class HttpNetworkSessionPeer; @@ -149,6 +156,7 @@ class NET_EXPORT HttpNetworkSession HttpServerProperties* const http_server_properties_; CertVerifier* const cert_verifier_; HttpAuthHandlerFactory* const http_auth_handler_factory_; + bool force_http_pipelining_; // Not const since it's modified by HttpNetworkSessionPeer for testing. ProxyService* proxy_service_; @@ -160,6 +168,8 @@ class NET_EXPORT HttpNetworkSession SpdySessionPool spdy_session_pool_; scoped_ptr<HttpStreamFactory> http_stream_factory_; std::set<HttpResponseBodyDrainer*> response_drainers_; + + Params params_; }; } // namespace net diff --git a/net/http/http_network_transaction.cc b/net/http/http_network_transaction.cc index 53e3856..57ff747 100644 --- a/net/http/http_network_transaction.cc +++ b/net/http/http_network_transaction.cc @@ -1209,6 +1209,14 @@ int HttpNetworkTransaction::HandleIOError(int error) { } break; case ERR_PIPELINE_EVICTION: + if (!session_->force_http_pipelining()) { + net_log_.AddEvent( + NetLog::TYPE_HTTP_TRANSACTION_RESTART_AFTER_ERROR, + make_scoped_refptr(new NetLogIntegerParameter("net_error", error))); + ResetConnectionAndRequestForResend(); + error = OK; + } + break; case ERR_SPDY_PING_FAILED: case ERR_SPDY_SERVER_REFUSED_STREAM: net_log_.AddEvent( diff --git a/net/http/http_pipelined_connection_impl.h b/net/http/http_pipelined_connection_impl.h index e09c3f9..3f9e269 100644 --- a/net/http/http_pipelined_connection_impl.h +++ b/net/http/http_pipelined_connection_impl.h @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -46,6 +46,24 @@ class SSLInfo; class NET_EXPORT_PRIVATE HttpPipelinedConnectionImpl : public HttpPipelinedConnection { public: + class Factory : public HttpPipelinedConnection::Factory { + public: + virtual HttpPipelinedConnection* CreateNewPipeline( + ClientSocketHandle* connection, + HttpPipelinedConnection::Delegate* delegate, + const HostPortPair& origin, + const SSLConfig& used_ssl_config, + const ProxyInfo& used_proxy_info, + const BoundNetLog& net_log, + bool was_npn_negotiated, + SSLClientSocket::NextProto protocol_negotiated) OVERRIDE { + return new HttpPipelinedConnectionImpl(connection, delegate, origin, + used_ssl_config, used_proxy_info, + net_log, was_npn_negotiated, + protocol_negotiated); + } + }; + HttpPipelinedConnectionImpl(ClientSocketHandle* connection, Delegate* delegate, const HostPortPair& origin, diff --git a/net/http/http_pipelined_host.cc b/net/http/http_pipelined_host.cc new file mode 100644 index 0000000..ca47780 --- /dev/null +++ b/net/http/http_pipelined_host.cc @@ -0,0 +1,17 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/http/http_pipelined_host.h" + +namespace net { + +HttpPipelinedHost::Key::Key(const HostPortPair& origin) + : origin_(origin) { +} + +bool HttpPipelinedHost::Key::operator<(const Key& rhs) const { + return origin_ < rhs.origin_; +} + +} // namespace net diff --git a/net/http/http_pipelined_host.h b/net/http/http_pipelined_host.h index 14c0f09..24b6e3a 100644 --- a/net/http/http_pipelined_host.h +++ b/net/http/http_pipelined_host.h @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -6,6 +6,7 @@ #define NET_HTTP_HTTP_PIPELINED_HOST_H_ #pragma once +#include "net/base/host_port_pair.h" #include "net/base/net_export.h" #include "net/http/http_pipelined_connection.h" #include "net/http/http_pipelined_host_capability.h" @@ -28,6 +29,19 @@ struct SSLConfig; // assigns requests to the least loaded pipelined connection. class NET_EXPORT_PRIVATE HttpPipelinedHost { public: + class NET_EXPORT_PRIVATE Key { + public: + Key(const HostPortPair& origin); + + // The host and port associated with this key. + const HostPortPair& origin() const { return origin_; } + + bool operator<(const Key& rhs) const; + + private: + const HostPortPair origin_; + }; + class Delegate { public: // Called when a pipelined host has no outstanding requests on any of its @@ -50,9 +64,10 @@ class NET_EXPORT_PRIVATE HttpPipelinedHost { // Returns a new HttpPipelinedHost. virtual HttpPipelinedHost* CreateNewHost( - Delegate* delegate, const HostPortPair& origin, + Delegate* delegate, const Key& key, HttpPipelinedConnection::Factory* factory, - HttpPipelinedHostCapability capability) = 0; + HttpPipelinedHostCapability capability, + bool force_pipelining) = 0; }; virtual ~HttpPipelinedHost() {} @@ -75,13 +90,12 @@ class NET_EXPORT_PRIVATE HttpPipelinedHost { // requests. virtual bool IsExistingPipelineAvailable() const = 0; - // Returns the host and port associated with this class. - virtual const HostPortPair& origin() const = 0; + // Returns a Key that uniquely identifies this host. + virtual const Key& GetKey() const = 0; // Creates a Value summary of this host's pipelines. Caller assumes // ownership of the returned Value. virtual base::Value* PipelineInfoToValue() const = 0; - }; } // namespace net diff --git a/net/http/http_pipelined_host_forced.cc b/net/http/http_pipelined_host_forced.cc new file mode 100644 index 0000000..7f0c91a --- /dev/null +++ b/net/http/http_pipelined_host_forced.cc @@ -0,0 +1,108 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/http/http_pipelined_host_forced.h" + +#include "base/values.h" +#include "net/http/http_pipelined_connection_impl.h" +#include "net/http/http_pipelined_stream.h" +#include "net/socket/buffered_write_stream_socket.h" +#include "net/socket/client_socket_handle.h" + +using base::DictionaryValue; +using base::ListValue; +using base::Value; + +namespace net { + +HttpPipelinedHostForced::HttpPipelinedHostForced( + HttpPipelinedHost::Delegate* delegate, + const Key& key, + HttpPipelinedConnection::Factory* factory) + : delegate_(delegate), + key_(key), + factory_(factory) { + if (!factory) { + factory_.reset(new HttpPipelinedConnectionImpl::Factory()); + } +} + +HttpPipelinedHostForced::~HttpPipelinedHostForced() { + CHECK(!pipeline_.get()); +} + +HttpPipelinedStream* HttpPipelinedHostForced::CreateStreamOnNewPipeline( + ClientSocketHandle* connection, + const SSLConfig& used_ssl_config, + const ProxyInfo& used_proxy_info, + const BoundNetLog& net_log, + bool was_npn_negotiated, + SSLClientSocket::NextProto protocol_negotiated) { + CHECK(!pipeline_.get()); + StreamSocket* wrapped_socket = connection->release_socket(); + BufferedWriteStreamSocket* buffered_socket = new BufferedWriteStreamSocket( + wrapped_socket); + connection->set_socket(buffered_socket); + pipeline_.reset(factory_->CreateNewPipeline( + connection, this, key_.origin(), used_ssl_config, used_proxy_info, + net_log, was_npn_negotiated, protocol_negotiated)); + return pipeline_->CreateNewStream(); +} + +HttpPipelinedStream* HttpPipelinedHostForced::CreateStreamOnExistingPipeline() { + if (!pipeline_.get()) { + return NULL; + } + return pipeline_->CreateNewStream(); +} + +bool HttpPipelinedHostForced::IsExistingPipelineAvailable() const { + return pipeline_.get() != NULL; +} + +const HttpPipelinedHost::Key& HttpPipelinedHostForced::GetKey() const { + return key_; +} + +void HttpPipelinedHostForced::OnPipelineEmpty( + HttpPipelinedConnection* pipeline) { + CHECK_EQ(pipeline_.get(), pipeline); + pipeline_.reset(); + delegate_->OnHostIdle(this); + // WARNING: We'll probably be deleted here. +} + +void HttpPipelinedHostForced::OnPipelineHasCapacity( + HttpPipelinedConnection* pipeline) { + CHECK_EQ(pipeline_.get(), pipeline); + delegate_->OnHostHasAdditionalCapacity(this); + if (!pipeline->depth()) { + OnPipelineEmpty(pipeline); + // WARNING: We might be deleted here. + } +} + +void HttpPipelinedHostForced::OnPipelineFeedback( + HttpPipelinedConnection* pipeline, + HttpPipelinedConnection::Feedback feedback) { + // We don't care. We always pipeline. +} + +Value* HttpPipelinedHostForced::PipelineInfoToValue() const { + ListValue* list_value = new ListValue(); + if (pipeline_.get()) { + DictionaryValue* pipeline_dict = new DictionaryValue; + pipeline_dict->SetString("host", key_.origin().ToString()); + pipeline_dict->SetBoolean("forced", true); + pipeline_dict->SetInteger("depth", pipeline_->depth()); + pipeline_dict->SetInteger("capacity", 1000); + pipeline_dict->SetBoolean("usable", pipeline_->usable()); + pipeline_dict->SetBoolean("active", pipeline_->active()); + pipeline_dict->SetInteger("source_id", pipeline_->net_log().source().id); + list_value->Append(pipeline_dict); + } + return list_value; +} + +} // namespace net diff --git a/net/http/http_pipelined_host_forced.h b/net/http/http_pipelined_host_forced.h new file mode 100644 index 0000000..fb222f5 --- /dev/null +++ b/net/http/http_pipelined_host_forced.h @@ -0,0 +1,84 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef NET_HTTP_HTTP_PIPELINED_HOST_FORCED_H_ +#define NET_HTTP_HTTP_PIPELINED_HOST_FORCED_H_ +#pragma once + +#include <string> + +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "net/base/host_port_pair.h" +#include "net/base/net_export.h" +#include "net/http/http_pipelined_connection.h" +#include "net/http/http_pipelined_host.h" +#include "net/http/http_pipelined_host_capability.h" + +namespace base { +class Value; +} + +namespace net { + +class BoundNetLog; +class ClientSocketHandle; +class HttpPipelinedStream; +class ProxyInfo; +struct SSLConfig; + +// Manages a single pipelined connection for requests to a host that are forced +// to use pipelining. Note that this is normally not used. It is intended to +// test the user's connection for pipelining compatibility. +class NET_EXPORT_PRIVATE HttpPipelinedHostForced + : public HttpPipelinedHost, + public HttpPipelinedConnection::Delegate { + public: + HttpPipelinedHostForced(HttpPipelinedHost::Delegate* delegate, + const Key& key, + HttpPipelinedConnection::Factory* factory); + virtual ~HttpPipelinedHostForced(); + + // HttpPipelinedHost interface + virtual HttpPipelinedStream* CreateStreamOnNewPipeline( + ClientSocketHandle* connection, + const SSLConfig& used_ssl_config, + const ProxyInfo& used_proxy_info, + const BoundNetLog& net_log, + bool was_npn_negotiated, + SSLClientSocket::NextProto protocol_negotiated) OVERRIDE; + + virtual HttpPipelinedStream* CreateStreamOnExistingPipeline() OVERRIDE; + + virtual bool IsExistingPipelineAvailable() const OVERRIDE; + + virtual const Key& GetKey() const OVERRIDE; + + virtual base::Value* PipelineInfoToValue() const OVERRIDE; + + // HttpPipelinedConnection::Delegate interface + + virtual void OnPipelineHasCapacity( + HttpPipelinedConnection* pipeline) OVERRIDE; + + virtual void OnPipelineFeedback( + HttpPipelinedConnection* pipeline, + HttpPipelinedConnection::Feedback feedback) OVERRIDE; + + private: + // Called when a pipeline is empty and there are no pending requests. Closes + // the connection. + void OnPipelineEmpty(HttpPipelinedConnection* pipeline); + + HttpPipelinedHost::Delegate* delegate_; + const Key key_; + scoped_ptr<HttpPipelinedConnection> pipeline_; + scoped_ptr<HttpPipelinedConnection::Factory> factory_; + + DISALLOW_COPY_AND_ASSIGN(HttpPipelinedHostForced); +}; + +} // namespace net + +#endif // NET_HTTP_HTTP_PIPELINED_HOST_FORCED_H_ diff --git a/net/http/http_pipelined_host_forced_unittest.cc b/net/http/http_pipelined_host_forced_unittest.cc new file mode 100644 index 0000000..de91b1b --- /dev/null +++ b/net/http/http_pipelined_host_forced_unittest.cc @@ -0,0 +1,106 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/http/http_pipelined_host_forced.h" + +#include "base/memory/scoped_ptr.h" +#include "net/base/ssl_config_service.h" +#include "net/http/http_pipelined_host_test_util.h" +#include "net/proxy/proxy_info.h" +#include "net/socket/client_socket_handle.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using testing::NiceMock; +using testing::Ref; +using testing::Return; + +namespace net { + +namespace { + +HttpPipelinedStream* kDummyStream = + reinterpret_cast<HttpPipelinedStream*>(24); + +class HttpPipelinedHostForcedTest : public testing::Test { + public: + HttpPipelinedHostForcedTest() + : key_(HostPortPair("host", 123)), + factory_(new MockPipelineFactory), // Owned by |host_|. + host_(new HttpPipelinedHostForced(&delegate_, key_, factory_)) { + } + + MockPipeline* AddTestPipeline() { + MockPipeline* pipeline = new MockPipeline(0, true, true); + EXPECT_CALL(*factory_, CreateNewPipeline(&connection_, host_.get(), + MatchesOrigin(key_.origin()), + Ref(ssl_config_), Ref(proxy_info_), + Ref(net_log_), true, + SSLClientSocket::kProtoSPDY21)) + .Times(1) + .WillOnce(Return(pipeline)); + EXPECT_CALL(*pipeline, CreateNewStream()) + .Times(1) + .WillOnce(Return(kDummyStream)); + EXPECT_EQ(kDummyStream, host_->CreateStreamOnNewPipeline( + &connection_, ssl_config_, proxy_info_, net_log_, true, + SSLClientSocket::kProtoSPDY21)); + return pipeline; + } + + ClientSocketHandle connection_; + NiceMock<MockHostDelegate> delegate_; + HttpPipelinedHost::Key key_; + MockPipelineFactory* factory_; + scoped_ptr<HttpPipelinedHostForced> host_; + + SSLConfig ssl_config_; + ProxyInfo proxy_info_; + BoundNetLog net_log_; +}; + +TEST_F(HttpPipelinedHostForcedTest, Delegate) { + EXPECT_TRUE(key_.origin().Equals(host_->GetKey().origin())); +} + +TEST_F(HttpPipelinedHostForcedTest, SingleUser) { + EXPECT_FALSE(host_->IsExistingPipelineAvailable()); + + MockPipeline* pipeline = AddTestPipeline(); + EXPECT_TRUE(host_->IsExistingPipelineAvailable()); + + EXPECT_CALL(delegate_, OnHostHasAdditionalCapacity(host_.get())) + .Times(1); + EXPECT_CALL(delegate_, OnHostIdle(host_.get())) + .Times(1); + host_->OnPipelineHasCapacity(pipeline); +} + +TEST_F(HttpPipelinedHostForcedTest, ReuseExisting) { + EXPECT_EQ(NULL, host_->CreateStreamOnExistingPipeline()); + + MockPipeline* pipeline = AddTestPipeline(); + EXPECT_CALL(*pipeline, CreateNewStream()) + .Times(1) + .WillOnce(Return(kDummyStream)); + EXPECT_EQ(kDummyStream, host_->CreateStreamOnExistingPipeline()); + + pipeline->SetState(1, true, true); + EXPECT_CALL(delegate_, OnHostHasAdditionalCapacity(host_.get())) + .Times(1); + EXPECT_CALL(delegate_, OnHostIdle(host_.get())) + .Times(0); + host_->OnPipelineHasCapacity(pipeline); + + pipeline->SetState(0, true, true); + EXPECT_CALL(delegate_, OnHostHasAdditionalCapacity(host_.get())) + .Times(1); + EXPECT_CALL(delegate_, OnHostIdle(host_.get())) + .Times(1); + host_->OnPipelineHasCapacity(pipeline); +} + +} // anonymous namespace + +} // namespace net diff --git a/net/http/http_pipelined_host_impl.cc b/net/http/http_pipelined_host_impl.cc index 8da86b7..5aa561f 100644 --- a/net/http/http_pipelined_host_impl.cc +++ b/net/http/http_pipelined_host_impl.cc @@ -19,36 +19,17 @@ namespace net { // costing too much performance. Until then, this is just a bad guess. static const int kNumKnownSuccessesThreshold = 3; -class HttpPipelinedConnectionImplFactory : - public HttpPipelinedConnection::Factory { - public: - HttpPipelinedConnection* CreateNewPipeline( - ClientSocketHandle* connection, - HttpPipelinedConnection::Delegate* delegate, - const HostPortPair& origin, - const SSLConfig& used_ssl_config, - const ProxyInfo& used_proxy_info, - const BoundNetLog& net_log, - bool was_npn_negotiated, - SSLClientSocket::NextProto protocol_negotiated) OVERRIDE { - return new HttpPipelinedConnectionImpl(connection, delegate, origin, - used_ssl_config, used_proxy_info, - net_log, was_npn_negotiated, - protocol_negotiated); - } -}; - HttpPipelinedHostImpl::HttpPipelinedHostImpl( HttpPipelinedHost::Delegate* delegate, - const HostPortPair& origin, + const HttpPipelinedHost::Key& key, HttpPipelinedConnection::Factory* factory, HttpPipelinedHostCapability capability) : delegate_(delegate), - origin_(origin), + key_(key), factory_(factory), capability_(capability) { if (!factory) { - factory_.reset(new HttpPipelinedConnectionImplFactory()); + factory_.reset(new HttpPipelinedConnectionImpl::Factory()); } } @@ -67,8 +48,8 @@ HttpPipelinedStream* HttpPipelinedHostImpl::CreateStreamOnNewPipeline( return NULL; } HttpPipelinedConnection* pipeline = factory_->CreateNewPipeline( - connection, this, origin_, used_ssl_config, used_proxy_info, net_log, - was_npn_negotiated, protocol_negotiated); + connection, this, key_.origin(), used_ssl_config, used_proxy_info, + net_log, was_npn_negotiated, protocol_negotiated); PipelineInfo info; pipelines_.insert(std::make_pair(pipeline, info)); return pipeline->CreateNewStream(); @@ -100,8 +81,8 @@ bool HttpPipelinedHostImpl::IsExistingPipelineAvailable() const { return false; } -const HostPortPair& HttpPipelinedHostImpl::origin() const { - return origin_; +const HttpPipelinedHost::Key& HttpPipelinedHostImpl::GetKey() const { + return key_; } void HttpPipelinedHostImpl::OnPipelineEmpty(HttpPipelinedConnection* pipeline) { @@ -214,7 +195,8 @@ Value* HttpPipelinedHostImpl::PipelineInfoToValue() const { for (PipelineInfoMap::const_iterator it = pipelines_.begin(); it != pipelines_.end(); ++it) { DictionaryValue* pipeline_dict = new DictionaryValue; - pipeline_dict->SetString("host", origin_.ToString()); + pipeline_dict->SetString("host", key_.origin().ToString()); + pipeline_dict->SetBoolean("forced", false); pipeline_dict->SetInteger("depth", it->first->depth()); pipeline_dict->SetInteger("capacity", GetPipelineCapacity()); pipeline_dict->SetBoolean("usable", it->first->usable()); diff --git a/net/http/http_pipelined_host_impl.h b/net/http/http_pipelined_host_impl.h index 48c282b..55e929e 100644 --- a/net/http/http_pipelined_host_impl.h +++ b/net/http/http_pipelined_host_impl.h @@ -37,7 +37,7 @@ class NET_EXPORT_PRIVATE HttpPipelinedHostImpl public HttpPipelinedConnection::Delegate { public: HttpPipelinedHostImpl(HttpPipelinedHost::Delegate* delegate, - const HostPortPair& origin, + const HttpPipelinedHost::Key& key, HttpPipelinedConnection::Factory* factory, HttpPipelinedHostCapability capability); virtual ~HttpPipelinedHostImpl(); @@ -66,7 +66,7 @@ class NET_EXPORT_PRIVATE HttpPipelinedHostImpl HttpPipelinedConnection* pipeline, HttpPipelinedConnection::Feedback feedback) OVERRIDE; - virtual const HostPortPair& origin() const OVERRIDE; + virtual const Key& GetKey() const OVERRIDE; // Creates a Value summary of this host's |pipelines_|. Caller assumes // ownership of the returned Value. @@ -105,7 +105,7 @@ class NET_EXPORT_PRIVATE HttpPipelinedHostImpl void NotifyAllPipelinesHaveCapacity(); HttpPipelinedHost::Delegate* delegate_; - const HostPortPair origin_; + const Key key_; PipelineInfoMap pipelines_; scoped_ptr<HttpPipelinedConnection::Factory> factory_; HttpPipelinedHostCapability capability_; diff --git a/net/http/http_pipelined_host_impl_unittest.cc b/net/http/http_pipelined_host_impl_unittest.cc index 48107b5..4559278 100644 --- a/net/http/http_pipelined_host_impl_unittest.cc +++ b/net/http/http_pipelined_host_impl_unittest.cc @@ -7,6 +7,7 @@ #include "base/memory/scoped_ptr.h" #include "net/base/ssl_config_service.h" #include "net/http/http_pipelined_connection.h" +#include "net/http/http_pipelined_host_test_util.h" #include "net/proxy/proxy_info.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" @@ -26,81 +27,25 @@ ClientSocketHandle* kDummyConnection = HttpPipelinedStream* kDummyStream = reinterpret_cast<HttpPipelinedStream*>(42); -class MockHostDelegate : public HttpPipelinedHost::Delegate { - public: - MOCK_METHOD1(OnHostIdle, void(HttpPipelinedHost* host)); - MOCK_METHOD1(OnHostHasAdditionalCapacity, void(HttpPipelinedHost* host)); - MOCK_METHOD2(OnHostDeterminedCapability, - void(HttpPipelinedHost* host, - HttpPipelinedHostCapability capability)); -}; - -class MockPipelineFactory : public HttpPipelinedConnection::Factory { - public: - MOCK_METHOD8(CreateNewPipeline, HttpPipelinedConnection*( - ClientSocketHandle* connection, - HttpPipelinedConnection::Delegate* delegate, - const HostPortPair& origin, - const SSLConfig& used_ssl_config, - const ProxyInfo& used_proxy_info, - const BoundNetLog& net_log, - bool was_npn_negotiated, - SSLClientSocket::NextProto protocol_negotiated)); -}; - -class MockPipeline : public HttpPipelinedConnection { - public: - MockPipeline(int depth, bool usable, bool active) - : depth_(depth), - usable_(usable), - active_(active) { - } - - void SetState(int depth, bool usable, bool active) { - depth_ = depth; - usable_ = usable; - active_ = active; - } - - virtual int depth() const OVERRIDE { return depth_; } - virtual bool usable() const OVERRIDE { return usable_; } - virtual bool active() const OVERRIDE { return active_; } - - MOCK_METHOD0(CreateNewStream, HttpPipelinedStream*()); - MOCK_METHOD1(OnStreamDeleted, void(int pipeline_id)); - MOCK_CONST_METHOD0(used_ssl_config, const SSLConfig&()); - MOCK_CONST_METHOD0(used_proxy_info, const ProxyInfo&()); - MOCK_CONST_METHOD0(net_log, const BoundNetLog&()); - MOCK_CONST_METHOD0(was_npn_negotiated, bool()); - MOCK_CONST_METHOD0(protocol_negotiated, SSLClientSocket::NextProto()); - - private: - int depth_; - bool usable_; - bool active_; -}; - -MATCHER_P(MatchesOrigin, expected, "") { return expected.Equals(arg); } - class HttpPipelinedHostImplTest : public testing::Test { public: HttpPipelinedHostImplTest() - : origin_("host", 123), + : key_(HostPortPair("host", 123)), factory_(new MockPipelineFactory), // Owned by host_. - host_(new HttpPipelinedHostImpl(&delegate_, origin_, factory_, + host_(new HttpPipelinedHostImpl(&delegate_, key_, factory_, PIPELINE_CAPABLE)) { } void SetCapability(HttpPipelinedHostCapability capability) { factory_ = new MockPipelineFactory; host_.reset(new HttpPipelinedHostImpl( - &delegate_, origin_, factory_, capability)); + &delegate_, key_, factory_, capability)); } MockPipeline* AddTestPipeline(int depth, bool usable, bool active) { MockPipeline* pipeline = new MockPipeline(depth, usable, active); EXPECT_CALL(*factory_, CreateNewPipeline(kDummyConnection, host_.get(), - MatchesOrigin(origin_), + MatchesOrigin(key_.origin()), Ref(ssl_config_), Ref(proxy_info_), Ref(net_log_), true, SSLClientSocket::kProtoSPDY21)) @@ -121,7 +66,7 @@ class HttpPipelinedHostImplTest : public testing::Test { } NiceMock<MockHostDelegate> delegate_; - HostPortPair origin_; + HttpPipelinedHost::Key key_; MockPipelineFactory* factory_; scoped_ptr<HttpPipelinedHostImpl> host_; @@ -131,7 +76,7 @@ class HttpPipelinedHostImplTest : public testing::Test { }; TEST_F(HttpPipelinedHostImplTest, Delegate) { - EXPECT_TRUE(origin_.Equals(host_->origin())); + EXPECT_TRUE(key_.origin().Equals(host_->GetKey().origin())); } TEST_F(HttpPipelinedHostImplTest, OnUnusablePipelineHasCapacity) { diff --git a/net/http/http_pipelined_host_pool.cc b/net/http/http_pipelined_host_pool.cc index 116c5bb..5adb8a4 100644 --- a/net/http/http_pipelined_host_pool.cc +++ b/net/http/http_pipelined_host_pool.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -8,6 +8,7 @@ #include "base/stl_util.h" #include "base/values.h" #include "net/http/http_pipelined_host_capability.h" +#include "net/http/http_pipelined_host_forced.h" #include "net/http/http_pipelined_host_impl.h" #include "net/http/http_server_properties.h" @@ -19,20 +20,28 @@ namespace net { class HttpPipelinedHostImplFactory : public HttpPipelinedHost::Factory { public: virtual HttpPipelinedHost* CreateNewHost( - HttpPipelinedHost::Delegate* delegate, const HostPortPair& origin, + HttpPipelinedHost::Delegate* delegate, + const HttpPipelinedHost::Key& key, HttpPipelinedConnection::Factory* factory, - HttpPipelinedHostCapability capability) OVERRIDE { - return new HttpPipelinedHostImpl(delegate, origin, factory, capability); + HttpPipelinedHostCapability capability, + bool force_pipelining) OVERRIDE { + if (force_pipelining) { + return new HttpPipelinedHostForced(delegate, key, factory); + } else { + return new HttpPipelinedHostImpl(delegate, key, factory, capability); + } } }; HttpPipelinedHostPool::HttpPipelinedHostPool( Delegate* delegate, HttpPipelinedHost::Factory* factory, - HttpServerProperties* http_server_properties) + HttpServerProperties* http_server_properties, + bool force_pipelining) : delegate_(delegate), factory_(factory), - http_server_properties_(http_server_properties) { + http_server_properties_(http_server_properties), + force_pipelining_(force_pipelining) { if (!factory) { factory_.reset(new HttpPipelinedHostImplFactory); } @@ -42,22 +51,22 @@ HttpPipelinedHostPool::~HttpPipelinedHostPool() { CHECK(host_map_.empty()); } -bool HttpPipelinedHostPool::IsHostEligibleForPipelining( - const HostPortPair& origin) { +bool HttpPipelinedHostPool::IsKeyEligibleForPipelining( + const HttpPipelinedHost::Key& key) { HttpPipelinedHostCapability capability = - http_server_properties_->GetPipelineCapability(origin); + http_server_properties_->GetPipelineCapability(key.origin()); return capability != PIPELINE_INCAPABLE; } HttpPipelinedStream* HttpPipelinedHostPool::CreateStreamOnNewPipeline( - const HostPortPair& origin, + const HttpPipelinedHost::Key& key, ClientSocketHandle* connection, const SSLConfig& used_ssl_config, const ProxyInfo& used_proxy_info, const BoundNetLog& net_log, bool was_npn_negotiated, SSLClientSocket::NextProto protocol_negotiated) { - HttpPipelinedHost* host = GetPipelinedHost(origin, true); + HttpPipelinedHost* host = GetPipelinedHost(key, true); if (!host) { return NULL; } @@ -68,17 +77,17 @@ HttpPipelinedStream* HttpPipelinedHostPool::CreateStreamOnNewPipeline( } HttpPipelinedStream* HttpPipelinedHostPool::CreateStreamOnExistingPipeline( - const HostPortPair& origin) { - HttpPipelinedHost* host = GetPipelinedHost(origin, false); + const HttpPipelinedHost::Key& key) { + HttpPipelinedHost* host = GetPipelinedHost(key, false); if (!host) { return NULL; } return host->CreateStreamOnExistingPipeline(); } -bool HttpPipelinedHostPool::IsExistingPipelineAvailableForOrigin( - const HostPortPair& origin) { - HttpPipelinedHost* host = GetPipelinedHost(origin, false); +bool HttpPipelinedHostPool::IsExistingPipelineAvailableForKey( + const HttpPipelinedHost::Key& key) { + HttpPipelinedHost* host = GetPipelinedHost(key, false); if (!host) { return false; } @@ -86,8 +95,8 @@ bool HttpPipelinedHostPool::IsExistingPipelineAvailableForOrigin( } HttpPipelinedHost* HttpPipelinedHostPool::GetPipelinedHost( - const HostPortPair& origin, bool create_if_not_found) { - HostMap::iterator host_it = host_map_.find(origin); + const HttpPipelinedHost::Key& key, bool create_if_not_found) { + HostMap::iterator host_it = host_map_.find(key); if (host_it != host_map_.end()) { CHECK(host_it->second); return host_it->second; @@ -96,33 +105,34 @@ HttpPipelinedHost* HttpPipelinedHostPool::GetPipelinedHost( } HttpPipelinedHostCapability capability = - http_server_properties_->GetPipelineCapability(origin); + http_server_properties_->GetPipelineCapability(key.origin()); if (capability == PIPELINE_INCAPABLE) { return NULL; } HttpPipelinedHost* host = factory_->CreateNewHost( - this, origin, NULL, capability); - host_map_[origin] = host; + this, key, NULL, capability, force_pipelining_); + host_map_[key] = host; return host; } void HttpPipelinedHostPool::OnHostIdle(HttpPipelinedHost* host) { - const HostPortPair& origin = host->origin(); - CHECK(ContainsKey(host_map_, origin)); - host_map_.erase(origin); + const HttpPipelinedHost::Key& key = host->GetKey(); + CHECK(ContainsKey(host_map_, key)); + host_map_.erase(key); delete host; } void HttpPipelinedHostPool::OnHostHasAdditionalCapacity( HttpPipelinedHost* host) { - delegate_->OnHttpPipelinedHostHasAdditionalCapacity(host->origin()); + delegate_->OnHttpPipelinedHostHasAdditionalCapacity(host); } void HttpPipelinedHostPool::OnHostDeterminedCapability( HttpPipelinedHost* host, HttpPipelinedHostCapability capability) { - http_server_properties_->SetPipelineCapability(host->origin(), capability); + http_server_properties_->SetPipelineCapability(host->GetKey().origin(), + capability); } Value* HttpPipelinedHostPool::PipelineInfoToValue() const { diff --git a/net/http/http_pipelined_host_pool.h b/net/http/http_pipelined_host_pool.h index 7cebbfe..1d0bbc6 100644 --- a/net/http/http_pipelined_host_pool.h +++ b/net/http/http_pipelined_host_pool.h @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -35,22 +35,23 @@ class NET_EXPORT_PRIVATE HttpPipelinedHostPool // Called when a HttpPipelinedHost has new capacity. Attempts to allocate // any pending pipeline-capable requests to pipelines. virtual void OnHttpPipelinedHostHasAdditionalCapacity( - const HostPortPair& origin) = 0; + HttpPipelinedHost* host) = 0; }; HttpPipelinedHostPool(Delegate* delegate, HttpPipelinedHost::Factory* factory, - HttpServerProperties* http_server_properties_); + HttpServerProperties* http_server_properties_, + bool force_pipelining); virtual ~HttpPipelinedHostPool(); - // Returns true if pipelining might work for |origin|. Generally, this returns - // true, unless |origin| is known to have failed pipelining recently. - bool IsHostEligibleForPipelining(const HostPortPair& origin); + // Returns true if pipelining might work for |key|. Generally, this returns + // true, unless |key| is known to have failed pipelining recently. + bool IsKeyEligibleForPipelining(const HttpPipelinedHost::Key& key); // Constructs a new pipeline on |connection| and returns a new // HttpPipelinedStream that uses it. HttpPipelinedStream* CreateStreamOnNewPipeline( - const HostPortPair& origin, + const HttpPipelinedHost::Key& key, ClientSocketHandle* connection, const SSLConfig& used_ssl_config, const ProxyInfo& used_proxy_info, @@ -61,11 +62,11 @@ class NET_EXPORT_PRIVATE HttpPipelinedHostPool // Tries to find an existing pipeline with capacity for a new request. If // successful, returns a new stream on that pipeline. Otherwise, returns NULL. HttpPipelinedStream* CreateStreamOnExistingPipeline( - const HostPortPair& origin); + const HttpPipelinedHost::Key& key); - // Returns true if a pipelined connection already exists for this origin and + // Returns true if a pipelined connection already exists for |key| and // can accept new requests. - bool IsExistingPipelineAvailableForOrigin(const HostPortPair& origin); + bool IsExistingPipelineAvailableForKey(const HttpPipelinedHost::Key& key); // Callbacks for HttpPipelinedHost. virtual void OnHostIdle(HttpPipelinedHost* host) OVERRIDE; @@ -81,15 +82,16 @@ class NET_EXPORT_PRIVATE HttpPipelinedHostPool base::Value* PipelineInfoToValue() const; private: - typedef std::map<const HostPortPair, HttpPipelinedHost*> HostMap; + typedef std::map<HttpPipelinedHost::Key, HttpPipelinedHost*> HostMap; - HttpPipelinedHost* GetPipelinedHost(const HostPortPair& origin, + HttpPipelinedHost* GetPipelinedHost(const HttpPipelinedHost::Key& key, bool create_if_not_found); Delegate* delegate_; scoped_ptr<HttpPipelinedHost::Factory> factory_; HostMap host_map_; HttpServerProperties* http_server_properties_; + bool force_pipelining_; DISALLOW_COPY_AND_ASSIGN(HttpPipelinedHostPool); }; diff --git a/net/http/http_pipelined_host_pool_unittest.cc b/net/http/http_pipelined_host_pool_unittest.cc index beb5807..9863678 100644 --- a/net/http/http_pipelined_host_pool_unittest.cc +++ b/net/http/http_pipelined_host_pool_unittest.cc @@ -1,10 +1,11 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "net/http/http_pipelined_host_pool.h" #include "base/memory/scoped_ptr.h" +#include "base/rand_util.h" #include "net/base/ssl_config_service.h" #include "net/http/http_pipelined_host.h" #include "net/http/http_pipelined_host_capability.h" @@ -30,21 +31,23 @@ HttpPipelinedStream* kDummyStream = class MockPoolDelegate : public HttpPipelinedHostPool::Delegate { public: MOCK_METHOD1(OnHttpPipelinedHostHasAdditionalCapacity, - void(const HostPortPair& origin)); + void(HttpPipelinedHost* host)); }; class MockHostFactory : public HttpPipelinedHost::Factory { public: - MOCK_METHOD4(CreateNewHost, HttpPipelinedHost*( - HttpPipelinedHost::Delegate* delegate, const HostPortPair& origin, + MOCK_METHOD5(CreateNewHost, HttpPipelinedHost*( + HttpPipelinedHost::Delegate* delegate, + const HttpPipelinedHost::Key& key, HttpPipelinedConnection::Factory* factory, - HttpPipelinedHostCapability capability)); + HttpPipelinedHostCapability capability, + bool force_pipelining)); }; class MockHost : public HttpPipelinedHost { public: - MockHost(const HostPortPair& origin) - : origin_(origin) { + MockHost(const Key& key) + : key_(key) { } MOCK_METHOD6(CreateStreamOnNewPipeline, HttpPipelinedStream*( @@ -58,42 +61,59 @@ class MockHost : public HttpPipelinedHost { MOCK_CONST_METHOD0(IsExistingPipelineAvailable, bool()); MOCK_CONST_METHOD0(PipelineInfoToValue, base::Value*()); - virtual const HostPortPair& origin() const OVERRIDE { return origin_; } + virtual const Key& GetKey() const OVERRIDE { return key_; } private: - HostPortPair origin_; + Key key_; }; class HttpPipelinedHostPoolTest : public testing::Test { public: HttpPipelinedHostPoolTest() - : origin_("host", 123), + : key_(HostPortPair("host", 123)), factory_(new MockHostFactory), // Owned by pool_. - host_(new MockHost(origin_)), // Owned by pool_. + host_(new MockHost(key_)), // Owned by pool_. http_server_properties_(new HttpServerPropertiesImpl), pool_(new HttpPipelinedHostPool(&delegate_, factory_, - http_server_properties_.get())), + http_server_properties_.get(), false)), was_npn_negotiated_(false), protocol_negotiated_(SSLClientSocket::kProtoUnknown) { } - void CreateDummyStream() { - EXPECT_CALL(*host_, CreateStreamOnNewPipeline(kDummyConnection, - Ref(ssl_config_), - Ref(proxy_info_), - Ref(net_log_), - was_npn_negotiated_, - protocol_negotiated_)) + void CreateDummyStream(const HttpPipelinedHost::Key& key, + ClientSocketHandle* connection, + HttpPipelinedStream* stream, + MockHost* host) { + EXPECT_CALL(*host, CreateStreamOnNewPipeline(connection, + Ref(ssl_config_), + Ref(proxy_info_), + Ref(net_log_), + was_npn_negotiated_, + protocol_negotiated_)) .Times(1) - .WillOnce(Return(kDummyStream)); - EXPECT_EQ(kDummyStream, - pool_->CreateStreamOnNewPipeline(origin_, kDummyConnection, + .WillOnce(Return(stream)); + EXPECT_EQ(stream, + pool_->CreateStreamOnNewPipeline(key, connection, ssl_config_, proxy_info_, net_log_, was_npn_negotiated_, protocol_negotiated_)); } - HostPortPair origin_; + MockHost* CreateDummyHost(const HttpPipelinedHost::Key& key) { + MockHost* mock_host = new MockHost(key); + EXPECT_CALL(*factory_, CreateNewHost(pool_.get(), Ref(key), _, + PIPELINE_UNKNOWN, false)) + .Times(1) + .WillOnce(Return(mock_host)); + ClientSocketHandle* dummy_connection = + reinterpret_cast<ClientSocketHandle*>(base::RandUint64()); + HttpPipelinedStream* dummy_stream = + reinterpret_cast<HttpPipelinedStream*>(base::RandUint64()); + CreateDummyStream(key, dummy_connection, dummy_stream, mock_host); + return mock_host; + } + + HttpPipelinedHost::Key key_; MockPoolDelegate delegate_; MockHostFactory* factory_; MockHost* host_; @@ -108,96 +128,131 @@ class HttpPipelinedHostPoolTest : public testing::Test { }; TEST_F(HttpPipelinedHostPoolTest, DefaultUnknown) { - EXPECT_TRUE(pool_->IsHostEligibleForPipelining(origin_)); - EXPECT_CALL(*factory_, CreateNewHost(pool_.get(), Ref(origin_), _, - PIPELINE_UNKNOWN)) + EXPECT_TRUE(pool_->IsKeyEligibleForPipelining(key_)); + EXPECT_CALL(*factory_, CreateNewHost(pool_.get(), Ref(key_), _, + PIPELINE_UNKNOWN, false)) .Times(1) .WillOnce(Return(host_)); - CreateDummyStream(); + CreateDummyStream(key_, kDummyConnection, kDummyStream, host_); pool_->OnHostIdle(host_); } TEST_F(HttpPipelinedHostPoolTest, RemembersIncapable) { - EXPECT_CALL(*factory_, CreateNewHost(pool_.get(), Ref(origin_), _, - PIPELINE_UNKNOWN)) + EXPECT_CALL(*factory_, CreateNewHost(pool_.get(), Ref(key_), _, + PIPELINE_UNKNOWN, false)) .Times(1) .WillOnce(Return(host_)); - CreateDummyStream(); + CreateDummyStream(key_, kDummyConnection, kDummyStream, host_); pool_->OnHostDeterminedCapability(host_, PIPELINE_INCAPABLE); pool_->OnHostIdle(host_); - EXPECT_FALSE(pool_->IsHostEligibleForPipelining(origin_)); - EXPECT_CALL(*factory_, CreateNewHost(pool_.get(), Ref(origin_), _, - PIPELINE_INCAPABLE)) + EXPECT_FALSE(pool_->IsKeyEligibleForPipelining(key_)); + EXPECT_CALL(*factory_, CreateNewHost(pool_.get(), Ref(key_), _, + PIPELINE_INCAPABLE, false)) .Times(0); EXPECT_EQ(NULL, - pool_->CreateStreamOnNewPipeline(origin_, kDummyConnection, + pool_->CreateStreamOnNewPipeline(key_, kDummyConnection, ssl_config_, proxy_info_, net_log_, was_npn_negotiated_, protocol_negotiated_)); } TEST_F(HttpPipelinedHostPoolTest, RemembersCapable) { - EXPECT_CALL(*factory_, CreateNewHost(pool_.get(), Ref(origin_), _, - PIPELINE_UNKNOWN)) + EXPECT_CALL(*factory_, CreateNewHost(pool_.get(), Ref(key_), _, + PIPELINE_UNKNOWN, false)) .Times(1) .WillOnce(Return(host_)); - CreateDummyStream(); + CreateDummyStream(key_, kDummyConnection, kDummyStream, host_); pool_->OnHostDeterminedCapability(host_, PIPELINE_CAPABLE); pool_->OnHostIdle(host_); - EXPECT_TRUE(pool_->IsHostEligibleForPipelining(origin_)); + EXPECT_TRUE(pool_->IsKeyEligibleForPipelining(key_)); - host_ = new MockHost(origin_); - EXPECT_CALL(*factory_, CreateNewHost(pool_.get(), Ref(origin_), _, - PIPELINE_CAPABLE)) + host_ = new MockHost(key_); + EXPECT_CALL(*factory_, CreateNewHost(pool_.get(), Ref(key_), _, + PIPELINE_CAPABLE, false)) .Times(1) .WillOnce(Return(host_)); - CreateDummyStream(); + CreateDummyStream(key_, kDummyConnection, kDummyStream, host_); pool_->OnHostIdle(host_); } TEST_F(HttpPipelinedHostPoolTest, IncapableIsSticky) { - EXPECT_CALL(*factory_, CreateNewHost(pool_.get(), Ref(origin_), _, - PIPELINE_UNKNOWN)) + EXPECT_CALL(*factory_, CreateNewHost(pool_.get(), Ref(key_), _, + PIPELINE_UNKNOWN, false)) .Times(1) .WillOnce(Return(host_)); - CreateDummyStream(); + CreateDummyStream(key_, kDummyConnection, kDummyStream, host_); pool_->OnHostDeterminedCapability(host_, PIPELINE_CAPABLE); pool_->OnHostDeterminedCapability(host_, PIPELINE_INCAPABLE); pool_->OnHostDeterminedCapability(host_, PIPELINE_CAPABLE); pool_->OnHostIdle(host_); - EXPECT_FALSE(pool_->IsHostEligibleForPipelining(origin_)); + EXPECT_FALSE(pool_->IsKeyEligibleForPipelining(key_)); } TEST_F(HttpPipelinedHostPoolTest, RemainsUnknownWithoutFeedback) { - EXPECT_CALL(*factory_, CreateNewHost(pool_.get(), Ref(origin_), _, - PIPELINE_UNKNOWN)) + EXPECT_CALL(*factory_, CreateNewHost(pool_.get(), Ref(key_), _, + PIPELINE_UNKNOWN, false)) .Times(1) .WillOnce(Return(host_)); - CreateDummyStream(); + CreateDummyStream(key_, kDummyConnection, kDummyStream, host_); pool_->OnHostIdle(host_); - EXPECT_TRUE(pool_->IsHostEligibleForPipelining(origin_)); + EXPECT_TRUE(pool_->IsKeyEligibleForPipelining(key_)); - host_ = new MockHost(origin_); - EXPECT_CALL(*factory_, CreateNewHost(pool_.get(), Ref(origin_), _, - PIPELINE_UNKNOWN)) + host_ = new MockHost(key_); + EXPECT_CALL(*factory_, CreateNewHost(pool_.get(), Ref(key_), _, + PIPELINE_UNKNOWN, false)) .Times(1) .WillOnce(Return(host_)); - CreateDummyStream(); + CreateDummyStream(key_, kDummyConnection, kDummyStream, host_); pool_->OnHostIdle(host_); } TEST_F(HttpPipelinedHostPoolTest, PopulatesServerProperties) { EXPECT_EQ(PIPELINE_UNKNOWN, - http_server_properties_->GetPipelineCapability(host_->origin())); + http_server_properties_->GetPipelineCapability( + host_->GetKey().origin())); pool_->OnHostDeterminedCapability(host_, PIPELINE_CAPABLE); EXPECT_EQ(PIPELINE_CAPABLE, - http_server_properties_->GetPipelineCapability(host_->origin())); + http_server_properties_->GetPipelineCapability( + host_->GetKey().origin())); + delete host_; // Must manually delete, because it's never added to |pool_|. +} + +TEST_F(HttpPipelinedHostPoolTest, MultipleKeys) { + HttpPipelinedHost::Key key1(HostPortPair("host", 123)); + HttpPipelinedHost::Key key2(HostPortPair("host", 456)); + HttpPipelinedHost::Key key3(HostPortPair("other", 456)); + HttpPipelinedHost::Key key4(HostPortPair("other", 789)); + MockHost* host1 = CreateDummyHost(key1); + MockHost* host2 = CreateDummyHost(key2); + MockHost* host3 = CreateDummyHost(key3); + + EXPECT_CALL(*host1, IsExistingPipelineAvailable()) + .Times(1) + .WillOnce(Return(true)); + EXPECT_EQ(true, pool_->IsExistingPipelineAvailableForKey(key1)); + + EXPECT_CALL(*host2, IsExistingPipelineAvailable()) + .Times(1) + .WillOnce(Return(false)); + EXPECT_EQ(false, pool_->IsExistingPipelineAvailableForKey(key2)); + + EXPECT_CALL(*host3, IsExistingPipelineAvailable()) + .Times(1) + .WillOnce(Return(true)); + EXPECT_EQ(true, pool_->IsExistingPipelineAvailableForKey(key3)); + + EXPECT_EQ(false, pool_->IsExistingPipelineAvailableForKey(key4)); + + pool_->OnHostIdle(host1); + pool_->OnHostIdle(host2); + pool_->OnHostIdle(host3); + delete host_; // Must manually delete, because it's never added to |pool_|. } diff --git a/net/http/http_pipelined_host_test_util.cc b/net/http/http_pipelined_host_test_util.cc new file mode 100644 index 0000000..6e58779 --- /dev/null +++ b/net/http/http_pipelined_host_test_util.cc @@ -0,0 +1,33 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/http/http_pipelined_host_test_util.h" + +#include "net/base/ssl_config_service.h" +#include "net/proxy/proxy_info.h" + +namespace net { + +MockHostDelegate::MockHostDelegate() { +} + +MockHostDelegate::~MockHostDelegate() { +} + +MockPipelineFactory::MockPipelineFactory() { +} + +MockPipelineFactory::~MockPipelineFactory() { +} + +MockPipeline::MockPipeline(int depth, bool usable, bool active) + : depth_(depth), + usable_(usable), + active_(active) { +} + +MockPipeline::~MockPipeline() { +} + +} // namespace net diff --git a/net/http/http_pipelined_host_test_util.h b/net/http/http_pipelined_host_test_util.h new file mode 100644 index 0000000..eeb7b34 --- /dev/null +++ b/net/http/http_pipelined_host_test_util.h @@ -0,0 +1,70 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/http/http_pipelined_connection.h" +#include "net/http/http_pipelined_host.h" +#include "testing/gmock/include/gmock/gmock.h" + +namespace net { + +class MockHostDelegate : public HttpPipelinedHost::Delegate { + public: + MockHostDelegate(); + virtual ~MockHostDelegate(); + + MOCK_METHOD1(OnHostIdle, void(HttpPipelinedHost* host)); + MOCK_METHOD1(OnHostHasAdditionalCapacity, void(HttpPipelinedHost* host)); + MOCK_METHOD2(OnHostDeterminedCapability, + void(HttpPipelinedHost* host, + HttpPipelinedHostCapability capability)); +}; + +class MockPipelineFactory : public HttpPipelinedConnection::Factory { + public: + MockPipelineFactory(); + virtual ~MockPipelineFactory(); + + MOCK_METHOD8(CreateNewPipeline, HttpPipelinedConnection*( + ClientSocketHandle* connection, + HttpPipelinedConnection::Delegate* delegate, + const HostPortPair& origin, + const SSLConfig& used_ssl_config, + const ProxyInfo& used_proxy_info, + const BoundNetLog& net_log, + bool was_npn_negotiated, + SSLClientSocket::NextProto protocol_negotiated)); +}; + +class MockPipeline : public HttpPipelinedConnection { + public: + MockPipeline(int depth, bool usable, bool active); + virtual ~MockPipeline(); + + void SetState(int depth, bool usable, bool active) { + depth_ = depth; + usable_ = usable; + active_ = active; + } + + virtual int depth() const OVERRIDE { return depth_; } + virtual bool usable() const OVERRIDE { return usable_; } + virtual bool active() const OVERRIDE { return active_; } + + MOCK_METHOD0(CreateNewStream, HttpPipelinedStream*()); + MOCK_METHOD1(OnStreamDeleted, void(int pipeline_id)); + MOCK_CONST_METHOD0(used_ssl_config, const SSLConfig&()); + MOCK_CONST_METHOD0(used_proxy_info, const ProxyInfo&()); + MOCK_CONST_METHOD0(net_log, const BoundNetLog&()); + MOCK_CONST_METHOD0(was_npn_negotiated, bool()); + MOCK_CONST_METHOD0(protocol_negotiated, SSLClientSocket::NextProto()); + + private: + int depth_; + bool usable_; + bool active_; +}; + +MATCHER_P(MatchesOrigin, expected, "") { return expected.Equals(arg); } + +} // namespace net diff --git a/net/http/http_pipelined_network_transaction_unittest.cc b/net/http/http_pipelined_network_transaction_unittest.cc index a11679c..65794da 100644 --- a/net/http/http_pipelined_network_transaction_unittest.cc +++ b/net/http/http_pipelined_network_transaction_unittest.cc @@ -51,7 +51,10 @@ class HttpPipelinedNetworkTransactionTest : public testing::Test { HttpStreamFactory::set_http_pipelining_enabled(default_pipelining_enabled_); } - void Initialize() { + void Initialize(bool force_http_pipelining) { + // Normally, this code could just go in SetUp(). For a few of these tests, + // we change the default number of sockets per group. That needs to be done + // before we construct the HttpNetworkSession. proxy_service_.reset(ProxyService::CreateDirect()); ssl_config_ = new SSLConfigServiceDefaults; auth_handler_factory_.reset(new HttpAuthHandlerMock::Factory()); @@ -63,6 +66,7 @@ class HttpPipelinedNetworkTransactionTest : public testing::Test { session_params.ssl_config_service = ssl_config_.get(); session_params.http_auth_handler_factory = auth_handler_factory_.get(); session_params.http_server_properties = &http_server_properties_; + session_params.force_http_pipelining = force_http_pipelining; session_ = new HttpNetworkSession(session_params); } @@ -78,7 +82,7 @@ class HttpPipelinedNetworkTransactionTest : public testing::Test { data_vector_.push_back(data); } - const HttpRequestInfo* GetRequestInfo(const char* filename) { + HttpRequestInfo* GetRequestInfo(const char* filename) { std::string url = StringPrintf("http://localhost/%s", filename); HttpRequestInfo* request_info = new HttpRequestInfo; request_info->url = GURL(url); @@ -88,11 +92,19 @@ class HttpPipelinedNetworkTransactionTest : public testing::Test { } void ExpectResponse(const std::string& expected, - HttpNetworkTransaction& transaction) { + HttpNetworkTransaction& transaction, + IoMode io_mode) { scoped_refptr<IOBuffer> buffer(new IOBuffer(expected.size())); - EXPECT_EQ(static_cast<int>(expected.size()), - transaction.Read(buffer.get(), expected.size(), - callback_.callback())); + if (io_mode == ASYNC) { + EXPECT_EQ(ERR_IO_PENDING, transaction.Read(buffer.get(), expected.size(), + callback_.callback())); + data_vector_[0]->RunFor(1); + EXPECT_EQ(static_cast<int>(expected.length()), callback_.WaitForResult()); + } else { + EXPECT_EQ(static_cast<int>(expected.size()), + transaction.Read(buffer.get(), expected.size(), + callback_.callback())); + } std::string actual(buffer->data(), expected.size()); EXPECT_THAT(actual, StrEq(expected)); EXPECT_EQ(OK, transaction.Read(buffer.get(), expected.size(), @@ -130,7 +142,7 @@ class HttpPipelinedNetworkTransactionTest : public testing::Test { one_read_callback.callback())); EXPECT_EQ(OK, two_callback.WaitForResult()); - ExpectResponse("two.html", two_transaction); + ExpectResponse("two.html", two_transaction, SYNCHRONOUS); } void CompleteFourRequests() { @@ -161,15 +173,15 @@ class HttpPipelinedNetworkTransactionTest : public testing::Test { four_transaction.Start(GetRequestInfo("four.html"), four_callback.callback(), BoundNetLog())); - ExpectResponse("one.html", *one_transaction.get()); + ExpectResponse("one.html", *one_transaction.get(), SYNCHRONOUS); EXPECT_EQ(OK, two_callback.WaitForResult()); - ExpectResponse("two.html", two_transaction); + ExpectResponse("two.html", two_transaction, SYNCHRONOUS); EXPECT_EQ(OK, three_callback.WaitForResult()); - ExpectResponse("three.html", three_transaction); + ExpectResponse("three.html", three_transaction, SYNCHRONOUS); one_transaction.reset(); EXPECT_EQ(OK, four_callback.WaitForResult()); - ExpectResponse("four.html", four_transaction); + ExpectResponse("four.html", four_transaction, SYNCHRONOUS); } DeterministicMockClientSocketFactory factory_; @@ -189,7 +201,7 @@ class HttpPipelinedNetworkTransactionTest : public testing::Test { }; TEST_F(HttpPipelinedNetworkTransactionTest, OneRequest) { - Initialize(); + Initialize(false); MockWrite writes[] = { MockWrite(SYNCHRONOUS, 0, "GET /test.html HTTP/1.1\r\n" @@ -208,11 +220,11 @@ TEST_F(HttpPipelinedNetworkTransactionTest, OneRequest) { transaction.Start(GetRequestInfo("test.html"), callback_.callback(), BoundNetLog())); EXPECT_EQ(OK, callback_.WaitForResult()); - ExpectResponse("test.html", transaction); + ExpectResponse("test.html", transaction, SYNCHRONOUS); } TEST_F(HttpPipelinedNetworkTransactionTest, ReusePipeline) { - Initialize(); + Initialize(false); MockWrite writes[] = { MockWrite(SYNCHRONOUS, 0, "GET /one.html HTTP/1.1\r\n" @@ -238,7 +250,7 @@ TEST_F(HttpPipelinedNetworkTransactionTest, ReusePipeline) { TEST_F(HttpPipelinedNetworkTransactionTest, ReusesOnSpaceAvailable) { int old_max_sockets = ClientSocketPoolManager::max_sockets_per_group(); ClientSocketPoolManager::set_max_sockets_per_group(1); - Initialize(); + Initialize(false); MockWrite writes[] = { MockWrite(SYNCHRONOUS, 0, "GET /one.html HTTP/1.1\r\n" @@ -276,7 +288,7 @@ TEST_F(HttpPipelinedNetworkTransactionTest, ReusesOnSpaceAvailable) { } TEST_F(HttpPipelinedNetworkTransactionTest, UnknownSizeEvictsToNewPipeline) { - Initialize(); + Initialize(false); MockWrite writes[] = { MockWrite(SYNCHRONOUS, 0, "GET /one.html HTTP/1.1\r\n" @@ -306,7 +318,7 @@ TEST_F(HttpPipelinedNetworkTransactionTest, UnknownSizeEvictsToNewPipeline) { } TEST_F(HttpPipelinedNetworkTransactionTest, ConnectionCloseEvictToNewPipeline) { - Initialize(); + Initialize(false); MockWrite writes[] = { MockWrite(SYNCHRONOUS, 0, "GET /one.html HTTP/1.1\r\n" @@ -340,7 +352,7 @@ TEST_F(HttpPipelinedNetworkTransactionTest, ConnectionCloseEvictToNewPipeline) { } TEST_F(HttpPipelinedNetworkTransactionTest, ErrorEvictsToNewPipeline) { - Initialize(); + Initialize(false); MockWrite writes[] = { MockWrite(SYNCHRONOUS, 0, "GET /one.html HTTP/1.1\r\n" @@ -385,11 +397,11 @@ TEST_F(HttpPipelinedNetworkTransactionTest, ErrorEvictsToNewPipeline) { EXPECT_EQ(ERR_FAILED, one_transaction.Read(buffer.get(), 1, callback_.callback())); EXPECT_EQ(OK, two_callback.WaitForResult()); - ExpectResponse("two.html", two_transaction); + ExpectResponse("two.html", two_transaction, SYNCHRONOUS); } TEST_F(HttpPipelinedNetworkTransactionTest, SendErrorEvictsToNewPipeline) { - Initialize(); + Initialize(false); MockWrite writes[] = { MockWrite(ASYNC, ERR_FAILED, 0), @@ -424,11 +436,11 @@ TEST_F(HttpPipelinedNetworkTransactionTest, SendErrorEvictsToNewPipeline) { EXPECT_EQ(ERR_FAILED, one_callback.WaitForResult()); EXPECT_EQ(OK, two_callback.WaitForResult()); - ExpectResponse("two.html", two_transaction); + ExpectResponse("two.html", two_transaction, SYNCHRONOUS); } TEST_F(HttpPipelinedNetworkTransactionTest, RedirectDrained) { - Initialize(); + Initialize(false); MockWrite writes[] = { MockWrite(SYNCHRONOUS, 0, "GET /redirect.html HTTP/1.1\r\n" @@ -467,11 +479,11 @@ TEST_F(HttpPipelinedNetworkTransactionTest, RedirectDrained) { data_vector_[0]->SetStop(10); EXPECT_EQ(OK, two_callback.WaitForResult()); - ExpectResponse("two.html", two_transaction); + ExpectResponse("two.html", two_transaction, SYNCHRONOUS); } TEST_F(HttpPipelinedNetworkTransactionTest, BasicHttpAuthentication) { - Initialize(); + Initialize(false); MockWrite writes[] = { MockWrite(SYNCHRONOUS, 0, "GET /one.html HTTP/1.1\r\n" @@ -514,11 +526,11 @@ TEST_F(HttpPipelinedNetworkTransactionTest, BasicHttpAuthentication) { AuthCredentials credentials(ASCIIToUTF16("user"), ASCIIToUTF16("pass")); EXPECT_EQ(OK, transaction.RestartWithAuth(credentials, callback_.callback())); - ExpectResponse("one.html", transaction); + ExpectResponse("one.html", transaction, SYNCHRONOUS); } TEST_F(HttpPipelinedNetworkTransactionTest, OldVersionDisablesPipelining) { - Initialize(); + Initialize(false); MockWrite writes[] = { MockWrite(SYNCHRONOUS, 0, "GET /pipelined.html HTTP/1.1\r\n" @@ -564,7 +576,7 @@ TEST_F(HttpPipelinedNetworkTransactionTest, OldVersionDisablesPipelining) { one_transaction.Start(GetRequestInfo("pipelined.html"), one_callback.callback(), BoundNetLog())); EXPECT_EQ(OK, one_callback.WaitForResult()); - ExpectResponse("pipelined.html", one_transaction); + ExpectResponse("pipelined.html", one_transaction, SYNCHRONOUS); CompleteTwoRequests(1, 4); } @@ -576,7 +588,7 @@ TEST_F(HttpPipelinedNetworkTransactionTest, PipelinesImmediatelyIfKnownGood) { // new HttpPipelinedConnection. int old_max_sockets = ClientSocketPoolManager::max_sockets_per_group(); ClientSocketPoolManager::set_max_sockets_per_group(1); - Initialize(); + Initialize(false); MockWrite writes[] = { MockWrite(SYNCHRONOUS, 0, "GET /one.html HTTP/1.1\r\n" @@ -640,9 +652,11 @@ TEST_F(HttpPipelinedNetworkTransactionTest, PipelinesImmediatelyIfKnownGood) { data_vector_[0]->RunFor(3); EXPECT_EQ(OK, second_one_callback.WaitForResult()); data_vector_[0]->StopAfter(100); - ExpectResponse("second-pipeline-one.html", second_one_transaction); + ExpectResponse("second-pipeline-one.html", second_one_transaction, + SYNCHRONOUS); EXPECT_EQ(OK, second_two_callback.WaitForResult()); - ExpectResponse("second-pipeline-two.html", second_two_transaction); + ExpectResponse("second-pipeline-two.html", second_two_transaction, + SYNCHRONOUS); ClientSocketPoolManager::set_max_sockets_per_group(old_max_sockets); } @@ -684,7 +698,7 @@ TEST_F(HttpPipelinedNetworkTransactionTest, OpenPipelinesWhileBinding) { // 6. The pipeline from #2 is deleted because it has no streams. // 7. On the second iteration, the host tries to notify the pipeline from step // #2 that it has capacity. This is a use-after-free. - Initialize(); + Initialize(false); MockWrite writes[] = { MockWrite(SYNCHRONOUS, 0, "GET /one.html HTTP/1.1\r\n" @@ -735,9 +749,115 @@ TEST_F(HttpPipelinedNetworkTransactionTest, OpenPipelinesWhileBinding) { data_vector_[0]->SetStop(10); EXPECT_EQ(OK, one_callback.WaitForResult()); - ExpectResponse("one.html", one_transaction); + ExpectResponse("one.html", one_transaction, SYNCHRONOUS); + EXPECT_EQ(OK, two_callback.WaitForResult()); + ExpectResponse("two.html", two_transaction, SYNCHRONOUS); +} + +TEST_F(HttpPipelinedNetworkTransactionTest, ForcedPipelineSharesConnection) { + Initialize(true); + + MockWrite writes[] = { + MockWrite(ASYNC, 0, "GET /one.html HTTP/1.1\r\n" + "Host: localhost\r\n" + "Connection: keep-alive\r\n\r\n" + "GET /two.html HTTP/1.1\r\n" + "Host: localhost\r\n" + "Connection: keep-alive\r\n\r\n"), + }; + MockRead reads[] = { + MockRead(ASYNC, 1, "HTTP/1.1 200 OK\r\n"), + MockRead(ASYNC, 2, "Content-Length: 8\r\n\r\n"), + MockRead(ASYNC, 3, "one.html"), + MockRead(ASYNC, 4, "HTTP/1.1 200 OK\r\n"), + MockRead(ASYNC, 5, "Content-Length: 8\r\n\r\n"), + MockRead(ASYNC, 6, "two.html"), + }; + AddExpectedConnection(reads, arraysize(reads), writes, arraysize(writes)); + + scoped_ptr<HttpNetworkTransaction> one_transaction( + new HttpNetworkTransaction(session_.get())); + TestCompletionCallback one_callback; + EXPECT_EQ(ERR_IO_PENDING, + one_transaction->Start(GetRequestInfo("one.html"), + one_callback.callback(), BoundNetLog())); + + HttpNetworkTransaction two_transaction(session_.get()); + TestCompletionCallback two_callback; + EXPECT_EQ(ERR_IO_PENDING, + two_transaction.Start(GetRequestInfo("two.html"), + two_callback.callback(), BoundNetLog())); + + data_vector_[0]->RunFor(3); // Send + 2 lines of headers. + EXPECT_EQ(OK, one_callback.WaitForResult()); + ExpectResponse("one.html", *one_transaction.get(), ASYNC); + one_transaction.reset(); + + data_vector_[0]->RunFor(2); // 2 lines of headers. EXPECT_EQ(OK, two_callback.WaitForResult()); - ExpectResponse("two.html", two_transaction); + ExpectResponse("two.html", two_transaction, ASYNC); +} + +TEST_F(HttpPipelinedNetworkTransactionTest, + ForcedPipelineConnectionErrorFailsBoth) { + Initialize(true); + + scoped_refptr<DeterministicSocketData> data( + new DeterministicSocketData(NULL, 0, NULL, 0)); + data->set_connect_data(MockConnect(ASYNC, ERR_FAILED)); + factory_.AddSocketDataProvider(data); + + scoped_ptr<HttpNetworkTransaction> one_transaction( + new HttpNetworkTransaction(session_.get())); + TestCompletionCallback one_callback; + EXPECT_EQ(ERR_IO_PENDING, + one_transaction->Start(GetRequestInfo("one.html"), + one_callback.callback(), BoundNetLog())); + + HttpNetworkTransaction two_transaction(session_.get()); + TestCompletionCallback two_callback; + EXPECT_EQ(ERR_IO_PENDING, + two_transaction.Start(GetRequestInfo("two.html"), + two_callback.callback(), BoundNetLog())); + + data->Run(); + EXPECT_EQ(ERR_FAILED, one_callback.WaitForResult()); + EXPECT_EQ(ERR_FAILED, two_callback.WaitForResult()); +} + +TEST_F(HttpPipelinedNetworkTransactionTest, ForcedPipelineEvictionIsFatal) { + Initialize(true); + + MockWrite writes[] = { + MockWrite(ASYNC, 0, "GET /one.html HTTP/1.1\r\n" + "Host: localhost\r\n" + "Connection: keep-alive\r\n\r\n" + "GET /two.html HTTP/1.1\r\n" + "Host: localhost\r\n" + "Connection: keep-alive\r\n\r\n"), + }; + MockRead reads[] = { + MockRead(ASYNC, ERR_FAILED, 1), + }; + AddExpectedConnection(reads, arraysize(reads), writes, arraysize(writes)); + + scoped_ptr<HttpNetworkTransaction> one_transaction( + new HttpNetworkTransaction(session_.get())); + TestCompletionCallback one_callback; + EXPECT_EQ(ERR_IO_PENDING, + one_transaction->Start(GetRequestInfo("one.html"), + one_callback.callback(), BoundNetLog())); + + HttpNetworkTransaction two_transaction(session_.get()); + TestCompletionCallback two_callback; + EXPECT_EQ(ERR_IO_PENDING, + two_transaction.Start(GetRequestInfo("two.html"), + two_callback.callback(), BoundNetLog())); + + data_vector_[0]->RunFor(2); + EXPECT_EQ(ERR_FAILED, one_callback.WaitForResult()); + one_transaction.reset(); + EXPECT_EQ(ERR_PIPELINE_EVICTION, two_callback.WaitForResult()); } } // anonymous namespace diff --git a/net/http/http_stream_factory_impl.cc b/net/http/http_stream_factory_impl.cc index 3eb067a..1236787 100644 --- a/net/http/http_stream_factory_impl.cc +++ b/net/http/http_stream_factory_impl.cc @@ -38,7 +38,8 @@ GURL UpgradeUrlToHttps(const GURL& original_url, int port) { HttpStreamFactoryImpl::HttpStreamFactoryImpl(HttpNetworkSession* session) : session_(session), http_pipelined_host_pool_(this, NULL, - session_->http_server_properties()) {} + session_->http_server_properties(), + session_->force_http_pipelining()) {} HttpStreamFactoryImpl::~HttpStreamFactoryImpl() { DCHECK(request_map_.empty()); @@ -232,12 +233,16 @@ void HttpStreamFactoryImpl::OnPreconnectsComplete(const Job* job) { } void HttpStreamFactoryImpl::OnHttpPipelinedHostHasAdditionalCapacity( - const HostPortPair& origin) { - HttpPipelinedStream* stream; - while (ContainsKey(http_pipelining_request_map_, origin) && - (stream = http_pipelined_host_pool_.CreateStreamOnExistingPipeline( - origin))) { - Request* request = *http_pipelining_request_map_[origin].begin(); + HttpPipelinedHost* host) { + while (ContainsKey(http_pipelining_request_map_, host->GetKey())) { + HttpPipelinedStream* stream = + http_pipelined_host_pool_.CreateStreamOnExistingPipeline( + host->GetKey()); + if (!stream) { + break; + } + + Request* request = *http_pipelining_request_map_[host->GetKey()].begin(); request->Complete(stream->was_npn_negotiated(), stream->protocol_negotiated(), false, // not using_spdy @@ -249,4 +254,16 @@ void HttpStreamFactoryImpl::OnHttpPipelinedHostHasAdditionalCapacity( } } +void HttpStreamFactoryImpl::AbortPipelinedRequestsWithKey( + const Job* job, const HttpPipelinedHost::Key& key, int status, + const SSLConfig& used_ssl_config) { + RequestSet requests_to_fail = http_pipelining_request_map_[key]; + requests_to_fail.erase(request_map_[job]); + for (RequestSet::const_iterator it = requests_to_fail.begin(); + it != requests_to_fail.end(); ++it) { + Request* request = *it; + request->OnStreamFailed(NULL, status, used_ssl_config); + } +} + } // namespace net diff --git a/net/http/http_stream_factory_impl.h b/net/http/http_stream_factory_impl.h index 36564ac..653c2e1 100644 --- a/net/http/http_stream_factory_impl.h +++ b/net/http/http_stream_factory_impl.h @@ -47,7 +47,7 @@ class NET_EXPORT_PRIVATE HttpStreamFactoryImpl : // HttpPipelinedHostPool::Delegate interface virtual void OnHttpPipelinedHostHasAdditionalCapacity( - const HostPortPair& origin) OVERRIDE; + HttpPipelinedHost* host) OVERRIDE; private: class Request; @@ -55,7 +55,7 @@ class NET_EXPORT_PRIVATE HttpStreamFactoryImpl : typedef std::set<Request*> RequestSet; typedef std::map<HostPortProxyPair, RequestSet> SpdySessionRequestMap; - typedef std::map<HostPortPair, RequestSet> HttpPipeliningRequestMap; + typedef std::map<HttpPipelinedHost::Key, RequestSet> HttpPipeliningRequestMap; bool GetAlternateProtocolRequestFor(const GURL& original_url, GURL* alternate_url) const; @@ -89,6 +89,11 @@ class NET_EXPORT_PRIVATE HttpStreamFactoryImpl : // Called when the Preconnect completes. Used for testing. virtual void OnPreconnectsCompleteInternal() {} + void AbortPipelinedRequestsWithKey(const Job* job, + const HttpPipelinedHost::Key& key, + int status, + const SSLConfig& used_ssl_config); + HttpNetworkSession* const session_; std::set<HostPortPair> tls_intolerant_servers_; diff --git a/net/http/http_stream_factory_impl_job.cc b/net/http/http_stream_factory_impl_job.cc index 0647732..9254026 100644 --- a/net/http/http_stream_factory_impl_job.cc +++ b/net/http/http_stream_factory_impl_job.cc @@ -562,6 +562,7 @@ int HttpStreamFactoryImpl::Job::DoStart() { origin_ = HostPortPair(request_info_.url.HostNoBrackets(), port); origin_url_ = HttpStreamFactory::ApplyHostMappingRules( request_info_.url, &origin_); + http_pipelining_key_.reset(new HttpPipelinedHost::Key(origin_)); net_log_.BeginEvent(NetLog::TYPE_HTTP_STREAM_JOB, HttpStreamJobParameters::Create(request_info_.url, @@ -686,12 +687,21 @@ int HttpStreamFactoryImpl::Job::DoInitConnection() { // TODO(simonjam): With pipelining, we might be better off using fewer // connections and thus should make fewer preconnections. Explore // preconnecting fewer than the requested num_connections. + // + // Separate note: A forced pipeline is always available if one exists for + // this key. This is different than normal pipelines, which may be + // unavailable or unusable. So, there is no need to worry about a race + // between when a pipeline becomes available and when this job blocks. existing_available_pipeline_ = stream_factory_->http_pipelined_host_pool_. - IsExistingPipelineAvailableForOrigin(origin_); + IsExistingPipelineAvailableForKey(*http_pipelining_key_.get()); if (existing_available_pipeline_) { return OK; } else { - request_->SetHttpPipeliningKey(origin_); + bool was_new_key = request_->SetHttpPipeliningKey( + *http_pipelining_key_.get()); + if (!was_new_key && session_->force_http_pipelining()) { + return ERR_IO_PENDING; + } } } @@ -755,6 +765,11 @@ int HttpStreamFactoryImpl::Job::DoInitConnectionComplete(int result) { waiting_job_ = NULL; } + if (result < 0 && session_->force_http_pipelining()) { + stream_factory_->AbortPipelinedRequestsWithKey( + this, *http_pipelining_key_.get(), result, server_ssl_config_); + } + // |result| may be the result of any of the stacked pools. The following // logic is used when determining how to interpret an error. // If |result| < 0: @@ -887,14 +902,16 @@ int HttpStreamFactoryImpl::Job::DoCreateStream() { bool using_proxy = (proxy_info_.is_http() || proxy_info_.is_https()) && request_info_.url.SchemeIs("http"); // TODO(simonjam): Support proxies. - if (existing_available_pipeline_) { + if (stream_factory_->http_pipelined_host_pool_. + IsExistingPipelineAvailableForKey(*http_pipelining_key_.get())) { stream_.reset(stream_factory_->http_pipelined_host_pool_. - CreateStreamOnExistingPipeline(origin_)); + CreateStreamOnExistingPipeline( + *http_pipelining_key_.get())); CHECK(stream_.get()); } else if (!using_proxy && IsRequestEligibleForPipelining()) { stream_.reset( stream_factory_->http_pipelined_host_pool_.CreateStreamOnNewPipeline( - origin_, + *http_pipelining_key_.get(), connection_.release(), server_ssl_config_, proxy_info_, @@ -1209,10 +1226,13 @@ bool HttpStreamFactoryImpl::Job::IsOrphaned() const { } bool HttpStreamFactoryImpl::Job::IsRequestEligibleForPipelining() { - if (!HttpStreamFactory::http_pipelining_enabled()) { + if (IsPreconnecting() || !request_) { return false; } - if (IsPreconnecting() || !request_) { + if (session_->force_http_pipelining()) { + return true; + } + if (!HttpStreamFactory::http_pipelining_enabled()) { return false; } if (using_ssl_) { @@ -1221,8 +1241,8 @@ bool HttpStreamFactoryImpl::Job::IsRequestEligibleForPipelining() { if (request_info_.method != "GET" && request_info_.method != "HEAD") { return false; } - return stream_factory_->http_pipelined_host_pool_.IsHostEligibleForPipelining( - origin_); + return stream_factory_->http_pipelined_host_pool_.IsKeyEligibleForPipelining( + *http_pipelining_key_.get()); } } // namespace net diff --git a/net/http/http_stream_factory_impl_job.h b/net/http/http_stream_factory_impl_job.h index ab15c8e..8e167da 100644 --- a/net/http/http_stream_factory_impl_job.h +++ b/net/http/http_stream_factory_impl_job.h @@ -13,6 +13,7 @@ #include "net/base/ssl_config_service.h" #include "net/http/http_auth.h" #include "net/http/http_auth_controller.h" +#include "net/http/http_pipelined_host.h" #include "net/http/http_request_info.h" #include "net/http/http_stream_factory_impl.h" #include "net/proxy/proxy_service.h" @@ -280,6 +281,9 @@ class HttpStreamFactoryImpl::Job { // Only used if |new_spdy_session_| is non-NULL. bool spdy_session_direct_; + // Key used to identify the HttpPipelinedHost for |request_|. + scoped_ptr<HttpPipelinedHost::Key> http_pipelining_key_; + // True if an existing pipeline can handle this job's request. bool existing_available_pipeline_; diff --git a/net/http/http_stream_factory_impl_request.cc b/net/http/http_stream_factory_impl_request.cc index cb8fcbf..451b159 100644 --- a/net/http/http_stream_factory_impl_request.cc +++ b/net/http/http_stream_factory_impl_request.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -57,14 +57,17 @@ void HttpStreamFactoryImpl::Request::SetSpdySessionKey( request_set.insert(this); } -void HttpStreamFactoryImpl::Request::SetHttpPipeliningKey( - const HostPortPair& http_pipelining_key) { +bool HttpStreamFactoryImpl::Request::SetHttpPipeliningKey( + const HttpPipelinedHost::Key& http_pipelining_key) { DCHECK(!http_pipelining_key_.get()); - http_pipelining_key_.reset(new HostPortPair(http_pipelining_key)); + http_pipelining_key_.reset(new HttpPipelinedHost::Key(http_pipelining_key)); + bool was_new_key = !ContainsKey(factory_->http_pipelining_request_map_, + http_pipelining_key); RequestSet& request_set = factory_->http_pipelining_request_map_[http_pipelining_key]; DCHECK(!ContainsKey(request_set, this)); request_set.insert(this); + return was_new_key; } void HttpStreamFactoryImpl::Request::AttachJob(Job* job) { @@ -130,7 +133,16 @@ void HttpStreamFactoryImpl::Request::OnStreamFailed( int status, const SSLConfig& used_ssl_config) { DCHECK_NE(OK, status); - if (!bound_job_.get()) { + // |job| should only be NULL if we're being canceled by a late bound + // HttpPipelinedConnection (one that was not created by a job in our |jobs_| + // set). + if (!job) { + DCHECK(!bound_job_.get()); + DCHECK(!jobs_.empty()); + // NOTE(willchan): We do *NOT* call OrphanJobs() here. The reason is because + // we *WANT* to cancel the unnecessary Jobs from other requests if another + // Job completes first. + } else if (!bound_job_.get()) { // Hey, we've got other jobs! Maybe one of them will succeed, let's just // ignore this failure. if (jobs_.size() > 1) { diff --git a/net/http/http_stream_factory_impl_request.h b/net/http/http_stream_factory_impl_request.h index ffa3a45..ea05420 100644 --- a/net/http/http_stream_factory_impl_request.h +++ b/net/http/http_stream_factory_impl_request.h @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -34,7 +34,8 @@ class HttpStreamFactoryImpl::Request : public HttpStreamRequest { // Called when the Job determines the appropriate |http_pipelining_key| for // the Request. Registers this Request with the factory, so that if an // existing pipeline becomes available, this Request can be late bound to it. - void SetHttpPipeliningKey(const HostPortPair& http_pipelining_key); + // Returns true if this is this key was new to the factory. + bool SetHttpPipeliningKey(const HttpPipelinedHost::Key& http_pipelining_key); // Attaches |job| to this request. Does not mean that Request will use |job|, // but Request will own |job|. @@ -113,7 +114,7 @@ class HttpStreamFactoryImpl::Request : public HttpStreamRequest { scoped_ptr<Job> bound_job_; std::set<HttpStreamFactoryImpl::Job*> jobs_; scoped_ptr<const HostPortProxyPair> spdy_session_key_; - scoped_ptr<const HostPortPair> http_pipelining_key_; + scoped_ptr<const HttpPipelinedHost::Key> http_pipelining_key_; bool completed_; bool was_npn_negotiated_; diff --git a/net/net.gyp b/net/net.gyp index 87c6d8e..b2b4aad 100644 --- a/net/net.gyp +++ b/net/net.gyp @@ -445,8 +445,11 @@ 'http/http_pipelined_connection.h', 'http/http_pipelined_connection_impl.cc', 'http/http_pipelined_connection_impl.h', + 'http/http_pipelined_host.cc', 'http/http_pipelined_host.h', 'http/http_pipelined_host_capability.h', + 'http/http_pipelined_host_forced.cc', + 'http/http_pipelined_host_forced.h', 'http/http_pipelined_host_impl.cc', 'http/http_pipelined_host_impl.h', 'http/http_pipelined_host_pool.cc', @@ -564,6 +567,8 @@ 'proxy/sync_host_resolver.h', 'proxy/sync_host_resolver_bridge.cc', 'proxy/sync_host_resolver_bridge.h', + 'socket/buffered_write_stream_socket.cc', + 'socket/buffered_write_stream_socket.h', 'socket/client_socket_factory.cc', 'socket/client_socket_factory.h', 'socket/client_socket_handle.cc', @@ -1120,8 +1125,11 @@ 'http/http_network_layer_unittest.cc', 'http/http_network_transaction_unittest.cc', 'http/http_pipelined_connection_impl_unittest.cc', + 'http/http_pipelined_host_forced_unittest.cc', 'http/http_pipelined_host_impl_unittest.cc', 'http/http_pipelined_host_pool_unittest.cc', + 'http/http_pipelined_host_test_util.cc', + 'http/http_pipelined_host_test_util.h', 'http/http_pipelined_network_transaction_unittest.cc', 'http/http_proxy_client_socket_pool_unittest.cc', 'http/http_request_headers_unittest.cc', @@ -1160,6 +1168,7 @@ 'proxy/proxy_server_unittest.cc', 'proxy/proxy_service_unittest.cc', 'proxy/sync_host_resolver_bridge_unittest.cc', + 'socket/buffered_write_stream_socket_unittest.cc', 'socket/client_socket_pool_base_unittest.cc', 'socket/deterministic_socket_data_unittest.cc', 'socket/mock_client_socket_pool_manager.cc', diff --git a/net/socket/buffered_write_stream_socket.cc b/net/socket/buffered_write_stream_socket.cc new file mode 100644 index 0000000..94c6d57 --- /dev/null +++ b/net/socket/buffered_write_stream_socket.cc @@ -0,0 +1,156 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/socket/buffered_write_stream_socket.h" + +#include "base/bind.h" +#include "base/location.h" +#include "base/message_loop.h" +#include "net/base/io_buffer.h" +#include "net/base/net_errors.h" + +namespace net { + +namespace { + +void AppendBuffer(GrowableIOBuffer* dst, IOBuffer* src, int src_len) { + int old_capacity = dst->capacity(); + dst->SetCapacity(old_capacity + src_len); + memcpy(dst->StartOfBuffer() + old_capacity, src->data(), src_len); +} + +} // anonymous namespace + +BufferedWriteStreamSocket::BufferedWriteStreamSocket( + StreamSocket* socket_to_wrap) + : wrapped_socket_(socket_to_wrap), + io_buffer_(new GrowableIOBuffer()), + backup_buffer_(new GrowableIOBuffer()), + ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)), + callback_pending_(false), + wrapped_write_in_progress_(false), + error_(0) { +} + +BufferedWriteStreamSocket::~BufferedWriteStreamSocket() { +} + +int BufferedWriteStreamSocket::Read(IOBuffer* buf, int buf_len, + const CompletionCallback& callback) { + return wrapped_socket_->Read(buf, buf_len, callback); +} + +int BufferedWriteStreamSocket::Write(IOBuffer* buf, int buf_len, + const CompletionCallback& callback) { + if (error_) { + return error_; + } + GrowableIOBuffer* idle_buffer = + wrapped_write_in_progress_ ? backup_buffer_.get() : io_buffer_.get(); + AppendBuffer(idle_buffer, buf, buf_len); + if (!callback_pending_) { + MessageLoop::current()->PostTask( + FROM_HERE, + base::Bind(&BufferedWriteStreamSocket::DoDelayedWrite, + weak_factory_.GetWeakPtr())); + callback_pending_ = true; + } + return buf_len; +} + +bool BufferedWriteStreamSocket::SetReceiveBufferSize(int32 size) { + return wrapped_socket_->SetReceiveBufferSize(size); +} + +bool BufferedWriteStreamSocket::SetSendBufferSize(int32 size) { + return wrapped_socket_->SetSendBufferSize(size); +} + +int BufferedWriteStreamSocket::Connect(const CompletionCallback& callback) { + return wrapped_socket_->Connect(callback); +} + +void BufferedWriteStreamSocket::Disconnect() { + wrapped_socket_->Disconnect(); +} + +bool BufferedWriteStreamSocket::IsConnected() const { + return wrapped_socket_->IsConnected(); +} + +bool BufferedWriteStreamSocket::IsConnectedAndIdle() const { + return wrapped_socket_->IsConnectedAndIdle(); +} + +int BufferedWriteStreamSocket::GetPeerAddress(AddressList* address) const { + return wrapped_socket_->GetPeerAddress(address); +} + +int BufferedWriteStreamSocket::GetLocalAddress(IPEndPoint* address) const { + return wrapped_socket_->GetLocalAddress(address); +} + +const BoundNetLog& BufferedWriteStreamSocket::NetLog() const { + return wrapped_socket_->NetLog(); +} + +void BufferedWriteStreamSocket::SetSubresourceSpeculation() { + wrapped_socket_->SetSubresourceSpeculation(); +} + +void BufferedWriteStreamSocket::SetOmniboxSpeculation() { + wrapped_socket_->SetOmniboxSpeculation(); +} + +bool BufferedWriteStreamSocket::WasEverUsed() const { + return wrapped_socket_->WasEverUsed(); +} + +bool BufferedWriteStreamSocket::UsingTCPFastOpen() const { + return wrapped_socket_->UsingTCPFastOpen(); +} + +int64 BufferedWriteStreamSocket::NumBytesRead() const { + return wrapped_socket_->NumBytesRead(); +} + +base::TimeDelta BufferedWriteStreamSocket::GetConnectTimeMicros() const { + return wrapped_socket_->GetConnectTimeMicros(); +} + +void BufferedWriteStreamSocket::DoDelayedWrite() { + int result = wrapped_socket_->Write( + io_buffer_, io_buffer_->RemainingCapacity(), + base::Bind(&BufferedWriteStreamSocket::OnIOComplete, + base::Unretained(this))); + if (result == ERR_IO_PENDING) { + callback_pending_ = true; + wrapped_write_in_progress_ = true; + } else { + OnIOComplete(result); + } +} + +void BufferedWriteStreamSocket::OnIOComplete(int result) { + callback_pending_ = false; + wrapped_write_in_progress_ = false; + if (backup_buffer_->RemainingCapacity()) { + AppendBuffer(io_buffer_.get(), backup_buffer_.get(), + backup_buffer_->RemainingCapacity()); + backup_buffer_->SetCapacity(0); + } + if (result < 0) { + error_ = result; + io_buffer_->SetCapacity(0); + } else { + io_buffer_->set_offset(io_buffer_->offset() + result); + if (io_buffer_->RemainingCapacity()) { + DoDelayedWrite(); + } else { + io_buffer_->SetCapacity(0); + } + } +} + +} // namespace net diff --git a/net/socket/buffered_write_stream_socket.h b/net/socket/buffered_write_stream_socket.h new file mode 100644 index 0000000..a504209 --- /dev/null +++ b/net/socket/buffered_write_stream_socket.h @@ -0,0 +1,78 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef NET_SOCKET_BUFFERED_WRITE_STREAM_SOCKET_H_ +#define NET_SOCKET_BUFFERED_WRITE_STREAM_SOCKET_H_ +#pragma once + +#include "base/memory/weak_ptr.h" +#include "net/base/net_log.h" +#include "net/socket/stream_socket.h" + +namespace base { +class TimeDelta; +} + +namespace net { + +class AddressList; +class GrowableIOBuffer; +class IPEndPoint; + +// A StreamSocket decorator. All functions are passed through to the wrapped +// socket, except for Write(). +// +// Writes are buffered locally so that multiple Write()s to this class are +// issued as only one Write() to the wrapped socket. This is useful to force +// multiple requests to be issued in a single packet, as is needed to trigger +// edge cases in HTTP pipelining. +// +// Note that the Write() always returns synchronously. It will either buffer the +// entire input or return the most recently reported error. +// +// There are no bounds on the local buffer size. Use carefully. +class NET_EXPORT_PRIVATE BufferedWriteStreamSocket : public StreamSocket { + public: + BufferedWriteStreamSocket(StreamSocket* socket_to_wrap); + virtual ~BufferedWriteStreamSocket(); + + // Socket interface + virtual int Read(IOBuffer* buf, int buf_len, + const CompletionCallback& callback) OVERRIDE; + virtual int Write(IOBuffer* buf, int buf_len, + const CompletionCallback& callback) OVERRIDE; + virtual bool SetReceiveBufferSize(int32 size) OVERRIDE; + virtual bool SetSendBufferSize(int32 size) OVERRIDE; + + // StreamSocket interface + virtual int Connect(const CompletionCallback& callback) OVERRIDE; + virtual void Disconnect() OVERRIDE; + virtual bool IsConnected() const OVERRIDE; + virtual bool IsConnectedAndIdle() const OVERRIDE; + virtual int GetPeerAddress(AddressList* address) const OVERRIDE; + virtual int GetLocalAddress(IPEndPoint* address) const OVERRIDE; + virtual const BoundNetLog& NetLog() const OVERRIDE; + virtual void SetSubresourceSpeculation() OVERRIDE; + virtual void SetOmniboxSpeculation() OVERRIDE; + virtual bool WasEverUsed() const OVERRIDE; + virtual bool UsingTCPFastOpen() const OVERRIDE; + virtual int64 NumBytesRead() const OVERRIDE; + virtual base::TimeDelta GetConnectTimeMicros() const OVERRIDE; + + private: + void DoDelayedWrite(); + void OnIOComplete(int result); + + scoped_ptr<StreamSocket> wrapped_socket_; + scoped_refptr<GrowableIOBuffer> io_buffer_; + scoped_refptr<GrowableIOBuffer> backup_buffer_; + base::WeakPtrFactory<BufferedWriteStreamSocket> weak_factory_; + bool callback_pending_; + bool wrapped_write_in_progress_; + int error_; +}; + +} // namespace net + +#endif // NET_SOCKET_STREAM_SOCKET_H_ diff --git a/net/socket/buffered_write_stream_socket_unittest.cc b/net/socket/buffered_write_stream_socket_unittest.cc new file mode 100644 index 0000000..9798a60 --- /dev/null +++ b/net/socket/buffered_write_stream_socket_unittest.cc @@ -0,0 +1,122 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/socket/buffered_write_stream_socket.h" + +#include "base/message_loop.h" +#include "base/memory/ref_counted.h" +#include "net/base/net_errors.h" +#include "net/base/net_log.h" +#include "net/socket/socket_test_util.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { + +namespace { + +class BufferedWriteStreamSocketTest : public testing::Test { + public: + void Finish() { + MessageLoop::current()->RunAllPending(); + EXPECT_TRUE(data_->at_read_eof()); + EXPECT_TRUE(data_->at_write_eof()); + } + + void Initialize(MockWrite* writes, size_t writes_count) { + data_ = new DeterministicSocketData(NULL, 0, writes, writes_count); + data_->set_connect_data(MockConnect(SYNCHRONOUS, 0)); + if (writes_count) { + data_->StopAfter(writes_count); + } + DeterministicMockTCPClientSocket* wrapped_socket = + new DeterministicMockTCPClientSocket(net_log_.net_log(), data_.get()); + data_->set_socket(wrapped_socket->AsWeakPtr()); + socket_.reset(new BufferedWriteStreamSocket(wrapped_socket)); + socket_->Connect(callback_.callback()); + } + + void TestWrite(const char* text) { + scoped_refptr<StringIOBuffer> buf(new StringIOBuffer(text)); + EXPECT_EQ(buf->size(), + socket_->Write(buf.get(), buf->size(), callback_.callback())); + } + + scoped_ptr<BufferedWriteStreamSocket> socket_; + scoped_refptr<DeterministicSocketData> data_; + BoundNetLog net_log_; + TestCompletionCallback callback_; +}; + +TEST_F(BufferedWriteStreamSocketTest, SingleWrite) { + MockWrite writes[] = { + MockWrite(SYNCHRONOUS, 0, "abc"), + }; + Initialize(writes, arraysize(writes)); + TestWrite("abc"); + Finish(); +} + +TEST_F(BufferedWriteStreamSocketTest, AsyncWrite) { + MockWrite writes[] = { + MockWrite(ASYNC, 0, "abc"), + }; + Initialize(writes, arraysize(writes)); + TestWrite("abc"); + data_->Run(); + Finish(); +} + +TEST_F(BufferedWriteStreamSocketTest, TwoWritesIntoOne) { + MockWrite writes[] = { + MockWrite(SYNCHRONOUS, 0, "abcdef"), + }; + Initialize(writes, arraysize(writes)); + TestWrite("abc"); + TestWrite("def"); + Finish(); +} + +TEST_F(BufferedWriteStreamSocketTest, WriteWhileBlocked) { + MockWrite writes[] = { + MockWrite(ASYNC, 0, "abc"), + MockWrite(ASYNC, 1, "def"), + MockWrite(ASYNC, 2, "ghi"), + }; + Initialize(writes, arraysize(writes)); + TestWrite("abc"); + MessageLoop::current()->RunAllPending(); + TestWrite("def"); + data_->RunFor(1); + TestWrite("ghi"); + data_->RunFor(1); + Finish(); +} + +TEST_F(BufferedWriteStreamSocketTest, ContinuesPartialWrite) { + MockWrite writes[] = { + MockWrite(ASYNC, 0, "abc"), + MockWrite(ASYNC, 1, "def"), + }; + Initialize(writes, arraysize(writes)); + TestWrite("abcdef"); + data_->Run(); + Finish(); +} + +TEST_F(BufferedWriteStreamSocketTest, TwoSeparateWrites) { + MockWrite writes[] = { + MockWrite(ASYNC, 0, "abc"), + MockWrite(ASYNC, 1, "def"), + }; + Initialize(writes, arraysize(writes)); + TestWrite("abc"); + data_->RunFor(1); + TestWrite("def"); + data_->RunFor(1); + Finish(); +} + +} // anonymous namespace + +} // namespace net |