blob: c7fdd5d0db1cdf920877be4c4e4a4dde429306e3 [file] [log] [blame]
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "net/http/http_stream_pool_attempt_manager.h"
#include <algorithm>
#include <map>
#include <memory>
#include <utility>
#include "base/containers/contains.h"
#include "base/containers/enum_set.h"
#include "base/debug/alias.h"
#include "base/functional/bind.h"
#include "base/memory/weak_ptr.h"
#include "base/metrics/histogram_functions.h"
#include "base/notreached.h"
#include "base/task/sequenced_task_runner.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "base/trace_event/trace_id_helper.h"
#include "net/base/completion_once_callback.h"
#include "net/base/host_port_pair.h"
#include "net/base/load_states.h"
#include "net/base/load_timing_info.h"
#include "net/base/net_error_details.h"
#include "net/base/net_errors.h"
#include "net/base/request_priority.h"
#include "net/base/tracing.h"
#include "net/dns/host_resolver.h"
#include "net/http/http_network_session.h"
#include "net/http/http_server_properties.h"
#include "net/http/http_stream_key.h"
#include "net/http/http_stream_pool_attempt_manager_quic_attempt.h"
#include "net/http/http_stream_pool_attempt_manager_tcp_based_attempt.h"
#include "net/http/http_stream_pool_group.h"
#include "net/http/http_stream_pool_handle.h"
#include "net/http/http_stream_pool_job.h"
#include "net/log/net_log_util.h"
#include "net/log/net_log_with_source.h"
#include "net/quic/quic_http_stream.h"
#include "net/quic/quic_session_alias_key.h"
#include "net/quic/quic_session_pool.h"
#include "net/socket/connection_attempts.h"
#include "net/socket/next_proto.h"
#include "net/socket/stream_attempt.h"
#include "net/socket/stream_socket_close_reason.h"
#include "net/socket/stream_socket_handle.h"
#include "net/socket/tcp_stream_attempt.h"
#include "net/socket/tls_stream_attempt.h"
#include "net/spdy/multiplexed_session_creation_initiator.h"
#include "net/spdy/spdy_http_stream.h"
#include "net/spdy/spdy_session.h"
#include "net/spdy/spdy_session_pool.h"
#include "net/ssl/ssl_cert_request_info.h"
#include "net/third_party/quiche/src/quiche/quic/core/quic_versions.h"
namespace net {
namespace {
StreamSocketHandle::SocketReuseType GetReuseTypeFromIdleStreamSocket(
const StreamSocket& stream_socket) {
return stream_socket.WasEverUsed()
? StreamSocketHandle::SocketReuseType::kReusedIdle
: StreamSocketHandle::SocketReuseType::kUnusedIdle;
}
base::Value::Dict GetServiceEndpointRequestAsValue(
HostResolver::ServiceEndpointRequest* request) {
base::Value::Dict dict;
base::Value::List endpoints;
for (const auto& endpoint : request->GetEndpointResults()) {
endpoints.Append(endpoint.ToValue());
}
dict.Set("endpoints", std::move(endpoints));
dict.Set("endpoints_crypto_ready", request->EndpointsCryptoReady());
return dict;
}
} // namespace
// static
std::string_view HttpStreamPool::AttemptManager::CanAttemptResultToString(
CanAttemptResult result) {
switch (result) {
case CanAttemptResult::kAttempt:
return "Attempt";
case CanAttemptResult::kReachedPoolLimit:
return "ReachedPoolLimit";
case CanAttemptResult::kNoPendingJob:
return "NoPendingJob";
case CanAttemptResult::kBlockedTcpBasedAttempt:
return "BlockedTcpBasedAttempt";
case CanAttemptResult::kThrottledForSpdy:
return "ThrottledForSpdy";
case CanAttemptResult::kReachedGroupLimit:
return "ReachedGroupLimit";
}
}
// static
std::string_view HttpStreamPool::AttemptManager::TcpBasedAttemptStateToString(
TcpBasedAttemptState state) {
switch (state) {
case TcpBasedAttemptState::kNotStarted:
return "NotStarted";
case TcpBasedAttemptState::kAttempting:
return "Attempting";
case TcpBasedAttemptState::kSucceededAtLeastOnce:
return "SucceededAtLeastOnce";
case TcpBasedAttemptState::kAllEndpointsFailed:
return "AllEndpointsFailed";
}
}
// static
std::string_view HttpStreamPool::AttemptManager::IPEndPointStateToString(
IPEndPointState state) {
switch (state) {
case IPEndPointState::kFailed:
return "Failed";
case IPEndPointState::kSlowAttempting:
return "SlowAttempting";
case IPEndPointState::kSlowSucceeded:
return "SlowSucceeded";
}
}
// static
std::string_view HttpStreamPool::AttemptManager::InitialAttemptStateToString(
InitialAttemptState state) {
switch (state) {
case InitialAttemptState::kOther:
return "Other";
case InitialAttemptState::kCanUseQuicWithKnownVersion:
return "CanUseQuicWithKnownVersion";
case InitialAttemptState::kCanUseQuicWithKnownVersionAndSupportsSpdy:
return "CanUseQuicWithKnownVersionAndSupportsSpdy";
case InitialAttemptState::kCanUseQuicWithUnknownVersion:
return "CanUseQuicWithUnknownVersion";
case InitialAttemptState::kCanUseQuicWithUnknownVersionAndSupportsSpdy:
return "CanUseQuicWithUnknownVersionAndSupportsSpdy";
case InitialAttemptState::kCannotUseQuicWithKnownVersion:
return "CannotUseQuicWithKnownVersion";
case InitialAttemptState::kCannotUseQuicWithKnownVersionAndSupportsSpdy:
return "CannotUseQuicWithKnownVersionAndSupportsSpdy";
case InitialAttemptState::kCannotUseQuicWithUnknownVersion:
return "CannotUseQuicWithUnknownVersion";
case InitialAttemptState::kCannotUseQuicWithUnknownVersionAndSupportsSpdy:
return "CannotUseQuicWithUnknownVersionAndSupportsSpdy";
}
}
HttpStreamPool::AttemptManager::AttemptManager(Group* group, NetLog* net_log)
: group_(group),
net_log_(NetLogWithSource::Make(
net_log,
NetLogSourceType::HTTP_STREAM_POOL_ATTEMPT_MANAGER)),
track_(base::trace_event::GetNextGlobalTraceId()),
created_time_(base::TimeTicks::Now()),
jobs_(NUM_PRIORITIES),
tcp_based_attempt_delay_(GetTcpBasedAttemptDelay()),
should_block_tcp_based_attempt_(!tcp_based_attempt_delay_.is_zero()) {
CHECK(group_);
TRACE_EVENT_BEGIN("net.stream", "AttemptManager::AttemptManager", track_,
"destination", stream_key().destination().Serialize());
net_log_.BeginEvent(
NetLogEventType::HTTP_STREAM_POOL_ATTEMPT_MANAGER_ALIVE, [&] {
base::Value::Dict dict;
dict.Set("stream_key", stream_key().ToValue());
dict.Set("tcp_based_attempt_delay",
static_cast<int>(tcp_based_attempt_delay_.InMilliseconds()));
dict.Set("should_block_tcp_based_attempt",
should_block_tcp_based_attempt_);
group_->net_log().source().AddToEventParameters(dict);
return dict;
});
group_->net_log().AddEventReferencingSource(
NetLogEventType::HTTP_STREAM_POOL_GROUP_ATTEMPT_MANAGER_CREATED,
net_log_.source());
base::UmaHistogramTimes("Net.HttpStreamPool.TcpBasedAttemptDelay",
tcp_based_attempt_delay_);
if (UsingTls()) {
SSLConfig ssl_config;
ssl_config.privacy_mode = stream_key().privacy_mode();
ssl_config.disable_cert_verification_network_fetches =
stream_key().disable_cert_network_fetches();
ssl_config.early_data_enabled =
http_network_session()->params().enable_early_data;
ssl_config.alpn_protos = http_network_session()->GetAlpnProtos();
ssl_config.application_settings =
http_network_session()->GetApplicationSettings();
http_network_session()->http_server_properties()->MaybeForceHTTP11(
stream_key().destination(), stream_key().network_anonymization_key(),
&ssl_config);
ssl_config.ignore_certificate_errors =
http_network_session()->params().ignore_certificate_errors;
ssl_config.network_anonymization_key =
stream_key().network_anonymization_key();
base_ssl_config_.emplace(std::move(ssl_config));
}
}
HttpStreamPool::AttemptManager::~AttemptManager() {
base::UmaHistogramTimes("Net.HttpStreamPool.AttemptManagerAliveTime",
base::TimeTicks::Now() - created_time_);
net_log().EndEvent(NetLogEventType::HTTP_STREAM_POOL_ATTEMPT_MANAGER_ALIVE);
group_->net_log().AddEventReferencingSource(
NetLogEventType::HTTP_STREAM_POOL_GROUP_ATTEMPT_MANAGER_DESTROYED,
net_log_.source());
TRACE_EVENT_END("net.stream", track_);
}
void HttpStreamPool::AttemptManager::StartJob(Job* job) {
CHECK(!is_failing_);
TRACE_EVENT_INSTANT("net.stream", "AttemptManager::StartJob", track_,
NetLogWithSourceToFlow(job->request_net_log()));
net_log_.AddEvent(
NetLogEventType::HTTP_STREAM_POOL_ATTEMPT_MANAGER_START_JOB, [&] {
base::Value::Dict dict;
dict.Set("priority", job->priority());
base::Value::List allowed_bad_certs_list;
for (const auto& cert_and_status : job->allowed_bad_certs()) {
allowed_bad_certs_list.Append(
cert_and_status.cert->subject().GetDisplayName());
}
dict.Set("allowed_bad_certs", std::move(allowed_bad_certs_list));
dict.Set("enable_ip_based_pooling", job->enable_ip_based_pooling());
dict.Set("enable_alternative_services",
job->enable_alternative_services());
dict.Set("quic_version",
quic::ParsedQuicVersionToString(job->quic_version()));
dict.Set("create_to_resume_ms",
static_cast<int>(job->CreateToResumeTime().InMilliseconds()));
job->net_log().source().AddToEventParameters(dict);
return dict;
});
job->request_net_log().AddEventReferencingSource(
NetLogEventType::HTTP_STREAM_POOL_ATTEMPT_MANAGER_JOB_BOUND,
net_log_.source());
job->net_log().AddEventReferencingSource(
NetLogEventType::HTTP_STREAM_POOL_ATTEMPT_MANAGER_JOB_BOUND,
net_log_.source());
if (job->respect_limits() == RespectLimits::kIgnore) {
limit_ignoring_jobs_.emplace(job);
}
if (!job->enable_ip_based_pooling()) {
ip_based_pooling_disabling_jobs_.emplace(job);
}
if (!job->enable_alternative_services()) {
alternative_service_disabling_jobs_.emplace(job);
}
// HttpStreamPool should check the existing QUIC/SPDY sessions before calling
// this method.
DCHECK(!CanUseExistingQuicSession());
DCHECK(!HasAvailableSpdySession());
jobs_.Insert(job, job->priority());
MaybeChangeServiceEndpointRequestPriority();
// Check idle streams. If found, notify the job that an HttpStream is ready.
std::unique_ptr<StreamSocket> stream_socket = group_->GetIdleStreamSocket();
if (stream_socket) {
CHECK(!group_->force_quic());
const StreamSocketHandle::SocketReuseType reuse_type =
GetReuseTypeFromIdleStreamSocket(*stream_socket);
// It's important to create an HttpBasicStream synchronously because we
// already took the ownership of the idle stream socket. If we don't create
// an HttpBasicStream here, another call of this method might exceed the
// per-group limit.
CreateTextBasedStreamAndNotify(std::move(stream_socket), reuse_type,
LoadTimingInfo::ConnectTiming());
return;
}
if (base_ssl_config_.has_value()) {
base_ssl_config_->allowed_bad_certs = job->allowed_bad_certs();
}
quic_version_ = job->quic_version();
StartInternal(job);
return;
}
void HttpStreamPool::AttemptManager::Preconnect(Job* job) {
CHECK(!is_failing_);
TRACE_EVENT_INSTANT("net.stream", "AttemptManager::Preconnect", track_,
NetLogWithSourceToFlow(job->request_net_log()));
// If `job` is resumed, there could be enough streams at this point.
if (group_->ActiveStreamSocketCount() >= job->num_streams()) {
NotifyJobOfPreconnectCompleteLater(job, OK);
return;
}
net_log_.AddEvent(
NetLogEventType::HTTP_STREAM_POOL_ATTEMPT_MANAGER_PRECONNECT, [&] {
base::Value::Dict dict;
dict.Set("num_streams", static_cast<int>(job->num_streams()));
dict.Set("quic_version",
quic::ParsedQuicVersionToString(job->quic_version()));
job->delegate_net_log().source().AddToEventParameters(dict);
return dict;
});
job->delegate_net_log().AddEventReferencingSource(
NetLogEventType::HTTP_STREAM_POOL_JOB_CONTROLLER_PRECONNECT_BOUND,
net_log_.source());
// HttpStreamPool should check the existing QUIC/SPDY sessions before calling
// this method.
DCHECK(!CanUseExistingQuicSession());
DCHECK(!HasAvailableSpdySession());
preconnect_jobs_.emplace(job);
quic_version_ = job->quic_version();
StartInternal(job);
}
void HttpStreamPool::AttemptManager::OnServiceEndpointsUpdated() {
TRACE_EVENT_INSTANT("net.stream", "AttemptManager::OnServiceEndpointsUpdated",
track_);
net_log().AddEvent(
NetLogEventType::HTTP_STREAM_POOL_ATTEMPT_MANAGER_DNS_RESOLUTION_UPDATED,
[&] {
return GetServiceEndpointRequestAsValue(
service_endpoint_request_.get());
});
ProcessServiceEndpointChanges();
}
void HttpStreamPool::AttemptManager::OnServiceEndpointRequestFinished(int rv) {
TRACE_EVENT_INSTANT("net.stream",
"AttemptManager::OnServiceEndpointRequestFinished",
track_, "result", rv);
CHECK(!service_endpoint_request_finished_);
CHECK(service_endpoint_request_);
service_endpoint_request_finished_ = true;
dns_resolution_end_time_ = base::TimeTicks::Now();
resolve_error_info_ = service_endpoint_request_->GetResolveErrorInfo();
net_log().AddEvent(
NetLogEventType::HTTP_STREAM_POOL_ATTEMPT_MANAGER_DNS_RESOLUTION_FINISHED,
[&] {
base::Value::Dict dict =
GetServiceEndpointRequestAsValue(service_endpoint_request_.get());
dict.Set("result", ErrorToString(rv));
dict.Set("resolve_error", resolve_error_info_.error);
return dict;
});
if (rv != OK) {
// If service endpoint resolution failed, record an empty endpoint and the
// result.
connection_attempts_.emplace_back(IPEndPoint(), rv);
HandleFinalError(rv);
return;
}
CHECK(!service_endpoint_request_->GetEndpointResults().empty());
ProcessServiceEndpointChanges();
}
bool HttpStreamPool::AttemptManager::IsSvcbOptional() {
CHECK(service_endpoint_request_);
CHECK(pool()->stream_attempt_params()->ssl_client_context);
// Optional when the destination is not a SVCB-capable or ECH is disabled.
if (!UsingTls() || !IsEchEnabled()) {
return true;
}
base::span<const ServiceEndpoint> endpoints =
service_endpoint_request_->GetEndpointResults();
return !HostResolver::AllProtocolEndpointsHaveEch(endpoints);
}
HttpStreamPool::AttemptManager::InitialAttemptState
HttpStreamPool::AttemptManager::CalculateInitialAttemptState() {
using enum InitialAttemptState;
bool supports_spdy = SupportsSpdy();
if (CanUseQuic()) {
if (quic_version_.IsKnown()) {
if (supports_spdy) {
return kCanUseQuicWithKnownVersionAndSupportsSpdy;
} else {
return kCanUseQuicWithKnownVersion;
}
} else {
if (supports_spdy) {
return kCanUseQuicWithUnknownVersionAndSupportsSpdy;
} else {
return kCanUseQuicWithUnknownVersion;
}
}
} else {
if (quic_version_.IsKnown()) {
if (supports_spdy) {
return kCannotUseQuicWithKnownVersionAndSupportsSpdy;
} else {
return kCannotUseQuicWithKnownVersion;
}
} else {
if (supports_spdy) {
return kCannotUseQuicWithUnknownVersionAndSupportsSpdy;
} else {
return kCannotUseQuicWithUnknownVersion;
}
}
}
}
void HttpStreamPool::AttemptManager::SetInitialAttemptState() {
CHECK(!initial_attempt_state_.has_value());
initial_attempt_state_ = CalculateInitialAttemptState();
net_log_.AddEvent(
NetLogEventType::HTTP_STREAM_POOL_ATTEMPT_MANAGER_INITIAL_ATTEMPT_STATE,
[&] {
return base::Value::Dict().Set(
"state", InitialAttemptStateToString(*initial_attempt_state_));
});
base::UmaHistogramEnumeration("Net.HttpStreamPool.InitialAttemptState2",
*initial_attempt_state_);
}
base::expected<SSLConfig, TlsStreamAttempt::GetSSLConfigError>
HttpStreamPool::AttemptManager::GetSSLConfig(const IPEndPoint& ip_endpoint) {
CHECK(service_endpoint_request_);
CHECK(service_endpoint_request_->EndpointsCryptoReady());
if (!IsEchEnabled()) {
return *base_ssl_config_;
}
const bool svcb_optional = IsSvcbOptional();
for (auto& endpoint : service_endpoint_request_->GetEndpointResults()) {
if (!IsEndpointUsableForTcpBasedAttempt(endpoint, svcb_optional)) {
continue;
}
const std::vector<IPEndPoint>& ip_endpoints = ip_endpoint.address().IsIPv4()
? endpoint.ipv4_endpoints
: endpoint.ipv6_endpoints;
if (base::Contains(ip_endpoints, ip_endpoint)) {
SSLConfig ssl_config = *base_ssl_config_;
ssl_config.ech_config_list = endpoint.metadata.ech_config_list;
return ssl_config;
}
}
return base::unexpected(TlsStreamAttempt::GetSSLConfigError::kAbort);
}
void HttpStreamPool::AttemptManager::ProcessPendingJob() {
if (is_failing_) {
return;
}
// Try to assign an idle stream to a job.
if (jobs_.size() > 0) {
std::unique_ptr<StreamSocket> stream_socket = group_->GetIdleStreamSocket();
if (stream_socket) {
const StreamSocketHandle::SocketReuseType reuse_type =
GetReuseTypeFromIdleStreamSocket(*stream_socket);
CreateTextBasedStreamAndNotify(std::move(stream_socket), reuse_type,
LoadTimingInfo::ConnectTiming());
return;
}
}
const size_t pending_job_count = PendingJobCount();
const size_t pending_preconnect_count = PendingPreconnectCount();
if (pending_job_count == 0 && pending_preconnect_count == 0) {
return;
}
DCHECK(!HasAvailableSpdySession());
MaybeAttemptTcpBased(/*exclude_ip_endpoint=*/std::nullopt,
/*max_attempts=*/1);
}
void HttpStreamPool::AttemptManager::CancelTcpBasedAttempts(
StreamSocketCloseReason reason) {
if (tcp_based_attempts_.empty()) {
return;
}
const size_t num_cancel_attempts = tcp_based_attempts_.size();
for (auto& attempt : tcp_based_attempts_) {
attempt->SetCancelReason(reason);
}
pool()->DecrementTotalConnectingStreamCount(num_cancel_attempts);
tcp_based_attempts_.clear();
slow_tcp_based_attempt_count_ = 0;
base::UmaHistogramCounts100(
base::StrCat({"Net.HttpStreamPool.TcpBasedAttemptCancelCount.",
StreamSocketCloseReasonToString(reason)}),
num_cancel_attempts);
std::erase_if(ip_endpoint_states_, [](const auto& it) {
return it.second == IPEndPointState::kSlowAttempting;
});
// If possible, try to complete asynchronously to avoid accessing deleted
// `this` and `group_`. `this` and/or `group_` can be accessed after leaving
// this method. Also, HttpStreamPool::OnSSLConfigChanged() calls this method
// when walking through all groups. If we destroy `this` here, we will break
// the loop.
MaybeCompleteLater();
}
void HttpStreamPool::AttemptManager::OnJobComplete(Job* job) {
preconnect_jobs_.erase(job);
ip_based_pooling_disabling_jobs_.erase(job);
alternative_service_disabling_jobs_.erase(job);
auto notified_it = notified_jobs_.find(job);
if (notified_it != notified_jobs_.end()) {
notified_jobs_.erase(notified_it);
} else {
for (JobQueue::Pointer pointer = jobs_.FirstMax(); !pointer.is_null();
pointer = jobs_.GetNextTowardsLastMin(pointer)) {
if (pointer.value() == job) {
RemoveJobFromQueue(pointer);
break;
}
}
}
MaybeCompleteLater();
}
void HttpStreamPool::AttemptManager::CancelJobs(int error) {
is_canceling_jobs_ = true;
HandleFinalError(error);
}
void HttpStreamPool::AttemptManager::CancelQuicAttempt(int error) {
if (quic_attempt_) {
quic_attempt_result_ = error;
quic_attempt_.reset();
}
}
size_t HttpStreamPool::AttemptManager::PendingJobCount() const {
return PendingCountInternal(jobs_.size());
}
size_t HttpStreamPool::AttemptManager::PendingPreconnectCount() const {
size_t num_streams = CalculateMaxPreconnectCount();
// Pending preconnect count is treated as zero when the maximum preconnect
// socket count is less than or equal to the active stream socket count.
// This behavior is for compatibility with the non-HEv3 code path. See
// TransportClientSocketPool::RequestSockets().
if (num_streams <= group_->ActiveStreamSocketCount()) {
return 0;
}
return PendingCountInternal(num_streams);
}
const HttpStreamKey& HttpStreamPool::AttemptManager::stream_key() const {
return group_->stream_key();
}
const SpdySessionKey& HttpStreamPool::AttemptManager::spdy_session_key() const {
return group_->spdy_session_key();
}
const QuicSessionAliasKey&
HttpStreamPool::AttemptManager::quic_session_alias_key() const {
return group_->quic_session_alias_key();
}
HttpNetworkSession* HttpStreamPool::AttemptManager::http_network_session()
const {
return group_->http_network_session();
}
SpdySessionPool* HttpStreamPool::AttemptManager::spdy_session_pool() const {
return http_network_session()->spdy_session_pool();
}
QuicSessionPool* HttpStreamPool::AttemptManager::quic_session_pool() const {
return http_network_session()->quic_session_pool();
}
HttpStreamPool* HttpStreamPool::AttemptManager::pool() {
return group_->pool();
}
const HttpStreamPool* HttpStreamPool::AttemptManager::pool() const {
return group_->pool();
}
int HttpStreamPool::AttemptManager::final_error_to_notify_jobs() const {
CHECK(final_error_to_notify_jobs_.has_value());
return *final_error_to_notify_jobs_;
}
const NetLogWithSource& HttpStreamPool::AttemptManager::net_log() {
return net_log_;
}
bool HttpStreamPool::AttemptManager::UsingTls() const {
return GURL::SchemeIsCryptographic(stream_key().destination().scheme());
}
bool HttpStreamPool::AttemptManager::RequiresHTTP11() {
return pool()->RequiresHTTP11(stream_key().destination(),
stream_key().network_anonymization_key());
}
LoadState HttpStreamPool::AttemptManager::GetLoadState() const {
if (group_->ReachedMaxStreamLimit()) {
return LOAD_STATE_WAITING_FOR_AVAILABLE_SOCKET;
}
if (pool()->ReachedMaxStreamLimit()) {
return LOAD_STATE_WAITING_FOR_STALLED_SOCKET_POOL;
}
LoadState load_state = LOAD_STATE_IDLE;
// When there are TCP based attempts, use most advanced one.
for (const auto& tcp_based_attempt : tcp_based_attempts_) {
load_state =
std::max(load_state, tcp_based_attempt->attempt()->GetLoadState());
// There should not be a load state later than LOAD_STATE_SSL_HANDSHAKE.
if (load_state == LOAD_STATE_SSL_HANDSHAKE) {
break;
}
}
if (load_state != LOAD_STATE_IDLE) {
return load_state;
}
if (service_endpoint_request_ && !service_endpoint_request_finished_) {
return LOAD_STATE_RESOLVING_HOST;
}
return LOAD_STATE_IDLE;
}
RequestPriority HttpStreamPool::AttemptManager::GetPriority() const {
// There are several cases where `jobs_` is empty (e.g. `this` only has
// preconnects, all jobs are already notified etc). Use IDLE for these cases.
if (jobs_.empty()) {
return RequestPriority::IDLE;
}
return static_cast<RequestPriority>(jobs_.FirstMax().priority());
}
bool HttpStreamPool::AttemptManager::IsStalledByPoolLimit() {
if (is_failing_) {
return false;
}
if (!GetIPEndPointToAttemptTcpBased().has_value()) {
return false;
}
if (CanUseExistingQuicSession()) {
// There could be a matching QUIC session if an existing QUIC session
// receives an HTTP/3 Origin frame while `this` is attempting QUIC session
// establishment. In such case, QuicSessionAttempt will close the new
// session later. See QuicSessionAttempt::DoConfirmConnection().
return false;
}
if (HasAvailableSpdySession()) {
CHECK_EQ(PendingPreconnectCount(), 0u);
return false;
}
switch (CanAttemptConnection()) {
case CanAttemptResult::kAttempt:
case CanAttemptResult::kReachedPoolLimit:
return true;
case CanAttemptResult::kNoPendingJob:
case CanAttemptResult::kBlockedTcpBasedAttempt:
case CanAttemptResult::kThrottledForSpdy:
case CanAttemptResult::kReachedGroupLimit:
return false;
}
}
void HttpStreamPool::AttemptManager::OnQuicAttemptComplete(
int rv,
NetErrorDetails details) {
CHECK(!quic_attempt_result_.has_value());
quic_attempt_result_ = rv;
net_error_details_ = std::move(details);
// Record completion time only when QuicAttempt actually attempted QUIC.
if (rv != ERR_DNS_NO_MATCHING_SUPPORTED_ALPN) {
base::UmaHistogramTimes(
base::StrCat({"Net.HttpStreamPool.QuicAttemptTime.",
rv == OK ? "Success" : "Failure"}),
base::TimeTicks::Now() - quic_attempt_->start_time());
}
quic_attempt_.reset();
net_log().AddEvent(
NetLogEventType::HTTP_STREAM_POOL_ATTEMPT_MANAGER_QUIC_ATTEMPT_COMPLETED,
[&] {
base::Value::Dict dict = GetStatesAsNetLogParams();
dict.Set("result", ErrorToString(rv));
if (net_error_details_.quic_connection_error != quic::QUIC_NO_ERROR) {
dict.Set("quic_error", quic::QuicErrorCodeToString(
net_error_details_.quic_connection_error));
}
if (rv == OK) {
QuicChromiumClientSession* quic_session =
quic_session_pool()->FindExistingSession(
quic_session_alias_key().session_key(),
quic_session_alias_key().destination());
if (quic_session) {
quic_session->net_log().source().AddToEventParameters(dict);
}
}
return dict;
});
MaybeMarkQuicBroken();
if (rv == OK) {
HandleQuicSessionReady(StreamSocketCloseReason::kQuicSessionCreated);
if (!jobs_.empty()) {
CreateQuicStreamAndNotify();
} else {
MaybeCompleteLater();
}
return;
}
if (tcp_based_attempt_state_ == TcpBasedAttemptState::kAllEndpointsFailed ||
!CanUseTcpBasedProtocols()) {
CancelTcpBasedAttemptDelayTimer();
HandleFinalError(rv);
return;
}
if (should_block_tcp_based_attempt_) {
CancelTcpBasedAttemptDelayTimer();
MaybeAttemptTcpBased();
} else {
MaybeCompleteLater();
}
}
base::Value::Dict HttpStreamPool::AttemptManager::GetInfoAsValue() const {
base::Value::Dict dict;
dict.Set("job_count_all", static_cast<int>(jobs_.size()));
dict.Set("job_count_pending", static_cast<int>(PendingJobCount()));
dict.Set("job_count_limit_ignoring",
static_cast<int>(limit_ignoring_jobs_.size()));
dict.Set("job_count_notified", static_cast<int>(notified_jobs_.size()));
dict.Set("preconnect_count_all", static_cast<int>(preconnect_jobs_.size()));
dict.Set("preconnect_count_pending",
static_cast<int>(PendingPreconnectCount()));
dict.Set("preconnect_count_notifying",
static_cast<int>(notifying_preconnect_completion_count_));
dict.Set("tcp_based_attempt_count", static_cast<int>(TcpBasedAttemptCount()));
dict.Set("slow_tcp_based_attempt_count",
static_cast<int>(slow_tcp_based_attempt_count_));
dict.Set("is_failing", is_failing_);
if (final_error_to_notify_jobs_.has_value()) {
dict.Set("final_error_to_notify_job", *final_error_to_notify_jobs_);
}
if (most_recent_tcp_error_.has_value()) {
dict.Set("most_recent_tcp_error", *most_recent_tcp_error_);
}
dict.Set("can_attempt_connection",
CanAttemptResultToString(CanAttemptConnection()));
dict.Set("service_endpoint_request_finished",
service_endpoint_request_finished_);
dict.Set("tcp_based_attempt_state",
TcpBasedAttemptStateToString(tcp_based_attempt_state_));
dict.Set("tcp_based_attempt_delay_ms",
static_cast<int>(tcp_based_attempt_delay_.InMilliseconds()));
dict.Set("should_block_tcp_based_attempt", should_block_tcp_based_attempt_);
int ssl_config_num_waiting_callbacks = 0;
if (!tcp_based_attempts_.empty()) {
base::Value::List tcp_based_attempts;
for (const auto& entry : tcp_based_attempts_) {
if (entry->IsWaitingSSLConfig()) {
++ssl_config_num_waiting_callbacks;
}
tcp_based_attempts.Append(entry->GetInfoAsValue());
}
dict.Set("tcp_based_attempts", std::move(tcp_based_attempts));
}
dict.Set("ssl_config_num_waiting_callbacks",
ssl_config_num_waiting_callbacks);
if (!ip_endpoint_states_.empty()) {
base::Value::List ip_endpoint_states;
for (const auto& [ip_endpoint, state] : ip_endpoint_states_) {
base::Value::Dict state_dict;
state_dict.Set("ip_endpoint", ip_endpoint.ToString());
state_dict.Set("state", IPEndPointStateToString(state));
ip_endpoint_states.Append(std::move(state_dict));
}
dict.Set("ip_endpoint_states", std::move(ip_endpoint_states));
}
if (quic_attempt_) {
dict.Set("quic_attempt", quic_attempt_->GetInfoAsValue());
}
if (quic_attempt_result_.has_value()) {
dict.Set("quic_attempt_result", ErrorToString(*quic_attempt_result_));
}
return dict;
}
MultiplexedSessionCreationInitiator
HttpStreamPool::AttemptManager::CalculateMultiplexedSessionCreationInitiator() {
// Iff we only have preconnect jobs, return `kPreconnect`.
if (!preconnect_jobs_.empty() && jobs_.empty() && notified_jobs_.empty()) {
return MultiplexedSessionCreationInitiator::kPreconnect;
}
return MultiplexedSessionCreationInitiator::kUnknown;
}
void HttpStreamPool::AttemptManager::SetOnCompleteCallbackForTesting(
base::OnceClosure callback) {
CHECK(on_complete_callback_for_testing_.is_null());
on_complete_callback_for_testing_ = std::move(callback);
}
void HttpStreamPool::AttemptManager::StartInternal(Job* job) {
RestrictAllowedProtocols(job->allowed_alpns());
UpdateTcpBasedAttemptState();
if (service_endpoint_request_ || service_endpoint_request_finished_) {
MaybeAttemptQuic();
MaybeAttemptTcpBased();
} else {
ResolveServiceEndpoint(job->priority());
}
}
void HttpStreamPool::AttemptManager::ResolveServiceEndpoint(
RequestPriority initial_priority) {
CHECK(!service_endpoint_request_);
HostResolver::ResolveHostParameters parameters;
parameters.initial_priority = initial_priority;
parameters.secure_dns_policy = stream_key().secure_dns_policy();
service_endpoint_request_ =
http_network_session()->host_resolver()->CreateServiceEndpointRequest(
HostResolver::Host(stream_key().destination()),
stream_key().network_anonymization_key(), net_log(),
std::move(parameters));
dns_resolution_start_time_ = base::TimeTicks::Now();
int rv = service_endpoint_request_->Start(this);
if (rv != ERR_IO_PENDING) {
OnServiceEndpointRequestFinished(rv);
}
}
void HttpStreamPool::AttemptManager::RestrictAllowedProtocols(
NextProtoSet allowed_alpns) {
allowed_alpns_ = base::Intersection(allowed_alpns_, allowed_alpns);
CHECK(!allowed_alpns_.empty());
if (!CanUseTcpBasedProtocols()) {
CancelTcpBasedAttempts(
StreamSocketCloseReason::kCannotUseTcpBasedProtocols);
}
if (!CanUseQuic()) {
// TODO(crbug.com/346835898): Use other error code?
CancelQuicAttempt(ERR_ABORTED);
UpdateTcpBasedAttemptState();
}
}
void HttpStreamPool::AttemptManager::
MaybeChangeServiceEndpointRequestPriority() {
if (service_endpoint_request_ && !service_endpoint_request_finished_) {
service_endpoint_request_->ChangeRequestPriority(GetPriority());
}
}
void HttpStreamPool::AttemptManager::ProcessServiceEndpointChanges() {
CHECK(!is_failing_);
CHECK(service_endpoint_request_);
// The order of the following checks is important, see the following comments.
// TODO(crbug.com/383606724): Figure out a better design and algorithms to
// handle attempts and existing sessions.
// For plain HTTP request, we need to wait for HTTPS RR because we could
// trigger HTTP -> HTTPS upgrade when HTTPS RR is received during the endpoint
// resolution.
if (!UsingTls() && !service_endpoint_request_->EndpointsCryptoReady() &&
!service_endpoint_request_finished_) {
return;
}
if (CanUseExistingQuicSessionAfterEndpointChanges()) {
CHECK(tcp_based_attempts_.empty());
return;
}
if (CanUseExistingSpdySessionAfterEndpointChanges()) {
CHECK(tcp_based_attempts_.empty());
return;
}
if (GetTcpBasedAttemptDelayBehavior() ==
TcpBasedAttemptDelayBehavior::kStartTimerOnFirstEndpointUpdate) {
MaybeRunTcpBasedAttemptDelayTimer();
}
MaybeNotifySSLConfigReady();
MaybeAttemptQuic();
MaybeAttemptTcpBased();
}
bool HttpStreamPool::AttemptManager::
CanUseExistingQuicSessionAfterEndpointChanges() {
if (!CanUseQuic()) {
return false;
}
if (CanUseExistingQuicSession()) {
CancelQuicAttempt(OK);
return true;
}
for (const auto& endpoint : service_endpoint_request_->GetEndpointResults()) {
if (!quic_session_pool()->HasMatchingIpSessionForServiceEndpoint(
quic_session_alias_key(), endpoint,
service_endpoint_request_->GetDnsAliasResults(), true)) {
continue;
}
CancelQuicAttempt(OK);
net_log_.AddEvent(
NetLogEventType::
HTTP_STREAM_POOL_ATTEMPT_MANAGER_EXISTING_QUIC_SESSION_MATCHED,
[&] {
base::Value::Dict dict;
QuicChromiumClientSession* quic_session =
quic_session_pool()->FindExistingSession(
quic_session_alias_key().session_key(),
quic_session_alias_key().destination());
CHECK(quic_session);
quic_session->net_log().source().AddToEventParameters(dict);
return dict;
});
base::UmaHistogramTimes(
"Net.HttpStreamPool.ExistingQuicSessionFoundTime",
base::TimeTicks::Now() - dns_resolution_start_time_);
HandleQuicSessionReady(StreamSocketCloseReason::kUsingExistingQuicSession);
CreateQuicStreamAndNotify();
return true;
}
return false;
}
bool HttpStreamPool::AttemptManager::
CanUseExistingSpdySessionAfterEndpointChanges() {
if (!IsIpBasedPoolingEnabled() || !UsingTls()) {
return false;
}
if (HasAvailableSpdySession()) {
return true;
}
for (const auto& endpoint : service_endpoint_request_->GetEndpointResults()) {
base::WeakPtr<SpdySession> spdy_session =
spdy_session_pool()->FindMatchingIpSessionForServiceEndpoint(
spdy_session_key(), endpoint,
service_endpoint_request_->GetDnsAliasResults());
if (!spdy_session) {
continue;
}
CHECK(spdy_session->IsAvailable());
net_log_.AddEvent(
NetLogEventType::
HTTP_STREAM_POOL_ATTEMPT_MANAGER_EXISTING_SPDY_SESSION_MATCHED,
[&] {
base::Value::Dict dict;
spdy_session->net_log().source().AddToEventParameters(dict);
return dict;
});
base::UmaHistogramTimes(
"Net.HttpStreamPool.ExistingSpdySessionFoundTime",
base::TimeTicks::Now() - dns_resolution_start_time_);
ip_matching_spdy_session_found_ = true;
HandleSpdySessionReady(spdy_session,
StreamSocketCloseReason::kUsingExistingSpdySession);
return true;
}
return false;
}
void HttpStreamPool::AttemptManager::MaybeNotifySSLConfigReady() {
if (!service_endpoint_request_->EndpointsCryptoReady()) {
return;
}
// Collect callbacks from TCP based attempts and invoke them later.
// Transferring callback ownership is important to avoid accessing TCP based
// attempts that could be destroyed while invoking callbacks.
std::vector<CompletionOnceCallback> callbacks;
for (const auto& attempt : tcp_based_attempts_) {
auto callback = attempt->MaybeTakeSSLConfigWaitingCallback();
if (callback.has_value()) {
callbacks.emplace_back(std::move(*callback));
}
}
for (auto& callback : callbacks) {
std::move(callback).Run(OK);
}
}
void HttpStreamPool::AttemptManager::MaybeAttemptQuic() {
CHECK(service_endpoint_request_);
if (is_failing_ || !CanUseQuic() || quic_attempt_result_.has_value() ||
!service_endpoint_request_->EndpointsCryptoReady()) {
return;
}
if (quic_attempt_) {
// TODO(crbug.com/346835898): Support multiple QUIC attempts.
return;
}
std::optional<QuicEndpoint> quic_endpoint = GetQuicEndpointToAttempt();
if (quic_endpoint.has_value()) {
quic_attempt_ =
std::make_unique<QuicAttempt>(this, std::move(*quic_endpoint));
quic_attempt_->Start();
return;
}
if (service_endpoint_request_finished_) {
// There is no QUIC endpoint to attempt.
OnQuicAttemptComplete(ERR_DNS_NO_MATCHING_SUPPORTED_ALPN,
NetErrorDetails());
}
}
void HttpStreamPool::AttemptManager::MaybeAttemptTcpBased(
std::optional<IPEndPoint> exclude_ip_endpoint,
std::optional<size_t> max_attempts) {
if (is_failing_) {
return;
}
if (!CanUseTcpBasedProtocols()) {
return;
}
if (CanUseQuic() && quic_attempt_result_.has_value() &&
*quic_attempt_result_ == OK) {
return;
}
// There might be multiple pending jobs. Make attempts as much as needed
// and allowed.
size_t num_attempts = 0;
const bool using_tls = UsingTls();
while (IsTcpBasedAttemptReady()) {
// TODO(crbug.com/346835898): Change to DCHECK once we stabilize the
// implementation.
CHECK(!HasAvailableSpdySession());
std::optional<IPEndPoint> ip_endpoint =
GetIPEndPointToAttemptTcpBased(exclude_ip_endpoint);
if (!ip_endpoint.has_value()) {
if (service_endpoint_request_finished_ && tcp_based_attempts_.empty()) {
tcp_based_attempt_state_ = TcpBasedAttemptState::kAllEndpointsFailed;
}
if (tcp_based_attempt_state_ ==
TcpBasedAttemptState::kAllEndpointsFailed &&
!quic_attempt_) {
// Tried all endpoints.
CHECK(most_recent_tcp_error_.has_value());
HandleFinalError(*most_recent_tcp_error_);
}
return;
}
if (tcp_based_attempt_state_ == TcpBasedAttemptState::kNotStarted) {
SetInitialAttemptState();
tcp_based_attempt_state_ = TcpBasedAttemptState::kAttempting;
}
CHECK(!preconnect_jobs_.empty() || group_->IdleStreamSocketCount() == 0);
auto tcp_based_attempt =
std::make_unique<TcpBasedAttempt>(this, using_tls, *ip_endpoint);
auto [attempt_iterator, inserted] =
tcp_based_attempts_.emplace(std::move(tcp_based_attempt));
CHECK(inserted);
pool()->IncrementTotalConnectingStreamCount();
(*attempt_iterator)->Start();
++num_attempts;
if (max_attempts.has_value() && num_attempts >= *max_attempts) {
break;
}
}
}
bool HttpStreamPool::AttemptManager::IsTcpBasedAttemptReady() {
switch (CanAttemptConnection()) {
case CanAttemptResult::kAttempt:
// If we ignore stream limits and the pool's limit has already reached,
// try to close as much as possible.
while (pool()->ReachedMaxStreamLimit()) {
CHECK(!ShouldRespectLimits());
if (!pool()->CloseOneIdleStreamSocket()) {
break;
}
}
return true;
case CanAttemptResult::kNoPendingJob:
return false;
case CanAttemptResult::kBlockedTcpBasedAttempt:
return false;
case CanAttemptResult::kThrottledForSpdy:
// TODO(crbug.com/346835898): Consider throttling less aggressively (e.g.
// allow TCP handshake but throttle TLS handshake) so that endpoints we've
// used HTTP/2 on aren't penalised on slow or lossy connections.
if (!spdy_throttle_timer_.IsRunning()) {
spdy_throttle_timer_.Start(
FROM_HERE, kSpdyThrottleDelay,
base::BindOnce(&AttemptManager::OnSpdyThrottleDelayPassed,
base::Unretained(this)));
}
return false;
case CanAttemptResult::kReachedGroupLimit:
// TODO(crbug.com/346835898): Better to handle cases where we partially
// attempted some connections.
NotifyPreconnectsComplete(ERR_PRECONNECT_MAX_SOCKET_LIMIT);
return false;
case CanAttemptResult::kReachedPoolLimit:
// If we can't attempt connection due to the pool's limit, try to close an
// idle stream in the pool.
if (!pool()->CloseOneIdleStreamSocket()) {
// Try to close idle SPDY sessions. SPDY sessions never release the
// underlying sockets immediately on close, so return false anyway.
spdy_session_pool()->CloseCurrentIdleSessions("Closing idle sessions");
// TODO(crbug.com/346835898): Better to handle cases where we partially
// attempted some connections.
NotifyPreconnectsComplete(ERR_PRECONNECT_MAX_SOCKET_LIMIT);
return false;
}
return true;
}
}
HttpStreamPool::AttemptManager::CanAttemptResult
HttpStreamPool::AttemptManager::CanAttemptConnection() const {
size_t pending_count = std::max(PendingJobCount(), PendingPreconnectCount());
if (pending_count == 0) {
return CanAttemptResult::kNoPendingJob;
}
if (ShouldThrottleAttemptForSpdy()) {
return CanAttemptResult::kThrottledForSpdy;
}
if (should_block_tcp_based_attempt_) {
return CanAttemptResult::kBlockedTcpBasedAttempt;
}
if (ShouldRespectLimits()) {
if (group_->ReachedMaxStreamLimit()) {
return CanAttemptResult::kReachedGroupLimit;
}
if (pool()->ReachedMaxStreamLimit()) {
return CanAttemptResult::kReachedPoolLimit;
}
}
return CanAttemptResult::kAttempt;
}
bool HttpStreamPool::AttemptManager::ShouldRespectLimits() const {
return limit_ignoring_jobs_.empty();
}
bool HttpStreamPool::AttemptManager::IsIpBasedPoolingEnabled() const {
return ip_based_pooling_disabling_jobs_.empty();
}
bool HttpStreamPool::AttemptManager::IsAlternativeServiceEnabled() const {
return alternative_service_disabling_jobs_.empty();
}
bool HttpStreamPool::AttemptManager::SupportsSpdy() const {
return http_network_session()->http_server_properties()->GetSupportsSpdy(
stream_key().destination(), stream_key().network_anonymization_key());
}
bool HttpStreamPool::AttemptManager::ShouldThrottleAttemptForSpdy() const {
if (!SupportsSpdy()) {
return false;
}
CHECK(UsingTls());
// The first attempt should not be blocked.
if (tcp_based_attempts_.empty()) {
return false;
}
if (spdy_throttle_delay_passed_) {
return false;
}
DCHECK(!HasAvailableSpdySession());
return true;
}
size_t HttpStreamPool::AttemptManager::CalculateMaxPreconnectCount() const {
size_t num_streams = 0;
for (const auto& job : preconnect_jobs_) {
num_streams = std::max(num_streams, job->num_streams());
}
return num_streams;
}
size_t HttpStreamPool::AttemptManager::PendingCountInternal(
size_t pending_count) const {
CHECK_GE(tcp_based_attempts_.size(), slow_tcp_based_attempt_count_);
// When SPDY throttle delay passed, treat all in-flight attempts as non-slow,
// to avoid attempting connections more than requested.
// TODO(crbug.com/346835898): This behavior is tricky. Figure out a better
// way to handle this situation.
size_t slow_count =
spdy_throttle_delay_passed_ ? 0 : slow_tcp_based_attempt_count_;
size_t non_slow_count = tcp_based_attempts_.size() - slow_count;
// The number of in-flight, non-slow attempts could be larger than the number
// of jobs (e.g. a job was cancelled in the middle of an attempt).
if (pending_count <= non_slow_count) {
return 0;
}
return pending_count - non_slow_count;
}
std::optional<IPEndPoint>
HttpStreamPool::AttemptManager::GetIPEndPointToAttemptTcpBased(
std::optional<IPEndPoint> exclude_ip_endpoint) {
// TODO(crbug.com/383824591): Add a trace event to see if this method is
// time consuming.
if (!service_endpoint_request_ ||
service_endpoint_request_->GetEndpointResults().empty()) {
return std::nullopt;
}
const bool svcb_optional = IsSvcbOptional();
std::optional<IPEndPoint> current_endpoint;
std::optional<IPEndPointState> current_state;
for (bool ip_v6 : {prefer_ipv6_, !prefer_ipv6_}) {
for (const auto& service_endpoint :
service_endpoint_request_->GetEndpointResults()) {
if (!IsEndpointUsableForTcpBasedAttempt(service_endpoint,
svcb_optional)) {
continue;
}
const std::vector<IPEndPoint>& ip_endpoints =
ip_v6 ? service_endpoint.ipv6_endpoints
: service_endpoint.ipv4_endpoints;
FindBetterIPEndPoint(ip_endpoints, exclude_ip_endpoint, current_state,
current_endpoint);
if (current_endpoint.has_value() && !current_state.has_value()) {
// This endpoint is fast or no connection attempt has been made to it
// yet.
return current_endpoint;
}
}
}
// No available IP endpoint, or `current_endpoint` is slow.
return current_endpoint;
}
void HttpStreamPool::AttemptManager::FindBetterIPEndPoint(
const std::vector<IPEndPoint>& ip_endpoints,
std::optional<IPEndPoint> exclude_ip_endpoint,
std::optional<IPEndPointState>& current_state,
std::optional<IPEndPoint>& current_endpoint) {
for (const auto& ip_endpoint : ip_endpoints) {
if (exclude_ip_endpoint.has_value() &&
ip_endpoint == *exclude_ip_endpoint) {
continue;
}
auto it = ip_endpoint_states_.find(ip_endpoint);
if (it == ip_endpoint_states_.end()) {
// If there is no state for the IP endpoint it means that we haven't tried
// the endpoint yet or previous attempt to the endpoint was fast. Just use
// it.
current_endpoint = ip_endpoint;
current_state = std::nullopt;
return;
}
switch (it->second) {
case IPEndPointState::kFailed:
continue;
case IPEndPointState::kSlowAttempting:
if (!current_endpoint.has_value() &&
!HasEnoughTcpBasedAttemptsForSlowIPEndPoint(ip_endpoint)) {
current_endpoint = ip_endpoint;
current_state = it->second;
}
continue;
case IPEndPointState::kSlowSucceeded:
const bool prefer_slow_succeeded =
!current_state.has_value() ||
*current_state == IPEndPointState::kSlowAttempting;
if (prefer_slow_succeeded &&
!HasEnoughTcpBasedAttemptsForSlowIPEndPoint(ip_endpoint)) {
current_endpoint = ip_endpoint;
current_state = it->second;
}
continue;
}
}
}
bool HttpStreamPool::AttemptManager::HasEnoughTcpBasedAttemptsForSlowIPEndPoint(
const IPEndPoint& ip_endpoint) {
// TODO(crbug.com/383824591): Consider modifying the value of
// IPEndPointStateMap to track the number of in-flight attempts per
// IPEndPoint, if this loop is a bottlenek.
size_t num_attempts = 0;
for (const auto& entry : tcp_based_attempts_) {
if (entry->attempt()->ip_endpoint() == ip_endpoint) {
++num_attempts;
}
}
return num_attempts >= std::max(jobs_.size(), CalculateMaxPreconnectCount());
}
std::optional<QuicEndpoint>
HttpStreamPool::AttemptManager::GetQuicEndpointToAttempt() {
const bool svcb_optional = IsSvcbOptional();
for (auto& service_endpoint :
service_endpoint_request()->GetEndpointResults()) {
quic::ParsedQuicVersion endpoint_quic_version =
quic_session_pool()->SelectQuicVersion(
quic_version_, service_endpoint.metadata, svcb_optional);
if (!endpoint_quic_version.IsKnown()) {
continue;
}
// TODO(crbug.com/346835898): Attempt more than one endpoints.
std::optional<IPEndPoint> ip_endpoint;
if (!service_endpoint.ipv6_endpoints.empty()) {
ip_endpoint = service_endpoint.ipv6_endpoints[0];
} else if (!service_endpoint.ipv4_endpoints.empty()) {
ip_endpoint = service_endpoint.ipv4_endpoints[0];
}
if (!ip_endpoint.has_value()) {
continue;
}
return QuicEndpoint(endpoint_quic_version, *ip_endpoint,
service_endpoint.metadata);
}
return std::nullopt;
}
void HttpStreamPool::AttemptManager::HandleFinalError(int error) {
// `this` may already be failing, e.g. IP address change happens while failing
// for a different reason.
if (is_failing_) {
return;
}
CHECK(!final_error_to_notify_jobs_.has_value());
final_error_to_notify_jobs_ = error;
is_failing_ = true;
service_endpoint_request_.reset();
net_log_.AddEvent(
NetLogEventType::HTTP_STREAM_POOL_ATTEMPT_MANAGER_NOTIFY_FAILURE, [&] {
base::Value::Dict dict = GetStatesAsNetLogParams();
dict.Set("net_error", final_error_to_notify_jobs());
return dict;
});
CancelTcpBasedAttempts(StreamSocketCloseReason::kAbort);
CancelQuicAttempt(final_error_to_notify_jobs());
NotifyPreconnectsComplete(final_error_to_notify_jobs());
NotifyJobOfFailure();
// `this` may be deleted.
}
HttpStreamPool::AttemptManager::FailureKind
HttpStreamPool::AttemptManager::DetermineFailureKind() {
if (is_canceling_jobs_) {
return FailureKind::kStreamFailed;
}
if (IsCertificateError(final_error_to_notify_jobs())) {
return FailureKind::kCertifcateError;
}
if (final_error_to_notify_jobs() == ERR_SSL_CLIENT_AUTH_CERT_NEEDED) {
return FailureKind::kNeedsClientAuth;
}
return FailureKind::kStreamFailed;
}
void HttpStreamPool::AttemptManager::NotifyJobOfFailure() {
CHECK(is_failing_);
const FailureKind kind = DetermineFailureKind();
base::WeakPtr<AttemptManager> weak_this = weak_ptr_factory_.GetWeakPtr();
while (Job* job = ExtractFirstJobToNotify()) {
// Ensure `this` isn't deleted while notifying the failure.
// TODO(crbug.com/414173943): Remove this check when we are certain that
// `this` won't be deleted.
CHECK(weak_this);
job->AddConnectionAttempts(connection_attempts_);
switch (kind) {
case FailureKind::kStreamFailed: {
TRACE_EVENT_INSTANT("net.stream", "AttemptManager::StreamFailed",
track_,
NetLogWithSourceToFlow(job->request_net_log()));
job->OnStreamFailed(final_error_to_notify_jobs(), net_error_details_,
resolve_error_info_);
break;
}
case FailureKind::kCertifcateError: {
CHECK(cert_error_ssl_info_.has_value());
TRACE_EVENT_INSTANT("net.stream", "AttemptManager::CertificateError",
track_,
NetLogWithSourceToFlow(job->request_net_log()));
job->OnCertificateError(final_error_to_notify_jobs(),
*cert_error_ssl_info_);
break;
}
case FailureKind::kNeedsClientAuth: {
CHECK(client_auth_cert_info_.get());
TRACE_EVENT_INSTANT("net.stream", "AttemptManager::NeedsClientAuth",
track_,
NetLogWithSourceToFlow(job->request_net_log()));
job->OnNeedsClientAuth(client_auth_cert_info_.get());
break;
}
}
}
}
void HttpStreamPool::AttemptManager::NotifyPreconnectsComplete(int rv) {
while (!preconnect_jobs_.empty()) {
raw_ptr<Job> job =
preconnect_jobs_.extract(preconnect_jobs_.begin()).value();
NotifyJobOfPreconnectCompleteLater(job, rv);
}
// TODO(crbug.com/396998469): Do we still need this? Remove if this is not
// needed.
MaybeCompleteLater();
}
void HttpStreamPool::AttemptManager::ProcessPreconnectsAfterAttemptComplete(
int rv,
size_t active_stream_count) {
std::vector<Job*> completed_jobs;
for (auto& job : preconnect_jobs_) {
if (job->num_streams() <= active_stream_count) {
completed_jobs.emplace_back(job.get());
}
}
for (auto* completed_job : completed_jobs) {
auto it = preconnect_jobs_.find(completed_job);
CHECK(it != preconnect_jobs_.end());
raw_ptr<Job> job = preconnect_jobs_.extract(it).value();
NotifyJobOfPreconnectCompleteLater(job, rv);
}
// TODO(crbug.com/396998469): Do we still need this? Remove if this is not
// needed.
if (preconnect_jobs_.empty()) {
MaybeCompleteLater();
}
}
void HttpStreamPool::AttemptManager::NotifyJobOfPreconnectCompleteLater(
Job* job,
int rv) {
++notifying_preconnect_completion_count_;
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(&AttemptManager::NotifyJobOfPreconnectComplete,
weak_ptr_factory_.GetWeakPtr(), job, rv));
}
// TODO(crbug.com/396998469): Ensure `job` isn't a dangling pointer. There are
// two paths to destroy `job`.
// 1) JobController::OnPreconnectComplete() is called via
// Job::OnPreconnectComplete().
// 2) JobController is destroyed as a part of HttpStreamPool destruction.
//
// In this method, we don't have to consider 1) because we are about to call
// Job::OnPreconnectComplete(). If 2) happens, `this` should have been destroyed
// too so we shouldn't reach here because we use "weak this" to post a task.
void HttpStreamPool::AttemptManager::NotifyJobOfPreconnectComplete(Job* job,
int rv) {
TRACE_EVENT_INSTANT("net.stream",
"AttemptManager::NotifyJobOfPreconnectComplete", track_,
NetLogWithSourceToFlow(job->request_net_log()));
CHECK_GT(notifying_preconnect_completion_count_, 0u);
--notifying_preconnect_completion_count_;
// We don't need to call MaybeCompleteLater() here, since `job` will call
// OnJobComplete() later.
job->OnPreconnectComplete(rv);
}
void HttpStreamPool::AttemptManager::CreateTextBasedStreamAndNotify(
std::unique_ptr<StreamSocket> stream_socket,
StreamSocketHandle::SocketReuseType reuse_type,
LoadTimingInfo::ConnectTiming connect_timing) {
NextProto negotiated_protocol = stream_socket->GetNegotiatedProtocol();
CHECK_NE(negotiated_protocol, NextProto::kProtoHTTP2);
std::unique_ptr<HttpStream> http_stream = group_->CreateTextBasedStream(
std::move(stream_socket), reuse_type, std::move(connect_timing));
CHECK(!ShouldRespectLimits() || group_->ActiveStreamSocketCount() <=
pool()->max_stream_sockets_per_group())
<< "active=" << group_->ActiveStreamSocketCount()
<< ", limit=" << pool()->max_stream_sockets_per_group();
NotifyStreamReady(std::move(http_stream), negotiated_protocol);
// `this` may be deleted.
}
bool HttpStreamPool::AttemptManager::HasAvailableSpdySession() const {
return spdy_session_pool()->HasAvailableSession(
spdy_session_key(), IsIpBasedPoolingEnabled(), /*is_websocket=*/false);
}
void HttpStreamPool::AttemptManager::CreateSpdyStreamAndNotify(
base::WeakPtr<SpdySession> spdy_session) {
CHECK(!is_canceling_jobs_);
CHECK(!is_failing_);
CHECK(spdy_session);
CHECK(spdy_session->IsAvailable());
std::set<std::string> dns_aliases =
http_network_session()->spdy_session_pool()->GetDnsAliasesForSessionKey(
spdy_session_key());
std::vector<std::unique_ptr<SpdyHttpStream>> streams(jobs_.size());
std::ranges::generate(streams, [&] {
return std::make_unique<SpdyHttpStream>(spdy_session, net_log().source(),
dns_aliases);
});
base::WeakPtr<AttemptManager> weak_this = weak_ptr_factory_.GetWeakPtr();
while (weak_this && !streams.empty()) {
std::unique_ptr<SpdyHttpStream> stream = std::move(streams.back());
streams.pop_back();
NotifyStreamReady(std::move(stream), NextProto::kProtoHTTP2);
// `this` may be deleted.
}
CHECK(!weak_this || jobs_.empty());
}
void HttpStreamPool::AttemptManager::CreateQuicStreamAndNotify() {
CHECK(!is_canceling_jobs_);
CHECK(!is_failing_);
QuicChromiumClientSession* quic_session =
quic_session_pool()->FindExistingSession(
quic_session_alias_key().session_key(),
quic_session_alias_key().destination());
CHECK(quic_session);
std::set<std::string> dns_aliases = quic_session->GetDnsAliasesForSessionKey(
quic_session_alias_key().session_key());
std::vector<std::unique_ptr<QuicHttpStream>> streams(jobs_.size());
std::ranges::generate(streams, [&] {
return std::make_unique<QuicHttpStream>(
quic_session->CreateHandle(stream_key().destination()), dns_aliases);
});
base::WeakPtr<AttemptManager> weak_this = weak_ptr_factory_.GetWeakPtr();
while (weak_this && !streams.empty()) {
std::unique_ptr<QuicHttpStream> stream = std::move(streams.back());
streams.pop_back();
NotifyStreamReady(std::move(stream), NextProto::kProtoQUIC);
// `this` may be deleted.
}
CHECK(!weak_this || jobs_.empty());
}
void HttpStreamPool::AttemptManager::NotifyStreamReady(
std::unique_ptr<HttpStream> stream,
NextProto negotiated_protocol) {
Job* job = ExtractFirstJobToNotify();
if (!job) {
// The ownership of the stream will be moved to the group as `stream` is
// going to be destructed.
return;
}
TRACE_EVENT_INSTANT("net.stream", "AttemptManager::NotifyStreamReady", track_,
NetLogWithSourceToFlow(job->request_net_log()),
"negotiated_protocol", negotiated_protocol);
job->OnStreamReady(std::move(stream), negotiated_protocol);
}
void HttpStreamPool::AttemptManager::HandleSpdySessionReady(
base::WeakPtr<SpdySession> spdy_session,
StreamSocketCloseReason refresh_group_reason) {
CHECK(!group_->force_quic());
CHECK(!is_failing_);
CHECK(spdy_session);
CHECK(spdy_session->IsAvailable());
TRACE_EVENT_INSTANT("net.stream", "AttemptManager::SpdySessionReady", track_);
group_->Refresh(kSwitchingToHttp2, refresh_group_reason);
NotifyPreconnectsComplete(OK);
CreateSpdyStreamAndNotify(spdy_session);
}
void HttpStreamPool::AttemptManager::HandleQuicSessionReady(
StreamSocketCloseReason refresh_group_reason) {
CHECK(!is_failing_);
CHECK(!quic_attempt_);
// TODO(crbug.com/415488524): Change to DCHECK once we confirm the bug is
// fixed.
CHECK(CanUseExistingQuicSession());
TRACE_EVENT_INSTANT("net.stream", "AttemptManager::QuicSessionReady", track_);
group_->Refresh(kSwitchingToHttp3, refresh_group_reason);
NotifyPreconnectsComplete(OK);
}
HttpStreamPool::Job* HttpStreamPool::AttemptManager::ExtractFirstJobToNotify() {
if (jobs_.empty()) {
return nullptr;
}
raw_ptr<Job> job = RemoveJobFromQueue(jobs_.FirstMax());
Job* job_raw_ptr = job.get();
notified_jobs_.emplace(std::move(job));
return job_raw_ptr;
}
raw_ptr<HttpStreamPool::Job> HttpStreamPool::AttemptManager::RemoveJobFromQueue(
JobQueue::Pointer job_pointer) {
// If the extracted job is the last job that ignores the limit, cancel
// in-flight attempts until the active stream count goes down to the limit.
raw_ptr<Job> job = jobs_.Erase(job_pointer);
limit_ignoring_jobs_.erase(job);
if (ShouldRespectLimits()) {
while (group_->ActiveStreamSocketCount() >
pool()->max_stream_sockets_per_group() &&
!tcp_based_attempts_.empty()) {
std::unique_ptr<TcpBasedAttempt> attempt = std::move(
tcp_based_attempts_.extract(tcp_based_attempts_.begin()).value());
if (attempt->is_slow()) {
--slow_tcp_based_attempt_count_;
}
pool()->DecrementTotalConnectingStreamCount();
attempt.reset();
}
}
return job;
}
void HttpStreamPool::AttemptManager::SetJobPriority(Job* job,
RequestPriority priority) {
for (JobQueue::Pointer pointer = jobs_.FirstMax(); !pointer.is_null();
pointer = jobs_.GetNextTowardsLastMin(pointer)) {
if (pointer.value() == job) {
if (pointer.priority() == priority) {
break;
}
raw_ptr<Job> entry = jobs_.Erase(pointer);
jobs_.Insert(std::move(entry), priority);
break;
}
}
MaybeChangeServiceEndpointRequestPriority();
}
void HttpStreamPool::AttemptManager::OnTcpBasedAttemptComplete(
TcpBasedAttempt* raw_attempt,
int rv) {
if (raw_attempt->is_slow()) {
CHECK_GT(slow_tcp_based_attempt_count_, 0u);
--slow_tcp_based_attempt_count_;
if (rv == OK) {
auto it = ip_endpoint_states_.find(raw_attempt->ip_endpoint());
CHECK(it != ip_endpoint_states_.end());
it->second = IPEndPointState::kSlowSucceeded;
}
}
auto it = tcp_based_attempts_.find(raw_attempt);
CHECK(it != tcp_based_attempts_.end());
std::unique_ptr<TcpBasedAttempt> tcp_based_attempt =
std::move(tcp_based_attempts_.extract(it).value());
pool()->DecrementTotalConnectingStreamCount();
if (rv != OK) {
HandleTcpBasedAttemptFailure(std::move(tcp_based_attempt), rv);
return;
}
CHECK_NE(tcp_based_attempt_state_, TcpBasedAttemptState::kAllEndpointsFailed);
if (tcp_based_attempt_state_ == TcpBasedAttemptState::kAttempting) {
tcp_based_attempt_state_ = TcpBasedAttemptState::kSucceededAtLeastOnce;
MaybeMarkQuicBroken();
}
LoadTimingInfo::ConnectTiming connect_timing =
tcp_based_attempt->attempt()->connect_timing();
connect_timing.domain_lookup_start = dns_resolution_start_time_;
// If the attempt started before DNS resolution completion, `connect_start`
// could be smaller than `dns_resolution_end_time_`. Use the smallest one.
connect_timing.domain_lookup_end =
dns_resolution_end_time_.is_null()
? connect_timing.connect_start
: std::min(connect_timing.connect_start, dns_resolution_end_time_);
std::unique_ptr<StreamSocket> stream_socket =
tcp_based_attempt->attempt()->ReleaseStreamSocket();
CHECK(stream_socket);
CHECK(service_endpoint_request_);
stream_socket->SetDnsAliases(service_endpoint_request_->GetDnsAliasResults());
spdy_throttle_timer_.Stop();
const auto reuse_type = StreamSocketHandle::SocketReuseType::kUnused;
if (stream_socket->GetNegotiatedProtocol() == NextProto::kProtoHTTP2) {
std::unique_ptr<HttpStreamPoolHandle> handle = group_->CreateHandle(
std::move(stream_socket), reuse_type, std::move(connect_timing));
base::WeakPtr<SpdySession> spdy_session;
int create_result =
spdy_session_pool()->CreateAvailableSessionFromSocketHandle(
spdy_session_key(), std::move(handle), net_log(),
MultiplexedSessionCreationInitiator::kUnknown, &spdy_session,
SpdySessionInitiator::kHttpStreamPoolAttemptManager);
if (create_result != OK) {
HandleTcpBasedAttemptFailure(std::move(tcp_based_attempt), create_result);
return;
}
HttpServerProperties* http_server_properties =
http_network_session()->http_server_properties();
http_server_properties->SetSupportsSpdy(
stream_key().destination(), stream_key().network_anonymization_key(),
/*supports_spdy=*/true);
base::UmaHistogramTimes(
"Net.HttpStreamPool.NewSpdySessionEstablishTime",
base::TimeTicks::Now() - tcp_based_attempt->start_time());
HandleSpdySessionReady(spdy_session,
StreamSocketCloseReason::kSpdySessionCreated);
return;
}
// We will create an active stream so +1 to the current active stream count.
ProcessPreconnectsAfterAttemptComplete(rv,
group_->ActiveStreamSocketCount() + 1);
CHECK_NE(stream_socket->GetNegotiatedProtocol(), NextProto::kProtoHTTP2);
CreateTextBasedStreamAndNotify(std::move(stream_socket), reuse_type,
std::move(connect_timing));
}
void HttpStreamPool::AttemptManager::OnTcpBasedAttemptSlow(
TcpBasedAttempt* raw_attempt) {
auto it = tcp_based_attempts_.find(raw_attempt);
CHECK(it != tcp_based_attempts_.end());
raw_attempt->set_is_slow(true);
++slow_tcp_based_attempt_count_;
// This will not overwrite the previous value, if it's already tagged as
// kSlowSucceeded (Nor will it overwrite other values).
ip_endpoint_states_.emplace(raw_attempt->attempt()->ip_endpoint(),
IPEndPointState::kSlowAttempting);
prefer_ipv6_ = !raw_attempt->attempt()->ip_endpoint().address().IsIPv6();
// Don't attempt the same IP endpoint.
MaybeAttemptTcpBased(/*exclude_ip_endpoint=*/raw_attempt->ip_endpoint());
}
void HttpStreamPool::AttemptManager::HandleTcpBasedAttemptFailure(
std::unique_ptr<TcpBasedAttempt> tcp_based_attempt,
int rv) {
CHECK_NE(rv, ERR_IO_PENDING);
connection_attempts_.emplace_back(tcp_based_attempt->ip_endpoint(), rv);
ip_endpoint_states_.insert_or_assign(tcp_based_attempt->ip_endpoint(),
IPEndPointState::kFailed);
if (tcp_based_attempt->is_aborted()) {
CHECK_EQ(rv, ERR_ABORTED);
// TODO(crbug.com/403373872): Reduce this failure.
most_recent_tcp_error_ = ERR_ABORTED;
return;
}
// We already removed `tcp_based_attempt` from `tcp_based_attempts_` so
// the active stream count is up-to-date.
ProcessPreconnectsAfterAttemptComplete(rv, group_->ActiveStreamSocketCount());
if (is_failing_) {
// `this` has already failed and is notifying jobs to the failure.
return;
}
if (rv == ERR_SSL_CLIENT_AUTH_CERT_NEEDED) {
CHECK(UsingTls());
client_auth_cert_info_ = tcp_based_attempt->attempt()->GetCertRequestInfo();
tcp_based_attempt.reset();
HandleFinalError(rv);
return;
}
if (IsCertificateError(rv)) {
// When a certificate error happened for an attempt, notifies all jobs of
// the error.
CHECK(UsingTls());
CHECK(tcp_based_attempt->attempt()->stream_socket());
SSLInfo ssl_info;
bool has_ssl_info =
tcp_based_attempt->attempt()->stream_socket()->GetSSLInfo(&ssl_info);
CHECK(has_ssl_info);
cert_error_ssl_info_ = ssl_info;
tcp_based_attempt.reset();
HandleFinalError(rv);
return;
}
most_recent_tcp_error_ = rv;
tcp_based_attempt.reset();
// Try to connect to a different destination, if any.
// TODO(crbug.com/383606724): Figure out better way to make connection
// attempts, see the review comment at
// https://chromium-review.googlesource.com/c/chromium/src/+/6160855/comment/60e04065_805b0b89/
MaybeAttemptTcpBased();
}
void HttpStreamPool::AttemptManager::OnSpdyThrottleDelayPassed() {
CHECK(!spdy_throttle_delay_passed_);
spdy_throttle_delay_passed_ = true;
MaybeAttemptTcpBased();
}
base::TimeDelta HttpStreamPool::AttemptManager::GetTcpBasedAttemptDelay() {
if (!CanUseQuic()) {
return base::TimeDelta();
}
return quic_session_pool()->GetTimeDelayForWaitingJob(
quic_session_alias_key().session_key());
}
void HttpStreamPool::AttemptManager::UpdateTcpBasedAttemptState() {
if (should_block_tcp_based_attempt_ && !CanUseQuic()) {
CancelTcpBasedAttemptDelayTimer();
}
}
void HttpStreamPool::AttemptManager::MaybeRunTcpBasedAttemptDelayTimer() {
if (!should_block_tcp_based_attempt_ ||
tcp_based_attempt_delay_timer_.IsRunning() ||
!CanUseTcpBasedProtocols()) {
return;
}
CHECK(!tcp_based_attempt_delay_.is_zero());
tcp_based_attempt_delay_timer_.Start(
FROM_HERE, tcp_based_attempt_delay_,
base::BindOnce(&AttemptManager::OnTcpBasedAttemptDelayPassed,
weak_ptr_factory_.GetWeakPtr()));
}
void HttpStreamPool::AttemptManager::CancelTcpBasedAttemptDelayTimer() {
should_block_tcp_based_attempt_ = false;
tcp_based_attempt_delay_timer_.Stop();
}
void HttpStreamPool::AttemptManager::OnTcpBasedAttemptDelayPassed() {
net_log().AddEvent(
NetLogEventType::
HTTP_STREAM_POOL_ATTEMPT_MANAGER_TCP_BASED_ATTEMPT_DELAY_PASSED,
[&] {
base::Value::Dict dict;
dict.Set("tcp_based_attempt_delay",
static_cast<int>(tcp_based_attempt_delay_.InMilliseconds()));
return dict;
});
CHECK(should_block_tcp_based_attempt_);
should_block_tcp_based_attempt_ = false;
MaybeAttemptTcpBased();
}
bool HttpStreamPool::AttemptManager::CanUseTcpBasedProtocols() {
return allowed_alpns_.HasAny(kTcpBasedProtocols);
}
bool HttpStreamPool::AttemptManager::CanUseQuic() {
return allowed_alpns_.HasAny(kQuicBasedProtocols) &&
pool()->CanUseQuic(stream_key().destination(),
stream_key().network_anonymization_key(),
IsIpBasedPoolingEnabled(),
IsAlternativeServiceEnabled());
}
bool HttpStreamPool::AttemptManager::CanUseExistingQuicSession() {
return pool()->CanUseExistingQuicSession(quic_session_alias_key(),
IsIpBasedPoolingEnabled(),
IsAlternativeServiceEnabled());
}
bool HttpStreamPool::AttemptManager::IsEchEnabled() const {
return pool()
->stream_attempt_params()
->ssl_client_context->config()
.ech_enabled;
}
bool HttpStreamPool::AttemptManager::IsEndpointUsableForTcpBasedAttempt(
const ServiceEndpoint& endpoint,
bool svcb_optional) {
// No ALPNs means that the endpoint is an authority A/AAAA endpoint, even if
// we are still in the middle of DNS resolution.
if (endpoint.metadata.supported_protocol_alpns.empty()) {
return svcb_optional;
}
// See https://www.rfc-editor.org/rfc/rfc9460.html#section-9.3. Endpoints are
// usable if there is an overlap between the endpoint's ALPNs and the
// configured ones.
for (const auto& alpn : endpoint.metadata.supported_protocol_alpns) {
if (base::Contains(http_network_session()->GetAlpnProtos(),
NextProtoFromString(alpn))) {
return true;
}
}
return false;
}
void HttpStreamPool::AttemptManager::MaybeMarkQuicBroken() {
if (!quic_attempt_result_.has_value() ||
tcp_based_attempt_state_ == TcpBasedAttemptState::kAttempting) {
return;
}
if (*quic_attempt_result_ == OK ||
*quic_attempt_result_ == ERR_DNS_NO_MATCHING_SUPPORTED_ALPN ||
*quic_attempt_result_ == ERR_NETWORK_CHANGED ||
*quic_attempt_result_ == ERR_INTERNET_DISCONNECTED) {
return;
}
// No brokenness to report if we didn't attempt TCP-based connection or all
// TCP-based attempts failed.
if (tcp_based_attempt_state_ == TcpBasedAttemptState::kNotStarted ||
tcp_based_attempt_state_ == TcpBasedAttemptState::kAllEndpointsFailed) {
return;
}
const url::SchemeHostPort& destination = stream_key().destination();
http_network_session()
->http_server_properties()
->MarkAlternativeServiceBroken(
AlternativeService(NextProto::kProtoQUIC, destination.host(),
destination.port()),
stream_key().network_anonymization_key());
}
base::Value::Dict HttpStreamPool::AttemptManager::GetStatesAsNetLogParams()
const {
if (VerboseNetLog()) {
return GetInfoAsValue();
}
base::Value::Dict dict;
dict.Set("num_active_sockets",
static_cast<int>(group_->ActiveStreamSocketCount()));
dict.Set("num_idle_sockets",
static_cast<int>(group_->IdleStreamSocketCount()));
dict.Set("num_total_sockets",
static_cast<int>(group_->ActiveStreamSocketCount()));
dict.Set("num_jobs", static_cast<int>(jobs_.size()));
dict.Set("num_notified_jobs", static_cast<int>(notified_jobs_.size()));
dict.Set("num_preconnects", static_cast<int>(preconnect_jobs_.size()));
dict.Set("num_tcp_based_attempts",
static_cast<int>(tcp_based_attempts_.size()));
dict.Set("num_slow_attempts",
static_cast<int>(slow_tcp_based_attempt_count_));
dict.Set("enable_ip_based_pooling", IsIpBasedPoolingEnabled());
dict.Set("enable_alternative_services", IsAlternativeServiceEnabled());
dict.Set("quic_attempt_alive", !!quic_attempt_);
if (quic_attempt_result_.has_value()) {
dict.Set("quic_attempt_result", ErrorToString(*quic_attempt_result_));
}
return dict;
}
bool HttpStreamPool::AttemptManager::CanComplete() const {
return jobs_.empty() && notified_jobs_.empty() && preconnect_jobs_.empty() &&
notifying_preconnect_completion_count_ == 0 &&
tcp_based_attempts_.empty() && !quic_attempt_;
}
void HttpStreamPool::AttemptManager::MaybeComplete() {
if (!CanComplete()) {
return;
}
CHECK(limit_ignoring_jobs_.empty());
CHECK(ip_based_pooling_disabling_jobs_.empty());
CHECK(alternative_service_disabling_jobs_.empty());
if (on_complete_callback_for_testing_) {
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, std::move(on_complete_callback_for_testing_));
}
group_->OnAttemptManagerComplete();
// `this` is deleted.
}
void HttpStreamPool::AttemptManager::MaybeCompleteLater() {
if (CanComplete()) {
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(&AttemptManager::MaybeComplete,
weak_ptr_factory_.GetWeakPtr()));
}
}
} // namespace net