// 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_impl.h" #include "base/stl_util.h" #include "base/values.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; HttpPipelinedHostImpl::HttpPipelinedHostImpl( HttpPipelinedHost::Delegate* delegate, const HttpPipelinedHost::Key& key, HttpPipelinedConnection::Factory* factory, HttpPipelinedHostCapability capability) : delegate_(delegate), key_(key), factory_(factory), capability_(capability) { if (!factory) { factory_.reset(new HttpPipelinedConnectionImpl::Factory()); } } 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, NextProto protocol_negotiated) { if (capability_ == PIPELINE_INCAPABLE) { return NULL; } HttpPipelinedConnection* pipeline = factory_->CreateNewPipeline( 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(); } HttpPipelinedStream* HttpPipelinedHostImpl::CreateStreamOnExistingPipeline() { HttpPipelinedConnection* available_pipeline = NULL; for (PipelineInfoMap::iterator it = pipelines_.begin(); it != pipelines_.end(); ++it) { if (CanPipelineAcceptRequests(it->first) && (!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 (CanPipelineAcceptRequests(it->first)) { return true; } } return false; } const HttpPipelinedHost::Key& HttpPipelinedHostImpl::GetKey() const { return key_; } 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 (CanPipelineAcceptRequests(pipeline)) { 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_ == PIPELINE_UNKNOWN) { capability_ = PIPELINE_PROBABLY_CAPABLE; NotifyAllPipelinesHaveCapacity(); } else if (capability_ == PIPELINE_PROBABLY_CAPABLE && pipelines_[pipeline].num_successes >= kNumKnownSuccessesThreshold) { capability_ = PIPELINE_CAPABLE; delegate_->OnHostDeterminedCapability(this, PIPELINE_CAPABLE); } break; case HttpPipelinedConnection::PIPELINE_SOCKET_ERROR: // Socket errors on the initial request - when no other requests are // pipelined - can't be due to pipelining. if (pipelines_[pipeline].num_successes > 0 || pipeline->depth() > 1) { // TODO(simonjam): This may be needlessly harsh. For example, pogo.com // only returns a socket error once after the root document, but is // otherwise able to pipeline just fine. Consider being more persistent // and only give up on pipelining if we get a couple of failures. capability_ = PIPELINE_INCAPABLE; delegate_->OnHostDeterminedCapability(this, PIPELINE_INCAPABLE); } break; case HttpPipelinedConnection::OLD_HTTP_VERSION: case HttpPipelinedConnection::AUTHENTICATION_REQUIRED: capability_ = PIPELINE_INCAPABLE; delegate_->OnHostDeterminedCapability(this, PIPELINE_INCAPABLE); break; case HttpPipelinedConnection::MUST_CLOSE_CONNECTION: break; } } int HttpPipelinedHostImpl::GetPipelineCapacity() const { int capacity = 0; switch (capability_) { case PIPELINE_CAPABLE: case PIPELINE_PROBABLY_CAPABLE: capacity = max_pipeline_depth(); break; case PIPELINE_INCAPABLE: CHECK(false); case PIPELINE_UNKNOWN: capacity = 1; break; default: CHECK(false) << "Unkown pipeline capability: " << capability_; } return capacity; } bool HttpPipelinedHostImpl::CanPipelineAcceptRequests( HttpPipelinedConnection* pipeline) const { return capability_ != PIPELINE_INCAPABLE && pipeline->usable() && pipeline->active() && pipeline->depth() < GetPipelineCapacity(); } void HttpPipelinedHostImpl::NotifyAllPipelinesHaveCapacity() { // Calling OnPipelineHasCapacity() can have side effects that include // deleting and removing entries from |pipelines_|. PipelineInfoMap pipelines_to_notify = pipelines_; for (PipelineInfoMap::iterator it = pipelines_to_notify.begin(); it != pipelines_to_notify.end(); ++it) { if (pipelines_.find(it->first) != pipelines_.end()) { OnPipelineHasCapacity(it->first); } } } base::Value* HttpPipelinedHostImpl::PipelineInfoToValue() const { base::ListValue* list_value = new base::ListValue(); for (PipelineInfoMap::const_iterator it = pipelines_.begin(); it != pipelines_.end(); ++it) { base::DictionaryValue* pipeline_dict = new base::DictionaryValue; 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()); pipeline_dict->SetBoolean("active", it->first->active()); pipeline_dict->SetInteger("source_id", it->first->net_log().source().id); list_value->Append(pipeline_dict); } return list_value; } HttpPipelinedHostImpl::PipelineInfo::PipelineInfo() : num_successes(0) { } } // namespace net