summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--net/http/http_pipelined_connection.h12
-rw-r--r--net/http/http_pipelined_connection_impl.cc44
-rw-r--r--net/http/http_pipelined_connection_impl.h4
-rw-r--r--net/http/http_pipelined_connection_impl_unittest.cc140
-rw-r--r--net/http/http_pipelined_host.cc108
-rw-r--r--net/http/http_pipelined_host.h70
-rw-r--r--net/http/http_pipelined_host_impl.cc183
-rw-r--r--net/http/http_pipelined_host_impl.h99
-rw-r--r--net/http/http_pipelined_host_impl_unittest.cc301
-rw-r--r--net/http/http_pipelined_host_pool.cc78
-rw-r--r--net/http/http_pipelined_host_pool.h36
-rw-r--r--net/http/http_pipelined_host_pool_unittest.cc185
-rw-r--r--net/http/http_pipelined_host_unittest.cc201
-rw-r--r--net/http/http_pipelined_network_transaction_unittest.cc224
-rw-r--r--net/http/http_stream_factory_impl.cc2
-rw-r--r--net/http/http_stream_factory_impl.h11
-rw-r--r--net/http/http_stream_factory_impl_job.cc8
-rw-r--r--net/http/http_stream_factory_impl_job.h2
-rw-r--r--net/net.gyp6
19 files changed, 1288 insertions, 426 deletions
diff --git a/net/http/http_pipelined_connection.h b/net/http/http_pipelined_connection.h
index 59e61d6..e8c332b 100644
--- a/net/http/http_pipelined_connection.h
+++ b/net/http/http_pipelined_connection.h
@@ -19,12 +19,24 @@ struct SSLConfig;
class NET_EXPORT_PRIVATE HttpPipelinedConnection {
public:
+ enum Feedback {
+ OK,
+ PIPELINE_SOCKET_ERROR,
+ OLD_HTTP_VERSION,
+ MUST_CLOSE_CONNECTION,
+ };
+
class Delegate {
public:
// Called when a pipeline has newly available capacity. This may be because
// the first request has been sent and the pipeline is now active. Or, it
// may be because a request successfully completed.
virtual void OnPipelineHasCapacity(HttpPipelinedConnection* pipeline) = 0;
+
+ // Called every time a pipeline receives headers. Lets the delegate know if
+ // the headers indicate that pipelining can be used.
+ virtual void OnPipelineFeedback(HttpPipelinedConnection* pipeline,
+ Feedback feedback) = 0;
};
class Factory {
diff --git a/net/http/http_pipelined_connection_impl.cc b/net/http/http_pipelined_connection_impl.cc
index 7792970..95d27a6 100644
--- a/net/http/http_pipelined_connection_impl.cc
+++ b/net/http/http_pipelined_connection_impl.cc
@@ -12,6 +12,7 @@
#include "net/http/http_response_body_drainer.h"
#include "net/http/http_response_headers.h"
#include "net/http/http_stream_parser.h"
+#include "net/http/http_version.h"
#include "net/socket/client_socket_handle.h"
namespace net {
@@ -167,7 +168,7 @@ int HttpPipelinedConnectionImpl::DoSendRequestLoop(int result) {
rv = DoEvictPendingSendRequests(rv);
break;
default:
- NOTREACHED() << "bad send state: " << state;
+ CHECK(false) << "bad send state: " << state;
rv = ERR_FAILED;
break;
}
@@ -337,7 +338,7 @@ int HttpPipelinedConnectionImpl::DoReadHeadersLoop(int result) {
case READ_STATE_NONE:
break;
default:
- NOTREACHED() << "bad read state";
+ CHECK(false) << "bad read state";
rv = ERR_FAILED;
break;
}
@@ -394,7 +395,7 @@ int HttpPipelinedConnectionImpl::DoStartNextDeferredRead(int result) {
break;
default:
- NOTREACHED() << "Unexpected read state: "
+ CHECK(false) << "Unexpected read state: "
<< stream_info_map_[next_id].state;
}
@@ -425,6 +426,8 @@ int HttpPipelinedConnectionImpl::DoReadHeadersComplete(int result) {
usable_ = false;
}
+ CheckHeadersForPipelineCompatibility(result, active_read_id_);
+
if (!read_still_on_call_stack_) {
QueueUserCallback(active_read_id_,
stream_info_map_[active_read_id_].read_headers_callback,
@@ -445,6 +448,7 @@ int HttpPipelinedConnectionImpl::DoReadStreamClosed() {
CHECK_EQ(stream_info_map_[active_read_id_].state, STREAM_CLOSED);
active_read_id_ = 0;
if (!usable_) {
+ // TODO(simonjam): Don't wait this long to evict.
read_next_state_ = READ_STATE_EVICT_PENDING_READS;
return OK;
}
@@ -527,7 +531,7 @@ void HttpPipelinedConnectionImpl::Close(int pipeline_id,
break;
default:
- NOTREACHED();
+ CHECK(false);
break;
}
}
@@ -624,6 +628,38 @@ void HttpPipelinedConnectionImpl::Drain(HttpPipelinedStream* stream,
// |drainer| will delete itself when done.
}
+void HttpPipelinedConnectionImpl::CheckHeadersForPipelineCompatibility(
+ int result,
+ int pipeline_id) {
+ if (result < OK) {
+ switch (result) {
+ // TODO(simonjam): Ignoring specific errors like this may not work.
+ // Collect metrics to see if this code is useful.
+ case ERR_ABORTED:
+ case ERR_INTERNET_DISCONNECTED:
+ // These errors are no fault of the server.
+ break;
+
+ default:
+ delegate_->OnPipelineFeedback(this, PIPELINE_SOCKET_ERROR);
+ return;
+ }
+ }
+ HttpResponseInfo* info = GetResponseInfo(pipeline_id);
+ const HttpVersion required_version(1, 1);
+ if (info->headers->GetParsedHttpVersion() < required_version) {
+ delegate_->OnPipelineFeedback(this, OLD_HTTP_VERSION);
+ return;
+ }
+ if (!info->headers->IsKeepAlive() || !CanFindEndOfResponse(pipeline_id)) {
+ usable_ = false;
+ delegate_->OnPipelineFeedback(this, MUST_CLOSE_CONNECTION);
+ return;
+ }
+ // TODO(simonjam): We should also check for, and work around, authentication.
+ delegate_->OnPipelineFeedback(this, OK);
+}
+
void HttpPipelinedConnectionImpl::QueueUserCallback(
int pipeline_id,
OldCompletionCallback* callback,
diff --git a/net/http/http_pipelined_connection_impl.h b/net/http/http_pipelined_connection_impl.h
index a63aabd..64577b7 100644
--- a/net/http/http_pipelined_connection_impl.h
+++ b/net/http/http_pipelined_connection_impl.h
@@ -256,6 +256,10 @@ class NET_EXPORT_PRIVATE HttpPipelinedConnectionImpl
// HttpPipelinedSockets indicates the connection was suddenly closed.
int DoEvictPendingReadHeaders(int result);
+ // Reports back to |delegate_| whether pipelining will work. This is called
+ // every time we receive headers.
+ void CheckHeadersForPipelineCompatibility(int result, int pipeline_id);
+
// Posts a task to fire the user's callback in response to SendRequest() or
// ReadResponseHeaders() completing on an underlying parser. This might be
// invoked in response to our own IO callbacks, or it may be invoked if the
diff --git a/net/http/http_pipelined_connection_impl_unittest.cc b/net/http/http_pipelined_connection_impl_unittest.cc
index 2c9beec..2c0d2df 100644
--- a/net/http/http_pipelined_connection_impl_unittest.cc
+++ b/net/http/http_pipelined_connection_impl_unittest.cc
@@ -31,9 +31,14 @@ class DummySocketParams : public base::RefCounted<DummySocketParams> {
REGISTER_SOCKET_PARAMS_FOR_POOL(MockTransportClientSocketPool,
DummySocketParams);
-class MockPipelineDelegate : public HttpPipelinedConnectionImpl::Delegate {
+namespace {
+
+class MockPipelineDelegate : public HttpPipelinedConnection::Delegate {
public:
MOCK_METHOD1(OnPipelineHasCapacity, void(HttpPipelinedConnection* pipeline));
+ MOCK_METHOD2(OnPipelineFeedback, void(
+ HttpPipelinedConnection* pipeline,
+ HttpPipelinedConnection::Feedback feedback));
};
class SuddenCloseObserver : public MessageLoop::TaskObserver {
@@ -1211,6 +1216,137 @@ TEST_F(HttpPipelinedConnectionImplTest, EvictIfDrainingChunkedEncoding) {
stream2->Close(false);
}
+TEST_F(HttpPipelinedConnectionImplTest, EvictionDueToMissingContentLength) {
+ MockWrite writes[] = {
+ MockWrite(false, 0, "GET /ok.html HTTP/1.1\r\n\r\n"),
+ MockWrite(false, 1, "GET /evicted.html HTTP/1.1\r\n\r\n"),
+ MockWrite(false, 2, "GET /rejected.html HTTP/1.1\r\n\r\n"),
+ };
+ MockRead reads[] = {
+ MockRead(true, 3, "HTTP/1.1 200 OK\r\n\r\n"),
+ MockRead(false, 4, "ok.html"),
+ MockRead(false, OK, 5),
+ };
+ Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+ scoped_ptr<HttpStream> ok_stream(NewTestStream("ok.html"));
+ scoped_ptr<HttpStream> evicted_stream(NewTestStream("evicted.html"));
+ scoped_ptr<HttpStream> rejected_stream(NewTestStream("rejected.html"));
+
+ HttpRequestHeaders headers;
+ HttpResponseInfo response;
+ EXPECT_EQ(OK, ok_stream->SendRequest(headers, NULL, &response, &callback_));
+ EXPECT_EQ(OK, evicted_stream->SendRequest(headers, NULL, &response,
+ &callback_));
+ EXPECT_EQ(OK, rejected_stream->SendRequest(headers, NULL, &response,
+ &callback_));
+
+ TestOldCompletionCallback ok_callback;
+ EXPECT_EQ(ERR_IO_PENDING, ok_stream->ReadResponseHeaders(&ok_callback));
+
+ TestOldCompletionCallback evicted_callback;
+ EXPECT_EQ(ERR_IO_PENDING,
+ evicted_stream->ReadResponseHeaders(&evicted_callback));
+
+ data_->RunFor(1);
+ EXPECT_LE(OK, ok_callback.WaitForResult());
+ data_->StopAfter(10);
+
+ ExpectResponse("ok.html", ok_stream, false);
+ ok_stream->Close(false);
+
+ EXPECT_EQ(ERR_PIPELINE_EVICTION,
+ rejected_stream->ReadResponseHeaders(&callback_));
+ rejected_stream->Close(true);
+ EXPECT_EQ(ERR_PIPELINE_EVICTION, evicted_callback.WaitForResult());
+ evicted_stream->Close(true);
+}
+
+TEST_F(HttpPipelinedConnectionImplTest, FeedbackOnSocketError) {
+ MockWrite writes[] = {
+ MockWrite(false, 0, "GET /ok.html HTTP/1.1\r\n\r\n"),
+ };
+ MockRead reads[] = {
+ MockRead(false, ERR_FAILED, 1),
+ };
+ Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+ EXPECT_CALL(delegate_,
+ OnPipelineFeedback(
+ pipeline_.get(),
+ HttpPipelinedConnection::PIPELINE_SOCKET_ERROR))
+ .Times(1);
+
+ scoped_ptr<HttpStream> stream(NewTestStream("ok.html"));
+ HttpRequestHeaders headers;
+ HttpResponseInfo response;
+ EXPECT_EQ(OK, stream->SendRequest(headers, NULL, &response, &callback_));
+ EXPECT_EQ(ERR_FAILED, stream->ReadResponseHeaders(&callback_));
+}
+
+TEST_F(HttpPipelinedConnectionImplTest, FeedbackOnHttp10) {
+ MockWrite writes[] = {
+ MockWrite(false, 0, "GET /ok.html HTTP/1.1\r\n\r\n"),
+ };
+ MockRead reads[] = {
+ MockRead(false, 1, "HTTP/1.0 200 OK\r\n"),
+ MockRead(false, 2, "Content-Length: 7\r\n"),
+ MockRead(false, 3, "Connection: keep-alive\r\n\r\n"),
+ MockRead(false, 4, "ok.html"),
+ };
+ Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+ EXPECT_CALL(delegate_,
+ OnPipelineFeedback(pipeline_.get(),
+ HttpPipelinedConnection::OLD_HTTP_VERSION))
+ .Times(1);
+
+ scoped_ptr<HttpStream> stream(NewTestStream("ok.html"));
+ TestSyncRequest(stream, "ok.html");
+}
+
+TEST_F(HttpPipelinedConnectionImplTest, FeedbackOnMustClose) {
+ MockWrite writes[] = {
+ MockWrite(false, 0, "GET /ok.html HTTP/1.1\r\n\r\n"),
+ };
+ MockRead reads[] = {
+ MockRead(false, 1, "HTTP/1.1 200 OK\r\n"),
+ MockRead(false, 2, "Content-Length: 7\r\n"),
+ MockRead(false, 3, "Connection: close\r\n\r\n"),
+ MockRead(false, 4, "ok.html"),
+ };
+ Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+ EXPECT_CALL(delegate_,
+ OnPipelineFeedback(
+ pipeline_.get(),
+ HttpPipelinedConnection::MUST_CLOSE_CONNECTION))
+ .Times(1);
+
+ scoped_ptr<HttpStream> stream(NewTestStream("ok.html"));
+ TestSyncRequest(stream, "ok.html");
+}
+
+TEST_F(HttpPipelinedConnectionImplTest, FeedbackOnNoContentLength) {
+ MockWrite writes[] = {
+ MockWrite(false, 0, "GET /ok.html HTTP/1.1\r\n\r\n"),
+ };
+ MockRead reads[] = {
+ MockRead(false, 1, "HTTP/1.1 200 OK\r\n\r\n"),
+ MockRead(false, 2, "ok.html"),
+ };
+ Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+ EXPECT_CALL(delegate_,
+ OnPipelineFeedback(
+ pipeline_.get(),
+ HttpPipelinedConnection::MUST_CLOSE_CONNECTION))
+ .Times(1);
+
+ scoped_ptr<HttpStream> stream(NewTestStream("ok.html"));
+ TestSyncRequest(stream, "ok.html");
+}
+
TEST_F(HttpPipelinedConnectionImplTest, OnPipelineHasCapacity) {
MockWrite writes[] = {
MockWrite(false, 0, "GET /ok.html HTTP/1.1\r\n\r\n"),
@@ -1250,4 +1386,6 @@ TEST_F(HttpPipelinedConnectionImplTest, OnPipelineHasCapacityWithoutSend) {
stream.reset(NULL);
}
+} // anonymous namespace
+
} // namespace net
diff --git a/net/http/http_pipelined_host.cc b/net/http/http_pipelined_host.cc
deleted file mode 100644
index 32d023d..0000000
--- a/net/http/http_pipelined_host.cc
+++ /dev/null
@@ -1,108 +0,0 @@
-// Copyright (c) 2011 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"
-
-#include "base/stl_util.h"
-#include "net/http/http_pipelined_connection_impl.h"
-#include "net/http/http_pipelined_stream.h"
-
-namespace net {
-
-class HttpPipelinedConnectionImplFactory :
- public HttpPipelinedConnection::Factory {
- public:
- HttpPipelinedConnection* CreateNewPipeline(
- ClientSocketHandle* connection,
- HttpPipelinedConnection::Delegate* delegate,
- const SSLConfig& used_ssl_config,
- const ProxyInfo& used_proxy_info,
- const BoundNetLog& net_log,
- bool was_npn_negotiated) OVERRIDE {
- return new HttpPipelinedConnectionImpl(connection, delegate,
- used_ssl_config, used_proxy_info,
- net_log, was_npn_negotiated);
- }
-};
-
-HttpPipelinedHost::HttpPipelinedHost(
- HttpPipelinedHost::Delegate* delegate,
- const HostPortPair& origin,
- HttpPipelinedConnection::Factory* factory)
- : delegate_(delegate),
- origin_(origin),
- factory_(factory) {
- if (!factory) {
- factory_.reset(new HttpPipelinedConnectionImplFactory());
- }
-}
-
-HttpPipelinedHost::~HttpPipelinedHost() {
- CHECK(pipelines_.empty());
-}
-
-HttpPipelinedStream* HttpPipelinedHost::CreateStreamOnNewPipeline(
- ClientSocketHandle* connection,
- const SSLConfig& used_ssl_config,
- const ProxyInfo& used_proxy_info,
- const BoundNetLog& net_log,
- bool was_npn_negotiated) {
- HttpPipelinedConnection* pipeline = factory_->CreateNewPipeline(
- connection, this, used_ssl_config, used_proxy_info, net_log,
- was_npn_negotiated);
- pipelines_.insert(pipeline);
- return pipeline->CreateNewStream();
-}
-
-HttpPipelinedStream* HttpPipelinedHost::CreateStreamOnExistingPipeline() {
- HttpPipelinedConnection* available_pipeline = NULL;
- for (std::set<HttpPipelinedConnection*>::iterator it = pipelines_.begin();
- it != pipelines_.end(); ++it) {
- if ((*it)->usable() &&
- (*it)->active() &&
- (*it)->depth() < max_pipeline_depth() &&
- (!available_pipeline || (*it)->depth() < available_pipeline->depth())) {
- available_pipeline = *it;
- }
- }
- if (!available_pipeline) {
- return NULL;
- }
- return available_pipeline->CreateNewStream();
-}
-
-bool HttpPipelinedHost::IsExistingPipelineAvailable() {
- for (std::set<HttpPipelinedConnection*>::iterator it = pipelines_.begin();
- it != pipelines_.end(); ++it) {
- if ((*it)->usable() &&
- (*it)->active() &&
- (*it)->depth() < max_pipeline_depth()) {
- return true;
- }
- }
- return false;
-}
-
-void HttpPipelinedHost::OnPipelineEmpty(HttpPipelinedConnection* pipeline) {
- CHECK(ContainsKey(pipelines_, pipeline));
- pipelines_.erase(pipeline);
- delete pipeline;
- if (pipelines_.empty()) {
- delegate_->OnHostIdle(this);
- // WARNING: We'll probably be deleted here.
- }
-}
-
-void HttpPipelinedHost::OnPipelineHasCapacity(
- HttpPipelinedConnection* pipeline) {
- if (pipeline->usable() && pipeline->depth() < max_pipeline_depth()) {
- delegate_->OnHostHasAdditionalCapacity(this);
- }
- if (!pipeline->depth()) {
- OnPipelineEmpty(pipeline);
- // WARNING: We might be deleted here.
- }
-}
-
-} // namespace net
diff --git a/net/http/http_pipelined_host.h b/net/http/http_pipelined_host.h
index ae3128d..c368313 100644
--- a/net/http/http_pipelined_host.h
+++ b/net/http/http_pipelined_host.h
@@ -6,11 +6,6 @@
#define NET_HTTP_HTTP_PIPELINED_HOST_H_
#pragma once
-#include <set>
-#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"
@@ -26,9 +21,16 @@ struct SSLConfig;
// Manages all of the pipelining state for specific host with active pipelined
// HTTP requests. Manages connection jobs, constructs pipelined streams, and
// assigns requests to the least loaded pipelined connection.
-class NET_EXPORT_PRIVATE HttpPipelinedHost
- : public HttpPipelinedConnection::Delegate {
+class NET_EXPORT_PRIVATE HttpPipelinedHost {
public:
+ enum Capability {
+ UNKNOWN,
+ INCAPABLE,
+ CAPABLE,
+ PROBABLY_CAPABLE, // We are using pipelining, but haven't processed enough
+ // requests to record this host as known to be capable.
+ };
+
class Delegate {
public:
// Called when a pipelined host has no outstanding requests on any of its
@@ -38,54 +40,44 @@ class NET_EXPORT_PRIVATE HttpPipelinedHost
// Called when a pipelined host has newly available pipeline capacity, like
// when a request completes.
virtual void OnHostHasAdditionalCapacity(HttpPipelinedHost* host) = 0;
+
+ // Called when a host determines if pipelining can be used.
+ virtual void OnHostDeterminedCapability(HttpPipelinedHost* host,
+ Capability capability) = 0;
+ };
+
+ class Factory {
+ public:
+ virtual ~Factory() {}
+
+ // Returns a new HttpPipelinedHost.
+ virtual HttpPipelinedHost* CreateNewHost(
+ Delegate* delegate, const HostPortPair& origin,
+ HttpPipelinedConnection::Factory* factory,
+ Capability capability) = 0;
};
- HttpPipelinedHost(Delegate* delegate, const HostPortPair& origin,
- HttpPipelinedConnection::Factory* factory);
- virtual ~HttpPipelinedHost();
+ virtual ~HttpPipelinedHost() {}
// Constructs a new pipeline on |connection| and returns a new
// HttpPipelinedStream that uses it.
- HttpPipelinedStream* CreateStreamOnNewPipeline(
+ virtual HttpPipelinedStream* CreateStreamOnNewPipeline(
ClientSocketHandle* connection,
const SSLConfig& used_ssl_config,
const ProxyInfo& used_proxy_info,
const BoundNetLog& net_log,
- bool was_npn_negotiated);
+ bool was_npn_negotiated) = 0;
// 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();
+ virtual HttpPipelinedStream* CreateStreamOnExistingPipeline() = 0;
// Returns true if we have a pipelined connection that can accept new
// requests.
- bool IsExistingPipelineAvailable();
-
- // Callbacks for HttpPipelinedConnection.
-
- // Called when a pipelined connection completes a request. Adds a pending
- // request to the pipeline if the pipeline is still usable.
- virtual void OnPipelineHasCapacity(
- HttpPipelinedConnection* pipeline) OVERRIDE;
-
- const HostPortPair& origin() const { return origin_; }
-
- private:
- // Called when a pipeline is empty and there are no pending requests. Closes
- // the connection.
- void OnPipelineEmpty(HttpPipelinedConnection* pipeline);
-
- // Adds the next pending request to the pipeline if it's still usuable.
- void AddRequestToPipeline(HttpPipelinedConnection* connection);
-
- int max_pipeline_depth() const { return 3; }
-
- Delegate* delegate_;
- const HostPortPair origin_;
- std::set<HttpPipelinedConnection*> pipelines_;
- scoped_ptr<HttpPipelinedConnection::Factory> factory_;
+ virtual bool IsExistingPipelineAvailable() const = 0;
- DISALLOW_COPY_AND_ASSIGN(HttpPipelinedHost);
+ // Returns the host and port associated with this class.
+ virtual const HostPortPair& origin() const = 0;
};
} // namespace net
diff --git a/net/http/http_pipelined_host_impl.cc b/net/http/http_pipelined_host_impl.cc
new file mode 100644
index 0000000..25014763
--- /dev/null
+++ b/net/http/http_pipelined_host_impl.cc
@@ -0,0 +1,183 @@
+// Copyright (c) 2011 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_impl.h"
+
+#include "base/stl_util.h"
+#include "net/http/http_pipelined_connection_impl.h"
+#include "net/http/http_pipelined_stream.h"
+
+namespace net {
+
+// TODO(simonjam): Run experiments to see what value minimizes evictions without
+// 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 SSLConfig& used_ssl_config,
+ const ProxyInfo& used_proxy_info,
+ const BoundNetLog& net_log,
+ bool was_npn_negotiated) OVERRIDE {
+ return new HttpPipelinedConnectionImpl(connection, delegate,
+ used_ssl_config, used_proxy_info,
+ net_log, was_npn_negotiated);
+ }
+};
+
+HttpPipelinedHostImpl::HttpPipelinedHostImpl(
+ HttpPipelinedHost::Delegate* delegate,
+ const HostPortPair& origin,
+ HttpPipelinedConnection::Factory* factory,
+ Capability capability)
+ : delegate_(delegate),
+ origin_(origin),
+ factory_(factory),
+ capability_(capability) {
+ if (!factory) {
+ factory_.reset(new HttpPipelinedConnectionImplFactory());
+ }
+}
+
+HttpPipelinedHostImpl::~HttpPipelinedHostImpl() {
+ CHECK(pipelines_.empty());
+}
+
+HttpPipelinedStream* HttpPipelinedHostImpl::CreateStreamOnNewPipeline(
+ ClientSocketHandle* connection,
+ const SSLConfig& used_ssl_config,
+ const ProxyInfo& used_proxy_info,
+ const BoundNetLog& net_log,
+ bool was_npn_negotiated) {
+ if (capability_ == INCAPABLE) {
+ return NULL;
+ }
+ HttpPipelinedConnection* pipeline = factory_->CreateNewPipeline(
+ connection, this, used_ssl_config, used_proxy_info, net_log,
+ was_npn_negotiated);
+ PipelineInfo info;
+ pipelines_.insert(std::make_pair(pipeline, info));
+ return pipeline->CreateNewStream();
+}
+
+HttpPipelinedStream* HttpPipelinedHostImpl::CreateStreamOnExistingPipeline() {
+ HttpPipelinedConnection* available_pipeline = NULL;
+ for (PipelineInfoMap::iterator it = pipelines_.begin();
+ it != pipelines_.end(); ++it) {
+ if (it->first->usable() &&
+ it->first->active() &&
+ it->first->depth() < GetPipelineCapacity() &&
+ (!available_pipeline ||
+ it->first->depth() < available_pipeline->depth())) {
+ available_pipeline = it->first;
+ }
+ }
+ if (!available_pipeline) {
+ return NULL;
+ }
+ return available_pipeline->CreateNewStream();
+}
+
+bool HttpPipelinedHostImpl::IsExistingPipelineAvailable() const {
+ for (PipelineInfoMap::const_iterator it = pipelines_.begin();
+ it != pipelines_.end(); ++it) {
+ if (it->first->usable() &&
+ it->first->active() &&
+ it->first->depth() < GetPipelineCapacity()) {
+ return true;
+ }
+ }
+ return false;
+}
+
+const HostPortPair& HttpPipelinedHostImpl::origin() const {
+ return origin_;
+}
+
+void HttpPipelinedHostImpl::OnPipelineEmpty(HttpPipelinedConnection* pipeline) {
+ CHECK(ContainsKey(pipelines_, pipeline));
+ pipelines_.erase(pipeline);
+ delete pipeline;
+ if (pipelines_.empty()) {
+ delegate_->OnHostIdle(this);
+ // WARNING: We'll probably be deleted here.
+ }
+}
+
+void HttpPipelinedHostImpl::OnPipelineHasCapacity(
+ HttpPipelinedConnection* pipeline) {
+ CHECK(ContainsKey(pipelines_, pipeline));
+ if (pipeline->usable() &&
+ capability_ != INCAPABLE &&
+ pipeline->depth() < GetPipelineCapacity()) {
+ delegate_->OnHostHasAdditionalCapacity(this);
+ }
+ if (!pipeline->depth()) {
+ OnPipelineEmpty(pipeline);
+ // WARNING: We might be deleted here.
+ }
+}
+
+void HttpPipelinedHostImpl::OnPipelineFeedback(
+ HttpPipelinedConnection* pipeline,
+ HttpPipelinedConnection::Feedback feedback) {
+ CHECK(ContainsKey(pipelines_, pipeline));
+ switch (feedback) {
+ case HttpPipelinedConnection::OK:
+ ++pipelines_[pipeline].num_successes;
+ if (capability_ == UNKNOWN) {
+ capability_ = PROBABLY_CAPABLE;
+ for (PipelineInfoMap::iterator it = pipelines_.begin();
+ it != pipelines_.end(); ++it) {
+ OnPipelineHasCapacity(it->first);
+ }
+ } else if (capability_ == PROBABLY_CAPABLE &&
+ pipelines_[pipeline].num_successes >=
+ kNumKnownSuccessesThreshold) {
+ capability_ = CAPABLE;
+ delegate_->OnHostDeterminedCapability(this, CAPABLE);
+ }
+ break;
+
+ case HttpPipelinedConnection::PIPELINE_SOCKET_ERROR:
+ case HttpPipelinedConnection::OLD_HTTP_VERSION:
+ capability_ = INCAPABLE;
+ delegate_->OnHostDeterminedCapability(this, INCAPABLE);
+ break;
+
+ case HttpPipelinedConnection::MUST_CLOSE_CONNECTION:
+ break;
+ }
+}
+
+int HttpPipelinedHostImpl::GetPipelineCapacity() const {
+ int capacity = 0;
+ switch (capability_) {
+ case CAPABLE:
+ case PROBABLY_CAPABLE:
+ capacity = max_pipeline_depth();
+ break;
+
+ case INCAPABLE:
+ CHECK(false);
+
+ case UNKNOWN:
+ capacity = 1;
+ break;
+
+ default:
+ CHECK(false) << "Unkown pipeline capability: " << capability_;
+ }
+ return capacity;
+}
+
+HttpPipelinedHostImpl::PipelineInfo::PipelineInfo()
+ : num_successes(0) {
+}
+
+} // namespace net
diff --git a/net/http/http_pipelined_host_impl.h b/net/http/http_pipelined_host_impl.h
new file mode 100644
index 0000000..af78005
--- /dev/null
+++ b/net/http/http_pipelined_host_impl.h
@@ -0,0 +1,99 @@
+// Copyright (c) 2011 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_IMPL_H_
+#define NET_HTTP_HTTP_PIPELINED_HOST_IMPL_H_
+#pragma once
+
+#include <map>
+#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"
+
+namespace net {
+
+class BoundNetLog;
+class ClientSocketHandle;
+class HttpPipelinedStream;
+class ProxyInfo;
+struct SSLConfig;
+
+// Manages all of the pipelining state for specific host with active pipelined
+// HTTP requests. Manages connection jobs, constructs pipelined streams, and
+// assigns requests to the least loaded pipelined connection.
+class NET_EXPORT_PRIVATE HttpPipelinedHostImpl
+ : public HttpPipelinedHost,
+ public HttpPipelinedConnection::Delegate {
+ public:
+ HttpPipelinedHostImpl(HttpPipelinedHost::Delegate* delegate,
+ const HostPortPair& origin,
+ HttpPipelinedConnection::Factory* factory,
+ Capability capability);
+ virtual ~HttpPipelinedHostImpl();
+
+ // 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) OVERRIDE;
+
+ virtual HttpPipelinedStream* CreateStreamOnExistingPipeline() OVERRIDE;
+
+ virtual bool IsExistingPipelineAvailable() const OVERRIDE;
+
+ // HttpPipelinedConnection::Delegate interface
+
+ // Called when a pipelined connection completes a request. Adds a pending
+ // request to the pipeline if the pipeline is still usable.
+ virtual void OnPipelineHasCapacity(
+ HttpPipelinedConnection* pipeline) OVERRIDE;
+
+ virtual void OnPipelineFeedback(
+ HttpPipelinedConnection* pipeline,
+ HttpPipelinedConnection::Feedback feedback) OVERRIDE;
+
+ virtual const HostPortPair& origin() const OVERRIDE;
+
+ // Returns the maximum number of in-flight pipelined requests we'll allow on a
+ // single connection.
+ NET_EXPORT_PRIVATE static int max_pipeline_depth() { return 3; }
+
+ private:
+ struct PipelineInfo {
+ PipelineInfo();
+
+ int num_successes;
+ };
+ typedef std::map<HttpPipelinedConnection*, PipelineInfo> PipelineInfoMap;
+
+ // Called when a pipeline is empty and there are no pending requests. Closes
+ // the connection.
+ void OnPipelineEmpty(HttpPipelinedConnection* pipeline);
+
+ // Adds the next pending request to the pipeline if it's still usuable.
+ void AddRequestToPipeline(HttpPipelinedConnection* connection);
+
+ // Returns the current pipeline capacity based on |capability_|. This should
+ // not be called if |capability_| is INCAPABLE.
+ int GetPipelineCapacity() const;
+
+ HttpPipelinedHost::Delegate* delegate_;
+ const HostPortPair origin_;
+ PipelineInfoMap pipelines_;
+ scoped_ptr<HttpPipelinedConnection::Factory> factory_;
+ Capability capability_;
+
+ DISALLOW_COPY_AND_ASSIGN(HttpPipelinedHostImpl);
+};
+
+} // namespace net
+
+#endif // NET_HTTP_HTTP_PIPELINED_HOST_IMPL_H_
diff --git a/net/http/http_pipelined_host_impl_unittest.cc b/net/http/http_pipelined_host_impl_unittest.cc
new file mode 100644
index 0000000..ed7d722
--- /dev/null
+++ b/net/http/http_pipelined_host_impl_unittest.cc
@@ -0,0 +1,301 @@
+// Copyright (c) 2011 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_impl.h"
+
+#include "base/memory/scoped_ptr.h"
+#include "net/base/ssl_config_service.h"
+#include "net/http/http_pipelined_connection.h"
+#include "net/proxy/proxy_info.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::_;
+using testing::NiceMock;
+using testing::Ref;
+using testing::Return;
+using testing::ReturnNull;
+
+namespace net {
+
+namespace {
+
+ClientSocketHandle* kDummyConnection =
+ reinterpret_cast<ClientSocketHandle*>(84);
+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,
+ HttpPipelinedHost::Capability capability));
+};
+
+class MockPipelineFactory : public HttpPipelinedConnection::Factory {
+ public:
+ MOCK_METHOD6(CreateNewPipeline, HttpPipelinedConnection*(
+ ClientSocketHandle* connection,
+ HttpPipelinedConnection::Delegate* delegate,
+ const SSLConfig& used_ssl_config,
+ const ProxyInfo& used_proxy_info,
+ const BoundNetLog& net_log,
+ bool was_npn_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(source, const NetLog::Source&());
+ MOCK_CONST_METHOD0(was_npn_negotiated, bool());
+
+ private:
+ int depth_;
+ bool usable_;
+ bool active_;
+};
+
+class HttpPipelinedHostImplTest : public testing::Test {
+ public:
+ HttpPipelinedHostImplTest()
+ : origin_("host", 123),
+ factory_(new MockPipelineFactory), // Owned by host_.
+ host_(new HttpPipelinedHostImpl(&delegate_, origin_, factory_,
+ HttpPipelinedHost::CAPABLE)) {
+ }
+
+ void SetCapability(HttpPipelinedHost::Capability capability) {
+ factory_ = new MockPipelineFactory;
+ host_.reset(new HttpPipelinedHostImpl(
+ &delegate_, origin_, factory_, capability));
+ }
+
+ MockPipeline* AddTestPipeline(int depth, bool usable, bool active) {
+ MockPipeline* pipeline = new MockPipeline(depth, usable, active);
+ EXPECT_CALL(*factory_, CreateNewPipeline(kDummyConnection, host_.get(),
+ Ref(ssl_config_), Ref(proxy_info_),
+ Ref(net_log_), true))
+ .Times(1)
+ .WillOnce(Return(pipeline));
+ EXPECT_CALL(*pipeline, CreateNewStream())
+ .Times(1)
+ .WillOnce(Return(kDummyStream));
+ EXPECT_EQ(kDummyStream, host_->CreateStreamOnNewPipeline(
+ kDummyConnection, ssl_config_, proxy_info_, net_log_, true));
+ return pipeline;
+ }
+
+ void ClearTestPipeline(MockPipeline* pipeline) {
+ pipeline->SetState(0, true, true);
+ host_->OnPipelineHasCapacity(pipeline);
+ }
+
+ NiceMock<MockHostDelegate> delegate_;
+ HostPortPair origin_;
+ MockPipelineFactory* factory_;
+ scoped_ptr<HttpPipelinedHostImpl> host_;
+
+ SSLConfig ssl_config_;
+ ProxyInfo proxy_info_;
+ BoundNetLog net_log_;
+};
+
+TEST_F(HttpPipelinedHostImplTest, Delegate) {
+ EXPECT_TRUE(origin_.Equals(host_->origin()));
+}
+
+TEST_F(HttpPipelinedHostImplTest, OnUnusablePipelineHasCapacity) {
+ MockPipeline* pipeline = AddTestPipeline(0, false, true);
+
+ EXPECT_CALL(delegate_, OnHostHasAdditionalCapacity(host_.get()))
+ .Times(0);
+ EXPECT_CALL(delegate_, OnHostIdle(host_.get()))
+ .Times(1);
+ host_->OnPipelineHasCapacity(pipeline);
+}
+
+TEST_F(HttpPipelinedHostImplTest, OnUsablePipelineHasCapacity) {
+ MockPipeline* pipeline = AddTestPipeline(1, true, true);
+
+ EXPECT_CALL(delegate_, OnHostHasAdditionalCapacity(host_.get()))
+ .Times(1);
+ EXPECT_CALL(delegate_, OnHostIdle(host_.get()))
+ .Times(0);
+
+ host_->OnPipelineHasCapacity(pipeline);
+
+ EXPECT_CALL(delegate_, OnHostHasAdditionalCapacity(host_.get()))
+ .Times(1);
+ EXPECT_CALL(delegate_, OnHostIdle(host_.get()))
+ .Times(1);
+ ClearTestPipeline(pipeline);
+}
+
+TEST_F(HttpPipelinedHostImplTest, IgnoresUnusablePipeline) {
+ MockPipeline* pipeline = AddTestPipeline(1, false, true);
+
+ EXPECT_FALSE(host_->IsExistingPipelineAvailable());
+ EXPECT_EQ(NULL, host_->CreateStreamOnExistingPipeline());
+
+ ClearTestPipeline(pipeline);
+}
+
+TEST_F(HttpPipelinedHostImplTest, IgnoresInactivePipeline) {
+ MockPipeline* pipeline = AddTestPipeline(1, true, false);
+
+ EXPECT_FALSE(host_->IsExistingPipelineAvailable());
+ EXPECT_EQ(NULL, host_->CreateStreamOnExistingPipeline());
+
+ ClearTestPipeline(pipeline);
+}
+
+TEST_F(HttpPipelinedHostImplTest, IgnoresFullPipeline) {
+ MockPipeline* pipeline = AddTestPipeline(
+ HttpPipelinedHostImpl::max_pipeline_depth(), true, true);
+
+ EXPECT_FALSE(host_->IsExistingPipelineAvailable());
+ EXPECT_EQ(NULL, host_->CreateStreamOnExistingPipeline());
+
+ ClearTestPipeline(pipeline);
+}
+
+TEST_F(HttpPipelinedHostImplTest, PicksLeastLoadedPipeline) {
+ MockPipeline* full_pipeline = AddTestPipeline(
+ HttpPipelinedHostImpl::max_pipeline_depth(), true, true);
+ MockPipeline* usable_pipeline = AddTestPipeline(
+ HttpPipelinedHostImpl::max_pipeline_depth() - 1, true, true);
+ MockPipeline* empty_pipeline = AddTestPipeline(0, true, true);
+
+ EXPECT_TRUE(host_->IsExistingPipelineAvailable());
+ EXPECT_CALL(*empty_pipeline, CreateNewStream())
+ .Times(1)
+ .WillOnce(ReturnNull());
+ EXPECT_EQ(NULL, host_->CreateStreamOnExistingPipeline());
+
+ ClearTestPipeline(full_pipeline);
+ ClearTestPipeline(usable_pipeline);
+ ClearTestPipeline(empty_pipeline);
+}
+
+TEST_F(HttpPipelinedHostImplTest, OpensUpOnPipelineSuccess) {
+ SetCapability(HttpPipelinedHost::UNKNOWN);
+ MockPipeline* pipeline = AddTestPipeline(1, true, true);
+
+ EXPECT_EQ(NULL, host_->CreateStreamOnExistingPipeline());
+ EXPECT_CALL(delegate_, OnHostHasAdditionalCapacity(host_.get()))
+ .Times(1);
+ host_->OnPipelineFeedback(pipeline, HttpPipelinedConnection::OK);
+
+ EXPECT_CALL(*pipeline, CreateNewStream())
+ .Times(1)
+ .WillOnce(Return(kDummyStream));
+ EXPECT_EQ(kDummyStream, host_->CreateStreamOnExistingPipeline());
+
+ EXPECT_CALL(delegate_, OnHostHasAdditionalCapacity(host_.get()))
+ .Times(1);
+ ClearTestPipeline(pipeline);
+}
+
+TEST_F(HttpPipelinedHostImplTest, OpensAllPipelinesOnPipelineSuccess) {
+ SetCapability(HttpPipelinedHost::UNKNOWN);
+ MockPipeline* pipeline1 = AddTestPipeline(1, false, true);
+ MockPipeline* pipeline2 = AddTestPipeline(1, true, true);
+
+ EXPECT_EQ(NULL, host_->CreateStreamOnExistingPipeline());
+ EXPECT_CALL(delegate_, OnHostHasAdditionalCapacity(host_.get()))
+ .Times(1);
+ host_->OnPipelineFeedback(pipeline1, HttpPipelinedConnection::OK);
+
+ EXPECT_CALL(*pipeline2, CreateNewStream())
+ .Times(1)
+ .WillOnce(Return(kDummyStream));
+ EXPECT_EQ(kDummyStream, host_->CreateStreamOnExistingPipeline());
+
+ EXPECT_CALL(delegate_, OnHostHasAdditionalCapacity(host_.get()))
+ .Times(2);
+ ClearTestPipeline(pipeline1);
+ ClearTestPipeline(pipeline2);
+}
+
+TEST_F(HttpPipelinedHostImplTest, ShutsDownOnOldVersion) {
+ SetCapability(HttpPipelinedHost::UNKNOWN);
+ MockPipeline* pipeline = AddTestPipeline(1, true, true);
+
+ EXPECT_EQ(NULL, host_->CreateStreamOnExistingPipeline());
+ EXPECT_CALL(delegate_, OnHostHasAdditionalCapacity(host_.get()))
+ .Times(0);
+ EXPECT_CALL(delegate_,
+ OnHostDeterminedCapability(host_.get(),
+ HttpPipelinedHost::INCAPABLE))
+ .Times(1);
+ host_->OnPipelineFeedback(pipeline,
+ HttpPipelinedConnection::OLD_HTTP_VERSION);
+
+ ClearTestPipeline(pipeline);
+ EXPECT_EQ(NULL, host_->CreateStreamOnNewPipeline(
+ kDummyConnection, ssl_config_, proxy_info_, net_log_, true));
+}
+
+TEST_F(HttpPipelinedHostImplTest, ConnectionCloseHasNoEffect) {
+ SetCapability(HttpPipelinedHost::UNKNOWN);
+ MockPipeline* pipeline = AddTestPipeline(1, true, true);
+
+ EXPECT_CALL(delegate_, OnHostHasAdditionalCapacity(host_.get()))
+ .Times(0);
+ EXPECT_CALL(delegate_, OnHostDeterminedCapability(host_.get(), _))
+ .Times(0);
+ host_->OnPipelineFeedback(pipeline,
+ HttpPipelinedConnection::MUST_CLOSE_CONNECTION);
+ EXPECT_EQ(NULL, host_->CreateStreamOnExistingPipeline());
+
+ EXPECT_CALL(delegate_, OnHostHasAdditionalCapacity(host_.get()))
+ .Times(1);
+ ClearTestPipeline(pipeline);
+}
+
+TEST_F(HttpPipelinedHostImplTest, SuccessesLeadToCapable) {
+ SetCapability(HttpPipelinedHost::UNKNOWN);
+ MockPipeline* pipeline = AddTestPipeline(1, true, true);
+
+ EXPECT_CALL(delegate_, OnHostHasAdditionalCapacity(host_.get()))
+ .Times(1);
+ EXPECT_CALL(delegate_,
+ OnHostDeterminedCapability(host_.get(),
+ HttpPipelinedHost::CAPABLE))
+ .Times(1);
+ host_->OnPipelineFeedback(pipeline, HttpPipelinedConnection::OK);
+
+ pipeline->SetState(3, true, true);
+ host_->OnPipelineFeedback(pipeline, HttpPipelinedConnection::OK);
+ host_->OnPipelineFeedback(pipeline, HttpPipelinedConnection::OK);
+
+ EXPECT_CALL(delegate_, OnHostHasAdditionalCapacity(host_.get()))
+ .Times(1);
+ ClearTestPipeline(pipeline);
+}
+
+} // anonymous namespace
+
+} // namespace net
diff --git a/net/http/http_pipelined_host_pool.cc b/net/http/http_pipelined_host_pool.cc
index bd238d8..fa705a0 100644
--- a/net/http/http_pipelined_host_pool.cc
+++ b/net/http/http_pipelined_host_pool.cc
@@ -6,19 +6,46 @@
#include "base/logging.h"
#include "base/stl_util.h"
-#include "net/http/http_pipelined_host.h"
-#include "net/http/http_stream_factory_impl.h"
+#include "net/http/http_pipelined_host_impl.h"
namespace net {
-HttpPipelinedHostPool::HttpPipelinedHostPool(HttpStreamFactoryImpl* factory)
- : factory_(factory) {
+// TODO(simonjam): Run experiments with different values of this to see what
+// value is good at avoiding evictions without eating too much memory. Until
+// then, this is just a bad guess.
+static const int kNumHostsToRemember = 200;
+
+class HttpPipelinedHostImplFactory : public HttpPipelinedHost::Factory {
+ public:
+ virtual HttpPipelinedHost* CreateNewHost(
+ HttpPipelinedHost::Delegate* delegate, const HostPortPair& origin,
+ HttpPipelinedConnection::Factory* factory,
+ HttpPipelinedHost::Capability capability) OVERRIDE {
+ return new HttpPipelinedHostImpl(delegate, origin, factory, capability);
+ }
+};
+
+HttpPipelinedHostPool::HttpPipelinedHostPool(
+ Delegate* delegate,
+ HttpPipelinedHost::Factory* factory)
+ : delegate_(delegate),
+ factory_(factory),
+ known_capability_map_(kNumHostsToRemember) {
+ if (!factory) {
+ factory_.reset(new HttpPipelinedHostImplFactory);
+ }
}
HttpPipelinedHostPool::~HttpPipelinedHostPool() {
CHECK(host_map_.empty());
}
+bool HttpPipelinedHostPool::IsHostEligibleForPipelining(
+ const HostPortPair& origin) {
+ HttpPipelinedHost::Capability capability = GetHostCapability(origin);
+ return capability != HttpPipelinedHost::INCAPABLE;
+}
+
HttpPipelinedStream* HttpPipelinedHostPool::CreateStreamOnNewPipeline(
const HostPortPair& origin,
ClientSocketHandle* connection,
@@ -27,6 +54,9 @@ HttpPipelinedStream* HttpPipelinedHostPool::CreateStreamOnNewPipeline(
const BoundNetLog& net_log,
bool was_npn_negotiated) {
HttpPipelinedHost* host = GetPipelinedHost(origin, true);
+ if (!host) {
+ return NULL;
+ }
return host->CreateStreamOnNewPipeline(connection, used_ssl_config,
used_proxy_info, net_log,
was_npn_negotiated);
@@ -52,14 +82,21 @@ bool HttpPipelinedHostPool::IsExistingPipelineAvailableForOrigin(
HttpPipelinedHost* HttpPipelinedHostPool::GetPipelinedHost(
const HostPortPair& origin, bool create_if_not_found) {
- HostMap::iterator it = host_map_.find(origin);
- if (it != host_map_.end()) {
- CHECK(it->second);
- return it->second;
+ HostMap::iterator host_it = host_map_.find(origin);
+ if (host_it != host_map_.end()) {
+ CHECK(host_it->second);
+ return host_it->second;
} else if (!create_if_not_found) {
return NULL;
}
- HttpPipelinedHost* host = new HttpPipelinedHost(this, origin, NULL);
+
+ HttpPipelinedHost::Capability capability = GetHostCapability(origin);
+ if (capability == HttpPipelinedHost::INCAPABLE) {
+ return NULL;
+ }
+
+ HttpPipelinedHost* host = factory_->CreateNewHost(
+ this, origin, NULL, capability);
host_map_[origin] = host;
return host;
}
@@ -67,14 +104,33 @@ HttpPipelinedHost* HttpPipelinedHostPool::GetPipelinedHost(
void HttpPipelinedHostPool::OnHostIdle(HttpPipelinedHost* host) {
const HostPortPair& origin = host->origin();
CHECK(ContainsKey(host_map_, origin));
- // TODO(simonjam): We should remember the pipeline state for each host.
host_map_.erase(origin);
delete host;
}
void HttpPipelinedHostPool::OnHostHasAdditionalCapacity(
HttpPipelinedHost* host) {
- factory_->OnHttpPipelinedHostHasAdditionalCapacity(host->origin());
+ delegate_->OnHttpPipelinedHostHasAdditionalCapacity(host->origin());
+}
+
+void HttpPipelinedHostPool::OnHostDeterminedCapability(
+ HttpPipelinedHost* host,
+ HttpPipelinedHost::Capability capability) {
+ CapabilityMap::iterator known_it = known_capability_map_.Get(host->origin());
+ if (known_it == known_capability_map_.end() ||
+ known_it->second != HttpPipelinedHost::INCAPABLE) {
+ known_capability_map_.Put(host->origin(), capability);
+ }
+}
+
+HttpPipelinedHost::Capability HttpPipelinedHostPool::GetHostCapability(
+ const HostPortPair& origin) {
+ HttpPipelinedHost::Capability capability = HttpPipelinedHost::UNKNOWN;
+ CapabilityMap::const_iterator it = known_capability_map_.Get(origin);
+ if (it != known_capability_map_.end()) {
+ capability = it->second;
+ }
+ return capability;
}
} // namespace net
diff --git a/net/http/http_pipelined_host_pool.h b/net/http/http_pipelined_host_pool.h
index 5e7c146..3be0cb5 100644
--- a/net/http/http_pipelined_host_pool.h
+++ b/net/http/http_pipelined_host_pool.h
@@ -9,21 +9,37 @@
#include <map>
#include "base/basictypes.h"
+#include "base/gtest_prod_util.h"
+#include "base/memory/mru_cache.h"
+#include "base/memory/scoped_ptr.h"
#include "net/http/http_pipelined_host.h"
namespace net {
class HostPortPair;
class HttpPipelinedStream;
-class HttpStreamFactoryImpl;
// Manages all of the pipelining state for specific host with active pipelined
// HTTP requests. Manages connection jobs, constructs pipelined streams, and
// assigns requests to the least loaded pipelined connection.
-class HttpPipelinedHostPool : public HttpPipelinedHost::Delegate {
+class NET_EXPORT_PRIVATE HttpPipelinedHostPool
+ : public HttpPipelinedHost::Delegate {
public:
- explicit HttpPipelinedHostPool(HttpStreamFactoryImpl* factory);
- ~HttpPipelinedHostPool();
+ class Delegate {
+ public:
+ // Called when a HttpPipelinedHost has new capacity. Attempts to allocate
+ // any pending pipeline-capable requests to pipelines.
+ virtual void OnHttpPipelinedHostHasAdditionalCapacity(
+ const HostPortPair& origin) = 0;
+ };
+
+ HttpPipelinedHostPool(Delegate* delegate,
+ HttpPipelinedHost::Factory* factory);
+ 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);
// Constructs a new pipeline on |connection| and returns a new
// HttpPipelinedStream that uses it.
@@ -49,14 +65,24 @@ class HttpPipelinedHostPool : public HttpPipelinedHost::Delegate {
virtual void OnHostHasAdditionalCapacity(HttpPipelinedHost* host) OVERRIDE;
+ virtual void OnHostDeterminedCapability(
+ HttpPipelinedHost* host,
+ HttpPipelinedHost::Capability capability) OVERRIDE;
+
private:
+ typedef base::MRUCache<HostPortPair,
+ HttpPipelinedHost::Capability> CapabilityMap;
typedef std::map<const HostPortPair, HttpPipelinedHost*> HostMap;
HttpPipelinedHost* GetPipelinedHost(const HostPortPair& origin,
bool create_if_not_found);
- HttpStreamFactoryImpl* factory_;
+ HttpPipelinedHost::Capability GetHostCapability(const HostPortPair& origin);
+
+ Delegate* delegate_;
+ scoped_ptr<HttpPipelinedHost::Factory> factory_;
HostMap host_map_;
+ CapabilityMap known_capability_map_;
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
new file mode 100644
index 0000000..928e4e9
--- /dev/null
+++ b/net/http/http_pipelined_host_pool_unittest.cc
@@ -0,0 +1,185 @@
+// Copyright (c) 2011 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 "net/base/ssl_config_service.h"
+#include "net/http/http_pipelined_host.h"
+#include "net/proxy/proxy_info.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::_;
+using testing::Ref;
+using testing::Return;
+using testing::ReturnNull;
+
+namespace net {
+
+namespace {
+
+ClientSocketHandle* kDummyConnection =
+ reinterpret_cast<ClientSocketHandle*>(188);
+HttpPipelinedStream* kDummyStream =
+ reinterpret_cast<HttpPipelinedStream*>(99);
+
+class MockPoolDelegate : public HttpPipelinedHostPool::Delegate {
+ public:
+ MOCK_METHOD1(OnHttpPipelinedHostHasAdditionalCapacity,
+ void(const HostPortPair& origin));
+};
+
+class MockHostFactory : public HttpPipelinedHost::Factory {
+ public:
+ MOCK_METHOD4(CreateNewHost, HttpPipelinedHost*(
+ HttpPipelinedHost::Delegate* delegate, const HostPortPair& origin,
+ HttpPipelinedConnection::Factory* factory,
+ HttpPipelinedHost::Capability capability));
+};
+
+class MockHost : public HttpPipelinedHost {
+ public:
+ MockHost(const HostPortPair& origin)
+ : origin_(origin) {
+ }
+
+ MOCK_METHOD5(CreateStreamOnNewPipeline, HttpPipelinedStream*(
+ ClientSocketHandle* connection,
+ const SSLConfig& used_ssl_config,
+ const ProxyInfo& used_proxy_info,
+ const BoundNetLog& net_log,
+ bool was_npn_negotiated));
+ MOCK_METHOD0(CreateStreamOnExistingPipeline, HttpPipelinedStream*());
+ MOCK_CONST_METHOD0(IsExistingPipelineAvailable, bool());
+
+ virtual const HostPortPair& origin() const OVERRIDE { return origin_; }
+
+ private:
+ HostPortPair origin_;
+};
+
+class HttpPipelinedHostPoolTest : public testing::Test {
+ public:
+ HttpPipelinedHostPoolTest()
+ : origin_("host", 123),
+ factory_(new MockHostFactory), // Owned by pool_.
+ host_(new MockHost(origin_)), // Owned by pool_.
+ pool_(new HttpPipelinedHostPool(&delegate_, factory_)),
+ was_npn_negotiated_(false) {
+ }
+
+ void CreateDummyStream() {
+ EXPECT_CALL(*host_, CreateStreamOnNewPipeline(kDummyConnection,
+ Ref(ssl_config_),
+ Ref(proxy_info_),
+ Ref(net_log_),
+ was_npn_negotiated_))
+ .Times(1)
+ .WillOnce(Return(kDummyStream));
+ EXPECT_EQ(kDummyStream,
+ pool_->CreateStreamOnNewPipeline(origin_, kDummyConnection,
+ ssl_config_, proxy_info_,
+ net_log_, was_npn_negotiated_));
+ }
+
+ HostPortPair origin_;
+ MockPoolDelegate delegate_;
+ MockHostFactory* factory_;
+ MockHost* host_;
+ scoped_ptr<HttpPipelinedHostPool> pool_;
+
+ const SSLConfig ssl_config_;
+ const ProxyInfo proxy_info_;
+ const BoundNetLog net_log_;
+ bool was_npn_negotiated_;
+};
+
+TEST_F(HttpPipelinedHostPoolTest, DefaultUnknown) {
+ EXPECT_TRUE(pool_->IsHostEligibleForPipelining(origin_));
+ EXPECT_CALL(*factory_, CreateNewHost(pool_.get(), Ref(origin_), _,
+ HttpPipelinedHost::UNKNOWN))
+ .Times(1)
+ .WillOnce(Return(host_));
+
+ CreateDummyStream();
+ pool_->OnHostIdle(host_);
+}
+
+TEST_F(HttpPipelinedHostPoolTest, RemembersIncapable) {
+ EXPECT_CALL(*factory_, CreateNewHost(pool_.get(), Ref(origin_), _,
+ HttpPipelinedHost::UNKNOWN))
+ .Times(1)
+ .WillOnce(Return(host_));
+
+ CreateDummyStream();
+ pool_->OnHostDeterminedCapability(host_, HttpPipelinedHost::INCAPABLE);
+ pool_->OnHostIdle(host_);
+ EXPECT_FALSE(pool_->IsHostEligibleForPipelining(origin_));
+ EXPECT_CALL(*factory_, CreateNewHost(pool_.get(), Ref(origin_), _,
+ HttpPipelinedHost::INCAPABLE))
+ .Times(0);
+ EXPECT_EQ(NULL,
+ pool_->CreateStreamOnNewPipeline(origin_, kDummyConnection,
+ ssl_config_, proxy_info_, net_log_,
+ was_npn_negotiated_));
+}
+
+TEST_F(HttpPipelinedHostPoolTest, RemembersCapable) {
+ EXPECT_CALL(*factory_, CreateNewHost(pool_.get(), Ref(origin_), _,
+ HttpPipelinedHost::UNKNOWN))
+ .Times(1)
+ .WillOnce(Return(host_));
+
+ CreateDummyStream();
+ pool_->OnHostDeterminedCapability(host_, HttpPipelinedHost::CAPABLE);
+ pool_->OnHostIdle(host_);
+ EXPECT_TRUE(pool_->IsHostEligibleForPipelining(origin_));
+
+ host_ = new MockHost(origin_);
+ EXPECT_CALL(*factory_, CreateNewHost(pool_.get(), Ref(origin_), _,
+ HttpPipelinedHost::CAPABLE))
+ .Times(1)
+ .WillOnce(Return(host_));
+ CreateDummyStream();
+ pool_->OnHostIdle(host_);
+}
+
+TEST_F(HttpPipelinedHostPoolTest, IncapableIsSticky) {
+ EXPECT_CALL(*factory_, CreateNewHost(pool_.get(), Ref(origin_), _,
+ HttpPipelinedHost::UNKNOWN))
+ .Times(1)
+ .WillOnce(Return(host_));
+
+ CreateDummyStream();
+ pool_->OnHostDeterminedCapability(host_, HttpPipelinedHost::CAPABLE);
+ pool_->OnHostDeterminedCapability(host_, HttpPipelinedHost::INCAPABLE);
+ pool_->OnHostDeterminedCapability(host_, HttpPipelinedHost::CAPABLE);
+ pool_->OnHostIdle(host_);
+ EXPECT_FALSE(pool_->IsHostEligibleForPipelining(origin_));
+}
+
+TEST_F(HttpPipelinedHostPoolTest, RemainsUnknownWithoutFeedback) {
+ EXPECT_CALL(*factory_, CreateNewHost(pool_.get(), Ref(origin_), _,
+ HttpPipelinedHost::UNKNOWN))
+ .Times(1)
+ .WillOnce(Return(host_));
+
+ CreateDummyStream();
+ pool_->OnHostIdle(host_);
+ EXPECT_TRUE(pool_->IsHostEligibleForPipelining(origin_));
+
+ host_ = new MockHost(origin_);
+ EXPECT_CALL(*factory_, CreateNewHost(pool_.get(), Ref(origin_), _,
+ HttpPipelinedHost::UNKNOWN))
+ .Times(1)
+ .WillOnce(Return(host_));
+
+ CreateDummyStream();
+ pool_->OnHostIdle(host_);
+}
+
+} // anonymous namespace
+
+} // namespace net
diff --git a/net/http/http_pipelined_host_unittest.cc b/net/http/http_pipelined_host_unittest.cc
deleted file mode 100644
index de78e9e..0000000
--- a/net/http/http_pipelined_host_unittest.cc
+++ /dev/null
@@ -1,201 +0,0 @@
-// Copyright (c) 2011 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"
-
-#include "net/base/ssl_config_service.h"
-#include "net/http/http_pipelined_connection.h"
-#include "net/proxy/proxy_info.h"
-#include "testing/gmock/include/gmock/gmock.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-using testing::_;
-using testing::NiceMock;
-using testing::Ref;
-using testing::Return;
-using testing::ReturnNull;
-
-static const int kMaxCapacity = 3;
-
-namespace net {
-
-static ClientSocketHandle* kDummyConnection =
- reinterpret_cast<ClientSocketHandle*>(84);
-static HttpPipelinedStream* kDummyStream =
- reinterpret_cast<HttpPipelinedStream*>(42);
-
-class MockHostDelegate : public HttpPipelinedHost::Delegate {
- public:
- MOCK_METHOD1(OnHostIdle, void(HttpPipelinedHost* host));
- MOCK_METHOD1(OnHostHasAdditionalCapacity, void(HttpPipelinedHost* host));
-};
-
-class MockPipelineFactory : public HttpPipelinedConnection::Factory {
- public:
- MOCK_METHOD6(CreateNewPipeline, HttpPipelinedConnection*(
- ClientSocketHandle* connection,
- HttpPipelinedConnection::Delegate* delegate,
- const SSLConfig& used_ssl_config,
- const ProxyInfo& used_proxy_info,
- const BoundNetLog& net_log,
- bool was_npn_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(source, const NetLog::Source&());
- MOCK_CONST_METHOD0(was_npn_negotiated, bool());
-
- private:
- int depth_;
- bool usable_;
- bool active_;
-};
-
-class HttpPipelinedHostTest : public testing::Test {
- public:
- HttpPipelinedHostTest()
- : origin_("host", 123),
- factory_(new MockPipelineFactory), // Owned by host_.
- host_(&delegate_, origin_, factory_) {
- }
-
- MockPipeline* AddTestPipeline(int depth, bool usable, bool active) {
- MockPipeline* pipeline = new MockPipeline(depth, usable, active);
- EXPECT_CALL(*factory_, CreateNewPipeline(kDummyConnection, &host_,
- Ref(ssl_config_), Ref(proxy_info_),
- Ref(net_log_), true))
- .Times(1)
- .WillOnce(Return(pipeline));
- EXPECT_CALL(*pipeline, CreateNewStream())
- .Times(1)
- .WillOnce(Return(kDummyStream));
- EXPECT_EQ(kDummyStream, host_.CreateStreamOnNewPipeline(
- kDummyConnection, ssl_config_, proxy_info_, net_log_, true));
- return pipeline;
- }
-
- void ClearTestPipeline(MockPipeline* pipeline) {
- pipeline->SetState(0, true, true);
- host_.OnPipelineHasCapacity(pipeline);
- }
-
- NiceMock<MockHostDelegate> delegate_;
- HostPortPair origin_;
- MockPipelineFactory* factory_;
- HttpPipelinedHost host_;
-
- SSLConfig ssl_config_;
- ProxyInfo proxy_info_;
- BoundNetLog net_log_;
-};
-
-TEST_F(HttpPipelinedHostTest, Delegate) {
- EXPECT_TRUE(origin_.Equals(host_.origin()));
-}
-
-TEST_F(HttpPipelinedHostTest, OnHostIdle) {
- MockPipeline* pipeline = AddTestPipeline(0, false, true);
-
- EXPECT_CALL(delegate_, OnHostHasAdditionalCapacity(&host_))
- .Times(0);
- EXPECT_CALL(delegate_, OnHostIdle(&host_))
- .Times(1);
- host_.OnPipelineHasCapacity(pipeline);
-}
-
-TEST_F(HttpPipelinedHostTest, OnHostHasAdditionalCapacity) {
- MockPipeline* pipeline = AddTestPipeline(1, true, true);
-
- EXPECT_CALL(delegate_, OnHostHasAdditionalCapacity(&host_))
- .Times(2);
- EXPECT_CALL(delegate_, OnHostIdle(&host_))
- .Times(0);
-
- host_.OnPipelineHasCapacity(pipeline);
-
- EXPECT_CALL(delegate_, OnHostIdle(&host_))
- .Times(1);
- ClearTestPipeline(pipeline);
-}
-
-TEST_F(HttpPipelinedHostTest, IgnoresUnusablePipeline) {
- MockPipeline* pipeline = AddTestPipeline(1, false, true);
-
- EXPECT_FALSE(host_.IsExistingPipelineAvailable());
- EXPECT_EQ(NULL, host_.CreateStreamOnExistingPipeline());
-
- ClearTestPipeline(pipeline);
-}
-
-TEST_F(HttpPipelinedHostTest, IgnoresInactivePipeline) {
- MockPipeline* pipeline = AddTestPipeline(1, true, false);
-
- EXPECT_FALSE(host_.IsExistingPipelineAvailable());
- EXPECT_EQ(NULL, host_.CreateStreamOnExistingPipeline());
-
- ClearTestPipeline(pipeline);
-}
-
-TEST_F(HttpPipelinedHostTest, IgnoresFullPipeline) {
- MockPipeline* pipeline = AddTestPipeline(kMaxCapacity, true, true);
-
- EXPECT_FALSE(host_.IsExistingPipelineAvailable());
- EXPECT_EQ(NULL, host_.CreateStreamOnExistingPipeline());
-
- ClearTestPipeline(pipeline);
-}
-
-TEST_F(HttpPipelinedHostTest, PicksLeastLoadedPipeline) {
- MockPipeline* full_pipeline = AddTestPipeline(kMaxCapacity, true, true);
- MockPipeline* usable_pipeline = AddTestPipeline(kMaxCapacity - 1, true, true);
- MockPipeline* empty_pipeline = AddTestPipeline(0, true, true);
-
- EXPECT_TRUE(host_.IsExistingPipelineAvailable());
- EXPECT_CALL(*empty_pipeline, CreateNewStream())
- .Times(1)
- .WillOnce(ReturnNull());
- EXPECT_EQ(NULL, host_.CreateStreamOnExistingPipeline());
-
- ClearTestPipeline(full_pipeline);
- ClearTestPipeline(usable_pipeline);
- ClearTestPipeline(empty_pipeline);
-}
-
-TEST_F(HttpPipelinedHostTest, EmptyPipelineIsRemoved) {
- MockPipeline* empty_pipeline = AddTestPipeline(0, true, true);
-
- EXPECT_TRUE(host_.IsExistingPipelineAvailable());
- EXPECT_CALL(*empty_pipeline, CreateNewStream())
- .Times(1)
- .WillOnce(Return(kDummyStream));
- EXPECT_EQ(kDummyStream, host_.CreateStreamOnExistingPipeline());
-
- ClearTestPipeline(empty_pipeline);
-
- EXPECT_FALSE(host_.IsExistingPipelineAvailable());
- EXPECT_EQ(NULL, host_.CreateStreamOnExistingPipeline());
-}
-
-} // namespace net
diff --git a/net/http/http_pipelined_network_transaction_unittest.cc b/net/http/http_pipelined_network_transaction_unittest.cc
index c342b2c..70e8d657 100644
--- a/net/http/http_pipelined_network_transaction_unittest.cc
+++ b/net/http/http_pipelined_network_transaction_unittest.cc
@@ -97,7 +97,7 @@ class HttpPipelinedNetworkTransactionTest : public testing::Test {
EXPECT_EQ(OK, transaction.Read(buffer.get(), expected.size(), &callback_));
}
- void CompleteTwoRequests() {
+ void CompleteTwoRequests(int data_index, int stop_at_step) {
scoped_ptr<HttpNetworkTransaction> one_transaction(
new HttpNetworkTransaction(session_.get()));
TestOldCompletionCallback one_callback;
@@ -117,9 +117,10 @@ class HttpPipelinedNetworkTransactionTest : public testing::Test {
EXPECT_EQ(ERR_IO_PENDING,
one_transaction->Read(buffer.get(), 8, &one_read_callback));
- data_vector_[0]->RunFor(2);
+ data_vector_[data_index]->SetStop(stop_at_step);
+ data_vector_[data_index]->Run();
EXPECT_EQ(8, one_read_callback.WaitForResult());
- data_vector_[0]->SetStop(10);
+ data_vector_[data_index]->SetStop(10);
std::string actual(buffer->data(), 8);
EXPECT_THAT(actual, StrEq("one.html"));
EXPECT_EQ(OK, one_transaction->Read(buffer.get(), 8, &one_read_callback));
@@ -128,6 +129,44 @@ class HttpPipelinedNetworkTransactionTest : public testing::Test {
ExpectResponse("two.html", two_transaction);
}
+ void CompleteFourRequests() {
+ scoped_ptr<HttpNetworkTransaction> one_transaction(
+ new HttpNetworkTransaction(session_.get()));
+ TestOldCompletionCallback one_callback;
+ EXPECT_EQ(ERR_IO_PENDING,
+ one_transaction->Start(GetRequestInfo("one.html"), &one_callback,
+ BoundNetLog()));
+ EXPECT_EQ(OK, one_callback.WaitForResult());
+
+ HttpNetworkTransaction two_transaction(session_.get());
+ TestOldCompletionCallback two_callback;
+ EXPECT_EQ(ERR_IO_PENDING,
+ two_transaction.Start(GetRequestInfo("two.html"), &two_callback,
+ BoundNetLog()));
+
+ HttpNetworkTransaction three_transaction(session_.get());
+ TestOldCompletionCallback three_callback;
+ EXPECT_EQ(ERR_IO_PENDING,
+ three_transaction.Start(GetRequestInfo("three.html"),
+ &three_callback, BoundNetLog()));
+
+ HttpNetworkTransaction four_transaction(session_.get());
+ TestOldCompletionCallback four_callback;
+ EXPECT_EQ(ERR_IO_PENDING,
+ four_transaction.Start(GetRequestInfo("four.html"),
+ &four_callback, BoundNetLog()));
+
+ ExpectResponse("one.html", *one_transaction.get());
+ EXPECT_EQ(OK, two_callback.WaitForResult());
+ ExpectResponse("two.html", two_transaction);
+ EXPECT_EQ(OK, three_callback.WaitForResult());
+ ExpectResponse("three.html", three_transaction);
+
+ one_transaction.reset();
+ EXPECT_EQ(OK, four_callback.WaitForResult());
+ ExpectResponse("four.html", four_transaction);
+ }
+
DeterministicMockClientSocketFactory factory_;
ClientSocketPoolHistograms histograms_;
MockTransportClientSocketPool pool_;
@@ -188,7 +227,7 @@ TEST_F(HttpPipelinedNetworkTransactionTest, ReusePipeline) {
};
AddExpectedConnection(reads, arraysize(reads), writes, arraysize(writes));
- CompleteTwoRequests();
+ CompleteTwoRequests(0, 5);
}
TEST_F(HttpPipelinedNetworkTransactionTest, ReusesOnSpaceAvailable) {
@@ -226,41 +265,7 @@ TEST_F(HttpPipelinedNetworkTransactionTest, ReusesOnSpaceAvailable) {
};
AddExpectedConnection(reads, arraysize(reads), writes, arraysize(writes));
- scoped_ptr<HttpNetworkTransaction> one_transaction(
- new HttpNetworkTransaction(session_.get()));
- TestOldCompletionCallback one_callback;
- EXPECT_EQ(ERR_IO_PENDING,
- one_transaction->Start(GetRequestInfo("one.html"), &one_callback,
- BoundNetLog()));
- EXPECT_EQ(OK, one_callback.WaitForResult());
-
- HttpNetworkTransaction two_transaction(session_.get());
- TestOldCompletionCallback two_callback;
- EXPECT_EQ(ERR_IO_PENDING,
- two_transaction.Start(GetRequestInfo("two.html"), &two_callback,
- BoundNetLog()));
-
- HttpNetworkTransaction three_transaction(session_.get());
- TestOldCompletionCallback three_callback;
- EXPECT_EQ(ERR_IO_PENDING,
- three_transaction.Start(GetRequestInfo("three.html"),
- &three_callback, BoundNetLog()));
-
- HttpNetworkTransaction four_transaction(session_.get());
- TestOldCompletionCallback four_callback;
- EXPECT_EQ(ERR_IO_PENDING,
- four_transaction.Start(GetRequestInfo("four.html"), &four_callback,
- BoundNetLog()));
-
- ExpectResponse("one.html", *one_transaction.get());
- EXPECT_EQ(OK, two_callback.WaitForResult());
- ExpectResponse("two.html", two_transaction);
- EXPECT_EQ(OK, three_callback.WaitForResult());
- ExpectResponse("three.html", three_transaction);
-
- one_transaction.reset();
- EXPECT_EQ(OK, four_callback.WaitForResult());
- ExpectResponse("four.html", four_transaction);
+ CompleteFourRequests();
ClientSocketPoolManager::set_max_sockets_per_group(old_max_sockets);
}
@@ -272,14 +277,11 @@ TEST_F(HttpPipelinedNetworkTransactionTest, UnknownSizeEvictsToNewPipeline) {
MockWrite(false, 0, "GET /one.html HTTP/1.1\r\n"
"Host: localhost\r\n"
"Connection: keep-alive\r\n\r\n"),
- MockWrite(false, 2, "GET /two.html HTTP/1.1\r\n"
- "Host: localhost\r\n"
- "Connection: keep-alive\r\n\r\n"),
};
MockRead reads[] = {
MockRead(false, 1, "HTTP/1.1 200 OK\r\n\r\n"),
- MockRead(true, 3, "one.html"),
- MockRead(false, OK, 4),
+ MockRead(true, 2, "one.html"),
+ MockRead(false, OK, 3),
};
AddExpectedConnection(reads, arraysize(reads), writes, arraysize(writes));
@@ -295,7 +297,7 @@ TEST_F(HttpPipelinedNetworkTransactionTest, UnknownSizeEvictsToNewPipeline) {
};
AddExpectedConnection(reads2, arraysize(reads2), writes2, arraysize(writes2));
- CompleteTwoRequests();
+ CompleteTwoRequests(0, 3);
}
TEST_F(HttpPipelinedNetworkTransactionTest, ConnectionCloseEvictToNewPipeline) {
@@ -329,7 +331,7 @@ TEST_F(HttpPipelinedNetworkTransactionTest, ConnectionCloseEvictToNewPipeline) {
};
AddExpectedConnection(reads2, arraysize(reads2), writes2, arraysize(writes2));
- CompleteTwoRequests();
+ CompleteTwoRequests(0, 5);
}
TEST_F(HttpPipelinedNetworkTransactionTest, ErrorEvictsToNewPipeline) {
@@ -508,6 +510,136 @@ TEST_F(HttpPipelinedNetworkTransactionTest, BasicHttpAuthentication) {
ExpectResponse("one.html", transaction);
}
+TEST_F(HttpPipelinedNetworkTransactionTest, OldVersionDisablesPipelining) {
+ Initialize();
+
+ MockWrite writes[] = {
+ MockWrite(false, 0, "GET /pipelined.html HTTP/1.1\r\n"
+ "Host: localhost\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+ };
+ MockRead reads[] = {
+ MockRead(false, 1, "HTTP/1.0 200 OK\r\n"),
+ MockRead(false, 2, "Content-Length: 14\r\n\r\n"),
+ MockRead(false, 3, "pipelined.html"),
+ };
+ AddExpectedConnection(reads, arraysize(reads), writes, arraysize(writes));
+
+ MockWrite writes2[] = {
+ MockWrite(false, 0, "GET /one.html HTTP/1.1\r\n"
+ "Host: localhost\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+ };
+ MockRead reads2[] = {
+ MockRead(false, 1, "HTTP/1.1 200 OK\r\n"),
+ MockRead(false, 2, "Content-Length: 8\r\n\r\n"),
+ MockRead(true, 3, "one.html"),
+ MockRead(false, OK, 4),
+ };
+ AddExpectedConnection(reads2, arraysize(reads2), writes2, arraysize(writes2));
+
+ MockWrite writes3[] = {
+ MockWrite(false, 0, "GET /two.html HTTP/1.1\r\n"
+ "Host: localhost\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+ };
+ MockRead reads3[] = {
+ MockRead(false, 1, "HTTP/1.1 200 OK\r\n"),
+ MockRead(false, 2, "Content-Length: 8\r\n\r\n"),
+ MockRead(false, 3, "two.html"),
+ MockRead(false, OK, 4),
+ };
+ AddExpectedConnection(reads3, arraysize(reads3), writes3, arraysize(writes3));
+
+ HttpNetworkTransaction one_transaction(session_.get());
+ TestOldCompletionCallback one_callback;
+ EXPECT_EQ(ERR_IO_PENDING,
+ one_transaction.Start(GetRequestInfo("pipelined.html"),
+ &one_callback, BoundNetLog()));
+ EXPECT_EQ(OK, one_callback.WaitForResult());
+ ExpectResponse("pipelined.html", one_transaction);
+
+ CompleteTwoRequests(1, 4);
+}
+
+TEST_F(HttpPipelinedNetworkTransactionTest, PipelinesImmediatelyIfKnownGood) {
+ // The first request gets us an HTTP/1.1. The next 3 test pipelining. When the
+ // 3rd request completes, we know pipelining is safe. After the first 4
+ // complete, the 5th and 6th should then be immediately sent pipelined on a
+ // new HttpPipelinedConnection.
+ int old_max_sockets = ClientSocketPoolManager::max_sockets_per_group();
+ ClientSocketPoolManager::set_max_sockets_per_group(1);
+ Initialize();
+
+ MockWrite writes[] = {
+ MockWrite(false, 0, "GET /one.html HTTP/1.1\r\n"
+ "Host: localhost\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+ MockWrite(false, 4, "GET /two.html HTTP/1.1\r\n"
+ "Host: localhost\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+ MockWrite(false, 7, "GET /three.html HTTP/1.1\r\n"
+ "Host: localhost\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+ MockWrite(false, 12, "GET /four.html HTTP/1.1\r\n"
+ "Host: localhost\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+ MockWrite(false, 16, "GET /second-pipeline-one.html HTTP/1.1\r\n"
+ "Host: localhost\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+ MockWrite(false, 17, "GET /second-pipeline-two.html HTTP/1.1\r\n"
+ "Host: localhost\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+ };
+ MockRead reads[] = {
+ MockRead(false, 1, "HTTP/1.1 200 OK\r\n"),
+ MockRead(false, 2, "Content-Length: 8\r\n\r\n"),
+ MockRead(false, 3, "one.html"),
+ MockRead(false, 5, "HTTP/1.1 200 OK\r\n"),
+ MockRead(false, 6, "Content-Length: 8\r\n\r\n"),
+ MockRead(false, 8, "two.html"),
+ MockRead(false, 9, "HTTP/1.1 200 OK\r\n"),
+ MockRead(false, 10, "Content-Length: 10\r\n\r\n"),
+ MockRead(false, 11, "three.html"),
+ MockRead(false, 13, "HTTP/1.1 200 OK\r\n"),
+ MockRead(false, 14, "Content-Length: 9\r\n\r\n"),
+ MockRead(false, 15, "four.html"),
+ MockRead(true, 18, "HTTP/1.1 200 OK\r\n"),
+ MockRead(true, 19, "Content-Length: 24\r\n\r\n"),
+ MockRead(false, 20, "second-pipeline-one.html"),
+ MockRead(false, 21, "HTTP/1.1 200 OK\r\n"),
+ MockRead(false, 22, "Content-Length: 24\r\n\r\n"),
+ MockRead(false, 23, "second-pipeline-two.html"),
+ };
+ AddExpectedConnection(reads, arraysize(reads), writes, arraysize(writes));
+
+ CompleteFourRequests();
+
+ HttpNetworkTransaction second_one_transaction(session_.get());
+ TestOldCompletionCallback second_one_callback;
+ EXPECT_EQ(ERR_IO_PENDING,
+ second_one_transaction.Start(
+ GetRequestInfo("second-pipeline-one.html"),
+ &second_one_callback, BoundNetLog()));
+ MessageLoop::current()->RunAllPending();
+
+ HttpNetworkTransaction second_two_transaction(session_.get());
+ TestOldCompletionCallback second_two_callback;
+ EXPECT_EQ(ERR_IO_PENDING,
+ second_two_transaction.Start(
+ GetRequestInfo("second-pipeline-two.html"),
+ &second_two_callback, BoundNetLog()));
+
+ 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);
+ EXPECT_EQ(OK, second_two_callback.WaitForResult());
+ ExpectResponse("second-pipeline-two.html", second_two_transaction);
+
+ ClientSocketPoolManager::set_max_sockets_per_group(old_max_sockets);
+}
+
} // anonymous namespace
} // namespace net
diff --git a/net/http/http_stream_factory_impl.cc b/net/http/http_stream_factory_impl.cc
index 9593dc4..dd7776f 100644
--- a/net/http/http_stream_factory_impl.cc
+++ b/net/http/http_stream_factory_impl.cc
@@ -37,7 +37,7 @@ GURL UpgradeUrlToHttps(const GURL& original_url, int port) {
HttpStreamFactoryImpl::HttpStreamFactoryImpl(HttpNetworkSession* session)
: session_(session),
- http_pipelined_host_pool_(this) {}
+ http_pipelined_host_pool_(this, NULL) {}
HttpStreamFactoryImpl::~HttpStreamFactoryImpl() {
DCHECK(request_map_.empty());
diff --git a/net/http/http_stream_factory_impl.h b/net/http/http_stream_factory_impl.h
index ce1a04eb..8f13f3f 100644
--- a/net/http/http_stream_factory_impl.h
+++ b/net/http/http_stream_factory_impl.h
@@ -21,12 +21,14 @@ class HttpNetworkSession;
class HttpPipelinedHost;
class SpdySession;
-class NET_EXPORT_PRIVATE HttpStreamFactoryImpl : public HttpStreamFactory {
+class NET_EXPORT_PRIVATE HttpStreamFactoryImpl :
+ public HttpStreamFactory,
+ public HttpPipelinedHostPool::Delegate {
public:
explicit HttpStreamFactoryImpl(HttpNetworkSession* session);
virtual ~HttpStreamFactoryImpl();
- // HttpStreamFactory Interface
+ // HttpStreamFactory interface
virtual HttpStreamRequest* RequestStream(
const HttpRequestInfo& info,
const SSLConfig& server_ssl_config,
@@ -42,10 +44,9 @@ class NET_EXPORT_PRIVATE HttpStreamFactoryImpl : public HttpStreamFactory {
virtual void AddTLSIntolerantServer(const HostPortPair& server) OVERRIDE;
virtual bool IsTLSIntolerantServer(const HostPortPair& server) const OVERRIDE;
- // Called when a HttpPipelinedHost has new capacity. Attempts to allocate any
- // pending pipeline-capable requests to pipelines.
+ // HttpPipelinedHostPool::Delegate interface
virtual void OnHttpPipelinedHostHasAdditionalCapacity(
- const HostPortPair& origin);
+ const HostPortPair& origin) OVERRIDE;
private:
class Request;
diff --git a/net/http/http_stream_factory_impl_job.cc b/net/http/http_stream_factory_impl_job.cc
index a6ca05e..e1bffb374 100644
--- a/net/http/http_stream_factory_impl_job.cc
+++ b/net/http/http_stream_factory_impl_job.cc
@@ -1137,7 +1137,7 @@ bool HttpStreamFactoryImpl::Job::IsOrphaned() const {
return !IsPreconnecting() && !request_;
}
-bool HttpStreamFactoryImpl::Job::IsRequestEligibleForPipelining() const {
+bool HttpStreamFactoryImpl::Job::IsRequestEligibleForPipelining() {
if (!HttpStreamFactory::http_pipelining_enabled()) {
return false;
}
@@ -1147,7 +1147,11 @@ bool HttpStreamFactoryImpl::Job::IsRequestEligibleForPipelining() const {
if (using_ssl_) {
return false;
}
- return request_info_.method == "GET" || request_info_.method == "HEAD";
+ if (request_info_.method != "GET" && request_info_.method != "HEAD") {
+ return false;
+ }
+ return stream_factory_->http_pipelined_host_pool_.IsHostEligibleForPipelining(
+ origin_);
}
} // namespace net
diff --git a/net/http/http_stream_factory_impl_job.h b/net/http/http_stream_factory_impl_job.h
index b14897c..7c95135 100644
--- a/net/http/http_stream_factory_impl_job.h
+++ b/net/http/http_stream_factory_impl_job.h
@@ -192,7 +192,7 @@ class HttpStreamFactoryImpl::Job {
// Should we force SPDY to run without SSL for this stream request.
bool ShouldForceSpdyWithoutSSL() const;
- bool IsRequestEligibleForPipelining() const;
+ bool IsRequestEligibleForPipelining();
// Record histograms of latency until Connect() completes.
static void LogHttpConnectedMetrics(const ClientSocketHandle& handle);
diff --git a/net/net.gyp b/net/net.gyp
index 2694b27..777f0f6 100644
--- a/net/net.gyp
+++ b/net/net.gyp
@@ -427,8 +427,9 @@
'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_impl.cc',
+ 'http/http_pipelined_host_impl.h',
'http/http_pipelined_host_pool.cc',
'http/http_pipelined_host_pool.h',
'http/http_pipelined_stream.cc',
@@ -1089,7 +1090,8 @@
'http/http_network_layer_unittest.cc',
'http/http_network_transaction_unittest.cc',
'http/http_pipelined_connection_impl_unittest.cc',
- 'http/http_pipelined_host_unittest.cc',
+ 'http/http_pipelined_host_impl_unittest.cc',
+ 'http/http_pipelined_host_pool_unittest.cc',
'http/http_pipelined_network_transaction_unittest.cc',
'http/http_proxy_client_socket_pool_unittest.cc',
'http/http_request_headers_unittest.cc',