summaryrefslogtreecommitdiffstats
path: root/netwerk/protocol/http/ConnectionEntry.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--netwerk/protocol/http/ConnectionEntry.cpp962
1 files changed, 962 insertions, 0 deletions
diff --git a/netwerk/protocol/http/ConnectionEntry.cpp b/netwerk/protocol/http/ConnectionEntry.cpp
new file mode 100644
index 0000000000..b7651fb169
--- /dev/null
+++ b/netwerk/protocol/http/ConnectionEntry.cpp
@@ -0,0 +1,962 @@
+/* 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"
+
+namespace mozilla {
+namespace net {
+
+// ConnectionEntry
+ConnectionEntry::~ConnectionEntry() {
+ LOG(("ConnectionEntry::~ConnectionEntry this=%p", this));
+
+ MOZ_ASSERT(!mIdleConns.Length());
+ MOZ_ASSERT(!mActiveConns.Length());
+ MOZ_ASSERT(!mHalfOpens.Length());
+ MOZ_ASSERT(!PendingQueueLength());
+ MOZ_ASSERT(!UrgentStartQueueLength());
+ MOZ_ASSERT(!mHalfOpenFastOpenBackups.Length());
+ MOZ_ASSERT(!mDoNotDestroy);
+}
+
+ConnectionEntry::ConnectionEntry(nsHttpConnectionInfo* ci)
+ : mConnInfo(ci),
+ mUsingSpdy(false),
+ mCanUseSpdy(true),
+ mPreferIPv4(false),
+ mPreferIPv6(false),
+ mUsedForConnection(false),
+ mDoNotDestroy(false) {
+ if (mConnInfo->FirstHopSSL() && !mConnInfo->IsHttp3()) {
+ mUseFastOpen = gHttpHandler->UseFastOpen();
+ } else {
+ // Only allow the TCP fast open on a secure connection.
+ mUseFastOpen = 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)
+ ? true
+ : false;
+}
+
+uint32_t ConnectionEntry::UnconnectedHalfOpens() const {
+ uint32_t unconnectedHalfOpens = 0;
+ for (uint32_t i = 0; i < mHalfOpens.Length(); ++i) {
+ if (!mHalfOpens[i]->HasConnected()) {
+ ++unconnectedHalfOpens;
+ }
+ }
+ return unconnectedHalfOpens;
+}
+
+bool ConnectionEntry::RemoveHalfOpen(HalfOpenSocket* halfOpen) {
+ bool isPrimary = false;
+ // A failure to create the transport object at all
+ // will result in it not being present in the halfopen table. That's expected.
+ if (mHalfOpens.RemoveElement(halfOpen)) {
+ isPrimary = true;
+ if (halfOpen->IsSpeculative()) {
+ Telemetry::AutoCounter<Telemetry::HTTPCONNMGR_UNUSED_SPECULATIVE_CONN>
+ unusedSpeculativeConn;
+ ++unusedSpeculativeConn;
+
+ if (halfOpen->IsFromPredictor()) {
+ Telemetry::AutoCounter<Telemetry::PREDICTOR_TOTAL_PRECONNECTS_UNUSED>
+ totalPreconnectsUnused;
+ ++totalPreconnectsUnused;
+ }
+ }
+
+ gHttpHandler->ConnMgr()->DecreaseNumHalfOpenConns();
+
+ } else {
+ mHalfOpenFastOpenBackups.RemoveElement(halfOpen);
+ }
+
+ if (!UnconnectedHalfOpens()) {
+ // 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::RemoveHalfOpen\n"
+ " failed to process pending queue\n"));
+ }
+ }
+
+ return isPrimary;
+}
+
+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();
+}
+
+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();
+}
+
+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();
+}
+
+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() && gHttpHandler->IsSpdyEnabled() &&
+ mUsingSpdy &&
+ (mHalfOpens.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 (UnconnectedHalfOpens()) {
+ 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 half-open'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() + UnconnectedHalfOpens();
+}
+
+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++;
+ }
+}
+
+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;
+}
+
+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();
+ }
+
+ for (int32_t index = mHalfOpenFastOpenBackups.Length() - 1; index >= 0;
+ --index) {
+ RefPtr<HalfOpenSocket> half = mHalfOpenFastOpenBackups[index];
+ half->CancelFastOpenConnection();
+ }
+}
+
+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()) {
+ // Iterate over all active connections and check them.
+ for (uint32_t index = 0; index < mActiveConns.Length(); ++index) {
+ RefPtr<nsHttpConnection> conn = do_QueryObject(mActiveConns[index]);
+ if (conn) {
+ conn->CheckForTraffic(true);
+ }
+ }
+ // 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();
+}
+
+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->DontReuse();
+ }
+ }
+
+ // Cancel any other pending connections - their associated transactions
+ // are in the pending queue and will be dispatched onto this new connection
+ for (int32_t index = HalfOpensLength() - 1; index >= 0; --index) {
+ RefPtr<HalfOpenSocket> half = mHalfOpens[index];
+ LOG((
+ "ConnectionEntry::MakeAllDontReuseExcept forcing halfopen abandon %p\n",
+ half.get()));
+ mHalfOpens[index]->Abandon();
+ }
+
+ for (int32_t index = mHalfOpenFastOpenBackups.Length() - 1; index >= 0;
+ --index) {
+ LOG(
+ ("ConnectionEntry::MakeAllDontReuseExcept shutting down connection in "
+ "fast "
+ "open state (%p) because new spdy connection (%p) takes "
+ "precedence\n",
+ mHalfOpenFastOpenBackups[index].get(), conn));
+ RefPtr<HalfOpenSocket> half = mHalfOpenFastOpenBackups[index];
+ half->CancelFastOpenConnection();
+ }
+}
+
+bool ConnectionEntry::FindConnToClaim(
+ PendingTransactionInfo* pendingTransInfo) {
+ nsHttpTransaction* trans = pendingTransInfo->Transaction();
+
+ uint32_t halfOpenLength = HalfOpensLength();
+ for (uint32_t i = 0; i < halfOpenLength; i++) {
+ auto halfOpen = mHalfOpens[i];
+ if (halfOpen->AcceptsTransaction(trans) &&
+ pendingTransInfo->TryClaimingHalfOpen(halfOpen)) {
+ // We've found a speculative connection or a connection that
+ // is free to be used in the half open 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 half open connection\n",
+ mConnInfo->HashKey().get()));
+
+ // return OK because we have essentially opened a new connection
+ // by converting a speculative half-open 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();
+ }
+ }
+ for (int32_t index = mHalfOpenFastOpenBackups.Length() - 1; index >= 0;
+ --index) {
+ LOG(
+ ("GetH2orH3ActiveConn() shutting down connection in fast "
+ "open state (%p) because we have an experienced spdy "
+ "connection (%p).\n",
+ mHalfOpenFastOpenBackups[index].get(), experienced));
+ RefPtr<HalfOpenSocket> half = mHalfOpenFastOpenBackups[index];
+ half->CancelFastOpenConnection();
+ }
+
+ 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::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"
+ " half-len=%zu pending=%zu"
+ " urgentStart pending=%zu\n",
+ this, mConnInfo->Origin(), IdleConnectionsLength(), ActiveConnsLength(),
+ mHalfOpens.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 half open sockets.
+ if (mHalfOpens.Length()) {
+ TimeStamp currentTime = TimeStamp::Now();
+ double maxConnectTime_ms = gHttpHandler->ConnectTimeout();
+
+ for (uint32_t index = mHalfOpens.Length(); index > 0;) {
+ index--;
+
+ HalfOpenSocket* half = mHalfOpens[index];
+ double delta = half->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 half open to %s after %.2fms.\n",
+ mConnInfo->HashKey().get(), delta));
+ if (half->SocketTransport()) {
+ half->SocketTransport()->Close(NS_ERROR_NET_TIMEOUT);
+ }
+ if (half->BackupTransport()) {
+ half->BackupTransport()->Close(NS_ERROR_NET_TIMEOUT);
+ }
+ }
+
+ // If this half open hangs around for 5 seconds after we've
+ // closed() it then just abandon the socket.
+ if (delta > maxConnectTime_ms + 5000) {
+ LOG(("Abandon half open to %s after %.2fms.\n",
+ mConnInfo->HashKey().get(), delta));
+ half->Abandon();
+ }
+ }
+ }
+ if (mHalfOpens.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;
+ }
+ }
+}
+
+void ConnectionEntry::InsertIntoHalfOpens(HalfOpenSocket* sock) {
+ mHalfOpens.AppendElement(sock);
+ gHttpHandler->ConnMgr()->IncreaseNumHalfOpenConns();
+}
+
+void ConnectionEntry::InsertIntoHalfOpenFastOpenBackups(HalfOpenSocket* sock) {
+ mHalfOpenFastOpenBackups.AppendElement(sock);
+}
+
+void ConnectionEntry::CloseAllHalfOpens() {
+ for (int32_t i = int32_t(HalfOpensLength()) - 1; i >= 0; i--) {
+ mHalfOpens[i]->Abandon();
+ }
+}
+
+void ConnectionEntry::RemoveHalfOpenFastOpenBackups(HalfOpenSocket* sock) {
+ mHalfOpenFastOpenBackups.RemoveElement(sock);
+}
+
+bool ConnectionEntry::IsInHalfOpens(HalfOpenSocket* sock) {
+ return mHalfOpens.Contains(sock);
+}
+
+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 < mHalfOpens.Length(); i++) {
+ HalfOpenSockets hSocket;
+ hSocket.speculative = mHalfOpens[i]->IsSpeculative();
+ data.halfOpens.AppendElement(hSocket);
+ }
+ 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 half-open sockets 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 half-open sockets belonging to the given transaction.
+ pendingTransInfo->AbandonHalfOpenAndForgetActiveConn();
+ 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 half opens and idle
+ // connections. This is to make sure the new echConfig will be used for the
+ // next connection.
+ CloseAllHalfOpens();
+ CloseIdleConnections();
+}
+
+} // namespace net
+} // namespace mozilla