gecko-dev/netwerk/protocol/http/ConnectionEntry.cpp
Kershaw Chang e04d585d85 Bug 1884349 - Make sure the pending connections will be closed, r=necko-reviewers,valentin
To make sure the pending connections to be closed soon, we just need to call DontReuse().
After it, the connection will be closed after the last transaction is done.

Differential Revision: https://phabricator.services.mozilla.com/D211747
2024-05-28 12:20:49 +00:00

1117 lines
35 KiB
C++

/* vim:set ts=4 sw=2 sts=2 et cin: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// HttpLog.h should generally be included first
#include "HttpLog.h"
// Log on level :5, instead of default :4.
#undef LOG
#define LOG(args) LOG5(args)
#undef LOG_ENABLED
#define LOG_ENABLED() LOG5_ENABLED()
#include "ConnectionEntry.h"
#include "nsQueryObject.h"
#include "mozilla/ChaosMode.h"
#include "mozilla/StaticPrefs_network.h"
#include "nsHttpHandler.h"
namespace mozilla {
namespace net {
// ConnectionEntry
ConnectionEntry::~ConnectionEntry() {
LOG(("ConnectionEntry::~ConnectionEntry this=%p", this));
MOZ_ASSERT(!mIdleConns.Length());
MOZ_ASSERT(!mActiveConns.Length());
MOZ_DIAGNOSTIC_ASSERT(!mDnsAndConnectSockets.Length());
MOZ_ASSERT(!PendingQueueLength());
MOZ_ASSERT(!UrgentStartQueueLength());
MOZ_ASSERT(!mDoNotDestroy);
}
ConnectionEntry::ConnectionEntry(nsHttpConnectionInfo* ci)
: mConnInfo(ci),
mUsingSpdy(false),
mCanUseSpdy(true),
mPreferIPv4(false),
mPreferIPv6(false),
mUsedForConnection(false),
mDoNotDestroy(false) {
LOG(("ConnectionEntry::ConnectionEntry this=%p key=%s", this,
ci->HashKey().get()));
}
bool ConnectionEntry::AvailableForDispatchNow() {
if (mIdleConns.Length() && mIdleConns[0]->CanReuse()) {
return true;
}
return gHttpHandler->ConnMgr()->GetH2orH3ActiveConn(this, false, false) !=
nullptr;
}
uint32_t ConnectionEntry::UnconnectedDnsAndConnectSockets() const {
uint32_t unconnectedDnsAndConnectSockets = 0;
for (uint32_t i = 0; i < mDnsAndConnectSockets.Length(); ++i) {
if (!mDnsAndConnectSockets[i]->HasConnected()) {
++unconnectedDnsAndConnectSockets;
}
}
return unconnectedDnsAndConnectSockets;
}
void ConnectionEntry::InsertIntoDnsAndConnectSockets(
DnsAndConnectSocket* sock) {
mDnsAndConnectSockets.AppendElement(sock);
gHttpHandler->ConnMgr()->IncreaseNumDnsAndConnectSockets();
}
void ConnectionEntry::RemoveDnsAndConnectSocket(DnsAndConnectSocket* dnsAndSock,
bool abandon) {
if (abandon) {
dnsAndSock->Abandon();
}
if (mDnsAndConnectSockets.RemoveElement(dnsAndSock)) {
gHttpHandler->ConnMgr()->DecreaseNumDnsAndConnectSockets();
}
if (!UnconnectedDnsAndConnectSockets()) {
// perhaps this reverted RestrictConnections()
// use the PostEvent version of processpendingq to avoid
// altering the pending q vector from an arbitrary stack
nsresult rv = gHttpHandler->ConnMgr()->ProcessPendingQ(mConnInfo);
if (NS_FAILED(rv)) {
LOG(
("ConnectionEntry::RemoveDnsAndConnectSocket\n"
" failed to process pending queue\n"));
}
}
}
void ConnectionEntry::CloseAllDnsAndConnectSockets() {
for (const auto& dnsAndSock : mDnsAndConnectSockets) {
dnsAndSock->Abandon();
gHttpHandler->ConnMgr()->DecreaseNumDnsAndConnectSockets();
}
mDnsAndConnectSockets.Clear();
nsresult rv = gHttpHandler->ConnMgr()->ProcessPendingQ(mConnInfo);
if (NS_FAILED(rv)) {
LOG(
("ConnectionEntry::CloseAllDnsAndConnectSockets\n"
" failed to process pending queue\n"));
}
}
void ConnectionEntry::DisallowHttp2() {
mCanUseSpdy = false;
// If we have any spdy connections, we want to go ahead and close them when
// they're done so we can free up some connections.
for (uint32_t i = 0; i < mActiveConns.Length(); ++i) {
if (mActiveConns[i]->UsingSpdy()) {
mActiveConns[i]->DontReuse();
}
}
for (uint32_t i = 0; i < mIdleConns.Length(); ++i) {
if (mIdleConns[i]->UsingSpdy()) {
mIdleConns[i]->DontReuse();
}
}
// Can't coalesce if we're not using spdy
mCoalescingKeys.Clear();
mAddresses.Clear();
}
void ConnectionEntry::DontReuseHttp3Conn() {
MOZ_ASSERT(mConnInfo->IsHttp3());
// If we have any spdy connections, we want to go ahead and close them when
// they're done so we can free up some connections.
for (uint32_t i = 0; i < mActiveConns.Length(); ++i) {
mActiveConns[i]->DontReuse();
}
// Can't coalesce if we're not using http3
mCoalescingKeys.Clear();
mAddresses.Clear();
}
void ConnectionEntry::RecordIPFamilyPreference(uint16_t family) {
LOG(("ConnectionEntry::RecordIPFamilyPreference %p, af=%u", this, family));
if (family == PR_AF_INET && !mPreferIPv6) {
mPreferIPv4 = true;
}
if (family == PR_AF_INET6 && !mPreferIPv4) {
mPreferIPv6 = true;
}
LOG((" %p prefer ipv4=%d, ipv6=%d", this, (bool)mPreferIPv4,
(bool)mPreferIPv6));
}
void ConnectionEntry::ResetIPFamilyPreference() {
LOG(("ConnectionEntry::ResetIPFamilyPreference %p", this));
mPreferIPv4 = false;
mPreferIPv6 = false;
}
bool net::ConnectionEntry::PreferenceKnown() const {
return (bool)mPreferIPv4 || (bool)mPreferIPv6;
}
size_t ConnectionEntry::PendingQueueLength() const {
return mPendingQ.PendingQueueLength();
}
size_t ConnectionEntry::PendingQueueLengthForWindow(uint64_t windowId) const {
return mPendingQ.PendingQueueLengthForWindow(windowId);
}
void ConnectionEntry::AppendPendingUrgentStartQ(
nsTArray<RefPtr<PendingTransactionInfo>>& result) {
mPendingQ.AppendPendingUrgentStartQ(result);
}
void ConnectionEntry::AppendPendingQForFocusedWindow(
uint64_t windowId, nsTArray<RefPtr<PendingTransactionInfo>>& result,
uint32_t maxCount) {
mPendingQ.AppendPendingQForFocusedWindow(windowId, result, maxCount);
LOG(
("ConnectionEntry::AppendPendingQForFocusedWindow [ci=%s], "
"pendingQ count=%zu for focused window (id=%" PRIu64 ")\n",
mConnInfo->HashKey().get(), result.Length(), windowId));
}
void ConnectionEntry::AppendPendingQForNonFocusedWindows(
uint64_t windowId, nsTArray<RefPtr<PendingTransactionInfo>>& result,
uint32_t maxCount) {
mPendingQ.AppendPendingQForNonFocusedWindows(windowId, result, maxCount);
LOG(
("ConnectionEntry::AppendPendingQForNonFocusedWindows [ci=%s], "
"pendingQ count=%zu for non focused window\n",
mConnInfo->HashKey().get(), result.Length()));
}
void ConnectionEntry::RemoveEmptyPendingQ() { mPendingQ.RemoveEmptyPendingQ(); }
void ConnectionEntry::InsertTransactionSorted(
nsTArray<RefPtr<PendingTransactionInfo>>& pendingQ,
PendingTransactionInfo* pendingTransInfo,
bool aInsertAsFirstForTheSamePriority /*= false*/) {
mPendingQ.InsertTransactionSorted(pendingQ, pendingTransInfo,
aInsertAsFirstForTheSamePriority);
}
void ConnectionEntry::ReschedTransaction(nsHttpTransaction* aTrans) {
mPendingQ.ReschedTransaction(aTrans);
}
void ConnectionEntry::InsertTransaction(
PendingTransactionInfo* pendingTransInfo,
bool aInsertAsFirstForTheSamePriority /* = false */) {
mPendingQ.InsertTransaction(pendingTransInfo,
aInsertAsFirstForTheSamePriority);
pendingTransInfo->Transaction()->OnPendingQueueInserted(mConnInfo->HashKey());
}
nsTArray<RefPtr<PendingTransactionInfo>>*
ConnectionEntry::GetTransactionPendingQHelper(nsAHttpTransaction* trans) {
return mPendingQ.GetTransactionPendingQHelper(trans);
}
bool ConnectionEntry::RestrictConnections() {
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
if (AvailableForDispatchNow()) {
// this might be a h2/spdy connection in this connection entry that
// is able to be immediately muxxed, or it might be one that
// was found in the same state through a coalescing hash
LOG(
("ConnectionEntry::RestrictConnections %p %s restricted due to "
"active >=h2\n",
this, mConnInfo->HashKey().get()));
return true;
}
// If this host is trying to negotiate a SPDY session right now,
// don't create any new ssl connections until the result of the
// negotiation is known.
bool doRestrict = mConnInfo->FirstHopSSL() &&
StaticPrefs::network_http_http2_enabled() && mUsingSpdy &&
(mDnsAndConnectSockets.Length() || mActiveConns.Length());
// If there are no restrictions, we are done
if (!doRestrict) {
return false;
}
// If the restriction is based on a tcp handshake in progress
// let that connect and then see if it was SPDY or not
if (UnconnectedDnsAndConnectSockets()) {
return true;
}
// There is a concern that a host is using a mix of HTTP/1 and SPDY.
// In that case we don't want to restrict connections just because
// there is a single active HTTP/1 session in use.
if (mUsingSpdy && mActiveConns.Length()) {
bool confirmedRestrict = false;
for (uint32_t index = 0; index < mActiveConns.Length(); ++index) {
HttpConnectionBase* conn = mActiveConns[index];
RefPtr<nsHttpConnection> connTCP = do_QueryObject(conn);
if ((connTCP && !connTCP->ReportedNPN()) || conn->CanDirectlyActivate()) {
confirmedRestrict = true;
break;
}
}
doRestrict = confirmedRestrict;
if (!confirmedRestrict) {
LOG(
("nsHttpConnectionMgr spdy connection restriction to "
"%s bypassed.\n",
mConnInfo->Origin()));
}
}
return doRestrict;
}
uint32_t ConnectionEntry::TotalActiveConnections() const {
// Add in the in-progress tcp connections, we will assume they are
// keepalive enabled.
// Exclude DnsAndConnectSocket's that has already created a usable connection.
// This prevents the limit being stuck on ipv6 connections that
// eventually time out after typical 21 seconds of no ACK+SYN reply.
return mActiveConns.Length() + UnconnectedDnsAndConnectSockets();
}
size_t ConnectionEntry::UrgentStartQueueLength() {
return mPendingQ.UrgentStartQueueLength();
}
void ConnectionEntry::PrintPendingQ() { mPendingQ.PrintPendingQ(); }
void ConnectionEntry::Compact() {
mIdleConns.Compact();
mActiveConns.Compact();
mPendingQ.Compact();
}
void ConnectionEntry::RemoveFromIdleConnectionsIndex(size_t inx) {
mIdleConns.RemoveElementAt(inx);
gHttpHandler->ConnMgr()->DecrementNumIdleConns();
}
bool ConnectionEntry::RemoveFromIdleConnections(nsHttpConnection* conn) {
if (!mIdleConns.RemoveElement(conn)) {
return false;
}
gHttpHandler->ConnMgr()->DecrementNumIdleConns();
return true;
}
void ConnectionEntry::CancelAllTransactions(nsresult reason) {
mPendingQ.CancelAllTransactions(reason);
}
nsresult ConnectionEntry::CloseIdleConnection(nsHttpConnection* conn) {
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
RefPtr<nsHttpConnection> deleteProtector(conn);
if (!RemoveFromIdleConnections(conn)) {
return NS_ERROR_UNEXPECTED;
}
// The connection is closed immediately no need to call EndIdleMonitoring.
conn->Close(NS_ERROR_ABORT);
return NS_OK;
}
void ConnectionEntry::CloseIdleConnections() {
while (mIdleConns.Length()) {
RefPtr<nsHttpConnection> conn(mIdleConns[0]);
RemoveFromIdleConnectionsIndex(0);
// The connection is closed immediately no need to call EndIdleMonitoring.
conn->Close(NS_ERROR_ABORT);
}
}
void ConnectionEntry::CloseIdleConnections(uint32_t maxToClose) {
uint32_t closed = 0;
while (mIdleConns.Length() && (closed < maxToClose)) {
RefPtr<nsHttpConnection> conn(mIdleConns[0]);
RemoveFromIdleConnectionsIndex(0);
// The connection is closed immediately no need to call EndIdleMonitoring.
conn->Close(NS_ERROR_ABORT);
closed++;
}
}
void ConnectionEntry::CloseH2WebsocketConnections() {
while (mH2WebsocketConns.Length()) {
RefPtr<HttpConnectionBase> conn(mH2WebsocketConns[0]);
mH2WebsocketConns.RemoveElementAt(0);
// safe to close connection since we are on the socket thread
// closing via transaction to break connection/transaction bond
conn->CloseTransaction(conn->Transaction(), NS_ERROR_ABORT, true);
}
}
nsresult ConnectionEntry::RemoveIdleConnection(nsHttpConnection* conn) {
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
if (!RemoveFromIdleConnections(conn)) {
return NS_ERROR_UNEXPECTED;
}
conn->EndIdleMonitoring();
return NS_OK;
}
bool ConnectionEntry::IsInIdleConnections(HttpConnectionBase* conn) {
RefPtr<nsHttpConnection> connTCP = do_QueryObject(conn);
return connTCP && mIdleConns.Contains(connTCP);
}
already_AddRefed<nsHttpConnection> ConnectionEntry::GetIdleConnection(
bool respectUrgency, bool urgentTrans, bool* onlyUrgent) {
RefPtr<nsHttpConnection> conn;
size_t index = 0;
while (!conn && (mIdleConns.Length() > index)) {
conn = mIdleConns[index];
if (!conn->CanReuse()) {
RemoveFromIdleConnectionsIndex(index);
LOG((" dropping stale connection: [conn=%p]\n", conn.get()));
conn->Close(NS_ERROR_ABORT);
conn = nullptr;
continue;
}
// non-urgent transactions can only be dispatched on non-urgent
// started or used connections.
if (respectUrgency && conn->IsUrgentStartPreferred() && !urgentTrans) {
LOG((" skipping urgent: [conn=%p]", conn.get()));
conn = nullptr;
++index;
continue;
}
*onlyUrgent = false;
RemoveFromIdleConnectionsIndex(index);
conn->EndIdleMonitoring();
LOG((" reusing connection: [conn=%p]\n", conn.get()));
}
return conn.forget();
}
nsresult ConnectionEntry::RemoveActiveConnection(HttpConnectionBase* conn) {
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
if (!mActiveConns.RemoveElement(conn)) {
return NS_ERROR_UNEXPECTED;
}
gHttpHandler->ConnMgr()->DecrementActiveConnCount(conn);
return NS_OK;
}
nsresult ConnectionEntry::RemovePendingConnection(HttpConnectionBase* conn) {
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
if (!mPendingConns.RemoveElement(conn)) {
return NS_ERROR_UNEXPECTED;
}
return NS_OK;
}
void ConnectionEntry::ClosePersistentConnections() {
LOG(("ConnectionEntry::ClosePersistentConnections [ci=%s]\n",
mConnInfo->HashKey().get()));
CloseIdleConnections();
int32_t activeCount = mActiveConns.Length();
for (int32_t i = 0; i < activeCount; i++) {
mActiveConns[i]->DontReuse();
}
mCoalescingKeys.Clear();
mAddresses.Clear();
}
uint32_t ConnectionEntry::PruneDeadConnections() {
uint32_t timeToNextExpire = UINT32_MAX;
for (int32_t len = mIdleConns.Length(); len > 0; --len) {
int32_t idx = len - 1;
RefPtr<nsHttpConnection> conn(mIdleConns[idx]);
if (!conn->CanReuse()) {
RemoveFromIdleConnectionsIndex(idx);
// The connection is closed immediately no need to call
// EndIdleMonitoring.
conn->Close(NS_ERROR_ABORT);
} else {
timeToNextExpire = std::min(timeToNextExpire, conn->TimeToLive());
}
}
if (mUsingSpdy) {
for (uint32_t i = 0; i < mActiveConns.Length(); ++i) {
RefPtr<nsHttpConnection> connTCP = do_QueryObject(mActiveConns[i]);
// Http3 has its own timers, it is not using this one.
if (connTCP && connTCP->UsingSpdy()) {
if (!connTCP->CanReuse()) {
// Marking it don't-reuse will create an active
// tear down if the spdy session is idle.
connTCP->DontReuse();
} else {
timeToNextExpire = std::min(timeToNextExpire, connTCP->TimeToLive());
}
}
}
}
return timeToNextExpire;
}
void ConnectionEntry::VerifyTraffic() {
if (!mConnInfo->IsHttp3()) {
for (uint32_t index = 0; index < mPendingConns.Length(); ++index) {
RefPtr<nsHttpConnection> conn = do_QueryObject(mPendingConns[index]);
if (conn) {
conn->CheckForTraffic(true);
}
}
uint32_t numConns = mActiveConns.Length();
if (numConns) {
// Walk the list backwards to allow us to remove entries easily.
for (int index = numConns - 1; index >= 0; index--) {
RefPtr<nsHttpConnection> conn = do_QueryObject(mActiveConns[index]);
if (conn) {
conn->CheckForTraffic(true);
if (conn->EverUsedSpdy() &&
StaticPrefs::
network_http_http2_move_to_pending_list_after_network_change()) {
mActiveConns.RemoveElementAt(index);
gHttpHandler->ConnMgr()->DecrementActiveConnCount(conn);
mPendingConns.AppendElement(conn);
// After DontReuse(), the connection will be closed after the last
// transition is done.
conn->DontReuse();
LOG(("Move active connection to pending list [conn=%p]\n",
conn.get()));
}
}
}
}
// Iterate the idle connections and unmark them for traffic checks.
for (uint32_t index = 0; index < mIdleConns.Length(); ++index) {
RefPtr<nsHttpConnection> conn = do_QueryObject(mIdleConns[index]);
if (conn) {
conn->CheckForTraffic(false);
}
}
}
}
void ConnectionEntry::InsertIntoIdleConnections_internal(
nsHttpConnection* conn) {
uint32_t idx;
for (idx = 0; idx < mIdleConns.Length(); idx++) {
nsHttpConnection* idleConn = mIdleConns[idx];
if (idleConn->MaxBytesRead() < conn->MaxBytesRead()) {
break;
}
}
mIdleConns.InsertElementAt(idx, conn);
}
void ConnectionEntry::InsertIntoIdleConnections(nsHttpConnection* conn) {
InsertIntoIdleConnections_internal(conn);
gHttpHandler->ConnMgr()->NewIdleConnectionAdded(conn->TimeToLive());
conn->BeginIdleMonitoring();
}
bool ConnectionEntry::IsInActiveConns(HttpConnectionBase* conn) {
return mActiveConns.Contains(conn);
}
void ConnectionEntry::InsertIntoActiveConns(HttpConnectionBase* conn) {
mActiveConns.AppendElement(conn);
gHttpHandler->ConnMgr()->IncrementActiveConnCount();
}
bool ConnectionEntry::IsInH2WebsocketConns(HttpConnectionBase* conn) {
return mH2WebsocketConns.Contains(conn);
}
void ConnectionEntry::InsertIntoH2WebsocketConns(HttpConnectionBase* conn) {
// no incrementing of connection count since it is just a "fake" connection
mH2WebsocketConns.AppendElement(conn);
}
void ConnectionEntry::RemoveH2WebsocketConns(HttpConnectionBase* conn) {
mH2WebsocketConns.RemoveElement(conn);
}
void ConnectionEntry::MakeAllDontReuseExcept(HttpConnectionBase* conn) {
for (uint32_t index = 0; index < mActiveConns.Length(); ++index) {
HttpConnectionBase* otherConn = mActiveConns[index];
if (otherConn != conn) {
LOG(
("ConnectionEntry::MakeAllDontReuseExcept shutting down old "
"connection (%p) "
"because new "
"spdy connection (%p) takes precedence\n",
otherConn, conn));
otherConn->SetCloseReason(
ConnectionCloseReason::CLOSE_EXISTING_CONN_FOR_COALESCING);
otherConn->DontReuse();
}
}
// Cancel any other pending connections - their associated transactions
// are in the pending queue and will be dispatched onto this new connection
CloseAllDnsAndConnectSockets();
}
bool ConnectionEntry::FindConnToClaim(
PendingTransactionInfo* pendingTransInfo) {
nsHttpTransaction* trans = pendingTransInfo->Transaction();
for (const auto& dnsAndSock : mDnsAndConnectSockets) {
if (dnsAndSock->AcceptsTransaction(trans) && dnsAndSock->Claim()) {
pendingTransInfo->RememberDnsAndConnectSocket(dnsAndSock);
// We've found a speculative connection or a connection that
// is free to be used in the DnsAndConnectSockets list.
// A free to be used connection is a connection that was
// open for a concrete transaction, but that trunsaction
// ended up using another connection.
LOG(
("ConnectionEntry::FindConnToClaim [ci = %s]\n"
"Found a speculative or a free-to-use DnsAndConnectSocket\n",
mConnInfo->HashKey().get()));
// return OK because we have essentially opened a new connection
// by converting a speculative DnsAndConnectSockets to general use
return true;
}
}
// consider null transactions that are being used to drive the ssl handshake
// if the transaction creating this connection can re-use persistent
// connections
if (trans->Caps() & NS_HTTP_ALLOW_KEEPALIVE) {
uint32_t activeLength = mActiveConns.Length();
for (uint32_t i = 0; i < activeLength; i++) {
if (pendingTransInfo->TryClaimingActiveConn(mActiveConns[i])) {
LOG(
("ConnectionEntry::FindConnectingSocket [ci = %s] "
"Claiming a null transaction for later use\n",
mConnInfo->HashKey().get()));
return true;
}
}
}
return false;
}
bool ConnectionEntry::MakeFirstActiveSpdyConnDontReuse() {
if (!mUsingSpdy) {
return false;
}
for (uint32_t index = 0; index < mActiveConns.Length(); ++index) {
HttpConnectionBase* conn = mActiveConns[index];
if (conn->UsingSpdy() && conn->CanReuse()) {
conn->DontReuse();
return true;
}
}
return false;
}
// Return an active h2 or h3 connection
// that can be directly activated or null.
HttpConnectionBase* ConnectionEntry::GetH2orH3ActiveConn() {
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
HttpConnectionBase* experienced = nullptr;
HttpConnectionBase* noExperience = nullptr;
uint32_t activeLen = mActiveConns.Length();
// activeLen should generally be 1.. this is a setup race being resolved
// take a conn who can activate and is experienced
for (uint32_t index = 0; index < activeLen; ++index) {
HttpConnectionBase* tmp = mActiveConns[index];
if (tmp->CanDirectlyActivate()) {
if (tmp->IsExperienced()) {
experienced = tmp;
break;
}
noExperience = tmp; // keep looking for a better option
}
}
// if that worked, cleanup anything else and exit
if (experienced) {
for (uint32_t index = 0; index < activeLen; ++index) {
HttpConnectionBase* tmp = mActiveConns[index];
// in the case where there is a functional h2 session, drop the others
if (tmp != experienced) {
tmp->DontReuse();
}
}
LOG(
("GetH2orH3ActiveConn() request for ent %p %s "
"found an active experienced connection %p in native connection "
"entry\n",
this, mConnInfo->HashKey().get(), experienced));
return experienced;
}
if (noExperience) {
LOG(
("GetH2orH3ActiveConn() request for ent %p %s "
"found an active but inexperienced connection %p in native connection "
"entry\n",
this, mConnInfo->HashKey().get(), noExperience));
return noExperience;
}
return nullptr;
}
void ConnectionEntry::CloseActiveConnections() {
while (mActiveConns.Length()) {
RefPtr<HttpConnectionBase> conn(mActiveConns[0]);
mActiveConns.RemoveElementAt(0);
gHttpHandler->ConnMgr()->DecrementActiveConnCount(conn);
// Since HttpConnectionBase::Close doesn't break the bond with
// the connection's transaction, we must explicitely tell it
// to close its transaction and not just self.
conn->CloseTransaction(conn->Transaction(), NS_ERROR_ABORT, true);
}
}
void ConnectionEntry::CloseAllActiveConnsWithNullTransactcion(
nsresult aCloseCode) {
for (uint32_t index = 0; index < mActiveConns.Length(); ++index) {
RefPtr<HttpConnectionBase> activeConn = mActiveConns[index];
nsAHttpTransaction* liveTransaction = activeConn->Transaction();
if (liveTransaction && liveTransaction->IsNullTransaction()) {
LOG(
("ConnectionEntry::CloseAllActiveConnsWithNullTransactcion "
"also canceling Null Transaction %p on conn %p\n",
liveTransaction, activeConn.get()));
activeConn->CloseTransaction(liveTransaction, aCloseCode);
}
}
}
void ConnectionEntry::ClosePendingConnections() {
while (mPendingConns.Length()) {
RefPtr<HttpConnectionBase> conn(mPendingConns[0]);
mPendingConns.RemoveElementAt(0);
// Since HttpConnectionBase::Close doesn't break the bond with
// the connection's transaction, we must explicitely tell it
// to close its transaction and not just self.
conn->CloseTransaction(conn->Transaction(), NS_ERROR_ABORT, true);
}
}
void ConnectionEntry::PruneNoTraffic() {
LOG((" pruning no traffic [ci=%s]\n", mConnInfo->HashKey().get()));
if (mConnInfo->IsHttp3()) {
return;
}
uint32_t numConns = mActiveConns.Length();
if (numConns) {
// Walk the list backwards to allow us to remove entries easily.
for (int index = numConns - 1; index >= 0; index--) {
RefPtr<nsHttpConnection> conn = do_QueryObject(mActiveConns[index]);
if (conn && conn->NoTraffic()) {
mActiveConns.RemoveElementAt(index);
gHttpHandler->ConnMgr()->DecrementActiveConnCount(conn);
conn->Close(NS_ERROR_ABORT);
LOG(
(" closed active connection due to no traffic "
"[conn=%p]\n",
conn.get()));
}
}
}
}
uint32_t ConnectionEntry::TimeoutTick() {
uint32_t timeoutTickNext = 3600; // 1hr
if (mConnInfo->IsHttp3()) {
return timeoutTickNext;
}
LOG(
("ConnectionEntry::TimeoutTick() this=%p host=%s "
"idle=%zu active=%zu"
" dnsAndSock-len=%zu pending=%zu"
" urgentStart pending=%zu\n",
this, mConnInfo->Origin(), IdleConnectionsLength(), ActiveConnsLength(),
mDnsAndConnectSockets.Length(), PendingQueueLength(),
UrgentStartQueueLength()));
// First call the tick handler for each active connection.
PRIntervalTime tickTime = PR_IntervalNow();
for (uint32_t index = 0; index < mActiveConns.Length(); ++index) {
RefPtr<nsHttpConnection> conn = do_QueryObject(mActiveConns[index]);
if (conn) {
uint32_t connNextTimeout = conn->ReadTimeoutTick(tickTime);
timeoutTickNext = std::min(timeoutTickNext, connNextTimeout);
}
}
// Now check for any stalled DnsAndConnectSockets.
if (mDnsAndConnectSockets.Length()) {
TimeStamp currentTime = TimeStamp::Now();
double maxConnectTime_ms = gHttpHandler->ConnectTimeout();
for (const auto& dnsAndSock : Reversed(mDnsAndConnectSockets)) {
double delta = dnsAndSock->Duration(currentTime);
// If the socket has timed out, close it so the waiting
// transaction will get the proper signal.
if (delta > maxConnectTime_ms) {
LOG(("Force timeout of DnsAndConnectSocket to %s after %.2fms.\n",
mConnInfo->HashKey().get(), delta));
dnsAndSock->CloseTransports(NS_ERROR_NET_TIMEOUT);
}
// If this DnsAndConnectSocket hangs around for 5 seconds after we've
// closed() it then just abandon the socket.
if (delta > maxConnectTime_ms + 5000) {
LOG(("Abandon DnsAndConnectSocket to %s after %.2fms.\n",
mConnInfo->HashKey().get(), delta));
RemoveDnsAndConnectSocket(dnsAndSock, true);
}
}
}
if (mDnsAndConnectSockets.Length()) {
timeoutTickNext = 1;
}
return timeoutTickNext;
}
void ConnectionEntry::MoveConnection(HttpConnectionBase* proxyConn,
ConnectionEntry* otherEnt) {
// To avoid changing mNumActiveConns/mNumIdleConns counter use internal
// functions.
RefPtr<HttpConnectionBase> deleteProtector(proxyConn);
if (mActiveConns.RemoveElement(proxyConn)) {
otherEnt->mActiveConns.AppendElement(proxyConn);
return;
}
RefPtr<nsHttpConnection> proxyConnTCP = do_QueryObject(proxyConn);
if (proxyConnTCP) {
if (mIdleConns.RemoveElement(proxyConnTCP)) {
otherEnt->InsertIntoIdleConnections_internal(proxyConnTCP);
return;
}
}
}
HttpRetParams ConnectionEntry::GetConnectionData() {
HttpRetParams data;
data.host = mConnInfo->Origin();
data.port = mConnInfo->OriginPort();
for (uint32_t i = 0; i < mActiveConns.Length(); i++) {
HttpConnInfo info;
RefPtr<nsHttpConnection> connTCP = do_QueryObject(mActiveConns[i]);
if (connTCP) {
info.ttl = connTCP->TimeToLive();
} else {
info.ttl = 0;
}
info.rtt = mActiveConns[i]->Rtt();
info.SetHTTPProtocolVersion(mActiveConns[i]->Version());
data.active.AppendElement(info);
}
for (uint32_t i = 0; i < mIdleConns.Length(); i++) {
HttpConnInfo info;
info.ttl = mIdleConns[i]->TimeToLive();
info.rtt = mIdleConns[i]->Rtt();
info.SetHTTPProtocolVersion(mIdleConns[i]->Version());
data.idle.AppendElement(info);
}
for (uint32_t i = 0; i < mDnsAndConnectSockets.Length(); i++) {
DnsAndConnectSockets dnsAndSock{};
dnsAndSock.speculative = mDnsAndConnectSockets[i]->IsSpeculative();
data.dnsAndSocks.AppendElement(dnsAndSock);
}
if (mConnInfo->IsHttp3()) {
data.httpVersion = "HTTP/3"_ns;
} else if (mUsingSpdy) {
data.httpVersion = "HTTP/2"_ns;
} else {
data.httpVersion = "HTTP <= 1.1"_ns;
}
data.ssl = mConnInfo->EndToEndSSL();
return data;
}
void ConnectionEntry::LogConnections() {
if (!mConnInfo->IsHttp3()) {
LOG(("active urgent conns ["));
for (HttpConnectionBase* conn : mActiveConns) {
RefPtr<nsHttpConnection> connTCP = do_QueryObject(conn);
MOZ_ASSERT(connTCP);
if (connTCP->IsUrgentStartPreferred()) {
LOG((" %p", conn));
}
}
LOG(("] active regular conns ["));
for (HttpConnectionBase* conn : mActiveConns) {
RefPtr<nsHttpConnection> connTCP = do_QueryObject(conn);
MOZ_ASSERT(connTCP);
if (!connTCP->IsUrgentStartPreferred()) {
LOG((" %p", conn));
}
}
LOG(("] idle urgent conns ["));
for (nsHttpConnection* conn : mIdleConns) {
if (conn->IsUrgentStartPreferred()) {
LOG((" %p", conn));
}
}
LOG(("] idle regular conns ["));
for (nsHttpConnection* conn : mIdleConns) {
if (!conn->IsUrgentStartPreferred()) {
LOG((" %p", conn));
}
}
} else {
LOG(("active conns ["));
for (HttpConnectionBase* conn : mActiveConns) {
LOG((" %p", conn));
}
MOZ_ASSERT(mIdleConns.Length() == 0);
}
LOG(("]"));
}
bool ConnectionEntry::RemoveTransFromPendingQ(nsHttpTransaction* aTrans) {
// We will abandon all DnsAndConnectSockets belonging to the given
// transaction.
nsTArray<RefPtr<PendingTransactionInfo>>* infoArray =
GetTransactionPendingQHelper(aTrans);
RefPtr<PendingTransactionInfo> pendingTransInfo;
int32_t transIndex =
infoArray ? infoArray->IndexOf(aTrans, 0, PendingComparator()) : -1;
if (transIndex >= 0) {
pendingTransInfo = (*infoArray)[transIndex];
infoArray->RemoveElementAt(transIndex);
}
if (!pendingTransInfo) {
return false;
}
// Abandon all DnsAndConnectSockets belonging to the given transaction.
nsWeakPtr tmp = pendingTransInfo->ForgetDnsAndConnectSocketAndActiveConn();
RefPtr<DnsAndConnectSocket> dnsAndSock = do_QueryReferent(tmp);
if (dnsAndSock) {
RemoveDnsAndConnectSocket(dnsAndSock, true);
}
return true;
}
void ConnectionEntry::MaybeUpdateEchConfig(nsHttpConnectionInfo* aConnInfo) {
if (!mConnInfo->HashKey().Equals(aConnInfo->HashKey())) {
return;
}
const nsCString& echConfig = aConnInfo->GetEchConfig();
if (mConnInfo->GetEchConfig().Equals(echConfig)) {
return;
}
LOG(("ConnectionEntry::MaybeUpdateEchConfig [ci=%s]\n",
mConnInfo->HashKey().get()));
mConnInfo->SetEchConfig(echConfig);
// If echConfig is changed, we should close all DnsAndConnectSockets and idle
// connections. This is to make sure the new echConfig will be used for the
// next connection.
CloseAllDnsAndConnectSockets();
CloseIdleConnections();
}
bool ConnectionEntry::MaybeProcessCoalescingKeys(nsIDNSAddrRecord* dnsRecord,
bool aIsHttp3) {
if (!mConnInfo || !mConnInfo->EndToEndSSL() || (!aIsHttp3 && !AllowHttp2()) ||
mConnInfo->UsingProxy() || !mCoalescingKeys.IsEmpty() || !dnsRecord) {
return false;
}
nsresult rv = dnsRecord->GetAddresses(mAddresses);
if (NS_FAILED(rv) || mAddresses.IsEmpty()) {
return false;
}
for (uint32_t i = 0; i < mAddresses.Length(); ++i) {
if ((mAddresses[i].raw.family == AF_INET && mAddresses[i].inet.ip == 0) ||
(mAddresses[i].raw.family == AF_INET6 &&
mAddresses[i].inet6.ip.u64[0] == 0 &&
mAddresses[i].inet6.ip.u64[1] == 0)) {
// Bug 1680249 - Don't create the coalescing key if the ip address is
// `0.0.0.0` or `::`.
LOG(
("ConnectionEntry::MaybeProcessCoalescingKeys skip creating "
"Coalescing Key for host [%s]",
mConnInfo->Origin()));
continue;
}
nsCString* newKey = mCoalescingKeys.AppendElement(nsCString());
newKey->SetLength(kIPv6CStrBufSize + 26);
mAddresses[i].ToStringBuffer(newKey->BeginWriting(), kIPv6CStrBufSize);
newKey->SetLength(strlen(newKey->BeginReading()));
if (mConnInfo->GetAnonymous()) {
newKey->AppendLiteral("~A:");
} else {
newKey->AppendLiteral("~.:");
}
if (mConnInfo->GetFallbackConnection()) {
newKey->AppendLiteral("~F:");
} else {
newKey->AppendLiteral("~.:");
}
newKey->AppendInt(mConnInfo->OriginPort());
newKey->AppendLiteral("/[");
nsAutoCString suffix;
mConnInfo->GetOriginAttributes().CreateSuffix(suffix);
newKey->Append(suffix);
newKey->AppendLiteral("]viaDNS");
LOG(
("ConnectionEntry::MaybeProcessCoalescingKeys "
"Established New Coalescing Key # %d for host "
"%s [%s]",
i, mConnInfo->Origin(), newKey->get()));
}
return true;
}
nsresult ConnectionEntry::CreateDnsAndConnectSocket(
nsAHttpTransaction* trans, uint32_t caps, bool speculative,
bool isFromPredictor, bool urgentStart, bool allow1918,
PendingTransactionInfo* pendingTransInfo) {
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
MOZ_ASSERT((speculative && !pendingTransInfo) ||
(!speculative && pendingTransInfo));
RefPtr<DnsAndConnectSocket> sock = new DnsAndConnectSocket(
mConnInfo, trans, caps, speculative, isFromPredictor, urgentStart);
if (speculative) {
sock->SetAllow1918(allow1918);
}
nsresult rv = sock->Init(this);
if (NS_FAILED(rv)) {
sock->Abandon();
return rv;
}
InsertIntoDnsAndConnectSockets(sock);
if (pendingTransInfo && sock->Claim()) {
pendingTransInfo->RememberDnsAndConnectSocket(sock);
}
return NS_OK;
}
bool ConnectionEntry::AllowToRetryDifferentIPFamilyForHttp3(nsresult aError) {
LOG(
("ConnectionEntry::AllowToRetryDifferentIPFamilyForHttp3 %p "
"error=%" PRIx32,
this, static_cast<uint32_t>(aError)));
if (!IsHttp3()) {
MOZ_ASSERT(false, "Should not be called for non Http/3 connection");
return false;
}
if (!StaticPrefs::network_http_http3_retry_different_ip_family()) {
return false;
}
// Only allow to retry with these two errors.
if (aError != NS_ERROR_CONNECTION_REFUSED &&
aError != NS_ERROR_PROXY_CONNECTION_REFUSED) {
return false;
}
// Already retried once.
if (mRetriedDifferentIPFamilyForHttp3) {
return false;
}
return true;
}
void ConnectionEntry::SetRetryDifferentIPFamilyForHttp3(uint16_t aIPFamily) {
LOG(("ConnectionEntry::SetRetryDifferentIPFamilyForHttp3 %p, af=%u", this,
aIPFamily));
mPreferIPv4 = false;
mPreferIPv6 = false;
if (aIPFamily == AF_INET) {
mPreferIPv6 = true;
}
if (aIPFamily == AF_INET6) {
mPreferIPv4 = true;
}
mRetriedDifferentIPFamilyForHttp3 = true;
LOG((" %p prefer ipv4=%d, ipv6=%d", this, (bool)mPreferIPv4,
(bool)mPreferIPv6));
MOZ_DIAGNOSTIC_ASSERT(mPreferIPv4 ^ mPreferIPv6);
}
void ConnectionEntry::SetServerCertHashes(
nsTArray<RefPtr<nsIWebTransportHash>>&& aHashes) {
mServerCertHashes = std::move(aHashes);
}
const nsTArray<RefPtr<nsIWebTransportHash>>&
ConnectionEntry::GetServerCertHashes() {
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
return mServerCertHashes;
}
} // namespace net
} // namespace mozilla