summaryrefslogtreecommitdiffstats
path: root/net
diff options
context:
space:
mode:
authorsimonjam@chromium.org <simonjam@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-03-01 21:31:31 +0000
committersimonjam@chromium.org <simonjam@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-03-01 21:31:31 +0000
commit5477d890353c732bbd0c16d8a3c5ce56a52ee2fe (patch)
treeb225cdf9c0dba2d2c77cc76b094551a8be9ba5cf /net
parent271398aa3ff859d3deace9ff3ca575f14d263964 (diff)
downloadchromium_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')
-rw-r--r--net/http/http_network_session.cc6
-rw-r--r--net/http/http_network_session.h14
-rw-r--r--net/http/http_network_transaction.cc8
-rw-r--r--net/http/http_pipelined_connection_impl.h20
-rw-r--r--net/http/http_pipelined_host.cc17
-rw-r--r--net/http/http_pipelined_host.h26
-rw-r--r--net/http/http_pipelined_host_forced.cc108
-rw-r--r--net/http/http_pipelined_host_forced.h84
-rw-r--r--net/http/http_pipelined_host_forced_unittest.cc106
-rw-r--r--net/http/http_pipelined_host_impl.cc36
-rw-r--r--net/http/http_pipelined_host_impl.h6
-rw-r--r--net/http/http_pipelined_host_impl_unittest.cc69
-rw-r--r--net/http/http_pipelined_host_pool.cc62
-rw-r--r--net/http/http_pipelined_host_pool.h26
-rw-r--r--net/http/http_pipelined_host_pool_unittest.cc167
-rw-r--r--net/http/http_pipelined_host_test_util.cc33
-rw-r--r--net/http/http_pipelined_host_test_util.h70
-rw-r--r--net/http/http_pipelined_network_transaction_unittest.cc186
-rw-r--r--net/http/http_stream_factory_impl.cc31
-rw-r--r--net/http/http_stream_factory_impl.h9
-rw-r--r--net/http/http_stream_factory_impl_job.cc38
-rw-r--r--net/http/http_stream_factory_impl_job.h4
-rw-r--r--net/http/http_stream_factory_impl_request.cc22
-rw-r--r--net/http/http_stream_factory_impl_request.h7
-rw-r--r--net/net.gyp9
-rw-r--r--net/socket/buffered_write_stream_socket.cc156
-rw-r--r--net/socket/buffered_write_stream_socket.h78
-rw-r--r--net/socket/buffered_write_stream_socket_unittest.cc122
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