summaryrefslogtreecommitdiffstats
path: root/netwerk/protocol/http/nsHttpConnectionMgr.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'netwerk/protocol/http/nsHttpConnectionMgr.cpp')
-rw-r--r--netwerk/protocol/http/nsHttpConnectionMgr.cpp4006
1 files changed, 4006 insertions, 0 deletions
diff --git a/netwerk/protocol/http/nsHttpConnectionMgr.cpp b/netwerk/protocol/http/nsHttpConnectionMgr.cpp
new file mode 100644
index 000000000..abae51e2f
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpConnectionMgr.cpp
@@ -0,0 +1,4006 @@
+/* vim:set ts=4 sw=4 sts=4 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 "nsHttpConnectionMgr.h"
+#include "nsHttpConnection.h"
+#include "nsHttpPipeline.h"
+#include "nsHttpHandler.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsNetCID.h"
+#include "nsCOMPtr.h"
+#include "nsNetUtil.h"
+#include "mozilla/net/DNS.h"
+#include "nsISocketTransport.h"
+#include "nsISSLSocketControl.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/net/DashboardTypes.h"
+#include "NullHttpTransaction.h"
+#include "nsIDNSRecord.h"
+#include "nsITransport.h"
+#include "nsInterfaceRequestorAgg.h"
+#include "nsIRequestContext.h"
+#include "nsISocketTransportService.h"
+#include <algorithm>
+#include "mozilla/ChaosMode.h"
+#include "mozilla/Unused.h"
+#include "nsIURI.h"
+
+#include "mozilla/Telemetry.h"
+
+namespace mozilla {
+namespace net {
+
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS(nsHttpConnectionMgr, nsIObserver)
+
+static void
+InsertTransactionSorted(nsTArray<RefPtr<nsHttpTransaction> > &pendingQ, nsHttpTransaction *trans)
+{
+ // insert into queue with smallest valued number first. search in reverse
+ // order under the assumption that many of the existing transactions will
+ // have the same priority (usually 0).
+
+ for (int32_t i = pendingQ.Length() - 1; i >= 0; --i) {
+ nsHttpTransaction *t = pendingQ[i];
+ if (trans->Priority() >= t->Priority()) {
+ if (ChaosMode::isActive(ChaosFeature::NetworkScheduling)) {
+ int32_t samePriorityCount;
+ for (samePriorityCount = 0; i - samePriorityCount >= 0; ++samePriorityCount) {
+ if (pendingQ[i - samePriorityCount]->Priority() != trans->Priority()) {
+ break;
+ }
+ }
+ // skip over 0...all of the elements with the same priority.
+ i -= ChaosMode::randomUint32LessThan(samePriorityCount + 1);
+ }
+ pendingQ.InsertElementAt(i+1, trans);
+ return;
+ }
+ }
+ pendingQ.InsertElementAt(0, trans);
+}
+
+//-----------------------------------------------------------------------------
+
+nsHttpConnectionMgr::nsHttpConnectionMgr()
+ : mReentrantMonitor("nsHttpConnectionMgr.mReentrantMonitor")
+ , mMaxConns(0)
+ , mMaxPersistConnsPerHost(0)
+ , mMaxPersistConnsPerProxy(0)
+ , mIsShuttingDown(false)
+ , mNumActiveConns(0)
+ , mNumIdleConns(0)
+ , mNumSpdyActiveConns(0)
+ , mNumHalfOpenConns(0)
+ , mTimeOfNextWakeUp(UINT64_MAX)
+ , mPruningNoTraffic(false)
+ , mTimeoutTickArmed(false)
+ , mTimeoutTickNext(1)
+{
+ LOG(("Creating nsHttpConnectionMgr @%p\n", this));
+}
+
+nsHttpConnectionMgr::~nsHttpConnectionMgr()
+{
+ LOG(("Destroying nsHttpConnectionMgr @%p\n", this));
+ if (mTimeoutTick)
+ mTimeoutTick->Cancel();
+}
+
+nsresult
+nsHttpConnectionMgr::EnsureSocketThreadTarget()
+{
+ nsresult rv;
+ nsCOMPtr<nsIEventTarget> sts;
+ nsCOMPtr<nsIIOService> ioService = do_GetIOService(&rv);
+ if (NS_SUCCEEDED(rv))
+ sts = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
+
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+
+ // do nothing if already initialized or if we've shut down
+ if (mSocketThreadTarget || mIsShuttingDown)
+ return NS_OK;
+
+ mSocketThreadTarget = sts;
+
+ return rv;
+}
+
+nsresult
+nsHttpConnectionMgr::Init(uint16_t maxConns,
+ uint16_t maxPersistConnsPerHost,
+ uint16_t maxPersistConnsPerProxy,
+ uint16_t maxRequestDelay,
+ uint16_t maxPipelinedRequests,
+ uint16_t maxOptimisticPipelinedRequests)
+{
+ LOG(("nsHttpConnectionMgr::Init\n"));
+
+ {
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+
+ mMaxConns = maxConns;
+ mMaxPersistConnsPerHost = maxPersistConnsPerHost;
+ mMaxPersistConnsPerProxy = maxPersistConnsPerProxy;
+ mMaxRequestDelay = maxRequestDelay;
+ mMaxPipelinedRequests = maxPipelinedRequests;
+ mMaxOptimisticPipelinedRequests = maxOptimisticPipelinedRequests;
+
+ mIsShuttingDown = false;
+ }
+
+ return EnsureSocketThreadTarget();
+}
+
+class BoolWrapper : public ARefBase
+{
+public:
+ BoolWrapper() : mBool(false) {}
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(BoolWrapper)
+
+public: // intentional!
+ bool mBool;
+
+private:
+ virtual ~BoolWrapper() {}
+};
+
+nsresult
+nsHttpConnectionMgr::Shutdown()
+{
+ LOG(("nsHttpConnectionMgr::Shutdown\n"));
+
+ RefPtr<BoolWrapper> shutdownWrapper = new BoolWrapper();
+ {
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+
+ // do nothing if already shutdown
+ if (!mSocketThreadTarget)
+ return NS_OK;
+
+ nsresult rv = PostEvent(&nsHttpConnectionMgr::OnMsgShutdown,
+ 0, shutdownWrapper);
+
+ // release our reference to the STS to prevent further events
+ // from being posted. this is how we indicate that we are
+ // shutting down.
+ mIsShuttingDown = true;
+ mSocketThreadTarget = nullptr;
+
+ if (NS_FAILED(rv)) {
+ NS_WARNING("unable to post SHUTDOWN message");
+ return rv;
+ }
+ }
+
+ // wait for shutdown event to complete
+ while (!shutdownWrapper->mBool) {
+ NS_ProcessNextEvent(NS_GetCurrentThread());
+ }
+
+ return NS_OK;
+}
+
+class ConnEvent : public Runnable
+{
+public:
+ ConnEvent(nsHttpConnectionMgr *mgr,
+ nsConnEventHandler handler, int32_t iparam, ARefBase *vparam)
+ : mMgr(mgr)
+ , mHandler(handler)
+ , mIParam(iparam)
+ , mVParam(vparam) {}
+
+ NS_IMETHOD Run() override
+ {
+ (mMgr->*mHandler)(mIParam, mVParam);
+ return NS_OK;
+ }
+
+private:
+ virtual ~ConnEvent() {}
+
+ RefPtr<nsHttpConnectionMgr> mMgr;
+ nsConnEventHandler mHandler;
+ int32_t mIParam;
+ RefPtr<ARefBase> mVParam;
+};
+
+nsresult
+nsHttpConnectionMgr::PostEvent(nsConnEventHandler handler,
+ int32_t iparam, ARefBase *vparam)
+{
+ EnsureSocketThreadTarget();
+
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+
+ nsresult rv;
+ if (!mSocketThreadTarget) {
+ NS_WARNING("cannot post event if not initialized");
+ rv = NS_ERROR_NOT_INITIALIZED;
+ }
+ else {
+ nsCOMPtr<nsIRunnable> event = new ConnEvent(this, handler, iparam, vparam);
+ rv = mSocketThreadTarget->Dispatch(event, NS_DISPATCH_NORMAL);
+ }
+ return rv;
+}
+
+void
+nsHttpConnectionMgr::PruneDeadConnectionsAfter(uint32_t timeInSeconds)
+{
+ LOG(("nsHttpConnectionMgr::PruneDeadConnectionsAfter\n"));
+
+ if(!mTimer)
+ mTimer = do_CreateInstance("@mozilla.org/timer;1");
+
+ // failure to create a timer is not a fatal error, but idle connections
+ // will not be cleaned up until we try to use them.
+ if (mTimer) {
+ mTimeOfNextWakeUp = timeInSeconds + NowInSeconds();
+ mTimer->Init(this, timeInSeconds*1000, nsITimer::TYPE_ONE_SHOT);
+ } else {
+ NS_WARNING("failed to create: timer for pruning the dead connections!");
+ }
+}
+
+void
+nsHttpConnectionMgr::ConditionallyStopPruneDeadConnectionsTimer()
+{
+ // Leave the timer in place if there are connections that potentially
+ // need management
+ if (mNumIdleConns || (mNumActiveConns && gHttpHandler->IsSpdyEnabled()))
+ return;
+
+ LOG(("nsHttpConnectionMgr::StopPruneDeadConnectionsTimer\n"));
+
+ // Reset mTimeOfNextWakeUp so that we can find a new shortest value.
+ mTimeOfNextWakeUp = UINT64_MAX;
+ if (mTimer) {
+ mTimer->Cancel();
+ mTimer = nullptr;
+ }
+}
+
+void
+nsHttpConnectionMgr::ConditionallyStopTimeoutTick()
+{
+ LOG(("nsHttpConnectionMgr::ConditionallyStopTimeoutTick "
+ "armed=%d active=%d\n", mTimeoutTickArmed, mNumActiveConns));
+
+ if (!mTimeoutTickArmed)
+ return;
+
+ if (mNumActiveConns)
+ return;
+
+ LOG(("nsHttpConnectionMgr::ConditionallyStopTimeoutTick stop==true\n"));
+
+ mTimeoutTick->Cancel();
+ mTimeoutTickArmed = false;
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpConnectionMgr::nsIObserver
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsHttpConnectionMgr::Observe(nsISupports *subject,
+ const char *topic,
+ const char16_t *data)
+{
+ LOG(("nsHttpConnectionMgr::Observe [topic=\"%s\"]\n", topic));
+
+ if (0 == strcmp(topic, NS_TIMER_CALLBACK_TOPIC)) {
+ nsCOMPtr<nsITimer> timer = do_QueryInterface(subject);
+ if (timer == mTimer) {
+ PruneDeadConnections();
+ }
+ else if (timer == mTimeoutTick) {
+ TimeoutTick();
+ } else if (timer == mTrafficTimer) {
+ PruneNoTraffic();
+ }
+ else {
+ MOZ_ASSERT(false, "unexpected timer-callback");
+ LOG(("Unexpected timer object\n"));
+ return NS_ERROR_UNEXPECTED;
+ }
+ }
+
+ return NS_OK;
+}
+
+
+//-----------------------------------------------------------------------------
+
+nsresult
+nsHttpConnectionMgr::AddTransaction(nsHttpTransaction *trans, int32_t priority)
+{
+ LOG(("nsHttpConnectionMgr::AddTransaction [trans=%p %d]\n", trans, priority));
+ return PostEvent(&nsHttpConnectionMgr::OnMsgNewTransaction, priority, trans);
+}
+
+nsresult
+nsHttpConnectionMgr::RescheduleTransaction(nsHttpTransaction *trans, int32_t priority)
+{
+ LOG(("nsHttpConnectionMgr::RescheduleTransaction [trans=%p %d]\n", trans, priority));
+ return PostEvent(&nsHttpConnectionMgr::OnMsgReschedTransaction, priority, trans);
+}
+
+nsresult
+nsHttpConnectionMgr::CancelTransaction(nsHttpTransaction *trans, nsresult reason)
+{
+ LOG(("nsHttpConnectionMgr::CancelTransaction [trans=%p reason=%x]\n", trans, reason));
+ return PostEvent(&nsHttpConnectionMgr::OnMsgCancelTransaction,
+ static_cast<int32_t>(reason), trans);
+}
+
+nsresult
+nsHttpConnectionMgr::PruneDeadConnections()
+{
+ return PostEvent(&nsHttpConnectionMgr::OnMsgPruneDeadConnections);
+}
+
+//
+// Called after a timeout. Check for active connections that have had no
+// traffic since they were "marked" and nuke them.
+nsresult
+nsHttpConnectionMgr::PruneNoTraffic()
+{
+ LOG(("nsHttpConnectionMgr::PruneNoTraffic\n"));
+ mPruningNoTraffic = true;
+ return PostEvent(&nsHttpConnectionMgr::OnMsgPruneNoTraffic);
+}
+
+nsresult
+nsHttpConnectionMgr::VerifyTraffic()
+{
+ LOG(("nsHttpConnectionMgr::VerifyTraffic\n"));
+ return PostEvent(&nsHttpConnectionMgr::OnMsgVerifyTraffic);
+}
+
+nsresult
+nsHttpConnectionMgr::DoShiftReloadConnectionCleanup(nsHttpConnectionInfo *aCI)
+{
+ return PostEvent(&nsHttpConnectionMgr::OnMsgDoShiftReloadConnectionCleanup,
+ 0, aCI);
+}
+
+class SpeculativeConnectArgs : public ARefBase
+{
+public:
+ SpeculativeConnectArgs() { mOverridesOK = false; }
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SpeculativeConnectArgs)
+
+public: // intentional!
+ RefPtr<NullHttpTransaction> mTrans;
+
+ bool mOverridesOK;
+ uint32_t mParallelSpeculativeConnectLimit;
+ bool mIgnoreIdle;
+ bool mIsFromPredictor;
+ bool mAllow1918;
+
+private:
+ virtual ~SpeculativeConnectArgs() {}
+ NS_DECL_OWNINGTHREAD
+};
+
+nsresult
+nsHttpConnectionMgr::SpeculativeConnect(nsHttpConnectionInfo *ci,
+ nsIInterfaceRequestor *callbacks,
+ uint32_t caps,
+ NullHttpTransaction *nullTransaction)
+{
+ MOZ_ASSERT(NS_IsMainThread(), "nsHttpConnectionMgr::SpeculativeConnect called off main thread!");
+
+ if (!IsNeckoChild()) {
+ // HACK: make sure PSM gets initialized on the main thread.
+ net_EnsurePSMInit();
+ }
+
+ LOG(("nsHttpConnectionMgr::SpeculativeConnect [ci=%s]\n",
+ ci->HashKey().get()));
+
+ nsCOMPtr<nsISpeculativeConnectionOverrider> overrider =
+ do_GetInterface(callbacks);
+
+ bool allow1918 = overrider ? overrider->GetAllow1918() : false;
+
+ // Hosts that are Local IP Literals should not be speculatively
+ // connected - Bug 853423.
+ if ((!allow1918) && ci && ci->HostIsLocalIPLiteral()) {
+ LOG(("nsHttpConnectionMgr::SpeculativeConnect skipping RFC1918 "
+ "address [%s]", ci->Origin()));
+ return NS_OK;
+ }
+
+ RefPtr<SpeculativeConnectArgs> args = new SpeculativeConnectArgs();
+
+ // Wrap up the callbacks and the target to ensure they're released on the target
+ // thread properly.
+ nsCOMPtr<nsIInterfaceRequestor> wrappedCallbacks;
+ NS_NewInterfaceRequestorAggregation(callbacks, nullptr, getter_AddRefs(wrappedCallbacks));
+
+ caps |= ci->GetAnonymous() ? NS_HTTP_LOAD_ANONYMOUS : 0;
+ caps |= NS_HTTP_ERROR_SOFTLY;
+ args->mTrans =
+ nullTransaction ? nullTransaction : new NullHttpTransaction(ci, wrappedCallbacks, caps);
+
+ if (overrider) {
+ args->mOverridesOK = true;
+ args->mParallelSpeculativeConnectLimit =
+ overrider->GetParallelSpeculativeConnectLimit();
+ args->mIgnoreIdle = overrider->GetIgnoreIdle();
+ args->mIsFromPredictor = overrider->GetIsFromPredictor();
+ args->mAllow1918 = overrider->GetAllow1918();
+ }
+
+ return PostEvent(&nsHttpConnectionMgr::OnMsgSpeculativeConnect, 0, args);
+}
+
+nsresult
+nsHttpConnectionMgr::GetSocketThreadTarget(nsIEventTarget **target)
+{
+ EnsureSocketThreadTarget();
+
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+ nsCOMPtr<nsIEventTarget> temp(mSocketThreadTarget);
+ temp.forget(target);
+ return NS_OK;
+}
+
+nsresult
+nsHttpConnectionMgr::ReclaimConnection(nsHttpConnection *conn)
+{
+ LOG(("nsHttpConnectionMgr::ReclaimConnection [conn=%p]\n", conn));
+ return PostEvent(&nsHttpConnectionMgr::OnMsgReclaimConnection, 0, conn);
+}
+
+// A structure used to marshall 2 pointers across the various necessary
+// threads to complete an HTTP upgrade.
+class nsCompleteUpgradeData : public ARefBase
+{
+public:
+ nsCompleteUpgradeData(nsAHttpConnection *aConn,
+ nsIHttpUpgradeListener *aListener)
+ : mConn(aConn)
+ , mUpgradeListener(aListener) { }
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(nsCompleteUpgradeData)
+
+ RefPtr<nsAHttpConnection> mConn;
+ nsCOMPtr<nsIHttpUpgradeListener> mUpgradeListener;
+private:
+ virtual ~nsCompleteUpgradeData() { }
+};
+
+nsresult
+nsHttpConnectionMgr::CompleteUpgrade(nsAHttpConnection *aConn,
+ nsIHttpUpgradeListener *aUpgradeListener)
+{
+ RefPtr<nsCompleteUpgradeData> data =
+ new nsCompleteUpgradeData(aConn, aUpgradeListener);
+ return PostEvent(&nsHttpConnectionMgr::OnMsgCompleteUpgrade, 0, data);
+}
+
+nsresult
+nsHttpConnectionMgr::UpdateParam(nsParamName name, uint16_t value)
+{
+ uint32_t param = (uint32_t(name) << 16) | uint32_t(value);
+ return PostEvent(&nsHttpConnectionMgr::OnMsgUpdateParam,
+ static_cast<int32_t>(param), nullptr);
+}
+
+nsresult
+nsHttpConnectionMgr::ProcessPendingQ(nsHttpConnectionInfo *ci)
+{
+ LOG(("nsHttpConnectionMgr::ProcessPendingQ [ci=%s]\n", ci->HashKey().get()));
+ return PostEvent(&nsHttpConnectionMgr::OnMsgProcessPendingQ, 0, ci);
+}
+
+nsresult
+nsHttpConnectionMgr::ProcessPendingQ()
+{
+ LOG(("nsHttpConnectionMgr::ProcessPendingQ [All CI]\n"));
+ return PostEvent(&nsHttpConnectionMgr::OnMsgProcessPendingQ, 0, nullptr);
+}
+
+void
+nsHttpConnectionMgr::OnMsgUpdateRequestTokenBucket(int32_t, ARefBase *param)
+{
+ EventTokenBucket *tokenBucket = static_cast<EventTokenBucket *>(param);
+ gHttpHandler->SetRequestTokenBucket(tokenBucket);
+}
+
+nsresult
+nsHttpConnectionMgr::UpdateRequestTokenBucket(EventTokenBucket *aBucket)
+{
+ // Call From main thread when a new EventTokenBucket has been made in order
+ // to post the new value to the socket thread.
+ return PostEvent(&nsHttpConnectionMgr::OnMsgUpdateRequestTokenBucket,
+ 0, aBucket);
+}
+
+nsresult
+nsHttpConnectionMgr::ClearConnectionHistory()
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ for (auto iter = mCT.Iter(); !iter.Done(); iter.Next()) {
+ nsAutoPtr<nsConnectionEntry>& ent = iter.Data();
+ if (ent->mIdleConns.Length() == 0 &&
+ ent->mActiveConns.Length() == 0 &&
+ ent->mHalfOpens.Length() == 0 &&
+ ent->mPendingQ.Length() == 0) {
+ iter.Remove();
+ }
+ }
+
+ return NS_OK;
+}
+
+
+nsHttpConnectionMgr::nsConnectionEntry *
+nsHttpConnectionMgr::LookupPreferredHash(nsHttpConnectionMgr::nsConnectionEntry *ent)
+{
+ nsConnectionEntry *preferred = nullptr;
+ uint32_t len = ent->mCoalescingKeys.Length();
+ for (uint32_t i = 0; !preferred && (i < len); ++i) {
+ preferred = mSpdyPreferredHash.Get(ent->mCoalescingKeys[i]);
+ }
+ return preferred;
+}
+
+void
+nsHttpConnectionMgr::StorePreferredHash(nsHttpConnectionMgr::nsConnectionEntry *ent)
+{
+ if (ent->mCoalescingKeys.IsEmpty()) {
+ return;
+ }
+
+ ent->mInPreferredHash = true;
+ uint32_t len = ent->mCoalescingKeys.Length();
+ for (uint32_t i = 0; i < len; ++i) {
+ mSpdyPreferredHash.Put(ent->mCoalescingKeys[i], ent);
+ }
+}
+
+void
+nsHttpConnectionMgr::RemovePreferredHash(nsHttpConnectionMgr::nsConnectionEntry *ent)
+{
+ if (!ent->mInPreferredHash || ent->mCoalescingKeys.IsEmpty()) {
+ return;
+ }
+
+ ent->mInPreferredHash = false;
+ uint32_t len = ent->mCoalescingKeys.Length();
+ for (uint32_t i = 0; i < len; ++i) {
+ mSpdyPreferredHash.Remove(ent->mCoalescingKeys[i]);
+ }
+}
+
+// Given a nsHttpConnectionInfo find the connection entry object that
+// contains either the nshttpconnection or nshttptransaction parameter.
+// Normally this is done by the hashkey lookup of connectioninfo,
+// but if spdy coalescing is in play it might be found in a redirected
+// entry
+nsHttpConnectionMgr::nsConnectionEntry *
+nsHttpConnectionMgr::LookupConnectionEntry(nsHttpConnectionInfo *ci,
+ nsHttpConnection *conn,
+ nsHttpTransaction *trans)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ if (!ci)
+ return nullptr;
+
+ nsConnectionEntry *ent = mCT.Get(ci->HashKey());
+
+ // If there is no sign of coalescing (or it is disabled) then just
+ // return the primary hash lookup
+ if (!ent || !ent->mUsingSpdy || ent->mCoalescingKeys.IsEmpty())
+ return ent;
+
+ // If there is no preferred coalescing entry for this host (or the
+ // preferred entry is the one that matched the mCT hash lookup) then
+ // there is only option
+ nsConnectionEntry *preferred = LookupPreferredHash(ent);
+ if (!preferred || (preferred == ent))
+ return ent;
+
+ if (conn) {
+ // The connection could be either in preferred or ent. It is most
+ // likely the only active connection in preferred - so start with that.
+ if (preferred->mActiveConns.Contains(conn))
+ return preferred;
+ if (preferred->mIdleConns.Contains(conn))
+ return preferred;
+ }
+
+ if (trans && preferred->mPendingQ.Contains(trans))
+ return preferred;
+
+ // Neither conn nor trans found in preferred, use the default entry
+ return ent;
+}
+
+nsresult
+nsHttpConnectionMgr::CloseIdleConnection(nsHttpConnection *conn)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ LOG(("nsHttpConnectionMgr::CloseIdleConnection %p conn=%p",
+ this, conn));
+
+ if (!conn->ConnectionInfo())
+ return NS_ERROR_UNEXPECTED;
+
+ nsConnectionEntry *ent = LookupConnectionEntry(conn->ConnectionInfo(),
+ conn, nullptr);
+
+ RefPtr<nsHttpConnection> deleteProtector(conn);
+ if (!ent || !ent->mIdleConns.RemoveElement(conn))
+ return NS_ERROR_UNEXPECTED;
+
+ conn->Close(NS_ERROR_ABORT);
+ mNumIdleConns--;
+ ConditionallyStopPruneDeadConnectionsTimer();
+ return NS_OK;
+}
+
+// This function lets a connection, after completing the NPN phase,
+// report whether or not it is using spdy through the usingSpdy
+// argument. It would not be necessary if NPN were driven out of
+// the connection manager. The connection entry associated with the
+// connection is then updated to indicate whether or not we want to use
+// spdy with that host and update the preliminary preferred host
+// entries used for de-sharding hostsnames.
+void
+nsHttpConnectionMgr::ReportSpdyConnection(nsHttpConnection *conn,
+ bool usingSpdy)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ nsConnectionEntry *ent = LookupConnectionEntry(conn->ConnectionInfo(),
+ conn, nullptr);
+
+ if (!ent)
+ return;
+
+ if (!usingSpdy)
+ return;
+
+ ent->mUsingSpdy = true;
+ mNumSpdyActiveConns++;
+
+ uint32_t ttl = conn->TimeToLive();
+ uint64_t timeOfExpire = NowInSeconds() + ttl;
+ if (!mTimer || timeOfExpire < mTimeOfNextWakeUp)
+ PruneDeadConnectionsAfter(ttl);
+
+ // Lookup preferred directly from the hash instead of using
+ // GetSpdyPreferredEnt() because we want to avoid the cert compatibility
+ // check at this point because the cert is never part of the hash
+ // lookup. Filtering on that has to be done at the time of use
+ // rather than the time of registration (i.e. now).
+ nsConnectionEntry *joinedConnection;
+ nsConnectionEntry *preferred = LookupPreferredHash(ent);
+
+ LOG(("ReportSpdyConnection %p,%s conn %p prefers %p,%s\n",
+ ent, ent->mConnInfo->Origin(), conn, preferred,
+ preferred ? preferred->mConnInfo->Origin() : ""));
+
+ if (!preferred) {
+ // this becomes the preferred entry
+ StorePreferredHash(ent);
+ preferred = ent;
+ } else if ((preferred != ent) &&
+ (joinedConnection = GetSpdyPreferredEnt(ent)) &&
+ (joinedConnection != ent)) {
+ //
+ // A connection entry (e.g. made with a different hostname) with
+ // the same IP address is preferred for future transactions over this
+ // connection entry. Gracefully close down the connection to help
+ // new transactions migrate over.
+
+ LOG(("ReportSpdyConnection graceful close of conn=%p ent=%p to "
+ "migrate to preferred (desharding)\n", conn, ent));
+ conn->DontReuse();
+ } else if (preferred != ent) {
+ LOG (("ReportSpdyConnection preferred host may be in false start or "
+ "may have insufficient cert. Leave mapping in place but do not "
+ "abandon this connection yet."));
+ }
+
+ if ((preferred == ent) && conn->CanDirectlyActivate()) {
+ // this is a new spdy connection to the preferred entry
+
+ // Cancel any other pending connections - their associated transactions
+ // are in the pending queue and will be dispatched onto this connection
+ for (int32_t index = ent->mHalfOpens.Length() - 1;
+ index >= 0; --index) {
+ LOG(("ReportSpdyConnection forcing halfopen abandon %p\n",
+ ent->mHalfOpens[index]));
+ ent->mHalfOpens[index]->Abandon();
+ }
+
+ if (ent->mActiveConns.Length() > 1) {
+ // this is a new connection to an established preferred spdy host.
+ // if there is more than 1 live and established spdy connection (e.g.
+ // some could still be handshaking, shutting down, etc..) then close
+ // this one down after any transactions that are on it are complete.
+ // This probably happened due to the parallel connection algorithm
+ // that is used only before the host is known to speak spdy.
+ for (uint32_t index = 0; index < ent->mActiveConns.Length(); ++index) {
+ nsHttpConnection *otherConn = ent->mActiveConns[index];
+ if (otherConn != conn) {
+ LOG(("ReportSpdyConnection shutting down connection (%p) because new "
+ "spdy connection (%p) takes precedence\n", otherConn, conn));
+ otherConn->DontReuse();
+ }
+ }
+ }
+ }
+
+ ProcessPendingQ(ent->mConnInfo);
+ PostEvent(&nsHttpConnectionMgr::OnMsgProcessAllSpdyPendingQ);
+}
+
+nsHttpConnectionMgr::nsConnectionEntry *
+nsHttpConnectionMgr::GetSpdyPreferredEnt(nsConnectionEntry *aOriginalEntry)
+{
+ if (!gHttpHandler->IsSpdyEnabled() ||
+ !gHttpHandler->CoalesceSpdy() ||
+ aOriginalEntry->mConnInfo->GetNoSpdy() ||
+ aOriginalEntry->mCoalescingKeys.IsEmpty()) {
+ return nullptr;
+ }
+
+ nsConnectionEntry *preferred = LookupPreferredHash(aOriginalEntry);
+
+ // if there is no redirection no cert validation is required
+ if (preferred == aOriginalEntry)
+ return aOriginalEntry;
+
+ // if there is no preferred host or it is no longer using spdy
+ // then skip pooling
+ if (!preferred || !preferred->mUsingSpdy)
+ return nullptr;
+
+ // if there is not an active spdy session in this entry then
+ // we cannot pool because the cert upon activation may not
+ // be the same as the old one. Active sessions are prohibited
+ // from changing certs.
+
+ nsHttpConnection *activeSpdy = nullptr;
+
+ for (uint32_t index = 0; index < preferred->mActiveConns.Length(); ++index) {
+ if (preferred->mActiveConns[index]->CanDirectlyActivate()) {
+ activeSpdy = preferred->mActiveConns[index];
+ break;
+ }
+ }
+
+ if (!activeSpdy) {
+ // remove the preferred status of this entry if it cannot be
+ // used for pooling.
+ RemovePreferredHash(preferred);
+ LOG(("nsHttpConnectionMgr::GetSpdyPreferredEnt "
+ "preferred host mapping %s to %s removed due to inactivity.\n",
+ aOriginalEntry->mConnInfo->Origin(),
+ preferred->mConnInfo->Origin()));
+
+ return nullptr;
+ }
+
+ // Check that the server cert supports redirection
+ nsresult rv;
+ bool isJoined = false;
+
+ nsCOMPtr<nsISupports> securityInfo;
+ nsCOMPtr<nsISSLSocketControl> sslSocketControl;
+ nsAutoCString negotiatedNPN;
+
+ activeSpdy->GetSecurityInfo(getter_AddRefs(securityInfo));
+ if (!securityInfo) {
+ NS_WARNING("cannot obtain spdy security info");
+ return nullptr;
+ }
+
+ sslSocketControl = do_QueryInterface(securityInfo, &rv);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("sslSocketControl QI Failed");
+ return nullptr;
+ }
+
+ // try all the spdy versions we support.
+ const SpdyInformation *info = gHttpHandler->SpdyInfo();
+ for (uint32_t index = SpdyInformation::kCount;
+ NS_SUCCEEDED(rv) && index > 0; --index) {
+ if (info->ProtocolEnabled(index - 1)) {
+ rv = sslSocketControl->JoinConnection(info->VersionString[index - 1],
+ aOriginalEntry->mConnInfo->GetOrigin(),
+ aOriginalEntry->mConnInfo->OriginPort(),
+ &isJoined);
+ if (NS_SUCCEEDED(rv) && isJoined) {
+ break;
+ }
+ }
+ }
+
+ if (NS_FAILED(rv) || !isJoined) {
+ LOG(("nsHttpConnectionMgr::GetSpdyPreferredEnt "
+ "Host %s cannot be confirmed to be joined "
+ "with %s connections. rv=%x isJoined=%d",
+ preferred->mConnInfo->Origin(), aOriginalEntry->mConnInfo->Origin(),
+ rv, isJoined));
+ Telemetry::Accumulate(Telemetry::SPDY_NPN_JOIN, false);
+ return nullptr;
+ }
+
+ // IP pooling confirmed
+ LOG(("nsHttpConnectionMgr::GetSpdyPreferredEnt "
+ "Host %s has cert valid for %s connections, "
+ "so %s will be coalesced with %s",
+ preferred->mConnInfo->Origin(), aOriginalEntry->mConnInfo->Origin(),
+ aOriginalEntry->mConnInfo->Origin(), preferred->mConnInfo->Origin()));
+ Telemetry::Accumulate(Telemetry::SPDY_NPN_JOIN, true);
+ return preferred;
+}
+
+//-----------------------------------------------------------------------------
+
+bool
+nsHttpConnectionMgr::ProcessPendingQForEntry(nsConnectionEntry *ent, bool considerAll)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ LOG(("nsHttpConnectionMgr::ProcessPendingQForEntry "
+ "[ci=%s ent=%p active=%d idle=%d queued=%d]\n",
+ ent->mConnInfo->HashKey().get(), ent, ent->mActiveConns.Length(),
+ ent->mIdleConns.Length(), ent->mPendingQ.Length()));
+
+ ProcessSpdyPendingQ(ent);
+
+ nsHttpTransaction *trans;
+ nsresult rv;
+ bool dispatchedSuccessfully = false;
+
+ // if !considerAll iterate the pending list until one is dispatched successfully.
+ // Keep iterating afterwards only until a transaction fails to dispatch.
+ // if considerAll == true then try and dispatch all items.
+ for (uint32_t i = 0; i < ent->mPendingQ.Length(); ) {
+ trans = ent->mPendingQ[i];
+
+ // When this transaction has already established a half-open
+ // connection, we want to prevent any duplicate half-open
+ // connections from being established and bound to this
+ // transaction. Allow only use of an idle persistent connection
+ // (if found) for transactions referred by a half-open connection.
+ bool alreadyHalfOpen = false;
+ for (int32_t j = 0; j < ((int32_t) ent->mHalfOpens.Length()); ++j) {
+ if (ent->mHalfOpens[j]->Transaction() == trans) {
+ alreadyHalfOpen = true;
+ break;
+ }
+ }
+
+ rv = TryDispatchTransaction(ent,
+ alreadyHalfOpen || !!trans->TunnelProvider(),
+ trans);
+ if (NS_SUCCEEDED(rv) || (rv != NS_ERROR_NOT_AVAILABLE)) {
+ if (NS_SUCCEEDED(rv))
+ LOG((" dispatching pending transaction...\n"));
+ else
+ LOG((" removing pending transaction based on "
+ "TryDispatchTransaction returning hard error %x\n", rv));
+
+ if (ent->mPendingQ.RemoveElement(trans)) {
+ // trans is now potentially destroyed
+ dispatchedSuccessfully = true;
+ continue; // dont ++i as we just made the array shorter
+ }
+
+ LOG((" transaction not found in pending queue\n"));
+ }
+
+ if (dispatchedSuccessfully && !considerAll)
+ break;
+
+ ++i;
+ }
+ return dispatchedSuccessfully;
+}
+
+bool
+nsHttpConnectionMgr::ProcessPendingQForEntry(nsHttpConnectionInfo *ci)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ nsConnectionEntry *ent = mCT.Get(ci->HashKey());
+ if (ent)
+ return ProcessPendingQForEntry(ent, false);
+ return false;
+}
+
+bool
+nsHttpConnectionMgr::SupportsPipelining(nsHttpConnectionInfo *ci)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ nsConnectionEntry *ent = mCT.Get(ci->HashKey());
+ if (ent)
+ return ent->SupportsPipelining();
+ return false;
+}
+
+// nsHttpPipelineFeedback used to hold references across events
+
+class nsHttpPipelineFeedback : public ARefBase
+{
+public:
+ nsHttpPipelineFeedback(nsHttpConnectionInfo *ci,
+ nsHttpConnectionMgr::PipelineFeedbackInfoType info,
+ nsHttpConnection *conn, uint32_t data)
+ : mConnInfo(ci)
+ , mConn(conn)
+ , mInfo(info)
+ , mData(data)
+ {
+ }
+
+
+ RefPtr<nsHttpConnectionInfo> mConnInfo;
+ RefPtr<nsHttpConnection> mConn;
+ nsHttpConnectionMgr::PipelineFeedbackInfoType mInfo;
+ uint32_t mData;
+private:
+ ~nsHttpPipelineFeedback() {}
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(nsHttpPipelineFeedback)
+};
+
+void
+nsHttpConnectionMgr::PipelineFeedbackInfo(nsHttpConnectionInfo *ci,
+ PipelineFeedbackInfoType info,
+ nsHttpConnection *conn,
+ uint32_t data)
+{
+ if (!ci)
+ return;
+
+ // Post this to the socket thread if we are not running there already
+ if (PR_GetCurrentThread() != gSocketThread) {
+ RefPtr<nsHttpPipelineFeedback> fb =
+ new nsHttpPipelineFeedback(ci, info, conn, data);
+ PostEvent(&nsHttpConnectionMgr::OnMsgProcessFeedback, 0, fb);
+ return;
+ }
+
+ nsConnectionEntry *ent = mCT.Get(ci->HashKey());
+ if (ent)
+ ent->OnPipelineFeedbackInfo(info, conn, data);
+}
+
+void
+nsHttpConnectionMgr::ReportFailedToProcess(nsIURI *uri)
+{
+ MOZ_ASSERT(uri);
+
+ nsAutoCString host;
+ int32_t port = -1;
+ nsAutoCString username;
+ bool usingSSL = false;
+ bool isHttp = false;
+
+ nsresult rv = uri->SchemeIs("https", &usingSSL);
+ if (NS_SUCCEEDED(rv) && usingSSL)
+ isHttp = true;
+ if (NS_SUCCEEDED(rv) && !isHttp)
+ rv = uri->SchemeIs("http", &isHttp);
+ if (NS_SUCCEEDED(rv))
+ rv = uri->GetAsciiHost(host);
+ if (NS_SUCCEEDED(rv))
+ rv = uri->GetPort(&port);
+ if (NS_SUCCEEDED(rv))
+ uri->GetUsername(username);
+ if (NS_FAILED(rv) || !isHttp || host.IsEmpty())
+ return;
+
+ // report the event for all the permutations of anonymous and
+ // private versions of this host
+ RefPtr<nsHttpConnectionInfo> ci =
+ new nsHttpConnectionInfo(host, port, EmptyCString(), username, nullptr,
+ NeckoOriginAttributes(), usingSSL);
+ ci->SetAnonymous(false);
+ ci->SetPrivate(false);
+ PipelineFeedbackInfo(ci, RedCorruptedContent, nullptr, 0);
+
+ ci = ci->Clone();
+ ci->SetAnonymous(false);
+ ci->SetPrivate(true);
+ PipelineFeedbackInfo(ci, RedCorruptedContent, nullptr, 0);
+
+ ci = ci->Clone();
+ ci->SetAnonymous(true);
+ ci->SetPrivate(false);
+ PipelineFeedbackInfo(ci, RedCorruptedContent, nullptr, 0);
+
+ ci = ci->Clone();
+ ci->SetAnonymous(true);
+ ci->SetPrivate(true);
+ PipelineFeedbackInfo(ci, RedCorruptedContent, nullptr, 0);
+}
+
+// we're at the active connection limit if any one of the following conditions is true:
+// (1) at max-connections
+// (2) keep-alive enabled and at max-persistent-connections-per-server/proxy
+// (3) keep-alive disabled and at max-connections-per-server
+bool
+nsHttpConnectionMgr::AtActiveConnectionLimit(nsConnectionEntry *ent, uint32_t caps)
+{
+ nsHttpConnectionInfo *ci = ent->mConnInfo;
+
+ LOG(("nsHttpConnectionMgr::AtActiveConnectionLimit [ci=%s caps=%x]\n",
+ ci->HashKey().get(), caps));
+
+ // update maxconns if potentially limited by the max socket count
+ // this requires a dynamic reduction in the max socket count to a point
+ // lower than the max-connections pref.
+ uint32_t maxSocketCount = gHttpHandler->MaxSocketCount();
+ if (mMaxConns > maxSocketCount) {
+ mMaxConns = maxSocketCount;
+ LOG(("nsHttpConnectionMgr %p mMaxConns dynamically reduced to %u",
+ this, mMaxConns));
+ }
+
+ // If there are more active connections than the global limit, then we're
+ // done. Purging idle connections won't get us below it.
+ if (mNumActiveConns >= mMaxConns) {
+ LOG((" num active conns == max conns\n"));
+ return true;
+ }
+
+ // 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.
+ uint32_t totalCount =
+ ent->mActiveConns.Length() + ent->UnconnectedHalfOpens();
+
+ uint16_t maxPersistConns;
+
+ if (ci->UsingHttpProxy() && !ci->UsingConnect())
+ maxPersistConns = mMaxPersistConnsPerProxy;
+ else
+ maxPersistConns = mMaxPersistConnsPerHost;
+
+ LOG((" connection count = %d, limit %d\n", totalCount, maxPersistConns));
+
+ // use >= just to be safe
+ bool result = (totalCount >= maxPersistConns);
+ LOG((" result: %s", result ? "true" : "false"));
+ return result;
+}
+
+void
+nsHttpConnectionMgr::ClosePersistentConnections(nsConnectionEntry *ent)
+{
+ LOG(("nsHttpConnectionMgr::ClosePersistentConnections [ci=%s]\n",
+ ent->mConnInfo->HashKey().get()));
+ while (ent->mIdleConns.Length()) {
+ RefPtr<nsHttpConnection> conn(ent->mIdleConns[0]);
+ ent->mIdleConns.RemoveElementAt(0);
+ mNumIdleConns--;
+ conn->Close(NS_ERROR_ABORT);
+ }
+
+ int32_t activeCount = ent->mActiveConns.Length();
+ for (int32_t i=0; i < activeCount; i++)
+ ent->mActiveConns[i]->DontReuse();
+}
+
+bool
+nsHttpConnectionMgr::RestrictConnections(nsConnectionEntry *ent)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ // 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 =
+ ent->mConnInfo->FirstHopSSL() && gHttpHandler->IsSpdyEnabled() &&
+ ent->mUsingSpdy && (ent->mHalfOpens.Length() || ent->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 (ent->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 (ent->mUsingSpdy && ent->mActiveConns.Length()) {
+ bool confirmedRestrict = false;
+ for (uint32_t index = 0; index < ent->mActiveConns.Length(); ++index) {
+ nsHttpConnection *conn = ent->mActiveConns[index];
+ if (!conn->ReportedNPN() || conn->CanDirectlyActivate()) {
+ confirmedRestrict = true;
+ break;
+ }
+ }
+ doRestrict = confirmedRestrict;
+ if (!confirmedRestrict) {
+ LOG(("nsHttpConnectionMgr spdy connection restriction to "
+ "%s bypassed.\n", ent->mConnInfo->Origin()));
+ }
+ }
+ return doRestrict;
+}
+
+// returns NS_OK if a connection was started
+// return NS_ERROR_NOT_AVAILABLE if a new connection cannot be made due to
+// ephemeral limits
+// returns other NS_ERROR on hard failure conditions
+nsresult
+nsHttpConnectionMgr::MakeNewConnection(nsConnectionEntry *ent,
+ nsHttpTransaction *trans)
+{
+ LOG(("nsHttpConnectionMgr::MakeNewConnection %p ent=%p trans=%p",
+ this, ent, trans));
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ uint32_t halfOpenLength = ent->mHalfOpens.Length();
+ for (uint32_t i = 0; i < halfOpenLength; i++) {
+ if (ent->mHalfOpens[i]->IsSpeculative()) {
+ // We've found a speculative connection in the half
+ // open list. Remove the speculative bit from it and that
+ // connection can later be used for this transaction
+ // (or another one in the pending queue) - we don't
+ // need to open a new connection here.
+ LOG(("nsHttpConnectionMgr::MakeNewConnection [ci = %s]\n"
+ "Found a speculative half open connection\n",
+ ent->mConnInfo->HashKey().get()));
+
+ uint32_t flags;
+ ent->mHalfOpens[i]->SetSpeculative(false);
+ nsISocketTransport *transport = ent->mHalfOpens[i]->SocketTransport();
+ if (transport && NS_SUCCEEDED(transport->GetConnectionFlags(&flags))) {
+ flags &= ~nsISocketTransport::DISABLE_RFC1918;
+ transport->SetConnectionFlags(flags);
+ }
+
+ Telemetry::AutoCounter<Telemetry::HTTPCONNMGR_USED_SPECULATIVE_CONN> usedSpeculativeConn;
+ ++usedSpeculativeConn;
+
+ if (ent->mHalfOpens[i]->IsFromPredictor()) {
+ Telemetry::AutoCounter<Telemetry::PREDICTOR_TOTAL_PRECONNECTS_USED> totalPreconnectsUsed;
+ ++totalPreconnectsUsed;
+ }
+
+ // return OK because we have essentially opened a new connection
+ // by converting a speculative half-open to general use
+ return NS_OK;
+ }
+ }
+
+ // 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 = ent->mActiveConns.Length();
+ for (uint32_t i = 0; i < activeLength; i++) {
+ nsAHttpTransaction *activeTrans = ent->mActiveConns[i]->Transaction();
+ NullHttpTransaction *nullTrans = activeTrans ? activeTrans->QueryNullTransaction() : nullptr;
+ if (nullTrans && nullTrans->Claim()) {
+ LOG(("nsHttpConnectionMgr::MakeNewConnection [ci = %s] "
+ "Claiming a null transaction for later use\n",
+ ent->mConnInfo->HashKey().get()));
+ return NS_OK;
+ }
+ }
+ }
+
+ // If this host is trying to negotiate a SPDY session right now,
+ // don't create any new connections until the result of the
+ // negotiation is known.
+ if (!(trans->Caps() & NS_HTTP_DISALLOW_SPDY) &&
+ (trans->Caps() & NS_HTTP_ALLOW_KEEPALIVE) &&
+ RestrictConnections(ent)) {
+ LOG(("nsHttpConnectionMgr::MakeNewConnection [ci = %s] "
+ "Not Available Due to RestrictConnections()\n",
+ ent->mConnInfo->HashKey().get()));
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // We need to make a new connection. If that is going to exceed the
+ // global connection limit then try and free up some room by closing
+ // an idle connection to another host. We know it won't select "ent"
+ // because we have already determined there are no idle connections
+ // to our destination
+
+ if ((mNumIdleConns + mNumActiveConns + 1 >= mMaxConns) && mNumIdleConns) {
+ // If the global number of connections is preventing the opening of new
+ // connections to a host without idle connections, then close them
+ // regardless of their TTL.
+ auto iter = mCT.Iter();
+ while (mNumIdleConns + mNumActiveConns + 1 >= mMaxConns &&
+ !iter.Done()) {
+ nsAutoPtr<nsConnectionEntry> &entry = iter.Data();
+ if (!entry->mIdleConns.Length()) {
+ iter.Next();
+ continue;
+ }
+ RefPtr<nsHttpConnection> conn(entry->mIdleConns[0]);
+ entry->mIdleConns.RemoveElementAt(0);
+ conn->Close(NS_ERROR_ABORT);
+ mNumIdleConns--;
+ ConditionallyStopPruneDeadConnectionsTimer();
+ }
+ }
+
+ if ((mNumIdleConns + mNumActiveConns + 1 >= mMaxConns) &&
+ mNumActiveConns && gHttpHandler->IsSpdyEnabled())
+ {
+ // If the global number of connections is preventing the opening of new
+ // connections to a host without idle connections, then close any spdy
+ // ASAP.
+ for (auto iter = mCT.Iter(); !iter.Done(); iter.Next()) {
+ nsAutoPtr<nsConnectionEntry> &entry = iter.Data();
+ if (!entry->mUsingSpdy) {
+ continue;
+ }
+
+ for (uint32_t index = 0;
+ index < entry->mActiveConns.Length();
+ ++index) {
+ nsHttpConnection *conn = entry->mActiveConns[index];
+ if (conn->UsingSpdy() && conn->CanReuse()) {
+ conn->DontReuse();
+ // Stop on <= (particularly =) because this dontreuse
+ // causes async close.
+ if (mNumIdleConns + mNumActiveConns + 1 <= mMaxConns) {
+ goto outerLoopEnd;
+ }
+ }
+ }
+ }
+ outerLoopEnd:
+ ;
+ }
+
+ if (AtActiveConnectionLimit(ent, trans->Caps()))
+ return NS_ERROR_NOT_AVAILABLE;
+
+ nsresult rv = CreateTransport(ent, trans, trans->Caps(), false, false, true);
+ if (NS_FAILED(rv)) {
+ /* hard failure */
+ LOG(("nsHttpConnectionMgr::MakeNewConnection [ci = %s trans = %p] "
+ "CreateTransport() hard failure.\n",
+ ent->mConnInfo->HashKey().get(), trans));
+ trans->Close(rv);
+ if (rv == NS_ERROR_NOT_AVAILABLE)
+ rv = NS_ERROR_FAILURE;
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+bool
+nsHttpConnectionMgr::AddToShortestPipeline(nsConnectionEntry *ent,
+ nsHttpTransaction *trans,
+ nsHttpTransaction::Classifier classification,
+ uint16_t depthLimit)
+{
+ if (classification == nsAHttpTransaction::CLASS_SOLO)
+ return false;
+
+ uint32_t maxdepth = ent->MaxPipelineDepth(classification);
+ if (maxdepth == 0) {
+ ent->CreditPenalty();
+ maxdepth = ent->MaxPipelineDepth(classification);
+ }
+
+ if (ent->PipelineState() == PS_RED)
+ return false;
+
+ if (ent->PipelineState() == PS_YELLOW && ent->mYellowConnection)
+ return false;
+
+ // The maximum depth of a pipeline in yellow is 1 pipeline of
+ // depth 2 for entire CI. When that transaction completes successfully
+ // we transition to green and that expands the allowed depth
+ // to any number of pipelines of up to depth 4. When a transaction
+ // queued at position 3 or deeper succeeds we open it all the way
+ // up to depths limited only by configuration. The staggered start
+ // in green is simply because a successful yellow test of depth=2
+ // might really just be a race condition (i.e. depth=1 from the
+ // server's point of view), while depth=3 is a stronger indicator -
+ // keeping the pipelines to a modest depth during that period limits
+ // the damage if something is going to go wrong.
+
+ maxdepth = std::min<uint32_t>(maxdepth, depthLimit);
+
+ if (maxdepth < 2)
+ return false;
+
+ nsAHttpTransaction *activeTrans;
+
+ nsHttpConnection *bestConn = nullptr;
+ uint32_t activeCount = ent->mActiveConns.Length();
+ uint32_t bestConnLength = 0;
+ uint32_t connLength;
+
+ for (uint32_t i = 0; i < activeCount; ++i) {
+ nsHttpConnection *conn = ent->mActiveConns[i];
+ if (!conn->SupportsPipelining())
+ continue;
+
+ if (conn->Classification() != classification)
+ continue;
+
+ activeTrans = conn->Transaction();
+ if (!activeTrans ||
+ activeTrans->IsDone() ||
+ NS_FAILED(activeTrans->Status()))
+ continue;
+
+ connLength = activeTrans->PipelineDepth();
+
+ if (maxdepth <= connLength)
+ continue;
+
+ if (!bestConn || (connLength < bestConnLength)) {
+ bestConn = conn;
+ bestConnLength = connLength;
+ }
+ }
+
+ if (!bestConn)
+ return false;
+
+ activeTrans = bestConn->Transaction();
+ nsresult rv = activeTrans->AddTransaction(trans);
+ if (NS_FAILED(rv))
+ return false;
+
+ LOG((" scheduling trans %p on pipeline at position %d\n",
+ trans, trans->PipelinePosition()));
+
+ if ((ent->PipelineState() == PS_YELLOW) && (trans->PipelinePosition() > 1))
+ ent->SetYellowConnection(bestConn);
+
+ if (!trans->GetPendingTime().IsNull()) {
+ if (trans->UsesPipelining())
+ AccumulateTimeDelta(
+ Telemetry::TRANSACTION_WAIT_TIME_HTTP_PIPELINES,
+ trans->GetPendingTime(), TimeStamp::Now());
+ else
+ AccumulateTimeDelta(
+ Telemetry::TRANSACTION_WAIT_TIME_HTTP,
+ trans->GetPendingTime(), TimeStamp::Now());
+ trans->SetPendingTime(false);
+ }
+ return true;
+}
+
+bool
+nsHttpConnectionMgr::IsUnderPressure(nsConnectionEntry *ent,
+ nsHttpTransaction::Classifier classification)
+{
+ // A connection entry is declared to be "under pressure" if most of the
+ // allowed parallel connections are already used up. In that case we want to
+ // favor existing pipelines over more parallelism so as to reserve any
+ // unused parallel connections for types that don't have existing pipelines.
+ //
+ // The definition of connection pressure is a pretty liberal one here - that
+ // is why we are using the more restrictive maxPersist* counters.
+ //
+ // Pipelines are also favored when the requested classification is already
+ // using 3 or more of the connections. Failure to do this could result in
+ // one class (e.g. images) establishing self replenishing queues on all the
+ // connections that would starve the other transaction types.
+
+ int32_t currentConns = ent->mActiveConns.Length();
+ int32_t maxConns =
+ (ent->mConnInfo->UsingHttpProxy() && !ent->mConnInfo->UsingConnect()) ?
+ mMaxPersistConnsPerProxy : mMaxPersistConnsPerHost;
+
+ // Leave room for at least 3 distinct types to operate concurrently,
+ // this satisfies the typical {html, js/css, img} page.
+ if (currentConns >= (maxConns - 2))
+ return true; /* prefer pipeline */
+
+ int32_t sameClass = 0;
+ for (int32_t i = 0; i < currentConns; ++i)
+ if (classification == ent->mActiveConns[i]->Classification())
+ if (++sameClass == 3)
+ return true; /* prefer pipeline */
+
+ return false; /* normal behavior */
+}
+
+// returns OK if a connection is found for the transaction
+// and the transaction is started.
+// returns ERROR_NOT_AVAILABLE if no connection can be found and it
+// should be queued until circumstances change
+// returns other ERROR when transaction has a hard failure and should
+// not remain in the pending queue
+nsresult
+nsHttpConnectionMgr::TryDispatchTransaction(nsConnectionEntry *ent,
+ bool onlyReusedConnection,
+ nsHttpTransaction *trans)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ LOG(("nsHttpConnectionMgr::TryDispatchTransaction without conn "
+ "[trans=%p ci=%p ci=%s caps=%x tunnelprovider=%p onlyreused=%d "
+ "active=%d idle=%d]\n", trans,
+ ent->mConnInfo.get(), ent->mConnInfo->HashKey().get(),
+ uint32_t(trans->Caps()), trans->TunnelProvider(),
+ onlyReusedConnection, ent->mActiveConns.Length(),
+ ent->mIdleConns.Length()));
+
+ nsHttpTransaction::Classifier classification = trans->Classification();
+ uint32_t caps = trans->Caps();
+
+ // no keep-alive means no pipelines either
+ if (!(caps & NS_HTTP_ALLOW_KEEPALIVE))
+ caps = caps & ~NS_HTTP_ALLOW_PIPELINING;
+
+ // 0 - If this should use spdy then dispatch it post haste.
+ // 1 - If there is connection pressure then see if we can pipeline this on
+ // a connection of a matching type instead of using a new conn
+ // 2 - If there is an idle connection, use it!
+ // 3 - if class == reval or script and there is an open conn of that type
+ // then pipeline onto shortest pipeline of that class if limits allow
+ // 4 - If we aren't up against our connection limit,
+ // then open a new one
+ // 5 - Try a pipeline if we haven't already - this will be unusual because
+ // it implies a low connection pressure situation where
+ // MakeNewConnection() failed.. that is possible, but unlikely, due to
+ // global limits
+ // 6 - no connection is available - queue it
+
+ bool attemptedOptimisticPipeline = !(caps & NS_HTTP_ALLOW_PIPELINING);
+ RefPtr<nsHttpConnection> unusedSpdyPersistentConnection;
+
+ // step 0
+ // look for existing spdy connection - that's always best because it is
+ // essentially pipelining without head of line blocking
+
+ if (!(caps & NS_HTTP_DISALLOW_SPDY) && gHttpHandler->IsSpdyEnabled()) {
+ RefPtr<nsHttpConnection> conn = GetSpdyPreferredConn(ent);
+ if (conn) {
+ if ((caps & NS_HTTP_ALLOW_KEEPALIVE) || !conn->IsExperienced()) {
+ LOG((" dispatch to spdy: [conn=%p]\n", conn.get()));
+ trans->RemoveDispatchedAsBlocking(); /* just in case */
+ DispatchTransaction(ent, trans, conn);
+ return NS_OK;
+ }
+ unusedSpdyPersistentConnection = conn;
+ }
+ }
+
+ // If this is not a blocking transaction and the request context for it is
+ // currently processing one or more blocking transactions then we
+ // need to just leave it in the queue until those are complete unless it is
+ // explicitly marked as unblocked.
+ if (!(caps & NS_HTTP_LOAD_AS_BLOCKING)) {
+ if (!(caps & NS_HTTP_LOAD_UNBLOCKED)) {
+ nsIRequestContext *requestContext = trans->RequestContext();
+ if (requestContext) {
+ uint32_t blockers = 0;
+ if (NS_SUCCEEDED(requestContext->GetBlockingTransactionCount(&blockers)) &&
+ blockers) {
+ // need to wait for blockers to clear
+ LOG((" blocked by request context: [rc=%p trans=%p blockers=%d]\n",
+ requestContext, trans, blockers));
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ }
+ }
+ } else {
+ // Mark the transaction and its load group as blocking right now to prevent
+ // other transactions from being reordered in the queue due to slow syns.
+ trans->DispatchedAsBlocking();
+ }
+
+ // step 1
+ // If connection pressure, then we want to favor pipelining of any kind
+ if (IsUnderPressure(ent, classification) && !attemptedOptimisticPipeline) {
+ attemptedOptimisticPipeline = true;
+ if (AddToShortestPipeline(ent, trans,
+ classification,
+ mMaxOptimisticPipelinedRequests)) {
+ LOG((" dispatched step 1 trans=%p\n", trans));
+ return NS_OK;
+ }
+ }
+
+ // Subject most transactions at high parallelism to rate pacing.
+ // It will only be actually submitted to the
+ // token bucket once, and if possible it is granted admission synchronously.
+ // It is important to leave a transaction in the pending queue when blocked by
+ // pacing so it can be found on cancel if necessary.
+ // Transactions that cause blocking or bypass it (e.g. js/css) are not rate
+ // limited.
+ if (gHttpHandler->UseRequestTokenBucket()) {
+ // submit even whitelisted transactions to the token bucket though they will
+ // not be slowed by it
+ bool runNow = trans->TryToRunPacedRequest();
+ if (!runNow) {
+ if ((mNumActiveConns - mNumSpdyActiveConns) <=
+ gHttpHandler->RequestTokenBucketMinParallelism()) {
+ runNow = true; // white list it
+ } else if (caps & (NS_HTTP_LOAD_AS_BLOCKING | NS_HTTP_LOAD_UNBLOCKED)) {
+ runNow = true; // white list it
+ }
+ }
+ if (!runNow) {
+ LOG((" blocked due to rate pacing trans=%p\n", trans));
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ }
+
+ // step 2
+ // consider an idle persistent connection
+ if (caps & NS_HTTP_ALLOW_KEEPALIVE) {
+ RefPtr<nsHttpConnection> conn;
+ while (!conn && (ent->mIdleConns.Length() > 0)) {
+ conn = ent->mIdleConns[0];
+ ent->mIdleConns.RemoveElementAt(0);
+ mNumIdleConns--;
+
+ // we check if the connection can be reused before even checking if
+ // it is a "matching" connection.
+ if (!conn->CanReuse()) {
+ LOG((" dropping stale connection: [conn=%p]\n", conn.get()));
+ conn->Close(NS_ERROR_ABORT);
+ conn = nullptr;
+ }
+ else {
+ LOG((" reusing connection [conn=%p]\n", conn.get()));
+ conn->EndIdleMonitoring();
+ }
+
+ // If there are no idle connections left at all, we need to make
+ // sure that we are not pruning dead connections anymore.
+ ConditionallyStopPruneDeadConnectionsTimer();
+ }
+ if (conn) {
+ // This will update the class of the connection to be the class of
+ // the transaction dispatched on it.
+ AddActiveConn(conn, ent);
+ DispatchTransaction(ent, trans, conn);
+ LOG((" dispatched step 2 (idle) trans=%p\n", trans));
+ return NS_OK;
+ }
+ }
+
+ // step 3
+ // consider pipelining scripts and revalidations
+ if (!attemptedOptimisticPipeline &&
+ (classification == nsHttpTransaction::CLASS_REVALIDATION ||
+ classification == nsHttpTransaction::CLASS_SCRIPT)) {
+ // Assignation kept here for documentation purpose; Never read after
+ attemptedOptimisticPipeline = true;
+ if (AddToShortestPipeline(ent, trans,
+ classification,
+ mMaxOptimisticPipelinedRequests)) {
+ LOG((" dispatched step 3 (pipeline) trans=%p\n", trans));
+ return NS_OK;
+ }
+ }
+
+ // step 4
+ if (!onlyReusedConnection) {
+ nsresult rv = MakeNewConnection(ent, trans);
+ if (NS_SUCCEEDED(rv)) {
+ // this function returns NOT_AVAILABLE for asynchronous connects
+ LOG((" dispatched step 4 (async new conn) trans=%p\n", trans));
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ if (rv != NS_ERROR_NOT_AVAILABLE) {
+ // not available return codes should try next step as they are
+ // not hard errors. Other codes should stop now
+ LOG((" failed step 4 (%x) trans=%p\n", rv, trans));
+ return rv;
+ }
+ } else if (trans->TunnelProvider() && trans->TunnelProvider()->MaybeReTunnel(trans)) {
+ LOG((" sort of dispatched step 4a tunnel requeue trans=%p\n", trans));
+ // the tunnel provider took responsibility for making a new tunnel
+ return NS_OK;
+ }
+
+ // step 5
+ if (caps & NS_HTTP_ALLOW_PIPELINING) {
+ if (AddToShortestPipeline(ent, trans,
+ classification,
+ mMaxPipelinedRequests)) {
+ LOG((" dispatched step 5 trans=%p\n", trans));
+ return NS_OK;
+ }
+ }
+
+ // step 6
+ if (unusedSpdyPersistentConnection) {
+ // to avoid deadlocks, we need to throw away this perfectly valid SPDY
+ // connection to make room for a new one that can service a no KEEPALIVE
+ // request
+ unusedSpdyPersistentConnection->DontReuse();
+ }
+
+ LOG((" not dispatched (queued) trans=%p\n", trans));
+ return NS_ERROR_NOT_AVAILABLE; /* queue it */
+}
+
+nsresult
+nsHttpConnectionMgr::DispatchTransaction(nsConnectionEntry *ent,
+ nsHttpTransaction *trans,
+ nsHttpConnection *conn)
+{
+ uint32_t caps = trans->Caps();
+ int32_t priority = trans->Priority();
+ nsresult rv;
+
+ LOG(("nsHttpConnectionMgr::DispatchTransaction "
+ "[ent-ci=%s %p trans=%p caps=%x conn=%p priority=%d]\n",
+ ent->mConnInfo->HashKey().get(), ent, trans, caps, conn, priority));
+
+ // It is possible for a rate-paced transaction to be dispatched independent
+ // of the token bucket when the amount of parallelization has changed or
+ // when a muxed connection (e.g. spdy or pipelines) becomes available.
+ trans->CancelPacing(NS_OK);
+
+ if (conn->UsingSpdy()) {
+ LOG(("Spdy Dispatch Transaction via Activate(). Transaction host = %s, "
+ "Connection host = %s\n",
+ trans->ConnectionInfo()->Origin(),
+ conn->ConnectionInfo()->Origin()));
+ rv = conn->Activate(trans, caps, priority);
+ MOZ_ASSERT(NS_SUCCEEDED(rv), "SPDY Cannot Fail Dispatch");
+ if (NS_SUCCEEDED(rv) && !trans->GetPendingTime().IsNull()) {
+ AccumulateTimeDelta(Telemetry::TRANSACTION_WAIT_TIME_SPDY,
+ trans->GetPendingTime(), TimeStamp::Now());
+ trans->SetPendingTime(false);
+ }
+ return rv;
+ }
+
+ MOZ_ASSERT(conn && !conn->Transaction(),
+ "DispatchTranaction() on non spdy active connection");
+
+ if (!(caps & NS_HTTP_ALLOW_PIPELINING))
+ conn->Classify(nsAHttpTransaction::CLASS_SOLO);
+ else
+ conn->Classify(trans->Classification());
+
+ rv = DispatchAbstractTransaction(ent, trans, caps, conn, priority);
+
+ if (NS_SUCCEEDED(rv) && !trans->GetPendingTime().IsNull()) {
+ if (trans->UsesPipelining())
+ AccumulateTimeDelta(Telemetry::TRANSACTION_WAIT_TIME_HTTP_PIPELINES,
+ trans->GetPendingTime(), TimeStamp::Now());
+ else
+ AccumulateTimeDelta(Telemetry::TRANSACTION_WAIT_TIME_HTTP,
+ trans->GetPendingTime(), TimeStamp::Now());
+ trans->SetPendingTime(false);
+ }
+ return rv;
+}
+
+//-----------------------------------------------------------------------------
+// ConnectionHandle
+//
+// thin wrapper around a real connection, used to keep track of references
+// to the connection to determine when the connection may be reused. the
+// transaction (or pipeline) owns a reference to this handle. this extra
+// layer of indirection greatly simplifies consumer code, avoiding the
+// need for consumer code to know when to give the connection back to the
+// connection manager.
+//
+class ConnectionHandle : public nsAHttpConnection
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSAHTTPCONNECTION(mConn)
+
+ explicit ConnectionHandle(nsHttpConnection *conn) : mConn(conn) { }
+ void Reset() { mConn = nullptr; }
+private:
+ virtual ~ConnectionHandle();
+ RefPtr<nsHttpConnection> mConn;
+};
+
+nsAHttpConnection *
+nsHttpConnectionMgr::MakeConnectionHandle(nsHttpConnection *aWrapped)
+{
+ return new ConnectionHandle(aWrapped);
+}
+
+ConnectionHandle::~ConnectionHandle()
+{
+ if (mConn) {
+ gHttpHandler->ReclaimConnection(mConn);
+ }
+}
+
+NS_IMPL_ISUPPORTS0(ConnectionHandle)
+
+// Use this method for dispatching nsAHttpTransction's. It can only safely be
+// used upon first use of a connection when NPN has not negotiated SPDY vs
+// HTTP/1 yet as multiplexing onto an existing SPDY session requires a
+// concrete nsHttpTransaction
+nsresult
+nsHttpConnectionMgr::DispatchAbstractTransaction(nsConnectionEntry *ent,
+ nsAHttpTransaction *aTrans,
+ uint32_t caps,
+ nsHttpConnection *conn,
+ int32_t priority)
+{
+ MOZ_ASSERT(!conn->UsingSpdy(),
+ "Spdy Must Not Use DispatchAbstractTransaction");
+ LOG(("nsHttpConnectionMgr::DispatchAbstractTransaction "
+ "[ci=%s trans=%p caps=%x conn=%p]\n",
+ ent->mConnInfo->HashKey().get(), aTrans, caps, conn));
+
+ /* Use pipeline datastructure even if connection does not currently qualify
+ to pipeline this transaction because a different pipeline-eligible
+ transaction might be placed on the active connection. Make an exception
+ for CLASS_SOLO as that connection will never pipeline until it goes
+ quiescent */
+
+ RefPtr<nsAHttpTransaction> transaction;
+ nsresult rv;
+ if (conn->Classification() != nsAHttpTransaction::CLASS_SOLO) {
+ LOG((" using pipeline datastructure.\n"));
+ RefPtr<nsHttpPipeline> pipeline;
+ rv = BuildPipeline(ent, aTrans, getter_AddRefs(pipeline));
+ if (!NS_SUCCEEDED(rv))
+ return rv;
+ transaction = pipeline;
+ }
+ else {
+ LOG((" not using pipeline datastructure due to class solo.\n"));
+ transaction = aTrans;
+ }
+
+ RefPtr<ConnectionHandle> handle = new ConnectionHandle(conn);
+
+ // give the transaction the indirect reference to the connection.
+ transaction->SetConnection(handle);
+
+ rv = conn->Activate(transaction, caps, priority);
+ if (NS_FAILED(rv)) {
+ LOG((" conn->Activate failed [rv=%x]\n", rv));
+ ent->mActiveConns.RemoveElement(conn);
+ if (conn == ent->mYellowConnection)
+ ent->OnYellowComplete();
+ DecrementActiveConnCount(conn);
+ ConditionallyStopTimeoutTick();
+
+ // sever back references to connection, and do so without triggering
+ // a call to ReclaimConnection ;-)
+ transaction->SetConnection(nullptr);
+ handle->Reset(); // destroy the connection
+ }
+
+ // As transaction goes out of scope it will drop the last refernece to the
+ // pipeline if activation failed, in which case this will destroy
+ // the pipeline, which will cause each the transactions owned by the
+ // pipeline to be restarted.
+
+ return rv;
+}
+
+nsresult
+nsHttpConnectionMgr::BuildPipeline(nsConnectionEntry *ent,
+ nsAHttpTransaction *firstTrans,
+ nsHttpPipeline **result)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ /* form a pipeline here even if nothing is pending so that we
+ can stream-feed it as new transactions arrive */
+
+ /* the first transaction can go in unconditionally - 1 transaction
+ on a nsHttpPipeline object is not a real HTTP pipeline */
+
+ RefPtr<nsHttpPipeline> pipeline = new nsHttpPipeline();
+ pipeline->AddTransaction(firstTrans);
+ pipeline.forget(result);
+ return NS_OK;
+}
+
+void
+nsHttpConnectionMgr::ReportProxyTelemetry(nsConnectionEntry *ent)
+{
+ enum { PROXY_NONE = 1, PROXY_HTTP = 2, PROXY_SOCKS = 3, PROXY_HTTPS = 4 };
+
+ if (!ent->mConnInfo->UsingProxy())
+ Telemetry::Accumulate(Telemetry::HTTP_PROXY_TYPE, PROXY_NONE);
+ else if (ent->mConnInfo->UsingHttpsProxy())
+ Telemetry::Accumulate(Telemetry::HTTP_PROXY_TYPE, PROXY_HTTPS);
+ else if (ent->mConnInfo->UsingHttpProxy())
+ Telemetry::Accumulate(Telemetry::HTTP_PROXY_TYPE, PROXY_HTTP);
+ else
+ Telemetry::Accumulate(Telemetry::HTTP_PROXY_TYPE, PROXY_SOCKS);
+}
+
+nsresult
+nsHttpConnectionMgr::ProcessNewTransaction(nsHttpTransaction *trans)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ // since "adds" and "cancels" are processed asynchronously and because
+ // various events might trigger an "add" directly on the socket thread,
+ // we must take care to avoid dispatching a transaction that has already
+ // been canceled (see bug 190001).
+ if (NS_FAILED(trans->Status())) {
+ LOG((" transaction was canceled... dropping event!\n"));
+ return NS_OK;
+ }
+
+ trans->SetPendingTime();
+
+ Http2PushedStream *pushedStream = trans->GetPushedStream();
+ if (pushedStream) {
+ LOG((" ProcessNewTransaction %p tied to h2 session push %p\n",
+ trans, pushedStream->Session()));
+ return pushedStream->Session()->
+ AddStream(trans, trans->Priority(), false, nullptr) ?
+ NS_OK : NS_ERROR_UNEXPECTED;
+ }
+
+ nsresult rv = NS_OK;
+ nsHttpConnectionInfo *ci = trans->ConnectionInfo();
+ MOZ_ASSERT(ci);
+
+ nsConnectionEntry *ent =
+ GetOrCreateConnectionEntry(ci, !!trans->TunnelProvider());
+
+ // SPDY coalescing of hostnames means we might redirect from this
+ // connection entry onto the preferred one.
+ nsConnectionEntry *preferredEntry = GetSpdyPreferredEnt(ent);
+ if (preferredEntry && (preferredEntry != ent)) {
+ LOG(("nsHttpConnectionMgr::ProcessNewTransaction trans=%p "
+ "redirected via coalescing from %s to %s\n", trans,
+ ent->mConnInfo->Origin(), preferredEntry->mConnInfo->Origin()));
+
+ ent = preferredEntry;
+ }
+
+ ReportProxyTelemetry(ent);
+
+ // Check if the transaction already has a sticky reference to a connection.
+ // If so, then we can just use it directly by transferring its reference
+ // to the new connection variable instead of searching for a new one
+
+ nsAHttpConnection *wrappedConnection = trans->Connection();
+ RefPtr<nsHttpConnection> conn;
+ if (wrappedConnection)
+ conn = wrappedConnection->TakeHttpConnection();
+
+ if (conn) {
+ MOZ_ASSERT(trans->Caps() & NS_HTTP_STICKY_CONNECTION);
+ LOG(("nsHttpConnectionMgr::ProcessNewTransaction trans=%p "
+ "sticky connection=%p\n", trans, conn.get()));
+
+ if (static_cast<int32_t>(ent->mActiveConns.IndexOf(conn)) == -1) {
+ LOG(("nsHttpConnectionMgr::ProcessNewTransaction trans=%p "
+ "sticky connection=%p needs to go on the active list\n", trans, conn.get()));
+
+ // make sure it isn't on the idle list - we expect this to be an
+ // unknown fresh connection
+ MOZ_ASSERT(static_cast<int32_t>(ent->mIdleConns.IndexOf(conn)) == -1);
+ MOZ_ASSERT(!conn->IsExperienced());
+
+ AddActiveConn(conn, ent); // make it active
+ }
+
+ trans->SetConnection(nullptr);
+ rv = DispatchTransaction(ent, trans, conn);
+ } else {
+ rv = TryDispatchTransaction(ent, !!trans->TunnelProvider(), trans);
+ }
+
+ if (NS_SUCCEEDED(rv)) {
+ LOG((" ProcessNewTransaction Dispatch Immediately trans=%p\n", trans));
+ return rv;
+ }
+
+ if (rv == NS_ERROR_NOT_AVAILABLE) {
+ LOG((" adding transaction to pending queue "
+ "[trans=%p pending-count=%u]\n",
+ trans, ent->mPendingQ.Length()+1));
+ // put this transaction on the pending queue...
+ InsertTransactionSorted(ent->mPendingQ, trans);
+ return NS_OK;
+ }
+
+ LOG((" ProcessNewTransaction Hard Error trans=%p rv=%x\n", trans, rv));
+ return rv;
+}
+
+
+void
+nsHttpConnectionMgr::AddActiveConn(nsHttpConnection *conn,
+ nsConnectionEntry *ent)
+{
+ ent->mActiveConns.AppendElement(conn);
+ mNumActiveConns++;
+ ActivateTimeoutTick();
+}
+
+void
+nsHttpConnectionMgr::DecrementActiveConnCount(nsHttpConnection *conn)
+{
+ mNumActiveConns--;
+ if (conn->EverUsedSpdy())
+ mNumSpdyActiveConns--;
+}
+
+void
+nsHttpConnectionMgr::StartedConnect()
+{
+ mNumActiveConns++;
+ ActivateTimeoutTick(); // likely disabled by RecvdConnect()
+}
+
+void
+nsHttpConnectionMgr::RecvdConnect()
+{
+ mNumActiveConns--;
+ ConditionallyStopTimeoutTick();
+}
+
+nsresult
+nsHttpConnectionMgr::CreateTransport(nsConnectionEntry *ent,
+ nsAHttpTransaction *trans,
+ uint32_t caps,
+ bool speculative,
+ bool isFromPredictor,
+ bool allow1918)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ RefPtr<nsHalfOpenSocket> sock = new nsHalfOpenSocket(ent, trans, caps);
+ if (speculative) {
+ sock->SetSpeculative(true);
+ sock->SetAllow1918(allow1918);
+ Telemetry::AutoCounter<Telemetry::HTTPCONNMGR_TOTAL_SPECULATIVE_CONN> totalSpeculativeConn;
+ ++totalSpeculativeConn;
+
+ if (isFromPredictor) {
+ sock->SetIsFromPredictor(true);
+ Telemetry::AutoCounter<Telemetry::PREDICTOR_TOTAL_PRECONNECTS_CREATED> totalPreconnectsCreated;
+ ++totalPreconnectsCreated;
+ }
+ }
+
+ // The socket stream holds the reference to the half open
+ // socket - so if the stream fails to init the half open
+ // will go away.
+ nsresult rv = sock->SetupPrimaryStreams();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ ent->mHalfOpens.AppendElement(sock);
+ mNumHalfOpenConns++;
+ return NS_OK;
+}
+
+// This function tries to dispatch the pending spdy transactions on
+// the connection entry sent in as an argument. It will do so on the
+// active spdy connection either in that same entry or in the
+// redirected 'preferred' entry for the same coalescing hash key if
+// coalescing is enabled.
+
+void
+nsHttpConnectionMgr::ProcessSpdyPendingQ(nsConnectionEntry *ent)
+{
+ nsHttpConnection *conn = GetSpdyPreferredConn(ent);
+ if (!conn || !conn->CanDirectlyActivate())
+ return;
+
+ nsTArray<RefPtr<nsHttpTransaction> > leftovers;
+ uint32_t index;
+
+ // Dispatch all the transactions we can
+ for (index = 0;
+ index < ent->mPendingQ.Length() && conn->CanDirectlyActivate();
+ ++index) {
+ nsHttpTransaction *trans = ent->mPendingQ[index];
+
+ if (!(trans->Caps() & NS_HTTP_ALLOW_KEEPALIVE) ||
+ trans->Caps() & NS_HTTP_DISALLOW_SPDY) {
+ leftovers.AppendElement(trans);
+ continue;
+ }
+
+ nsresult rv = DispatchTransaction(ent, trans, conn);
+ if (NS_FAILED(rv)) {
+ // this cannot happen, but if due to some bug it does then
+ // close the transaction
+ MOZ_ASSERT(false, "Dispatch SPDY Transaction");
+ LOG(("ProcessSpdyPendingQ Dispatch Transaction failed trans=%p\n",
+ trans));
+ trans->Close(rv);
+ }
+ }
+
+ // Slurp up the rest of the pending queue into our leftovers bucket (we
+ // might have some left if conn->CanDirectlyActivate returned false)
+ for (; index < ent->mPendingQ.Length(); ++index) {
+ nsHttpTransaction *trans = ent->mPendingQ[index];
+ leftovers.AppendElement(trans);
+ }
+
+ // Put the leftovers back in the pending queue and get rid of the
+ // transactions we dispatched
+ leftovers.SwapElements(ent->mPendingQ);
+ leftovers.Clear();
+}
+
+void
+nsHttpConnectionMgr::OnMsgProcessAllSpdyPendingQ(int32_t, ARefBase *)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ LOG(("nsHttpConnectionMgr::OnMsgProcessAllSpdyPendingQ\n"));
+ for (auto iter = mCT.Iter(); !iter.Done(); iter.Next()) {
+ ProcessSpdyPendingQ(iter.Data());
+ }
+}
+
+nsHttpConnection *
+nsHttpConnectionMgr::GetSpdyPreferredConn(nsConnectionEntry *ent)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ MOZ_ASSERT(ent);
+
+ nsConnectionEntry *preferred = GetSpdyPreferredEnt(ent);
+ // this entry is spdy-enabled if it is involved in a redirect
+ if (preferred) {
+ // all new connections for this entry will use spdy too
+ ent->mUsingSpdy = true;
+ } else {
+ preferred = ent;
+ }
+
+ if (!preferred->mUsingSpdy) {
+ return nullptr;
+ }
+
+ nsHttpConnection *rv = nullptr;
+ uint32_t activeLen = preferred->mActiveConns.Length();
+ uint32_t index;
+
+ // activeLen should generally be 1.. this is a setup race being resolved
+ // take a conn who can activate and is experienced
+ for (index = 0; index < activeLen; ++index) {
+ nsHttpConnection *tmp = preferred->mActiveConns[index];
+ if (tmp->CanDirectlyActivate() && tmp->IsExperienced()) {
+ rv = tmp;
+ break;
+ }
+ }
+
+ // if that worked, cleanup anything else
+ if (rv) {
+ for (index = 0; index < activeLen; ++index) {
+ nsHttpConnection *tmp = preferred->mActiveConns[index];
+ // in the case where there is a functional h2 session, drop the others
+ if (tmp != rv) {
+ tmp->DontReuse();
+ }
+ }
+ return rv;
+ }
+
+ // take a conn who can activate and leave the rest alone
+ for (index = 0; index < activeLen; ++index) {
+ nsHttpConnection *tmp = preferred->mActiveConns[index];
+ if (tmp->CanDirectlyActivate()) {
+ rv = tmp;
+ break;
+ }
+ }
+ return rv;
+}
+
+//-----------------------------------------------------------------------------
+
+void
+nsHttpConnectionMgr::OnMsgShutdown(int32_t, ARefBase *param)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ LOG(("nsHttpConnectionMgr::OnMsgShutdown\n"));
+
+ gHttpHandler->StopRequestTokenBucket();
+
+ for (auto iter = mCT.Iter(); !iter.Done(); iter.Next()) {
+ nsAutoPtr<nsConnectionEntry>& ent = iter.Data();
+
+ // Close all active connections.
+ while (ent->mActiveConns.Length()) {
+ RefPtr<nsHttpConnection> conn(ent->mActiveConns[0]);
+ ent->mActiveConns.RemoveElementAt(0);
+ DecrementActiveConnCount(conn);
+ // Since nsHttpConnection::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);
+ }
+
+ // Close all idle connections.
+ while (ent->mIdleConns.Length()) {
+ RefPtr<nsHttpConnection> conn(ent->mIdleConns[0]);
+
+ ent->mIdleConns.RemoveElementAt(0);
+ mNumIdleConns--;
+
+ conn->Close(NS_ERROR_ABORT);
+ }
+
+ // If all idle connections are removed we can stop pruning dead
+ // connections.
+ ConditionallyStopPruneDeadConnectionsTimer();
+
+ // Close all pending transactions.
+ while (ent->mPendingQ.Length()) {
+ nsHttpTransaction *trans = ent->mPendingQ[0];
+ trans->Close(NS_ERROR_ABORT);
+ ent->mPendingQ.RemoveElementAt(0);
+ }
+
+ // Close all half open tcp connections.
+ for (int32_t i = int32_t(ent->mHalfOpens.Length()) - 1; i >= 0; i--) {
+ ent->mHalfOpens[i]->Abandon();
+ }
+
+ iter.Remove();
+ }
+
+ if (mTimeoutTick) {
+ mTimeoutTick->Cancel();
+ mTimeoutTick = nullptr;
+ mTimeoutTickArmed = false;
+ }
+ if (mTimer) {
+ mTimer->Cancel();
+ mTimer = nullptr;
+ }
+ if (mTrafficTimer) {
+ mTrafficTimer->Cancel();
+ mTrafficTimer = nullptr;
+ }
+
+ // signal shutdown complete
+ nsCOMPtr<nsIRunnable> runnable =
+ new ConnEvent(this, &nsHttpConnectionMgr::OnMsgShutdownConfirm,
+ 0, param);
+ NS_DispatchToMainThread(runnable);
+}
+
+void
+nsHttpConnectionMgr::OnMsgShutdownConfirm(int32_t priority, ARefBase *param)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ LOG(("nsHttpConnectionMgr::OnMsgShutdownConfirm\n"));
+
+ BoolWrapper *shutdown = static_cast<BoolWrapper *>(param);
+ shutdown->mBool = true;
+}
+
+void
+nsHttpConnectionMgr::OnMsgNewTransaction(int32_t priority, ARefBase *param)
+{
+ LOG(("nsHttpConnectionMgr::OnMsgNewTransaction [trans=%p]\n", param));
+
+ nsHttpTransaction *trans = static_cast<nsHttpTransaction *>(param);
+ trans->SetPriority(priority);
+ nsresult rv = ProcessNewTransaction(trans);
+ if (NS_FAILED(rv))
+ trans->Close(rv); // for whatever its worth
+}
+
+void
+nsHttpConnectionMgr::OnMsgReschedTransaction(int32_t priority, ARefBase *param)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ LOG(("nsHttpConnectionMgr::OnMsgReschedTransaction [trans=%p]\n", param));
+
+ RefPtr<nsHttpTransaction> trans = static_cast<nsHttpTransaction *>(param);
+ trans->SetPriority(priority);
+
+ nsConnectionEntry *ent = LookupConnectionEntry(trans->ConnectionInfo(),
+ nullptr, trans);
+
+ if (ent) {
+ int32_t index = ent->mPendingQ.IndexOf(trans);
+ if (index >= 0) {
+ ent->mPendingQ.RemoveElementAt(index);
+ InsertTransactionSorted(ent->mPendingQ, trans);
+ }
+ }
+}
+
+void
+nsHttpConnectionMgr::OnMsgCancelTransaction(int32_t reason, ARefBase *param)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ LOG(("nsHttpConnectionMgr::OnMsgCancelTransaction [trans=%p]\n", param));
+
+ nsresult closeCode = static_cast<nsresult>(reason);
+
+ // caller holds a ref to param/trans on stack
+ nsHttpTransaction *trans = static_cast<nsHttpTransaction *>(param);
+
+ //
+ // if the transaction owns a connection and the transaction is not done,
+ // then ask the connection to close the transaction. otherwise, close the
+ // transaction directly (removing it from the pending queue first).
+ //
+ RefPtr<nsAHttpConnection> conn(trans->Connection());
+ if (conn && !trans->IsDone()) {
+ conn->CloseTransaction(trans, closeCode);
+ } else {
+ nsConnectionEntry *ent =
+ LookupConnectionEntry(trans->ConnectionInfo(), nullptr, trans);
+
+ if (ent) {
+ int32_t transIndex = ent->mPendingQ.IndexOf(trans);
+ if (transIndex >= 0) {
+ LOG(("nsHttpConnectionMgr::OnMsgCancelTransaction [trans=%p]"
+ " found in pending queue\n", trans));
+ ent->mPendingQ.RemoveElementAt(transIndex);
+ }
+
+ // Abandon all half-open sockets belonging to the given transaction.
+ for (uint32_t index = 0;
+ index < ent->mHalfOpens.Length();
+ ++index) {
+ nsHalfOpenSocket *half = ent->mHalfOpens[index];
+ if (trans == half->Transaction()) {
+ half->Abandon();
+ // there is only one, and now mHalfOpens[] has been changed.
+ break;
+ }
+ }
+ }
+
+ trans->Close(closeCode);
+
+ // Cancel is a pretty strong signal that things might be hanging
+ // so we want to cancel any null transactions related to this connection
+ // entry. They are just optimizations, but they aren't hooked up to
+ // anything that might get canceled from the rest of gecko, so best
+ // to assume that's what was meant by the cancel we did receive if
+ // it only applied to something in the queue.
+ for (uint32_t index = 0;
+ ent && (index < ent->mActiveConns.Length());
+ ++index) {
+ nsHttpConnection *activeConn = ent->mActiveConns[index];
+ nsAHttpTransaction *liveTransaction = activeConn->Transaction();
+ if (liveTransaction && liveTransaction->IsNullTransaction()) {
+ LOG(("nsHttpConnectionMgr::OnMsgCancelTransaction [trans=%p] "
+ "also canceling Null Transaction %p on conn %p\n",
+ trans, liveTransaction, activeConn));
+ activeConn->CloseTransaction(liveTransaction, closeCode);
+ }
+ }
+ }
+}
+
+void
+nsHttpConnectionMgr::OnMsgProcessPendingQ(int32_t, ARefBase *param)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ nsHttpConnectionInfo *ci = static_cast<nsHttpConnectionInfo *>(param);
+
+ if (!ci) {
+ LOG(("nsHttpConnectionMgr::OnMsgProcessPendingQ [ci=nullptr]\n"));
+ // Try and dispatch everything
+ for (auto iter = mCT.Iter(); !iter.Done(); iter.Next()) {
+ ProcessPendingQForEntry(iter.Data(), true);
+ }
+ return;
+ }
+
+ LOG(("nsHttpConnectionMgr::OnMsgProcessPendingQ [ci=%s]\n",
+ ci->HashKey().get()));
+
+ // start by processing the queue identified by the given connection info.
+ nsConnectionEntry *ent = mCT.Get(ci->HashKey());
+ if (!(ent && ProcessPendingQForEntry(ent, false))) {
+ // if we reach here, it means that we couldn't dispatch a transaction
+ // for the specified connection info. walk the connection table...
+ for (auto iter = mCT.Iter(); !iter.Done(); iter.Next()) {
+ if (ProcessPendingQForEntry(iter.Data(), false)) {
+ break;
+ }
+ }
+ }
+}
+
+nsresult
+nsHttpConnectionMgr::CancelTransactions(nsHttpConnectionInfo *ci, nsresult code)
+{
+ LOG(("nsHttpConnectionMgr::CancelTransactions %s\n",ci->HashKey().get()));
+
+ int32_t intReason = static_cast<int32_t>(code);
+ return PostEvent(&nsHttpConnectionMgr::OnMsgCancelTransactions, intReason, ci);
+}
+
+void
+nsHttpConnectionMgr::OnMsgCancelTransactions(int32_t code, ARefBase *param)
+{
+ nsresult reason = static_cast<nsresult>(code);
+ nsHttpConnectionInfo *ci = static_cast<nsHttpConnectionInfo *>(param);
+ nsConnectionEntry *ent = mCT.Get(ci->HashKey());
+ LOG(("nsHttpConnectionMgr::OnMsgCancelTransactions %s %p\n",
+ ci->HashKey().get(), ent));
+ if (!ent) {
+ return;
+ }
+
+ for (int32_t i = ent->mPendingQ.Length() - 1; i >= 0; --i) {
+ nsHttpTransaction *trans = ent->mPendingQ[i];
+ LOG(("nsHttpConnectionMgr::OnMsgCancelTransactions %s %p %p\n",
+ ci->HashKey().get(), ent, trans));
+ trans->Close(reason);
+ ent->mPendingQ.RemoveElementAt(i);
+ }
+}
+
+void
+nsHttpConnectionMgr::OnMsgPruneDeadConnections(int32_t, ARefBase *)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ LOG(("nsHttpConnectionMgr::OnMsgPruneDeadConnections\n"));
+
+ // Reset mTimeOfNextWakeUp so that we can find a new shortest value.
+ mTimeOfNextWakeUp = UINT64_MAX;
+
+ // check canreuse() for all idle connections plus any active connections on
+ // connection entries that are using spdy.
+ if (mNumIdleConns || (mNumActiveConns && gHttpHandler->IsSpdyEnabled())) {
+ for (auto iter = mCT.Iter(); !iter.Done(); iter.Next()) {
+ nsAutoPtr<nsConnectionEntry>& ent = iter.Data();
+
+ LOG((" pruning [ci=%s]\n", ent->mConnInfo->HashKey().get()));
+
+ // Find out how long it will take for next idle connection to not
+ // be reusable anymore.
+ uint32_t timeToNextExpire = UINT32_MAX;
+ int32_t count = ent->mIdleConns.Length();
+ if (count > 0) {
+ for (int32_t i = count - 1; i >= 0; --i) {
+ RefPtr<nsHttpConnection> conn(ent->mIdleConns[i]);
+ if (!conn->CanReuse()) {
+ ent->mIdleConns.RemoveElementAt(i);
+ conn->Close(NS_ERROR_ABORT);
+ mNumIdleConns--;
+ } else {
+ timeToNextExpire =
+ std::min(timeToNextExpire, conn->TimeToLive());
+ }
+ }
+ }
+
+ if (ent->mUsingSpdy) {
+ for (uint32_t i = 0; i < ent->mActiveConns.Length(); ++i) {
+ nsHttpConnection* conn = ent->mActiveConns[i];
+ if (conn->UsingSpdy()) {
+ if (!conn->CanReuse()) {
+ // Marking it don't-reuse will create an active
+ // tear down if the spdy session is idle.
+ conn->DontReuse();
+ } else {
+ timeToNextExpire =
+ std::min(timeToNextExpire, conn->TimeToLive());
+ }
+ }
+ }
+ }
+
+ // If time to next expire found is shorter than time to next
+ // wake-up, we need to change the time for next wake-up.
+ if (timeToNextExpire != UINT32_MAX) {
+ uint32_t now = NowInSeconds();
+ uint64_t timeOfNextExpire = now + timeToNextExpire;
+ // If pruning of dead connections is not already scheduled to
+ // happen or time found for next connection to expire is is
+ // before mTimeOfNextWakeUp, we need to schedule the pruning to
+ // happen after timeToNextExpire.
+ if (!mTimer || timeOfNextExpire < mTimeOfNextWakeUp) {
+ PruneDeadConnectionsAfter(timeToNextExpire);
+ }
+ } else {
+ ConditionallyStopPruneDeadConnectionsTimer();
+ }
+
+ // If this entry is empty, we have too many entries, and this
+ // doesn't represent some painfully determined red condition, then
+ // we can clean it up and restart from yellow.
+ if (ent->PipelineState() != PS_RED &&
+ mCT.Count() > 125 &&
+ ent->mIdleConns.Length() == 0 &&
+ ent->mActiveConns.Length() == 0 &&
+ ent->mHalfOpens.Length() == 0 &&
+ ent->mPendingQ.Length() == 0 &&
+ (!ent->mUsingSpdy || mCT.Count() > 300)) {
+ LOG((" removing empty connection entry\n"));
+ iter.Remove();
+ continue;
+ }
+
+ // Otherwise use this opportunity to compact our arrays...
+ ent->mIdleConns.Compact();
+ ent->mActiveConns.Compact();
+ ent->mPendingQ.Compact();
+ }
+ }
+}
+
+void
+nsHttpConnectionMgr::OnMsgPruneNoTraffic(int32_t, ARefBase *)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ LOG(("nsHttpConnectionMgr::OnMsgPruneNoTraffic\n"));
+
+ // Prune connections without traffic
+ for (auto iter = mCT.Iter(); !iter.Done(); iter.Next()) {
+
+ // Close the connections with no registered traffic.
+ nsAutoPtr<nsConnectionEntry>& ent = iter.Data();
+
+ LOG((" pruning no traffic [ci=%s]\n",
+ ent->mConnInfo->HashKey().get()));
+
+ uint32_t numConns = ent->mActiveConns.Length();
+ if (numConns) {
+ // Walk the list backwards to allow us to remove entries easily.
+ for (int index = numConns - 1; index >= 0; index--) {
+ if (ent->mActiveConns[index]->NoTraffic()) {
+ RefPtr<nsHttpConnection> conn = ent->mActiveConns[index];
+ ent->mActiveConns.RemoveElementAt(index);
+ DecrementActiveConnCount(conn);
+ conn->Close(NS_ERROR_ABORT);
+ LOG((" closed active connection due to no traffic "
+ "[conn=%p]\n", conn.get()));
+ }
+ }
+ }
+ }
+
+ mPruningNoTraffic = false; // not pruning anymore
+}
+
+void
+nsHttpConnectionMgr::OnMsgVerifyTraffic(int32_t, ARefBase *)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ LOG(("nsHttpConnectionMgr::OnMsgVerifyTraffic\n"));
+
+ if (mPruningNoTraffic) {
+ // Called in the time gap when the timeout to prune notraffic
+ // connections has triggered but the pruning hasn't happened yet.
+ return;
+ }
+
+ // Mark connections for traffic verification
+ for (auto iter = mCT.Iter(); !iter.Done(); iter.Next()) {
+ nsAutoPtr<nsConnectionEntry>& ent = iter.Data();
+
+ // Iterate over all active connections and check them.
+ for (uint32_t index = 0; index < ent->mActiveConns.Length(); ++index) {
+ ent->mActiveConns[index]->CheckForTraffic(true);
+ }
+ // Iterate the idle connections and unmark them for traffic checks.
+ for (uint32_t index = 0; index < ent->mIdleConns.Length(); ++index) {
+ ent->mIdleConns[index]->CheckForTraffic(false);
+ }
+ }
+
+ // If the timer is already there. we just re-init it
+ if(!mTrafficTimer) {
+ mTrafficTimer = do_CreateInstance("@mozilla.org/timer;1");
+ }
+
+ // failure to create a timer is not a fatal error, but dead
+ // connections will not be cleaned up as nicely
+ if (mTrafficTimer) {
+ // Give active connections time to get more traffic before killing
+ // them off. Default: 5000 milliseconds
+ mTrafficTimer->Init(this, gHttpHandler->NetworkChangedTimeout(),
+ nsITimer::TYPE_ONE_SHOT);
+ } else {
+ NS_WARNING("failed to create timer for VerifyTraffic!");
+ }
+}
+
+void
+nsHttpConnectionMgr::OnMsgDoShiftReloadConnectionCleanup(int32_t, ARefBase *param)
+{
+ LOG(("nsHttpConnectionMgr::OnMsgDoShiftReloadConnectionCleanup\n"));
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ nsHttpConnectionInfo *ci = static_cast<nsHttpConnectionInfo *>(param);
+
+ for (auto iter = mCT.Iter(); !iter.Done(); iter.Next()) {
+ ClosePersistentConnections(iter.Data());
+ }
+
+ if (ci)
+ ResetIPFamilyPreference(ci);
+}
+
+void
+nsHttpConnectionMgr::OnMsgReclaimConnection(int32_t, ARefBase *param)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ LOG(("nsHttpConnectionMgr::OnMsgReclaimConnection [conn=%p]\n", param));
+
+ nsHttpConnection *conn = static_cast<nsHttpConnection *>(param);
+
+ //
+ // 1) remove the connection from the active list
+ // 2) if keep-alive, add connection to idle list
+ // 3) post event to process the pending transaction queue
+ //
+
+ nsConnectionEntry *ent = LookupConnectionEntry(conn->ConnectionInfo(),
+ conn, nullptr);
+ if (!ent) {
+ // this can happen if the connection is made outside of the
+ // connection manager and is being "reclaimed" for use with
+ // future transactions. HTTP/2 tunnels work like this.
+ ent = GetOrCreateConnectionEntry(conn->ConnectionInfo(), true);
+ LOG(("nsHttpConnectionMgr::OnMsgReclaimConnection conn %p "
+ "forced new hash entry %s\n",
+ conn, conn->ConnectionInfo()->HashKey().get()));
+ }
+
+ MOZ_ASSERT(ent);
+ RefPtr<nsHttpConnectionInfo> ci(ent->mConnInfo);
+
+ // If the connection is in the active list, remove that entry
+ // and the reference held by the mActiveConns list.
+ // This is never the final reference on conn as the event context
+ // is also holding one that is released at the end of this function.
+
+ if (conn->EverUsedSpdy()) {
+ // Spdy connections aren't reused in the traditional HTTP way in
+ // the idleconns list, they are actively multplexed as active
+ // conns. Even when they have 0 transactions on them they are
+ // considered active connections. So when one is reclaimed it
+ // is really complete and is meant to be shut down and not
+ // reused.
+ conn->DontReuse();
+ }
+
+ // a connection that still holds a reference to a transaction was
+ // not closed naturally (i.e. it was reset or aborted) and is
+ // therefore not something that should be reused.
+ if (conn->Transaction()) {
+ conn->DontReuse();
+ }
+
+ if (ent->mActiveConns.RemoveElement(conn)) {
+ if (conn == ent->mYellowConnection) {
+ ent->OnYellowComplete();
+ }
+ DecrementActiveConnCount(conn);
+ ConditionallyStopTimeoutTick();
+ }
+
+ if (conn->CanReuse()) {
+ LOG((" adding connection to idle list\n"));
+ // Keep The idle connection list sorted with the connections that
+ // have moved the largest data pipelines at the front because these
+ // connections have the largest cwnds on the server.
+
+ // The linear search is ok here because the number of idleconns
+ // in a single entry is generally limited to a small number (i.e. 6)
+
+ uint32_t idx;
+ for (idx = 0; idx < ent->mIdleConns.Length(); idx++) {
+ nsHttpConnection *idleConn = ent->mIdleConns[idx];
+ if (idleConn->MaxBytesRead() < conn->MaxBytesRead())
+ break;
+ }
+
+ ent->mIdleConns.InsertElementAt(idx, conn);
+ mNumIdleConns++;
+ conn->BeginIdleMonitoring();
+
+ // If the added connection was first idle connection or has shortest
+ // time to live among the watched connections, pruning dead
+ // connections needs to be done when it can't be reused anymore.
+ uint32_t timeToLive = conn->TimeToLive();
+ if(!mTimer || NowInSeconds() + timeToLive < mTimeOfNextWakeUp)
+ PruneDeadConnectionsAfter(timeToLive);
+ } else {
+ LOG((" connection cannot be reused; closing connection\n"));
+ conn->Close(NS_ERROR_ABORT);
+ }
+
+ OnMsgProcessPendingQ(0, ci);
+}
+
+void
+nsHttpConnectionMgr::OnMsgCompleteUpgrade(int32_t, ARefBase *param)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ nsCompleteUpgradeData *data = static_cast<nsCompleteUpgradeData *>(param);
+ LOG(("nsHttpConnectionMgr::OnMsgCompleteUpgrade "
+ "this=%p conn=%p listener=%p\n", this, data->mConn.get(),
+ data->mUpgradeListener.get()));
+
+ nsCOMPtr<nsISocketTransport> socketTransport;
+ nsCOMPtr<nsIAsyncInputStream> socketIn;
+ nsCOMPtr<nsIAsyncOutputStream> socketOut;
+
+ nsresult rv;
+ rv = data->mConn->TakeTransport(getter_AddRefs(socketTransport),
+ getter_AddRefs(socketIn),
+ getter_AddRefs(socketOut));
+
+ if (NS_SUCCEEDED(rv))
+ data->mUpgradeListener->OnTransportAvailable(socketTransport,
+ socketIn,
+ socketOut);
+}
+
+void
+nsHttpConnectionMgr::OnMsgUpdateParam(int32_t inParam, ARefBase *)
+{
+ uint32_t param = static_cast<uint32_t>(inParam);
+ uint16_t name = ((param) & 0xFFFF0000) >> 16;
+ uint16_t value = param & 0x0000FFFF;
+
+ switch (name) {
+ case MAX_CONNECTIONS:
+ mMaxConns = value;
+ break;
+ case MAX_PERSISTENT_CONNECTIONS_PER_HOST:
+ mMaxPersistConnsPerHost = value;
+ break;
+ case MAX_PERSISTENT_CONNECTIONS_PER_PROXY:
+ mMaxPersistConnsPerProxy = value;
+ break;
+ case MAX_REQUEST_DELAY:
+ mMaxRequestDelay = value;
+ break;
+ case MAX_PIPELINED_REQUESTS:
+ mMaxPipelinedRequests = value;
+ break;
+ case MAX_OPTIMISTIC_PIPELINED_REQUESTS:
+ mMaxOptimisticPipelinedRequests = value;
+ break;
+ default:
+ NS_NOTREACHED("unexpected parameter name");
+ }
+}
+
+// nsHttpConnectionMgr::nsConnectionEntry
+nsHttpConnectionMgr::nsConnectionEntry::~nsConnectionEntry()
+{
+ MOZ_COUNT_DTOR(nsConnectionEntry);
+ gHttpHandler->ConnMgr()->RemovePreferredHash(this);
+}
+
+void
+nsHttpConnectionMgr::OnMsgProcessFeedback(int32_t, ARefBase *param)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ nsHttpPipelineFeedback *fb = static_cast<nsHttpPipelineFeedback *>(param);
+ PipelineFeedbackInfo(fb->mConnInfo, fb->mInfo, fb->mConn, fb->mData);
+}
+
+// Read Timeout Tick handlers
+
+void
+nsHttpConnectionMgr::ActivateTimeoutTick()
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ LOG(("nsHttpConnectionMgr::ActivateTimeoutTick() "
+ "this=%p mTimeoutTick=%p\n", this, mTimeoutTick.get()));
+
+ // The timer tick should be enabled if it is not already pending.
+ // Upon running the tick will rearm itself if there are active
+ // connections available.
+
+ if (mTimeoutTick && mTimeoutTickArmed) {
+ // make sure we get one iteration on a quick tick
+ if (mTimeoutTickNext > 1) {
+ mTimeoutTickNext = 1;
+ mTimeoutTick->SetDelay(1000);
+ }
+ return;
+ }
+
+ if (!mTimeoutTick) {
+ mTimeoutTick = do_CreateInstance(NS_TIMER_CONTRACTID);
+ if (!mTimeoutTick) {
+ NS_WARNING("failed to create timer for http timeout management");
+ return;
+ }
+ mTimeoutTick->SetTarget(mSocketThreadTarget);
+ }
+
+ MOZ_ASSERT(!mTimeoutTickArmed, "timer tick armed");
+ mTimeoutTickArmed = true;
+ mTimeoutTick->Init(this, 1000, nsITimer::TYPE_REPEATING_SLACK);
+}
+
+void
+nsHttpConnectionMgr::TimeoutTick()
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ MOZ_ASSERT(mTimeoutTick, "no readtimeout tick");
+
+ LOG(("nsHttpConnectionMgr::TimeoutTick active=%d\n", mNumActiveConns));
+ // The next tick will be between 1 second and 1 hr
+ // Set it to the max value here, and the TimeoutTick()s can
+ // reduce it to their local needs.
+ mTimeoutTickNext = 3600; // 1hr
+
+ for (auto iter = mCT.Iter(); !iter.Done(); iter.Next()) {
+ nsAutoPtr<nsConnectionEntry>& ent = iter.Data();
+
+ LOG(("nsHttpConnectionMgr::TimeoutTick() this=%p host=%s "
+ "idle=%d active=%d half-len=%d pending=%d\n",
+ this, ent->mConnInfo->Origin(), ent->mIdleConns.Length(),
+ ent->mActiveConns.Length(), ent->mHalfOpens.Length(),
+ ent->mPendingQ.Length()));
+
+ // First call the tick handler for each active connection.
+ PRIntervalTime tickTime = PR_IntervalNow();
+ for (uint32_t index = 0; index < ent->mActiveConns.Length(); ++index) {
+ uint32_t connNextTimeout =
+ ent->mActiveConns[index]->ReadTimeoutTick(tickTime);
+ mTimeoutTickNext = std::min(mTimeoutTickNext, connNextTimeout);
+ }
+
+ // Now check for any stalled half open sockets.
+ if (ent->mHalfOpens.Length()) {
+ TimeStamp currentTime = TimeStamp::Now();
+ double maxConnectTime_ms = gHttpHandler->ConnectTimeout();
+
+ for (uint32_t index = ent->mHalfOpens.Length(); index > 0; ) {
+ index--;
+
+ nsHalfOpenSocket *half = ent->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",
+ ent->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",
+ ent->mConnInfo->HashKey().get(), delta));
+ half->Abandon();
+ }
+ }
+ }
+ if (ent->mHalfOpens.Length()) {
+ mTimeoutTickNext = 1;
+ }
+ }
+
+ if (mTimeoutTick) {
+ mTimeoutTickNext = std::max(mTimeoutTickNext, 1U);
+ mTimeoutTick->SetDelay(mTimeoutTickNext * 1000);
+ }
+}
+
+// GetOrCreateConnectionEntry finds a ent for a particular CI for use in
+// dispatching a transaction according to these rules
+// 1] use an ent that matches the ci that can be dispatched immediately
+// 2] otherwise use an ent of wildcard(ci) than can be dispatched immediately
+// 3] otherwise create an ent that matches ci and make new conn on it
+
+nsHttpConnectionMgr::nsConnectionEntry *
+nsHttpConnectionMgr::GetOrCreateConnectionEntry(nsHttpConnectionInfo *specificCI,
+ bool prohibitWildCard)
+{
+ // step 1
+ nsConnectionEntry *specificEnt = mCT.Get(specificCI->HashKey());
+ if (specificEnt && specificEnt->AvailableForDispatchNow()) {
+ return specificEnt;
+ }
+
+ if (!specificCI->UsingHttpsProxy()) {
+ prohibitWildCard = true;
+ }
+
+ // step 2
+ if (!prohibitWildCard) {
+ RefPtr<nsHttpConnectionInfo> wildCardProxyCI;
+ specificCI->CreateWildCard(getter_AddRefs(wildCardProxyCI));
+ nsConnectionEntry *wildCardEnt = mCT.Get(wildCardProxyCI->HashKey());
+ if (wildCardEnt && wildCardEnt->AvailableForDispatchNow()) {
+ return wildCardEnt;
+ }
+ }
+
+ // step 3
+ if (!specificEnt) {
+ RefPtr<nsHttpConnectionInfo> clone(specificCI->Clone());
+ specificEnt = new nsConnectionEntry(clone);
+ mCT.Put(clone->HashKey(), specificEnt);
+ }
+ return specificEnt;
+}
+
+nsresult
+ConnectionHandle::OnHeadersAvailable(nsAHttpTransaction *trans,
+ nsHttpRequestHead *req,
+ nsHttpResponseHead *resp,
+ bool *reset)
+{
+ return mConn->OnHeadersAvailable(trans, req, resp, reset);
+}
+
+void
+ConnectionHandle::CloseTransaction(nsAHttpTransaction *trans, nsresult reason)
+{
+ mConn->CloseTransaction(trans, reason);
+}
+
+nsresult
+ConnectionHandle::TakeTransport(nsISocketTransport **aTransport,
+ nsIAsyncInputStream **aInputStream,
+ nsIAsyncOutputStream **aOutputStream)
+{
+ return mConn->TakeTransport(aTransport, aInputStream, aOutputStream);
+}
+
+void
+nsHttpConnectionMgr::OnMsgSpeculativeConnect(int32_t, ARefBase *param)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ SpeculativeConnectArgs *args = static_cast<SpeculativeConnectArgs *>(param);
+
+ LOG(("nsHttpConnectionMgr::OnMsgSpeculativeConnect [ci=%s]\n",
+ args->mTrans->ConnectionInfo()->HashKey().get()));
+
+ nsConnectionEntry *ent =
+ GetOrCreateConnectionEntry(args->mTrans->ConnectionInfo(), false);
+
+ // If spdy has previously made a preferred entry for this host via
+ // the ip pooling rules. If so, connect to the preferred host instead of
+ // the one directly passed in here.
+ nsConnectionEntry *preferredEntry = GetSpdyPreferredEnt(ent);
+ if (preferredEntry)
+ ent = preferredEntry;
+
+ uint32_t parallelSpeculativeConnectLimit =
+ gHttpHandler->ParallelSpeculativeConnectLimit();
+ bool ignoreIdle = false;
+ bool isFromPredictor = false;
+ bool allow1918 = false;
+
+ if (args->mOverridesOK) {
+ parallelSpeculativeConnectLimit = args->mParallelSpeculativeConnectLimit;
+ ignoreIdle = args->mIgnoreIdle;
+ isFromPredictor = args->mIsFromPredictor;
+ allow1918 = args->mAllow1918;
+ }
+
+ bool keepAlive = args->mTrans->Caps() & NS_HTTP_ALLOW_KEEPALIVE;
+ if (mNumHalfOpenConns < parallelSpeculativeConnectLimit &&
+ ((ignoreIdle && (ent->mIdleConns.Length() < parallelSpeculativeConnectLimit)) ||
+ !ent->mIdleConns.Length()) &&
+ !(keepAlive && RestrictConnections(ent)) &&
+ !AtActiveConnectionLimit(ent, args->mTrans->Caps())) {
+ CreateTransport(ent, args->mTrans, args->mTrans->Caps(), true, isFromPredictor, allow1918);
+ } else {
+ LOG(("OnMsgSpeculativeConnect Transport "
+ "not created due to existing connection count\n"));
+ }
+}
+
+bool
+ConnectionHandle::IsPersistent()
+{
+ return mConn->IsPersistent();
+}
+
+bool
+ConnectionHandle::IsReused()
+{
+ return mConn->IsReused();
+}
+
+void
+ConnectionHandle::DontReuse()
+{
+ mConn->DontReuse();
+}
+
+nsresult
+ConnectionHandle::PushBack(const char *buf, uint32_t bufLen)
+{
+ return mConn->PushBack(buf, bufLen);
+}
+
+
+//////////////////////// nsHalfOpenSocket
+
+NS_IMPL_ISUPPORTS(nsHttpConnectionMgr::nsHalfOpenSocket,
+ nsIOutputStreamCallback,
+ nsITransportEventSink,
+ nsIInterfaceRequestor,
+ nsITimerCallback)
+
+nsHttpConnectionMgr::
+nsHalfOpenSocket::nsHalfOpenSocket(nsConnectionEntry *ent,
+ nsAHttpTransaction *trans,
+ uint32_t caps)
+ : mEnt(ent)
+ , mTransaction(trans)
+ , mDispatchedMTransaction(false)
+ , mCaps(caps)
+ , mSpeculative(false)
+ , mIsFromPredictor(false)
+ , mAllow1918(true)
+ , mHasConnected(false)
+ , mPrimaryConnectedOK(false)
+ , mBackupConnectedOK(false)
+{
+ MOZ_ASSERT(ent && trans, "constructor with null arguments");
+ LOG(("Creating nsHalfOpenSocket [this=%p trans=%p ent=%s key=%s]\n",
+ this, trans, ent->mConnInfo->Origin(), ent->mConnInfo->HashKey().get()));
+}
+
+nsHttpConnectionMgr::nsHalfOpenSocket::~nsHalfOpenSocket()
+{
+ MOZ_ASSERT(!mStreamOut);
+ MOZ_ASSERT(!mBackupStreamOut);
+ MOZ_ASSERT(!mSynTimer);
+ LOG(("Destroying nsHalfOpenSocket [this=%p]\n", this));
+
+ if (mEnt)
+ mEnt->RemoveHalfOpen(this);
+}
+
+nsresult
+nsHttpConnectionMgr::
+nsHalfOpenSocket::SetupStreams(nsISocketTransport **transport,
+ nsIAsyncInputStream **instream,
+ nsIAsyncOutputStream **outstream,
+ bool isBackup)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ nsresult rv;
+ const char *socketTypes[1];
+ uint32_t typeCount = 0;
+ const nsHttpConnectionInfo *ci = mEnt->mConnInfo;
+ if (ci->FirstHopSSL()) {
+ socketTypes[typeCount++] = "ssl";
+ } else {
+ socketTypes[typeCount] = gHttpHandler->DefaultSocketType();
+ if (socketTypes[typeCount]) {
+ typeCount++;
+ }
+ }
+
+ nsCOMPtr<nsISocketTransport> socketTransport;
+ nsCOMPtr<nsISocketTransportService> sts;
+
+ sts = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ LOG(("nsHalfOpenSocket::SetupStreams [this=%p ent=%s] "
+ "setup routed transport to origin %s:%d via %s:%d\n",
+ this, ci->HashKey().get(),
+ ci->Origin(), ci->OriginPort(), ci->RoutedHost(), ci->RoutedPort()));
+
+ nsCOMPtr<nsIRoutedSocketTransportService> routedSTS(do_QueryInterface(sts));
+ if (routedSTS) {
+ rv = routedSTS->CreateRoutedTransport(
+ socketTypes, typeCount,
+ ci->GetOrigin(), ci->OriginPort(), ci->GetRoutedHost(), ci->RoutedPort(),
+ ci->ProxyInfo(), getter_AddRefs(socketTransport));
+ } else {
+ if (!ci->GetRoutedHost().IsEmpty()) {
+ // There is a route requested, but the legacy nsISocketTransportService
+ // can't handle it.
+ // Origin should be reachable on origin host name, so this should
+ // not be a problem - but log it.
+ LOG(("nsHalfOpenSocket this=%p using legacy nsISocketTransportService "
+ "means explicit route %s:%d will be ignored.\n", this,
+ ci->RoutedHost(), ci->RoutedPort()));
+ }
+
+ rv = sts->CreateTransport(socketTypes, typeCount,
+ ci->GetOrigin(), ci->OriginPort(),
+ ci->ProxyInfo(),
+ getter_AddRefs(socketTransport));
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t tmpFlags = 0;
+ if (mCaps & NS_HTTP_REFRESH_DNS)
+ tmpFlags = nsISocketTransport::BYPASS_CACHE;
+
+ if (mCaps & NS_HTTP_LOAD_ANONYMOUS)
+ tmpFlags |= nsISocketTransport::ANONYMOUS_CONNECT;
+
+ if (ci->GetPrivate())
+ tmpFlags |= nsISocketTransport::NO_PERMANENT_STORAGE;
+
+ if ((mCaps & NS_HTTP_BE_CONSERVATIVE) || ci->GetBeConservative()) {
+ LOG(("Setting Socket to BE_CONSERVATIVE"));
+ tmpFlags |= nsISocketTransport::BE_CONSERVATIVE;
+ }
+
+ // For backup connections, we disable IPv6. That's because some users have
+ // broken IPv6 connectivity (leading to very long timeouts), and disabling
+ // IPv6 on the backup connection gives them a much better user experience
+ // with dual-stack hosts, though they still pay the 250ms delay for each new
+ // connection. This strategy is also known as "happy eyeballs".
+ if (mEnt->mPreferIPv6) {
+ tmpFlags |= nsISocketTransport::DISABLE_IPV4;
+ }
+ else if (mEnt->mPreferIPv4 ||
+ (isBackup && gHttpHandler->FastFallbackToIPv4())) {
+ tmpFlags |= nsISocketTransport::DISABLE_IPV6;
+ }
+
+ if (!Allow1918()) {
+ tmpFlags |= nsISocketTransport::DISABLE_RFC1918;
+ }
+
+ socketTransport->SetConnectionFlags(tmpFlags);
+
+ NeckoOriginAttributes originAttributes =
+ mEnt->mConnInfo->GetOriginAttributes();
+ if (originAttributes != NeckoOriginAttributes()) {
+ socketTransport->SetOriginAttributes(originAttributes);
+ }
+
+ socketTransport->SetQoSBits(gHttpHandler->GetQoSBits());
+
+ if (!ci->GetNetworkInterfaceId().IsEmpty()) {
+ socketTransport->SetNetworkInterfaceId(ci->GetNetworkInterfaceId());
+ }
+
+ rv = socketTransport->SetEventSink(this, nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = socketTransport->SetSecurityCallbacks(this);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ Telemetry::Accumulate(Telemetry::HTTP_CONNECTION_ENTRY_CACHE_HIT_1,
+ mEnt->mUsedForConnection);
+ mEnt->mUsedForConnection = true;
+
+ nsCOMPtr<nsIOutputStream> sout;
+ rv = socketTransport->OpenOutputStream(nsITransport::OPEN_UNBUFFERED,
+ 0, 0,
+ getter_AddRefs(sout));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIInputStream> sin;
+ rv = socketTransport->OpenInputStream(nsITransport::OPEN_UNBUFFERED,
+ 0, 0,
+ getter_AddRefs(sin));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ socketTransport.forget(transport);
+ CallQueryInterface(sin, instream);
+ CallQueryInterface(sout, outstream);
+
+ rv = (*outstream)->AsyncWait(this, 0, 0, nullptr);
+ if (NS_SUCCEEDED(rv))
+ gHttpHandler->ConnMgr()->StartedConnect();
+
+ return rv;
+}
+
+nsresult
+nsHttpConnectionMgr::nsHalfOpenSocket::SetupPrimaryStreams()
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ nsresult rv;
+
+ mPrimarySynStarted = TimeStamp::Now();
+ rv = SetupStreams(getter_AddRefs(mSocketTransport),
+ getter_AddRefs(mStreamIn),
+ getter_AddRefs(mStreamOut),
+ false);
+ LOG(("nsHalfOpenSocket::SetupPrimaryStream [this=%p ent=%s rv=%x]",
+ this, mEnt->mConnInfo->Origin(), rv));
+ if (NS_FAILED(rv)) {
+ if (mStreamOut)
+ mStreamOut->AsyncWait(nullptr, 0, 0, nullptr);
+ mStreamOut = nullptr;
+ mStreamIn = nullptr;
+ mSocketTransport = nullptr;
+ }
+ return rv;
+}
+
+nsresult
+nsHttpConnectionMgr::nsHalfOpenSocket::SetupBackupStreams()
+{
+ MOZ_ASSERT(mTransaction);
+ MOZ_ASSERT(!mTransaction->IsNullTransaction(),
+ "null transactions dont have backup streams");
+
+ mBackupSynStarted = TimeStamp::Now();
+ nsresult rv = SetupStreams(getter_AddRefs(mBackupTransport),
+ getter_AddRefs(mBackupStreamIn),
+ getter_AddRefs(mBackupStreamOut),
+ true);
+ LOG(("nsHalfOpenSocket::SetupBackupStream [this=%p ent=%s rv=%x]",
+ this, mEnt->mConnInfo->Origin(), rv));
+ if (NS_FAILED(rv)) {
+ if (mBackupStreamOut)
+ mBackupStreamOut->AsyncWait(nullptr, 0, 0, nullptr);
+ mBackupStreamOut = nullptr;
+ mBackupStreamIn = nullptr;
+ mBackupTransport = nullptr;
+ }
+ return rv;
+}
+
+void
+nsHttpConnectionMgr::nsHalfOpenSocket::SetupBackupTimer()
+{
+ uint16_t timeout = gHttpHandler->GetIdleSynTimeout();
+ MOZ_ASSERT(!mSynTimer, "timer already initd");
+ if (timeout && !mTransaction->IsDone() && !mTransaction->IsNullTransaction()) {
+ // Setup the timer that will establish a backup socket
+ // if we do not get a writable event on the main one.
+ // We do this because a lost SYN takes a very long time
+ // to repair at the TCP level.
+ //
+ // Failure to setup the timer is something we can live with,
+ // so don't return an error in that case.
+ nsresult rv;
+ mSynTimer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ mSynTimer->InitWithCallback(this, timeout, nsITimer::TYPE_ONE_SHOT);
+ LOG(("nsHalfOpenSocket::SetupBackupTimer() [this=%p]", this));
+ }
+ } else if (timeout) {
+ LOG(("nsHalfOpenSocket::SetupBackupTimer() [this=%p], did not arm\n", this));
+ }
+}
+
+void
+nsHttpConnectionMgr::nsHalfOpenSocket::CancelBackupTimer()
+{
+ // If the syntimer is still armed, we can cancel it because no backup
+ // socket should be formed at this point
+ if (!mSynTimer)
+ return;
+
+ LOG(("nsHalfOpenSocket::CancelBackupTimer()"));
+ mSynTimer->Cancel();
+ mSynTimer = nullptr;
+}
+
+void
+nsHttpConnectionMgr::nsHalfOpenSocket::Abandon()
+{
+ LOG(("nsHalfOpenSocket::Abandon [this=%p ent=%s] %p %p %p %p",
+ this, mEnt->mConnInfo->Origin(),
+ mSocketTransport.get(), mBackupTransport.get(),
+ mStreamOut.get(), mBackupStreamOut.get()));
+
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ RefPtr<nsHalfOpenSocket> deleteProtector(this);
+
+ // Tell socket (and backup socket) to forget the half open socket.
+ if (mSocketTransport) {
+ mSocketTransport->SetEventSink(nullptr, nullptr);
+ mSocketTransport->SetSecurityCallbacks(nullptr);
+ mSocketTransport = nullptr;
+ }
+ if (mBackupTransport) {
+ mBackupTransport->SetEventSink(nullptr, nullptr);
+ mBackupTransport->SetSecurityCallbacks(nullptr);
+ mBackupTransport = nullptr;
+ }
+
+ // Tell output stream (and backup) to forget the half open socket.
+ if (mStreamOut) {
+ gHttpHandler->ConnMgr()->RecvdConnect();
+ mStreamOut->AsyncWait(nullptr, 0, 0, nullptr);
+ mStreamOut = nullptr;
+ }
+ if (mBackupStreamOut) {
+ gHttpHandler->ConnMgr()->RecvdConnect();
+ mBackupStreamOut->AsyncWait(nullptr, 0, 0, nullptr);
+ mBackupStreamOut = nullptr;
+ }
+
+ // Lose references to input stream (and backup).
+ mStreamIn = mBackupStreamIn = nullptr;
+
+ // Stop the timer - we don't want any new backups.
+ CancelBackupTimer();
+
+ // Remove the half open from the connection entry.
+ if (mEnt)
+ mEnt->RemoveHalfOpen(this);
+ mEnt = nullptr;
+}
+
+double
+nsHttpConnectionMgr::nsHalfOpenSocket::Duration(TimeStamp epoch)
+{
+ if (mPrimarySynStarted.IsNull())
+ return 0;
+
+ return (epoch - mPrimarySynStarted).ToMilliseconds();
+}
+
+
+NS_IMETHODIMP // method for nsITimerCallback
+nsHttpConnectionMgr::nsHalfOpenSocket::Notify(nsITimer *timer)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ MOZ_ASSERT(timer == mSynTimer, "wrong timer");
+ MOZ_ASSERT(mTransaction && !mTransaction->IsNullTransaction(),
+ "null transactions dont have backup streams");
+
+ SetupBackupStreams();
+
+ mSynTimer = nullptr;
+ return NS_OK;
+}
+
+// method for nsIAsyncOutputStreamCallback
+NS_IMETHODIMP
+nsHttpConnectionMgr::
+nsHalfOpenSocket::OnOutputStreamReady(nsIAsyncOutputStream *out)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ MOZ_ASSERT(out == mStreamOut || out == mBackupStreamOut,
+ "stream mismatch");
+ LOG(("nsHalfOpenSocket::OnOutputStreamReady [this=%p ent=%s %s]\n",
+ this, mEnt->mConnInfo->Origin(),
+ out == mStreamOut ? "primary" : "backup"));
+ int32_t index;
+ nsresult rv;
+
+ gHttpHandler->ConnMgr()->RecvdConnect();
+
+ CancelBackupTimer();
+
+ // assign the new socket to the http connection
+ RefPtr<nsHttpConnection> conn = new nsHttpConnection();
+ LOG(("nsHalfOpenSocket::OnOutputStreamReady "
+ "Created new nshttpconnection %p\n", conn.get()));
+
+ // Some capabilities are needed before a transaciton actually gets
+ // scheduled (e.g. how to negotiate false start)
+ conn->SetTransactionCaps(mTransaction->Caps());
+
+ NetAddr peeraddr;
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ mTransaction->GetSecurityCallbacks(getter_AddRefs(callbacks));
+ if (out == mStreamOut) {
+ TimeDuration rtt = TimeStamp::Now() - mPrimarySynStarted;
+ rv = conn->Init(mEnt->mConnInfo,
+ gHttpHandler->ConnMgr()->mMaxRequestDelay,
+ mSocketTransport, mStreamIn, mStreamOut,
+ mPrimaryConnectedOK, callbacks,
+ PR_MillisecondsToInterval(
+ static_cast<uint32_t>(rtt.ToMilliseconds())));
+
+ if (NS_SUCCEEDED(mSocketTransport->GetPeerAddr(&peeraddr)))
+ mEnt->RecordIPFamilyPreference(peeraddr.raw.family);
+
+ // The nsHttpConnection object now owns these streams and sockets
+ mStreamOut = nullptr;
+ mStreamIn = nullptr;
+ mSocketTransport = nullptr;
+ } else if (out == mBackupStreamOut) {
+ MOZ_ASSERT(!mTransaction->IsNullTransaction(),
+ "null transactions dont have backup streams");
+ TimeDuration rtt = TimeStamp::Now() - mBackupSynStarted;
+ rv = conn->Init(mEnt->mConnInfo,
+ gHttpHandler->ConnMgr()->mMaxRequestDelay,
+ mBackupTransport, mBackupStreamIn, mBackupStreamOut,
+ mBackupConnectedOK, callbacks,
+ PR_MillisecondsToInterval(
+ static_cast<uint32_t>(rtt.ToMilliseconds())));
+
+ if (NS_SUCCEEDED(mBackupTransport->GetPeerAddr(&peeraddr)))
+ mEnt->RecordIPFamilyPreference(peeraddr.raw.family);
+
+ // The nsHttpConnection object now owns these streams and sockets
+ mBackupStreamOut = nullptr;
+ mBackupStreamIn = nullptr;
+ mBackupTransport = nullptr;
+ } else {
+ MOZ_ASSERT(false, "unexpected stream");
+ rv = NS_ERROR_UNEXPECTED;
+ }
+
+ if (NS_FAILED(rv)) {
+ LOG(("nsHalfOpenSocket::OnOutputStreamReady "
+ "conn->init (%p) failed %x\n", conn.get(), rv));
+ return rv;
+ }
+
+ // This half-open socket has created a connection. This flag excludes it
+ // from counter of actual connections used for checking limits.
+ mHasConnected = true;
+
+ // if this is still in the pending list, remove it and dispatch it
+ index = mEnt->mPendingQ.IndexOf(mTransaction);
+ if (index != -1) {
+ MOZ_ASSERT(!mSpeculative,
+ "Speculative Half Open found mTransaction");
+ RefPtr<nsHttpTransaction> temp = mEnt->mPendingQ[index];
+ mEnt->mPendingQ.RemoveElementAt(index);
+ gHttpHandler->ConnMgr()->AddActiveConn(conn, mEnt);
+ rv = gHttpHandler->ConnMgr()->DispatchTransaction(mEnt, temp, conn);
+ } else {
+ // this transaction was dispatched off the pending q before all the
+ // sockets established themselves.
+
+ // After about 1 second allow for the possibility of restarting a
+ // transaction due to server close. Keep at sub 1 second as that is the
+ // minimum granularity we can expect a server to be timing out with.
+ conn->SetIsReusedAfter(950);
+
+ // if we are using ssl and no other transactions are waiting right now,
+ // then form a null transaction to drive the SSL handshake to
+ // completion. Afterwards the connection will be 100% ready for the next
+ // transaction to use it. Make an exception for SSL tunneled HTTP proxy as the
+ // NullHttpTransaction does not know how to drive Connect
+ if (mEnt->mConnInfo->FirstHopSSL() && !mEnt->mPendingQ.Length() &&
+ !mEnt->mConnInfo->UsingConnect()) {
+ LOG(("nsHalfOpenSocket::OnOutputStreamReady null transaction will "
+ "be used to finish SSL handshake on conn %p\n", conn.get()));
+ RefPtr<nsAHttpTransaction> trans;
+ if (mTransaction->IsNullTransaction() && !mDispatchedMTransaction) {
+ // null transactions cannot be put in the entry queue, so that
+ // explains why it is not present.
+ mDispatchedMTransaction = true;
+ trans = mTransaction;
+ } else {
+ trans = new NullHttpTransaction(mEnt->mConnInfo,
+ callbacks,
+ mCaps & ~NS_HTTP_ALLOW_PIPELINING);
+ }
+
+ gHttpHandler->ConnMgr()->AddActiveConn(conn, mEnt);
+ conn->Classify(nsAHttpTransaction::CLASS_SOLO);
+ rv = gHttpHandler->ConnMgr()->
+ DispatchAbstractTransaction(mEnt, trans, mCaps, conn, 0);
+ } else {
+ // otherwise just put this in the persistent connection pool
+ LOG(("nsHalfOpenSocket::OnOutputStreamReady no transaction match "
+ "returning conn %p to pool\n", conn.get()));
+ gHttpHandler->ConnMgr()->OnMsgReclaimConnection(0, conn);
+ }
+ }
+
+ return rv;
+}
+
+// method for nsITransportEventSink
+NS_IMETHODIMP
+nsHttpConnectionMgr::nsHalfOpenSocket::OnTransportStatus(nsITransport *trans,
+ nsresult status,
+ int64_t progress,
+ int64_t progressMax)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ if (mTransaction)
+ mTransaction->OnTransportStatus(trans, status, progress);
+
+ MOZ_ASSERT(trans == mSocketTransport || trans == mBackupTransport);
+ if (status == NS_NET_STATUS_CONNECTED_TO) {
+ if (trans == mSocketTransport) {
+ mPrimaryConnectedOK = true;
+ } else {
+ mBackupConnectedOK = true;
+ }
+ }
+
+ // The rest of this method only applies to the primary transport
+ if (trans != mSocketTransport) {
+ return NS_OK;
+ }
+
+ // if we are doing spdy coalescing and haven't recorded the ip address
+ // for this entry before then make the hash key if our dns lookup
+ // just completed. We can't do coalescing if using a proxy because the
+ // ip addresses are not available to the client.
+
+ if (status == NS_NET_STATUS_CONNECTING_TO &&
+ gHttpHandler->IsSpdyEnabled() &&
+ gHttpHandler->CoalesceSpdy() &&
+ mEnt && mEnt->mConnInfo && mEnt->mConnInfo->EndToEndSSL() &&
+ !mEnt->mConnInfo->UsingProxy() &&
+ mEnt->mCoalescingKeys.IsEmpty()) {
+
+ nsCOMPtr<nsIDNSRecord> dnsRecord(do_GetInterface(mSocketTransport));
+ nsTArray<NetAddr> addressSet;
+ nsresult rv = NS_ERROR_NOT_AVAILABLE;
+ if (dnsRecord) {
+ rv = dnsRecord->GetAddresses(addressSet);
+ }
+
+ if (NS_SUCCEEDED(rv) && !addressSet.IsEmpty()) {
+ for (uint32_t i = 0; i < addressSet.Length(); ++i) {
+ nsCString *newKey = mEnt->mCoalescingKeys.AppendElement(nsCString());
+ newKey->SetCapacity(kIPv6CStrBufSize + 26);
+ NetAddrToString(&addressSet[i], newKey->BeginWriting(), kIPv6CStrBufSize);
+ newKey->SetLength(strlen(newKey->BeginReading()));
+ if (mEnt->mConnInfo->GetAnonymous()) {
+ newKey->AppendLiteral("~A:");
+ } else {
+ newKey->AppendLiteral("~.:");
+ }
+ newKey->AppendInt(mEnt->mConnInfo->OriginPort());
+ LOG(("nsHttpConnectionMgr::nsHalfOpenSocket::OnTransportStatus "
+ "STATUS_CONNECTING_TO Established New Coalescing Key # %d for host "
+ "%s [%s]", i, mEnt->mConnInfo->Origin(), newKey->get()));
+ }
+ gHttpHandler->ConnMgr()->ProcessSpdyPendingQ(mEnt);
+ }
+ }
+
+ switch (status) {
+ case NS_NET_STATUS_CONNECTING_TO:
+ // Passed DNS resolution, now trying to connect, start the backup timer
+ // only prevent creating another backup transport.
+ // We also check for mEnt presence to not instantiate the timer after
+ // this half open socket has already been abandoned. It may happen
+ // when we get this notification right between main-thread calls to
+ // nsHttpConnectionMgr::Shutdown and nsSocketTransportService::Shutdown
+ // where the first abandons all half open socket instances and only
+ // after that the second stops the socket thread.
+ if (mEnt && !mBackupTransport && !mSynTimer)
+ SetupBackupTimer();
+ break;
+
+ case NS_NET_STATUS_CONNECTED_TO:
+ // TCP connection's up, now transfer or SSL negotiantion starts,
+ // no need for backup socket
+ CancelBackupTimer();
+ break;
+
+ default:
+ break;
+ }
+
+ return NS_OK;
+}
+
+// method for nsIInterfaceRequestor
+NS_IMETHODIMP
+nsHttpConnectionMgr::nsHalfOpenSocket::GetInterface(const nsIID &iid,
+ void **result)
+{
+ if (mTransaction) {
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ mTransaction->GetSecurityCallbacks(getter_AddRefs(callbacks));
+ if (callbacks)
+ return callbacks->GetInterface(iid, result);
+ }
+ return NS_ERROR_NO_INTERFACE;
+}
+
+
+already_AddRefed<nsHttpConnection>
+ConnectionHandle::TakeHttpConnection()
+{
+ // return our connection object to the caller and clear it internally
+ // do not drop our reference - the caller now owns it.
+ MOZ_ASSERT(mConn);
+ return mConn.forget();
+}
+
+uint32_t
+ConnectionHandle::CancelPipeline(nsresult reason)
+{
+ // no pipeline to cancel
+ return 0;
+}
+
+nsAHttpTransaction::Classifier
+ConnectionHandle::Classification()
+{
+ if (mConn)
+ return mConn->Classification();
+
+ LOG(("ConnectionHandle::Classification this=%p "
+ "has null mConn using CLASS_SOLO default", this));
+ return nsAHttpTransaction::CLASS_SOLO;
+}
+
+// nsConnectionEntry
+
+nsHttpConnectionMgr::
+nsConnectionEntry::nsConnectionEntry(nsHttpConnectionInfo *ci)
+ : mConnInfo(ci)
+ , mPipelineState(PS_YELLOW)
+ , mYellowGoodEvents(0)
+ , mYellowBadEvents(0)
+ , mYellowConnection(nullptr)
+ , mGreenDepth(kPipelineOpen)
+ , mPipeliningPenalty(0)
+ , mUsingSpdy(false)
+ , mInPreferredHash(false)
+ , mPreferIPv4(false)
+ , mPreferIPv6(false)
+ , mUsedForConnection(false)
+{
+ MOZ_COUNT_CTOR(nsConnectionEntry);
+ if (gHttpHandler->GetPipelineAggressive()) {
+ mGreenDepth = kPipelineUnlimited;
+ mPipelineState = PS_GREEN;
+ }
+ mInitialGreenDepth = mGreenDepth;
+ memset(mPipeliningClassPenalty, 0, sizeof(int16_t) * nsAHttpTransaction::CLASS_MAX);
+}
+
+bool
+nsHttpConnectionMgr::nsConnectionEntry::AvailableForDispatchNow()
+{
+ if (mIdleConns.Length() && mIdleConns[0]->CanReuse()) {
+ return true;
+ }
+
+ return gHttpHandler->ConnMgr()->
+ GetSpdyPreferredConn(this) ? true : false;
+}
+
+bool
+nsHttpConnectionMgr::nsConnectionEntry::SupportsPipelining()
+{
+ return mPipelineState != nsHttpConnectionMgr::PS_RED;
+}
+
+nsHttpConnectionMgr::PipeliningState
+nsHttpConnectionMgr::nsConnectionEntry::PipelineState()
+{
+ return mPipelineState;
+}
+
+void
+nsHttpConnectionMgr::
+nsConnectionEntry::OnPipelineFeedbackInfo(
+ nsHttpConnectionMgr::PipelineFeedbackInfoType info,
+ nsHttpConnection *conn,
+ uint32_t data)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ if (mPipelineState == PS_YELLOW) {
+ if (info & kPipelineInfoTypeBad)
+ mYellowBadEvents++;
+ else if (info & (kPipelineInfoTypeNeutral | kPipelineInfoTypeGood))
+ mYellowGoodEvents++;
+ }
+
+ if (mPipelineState == PS_GREEN && info == GoodCompletedOK) {
+ int32_t depth = data;
+ LOG(("Transaction completed at pipeline depth of %d. Host = %s\n",
+ depth, mConnInfo->Origin()));
+
+ if (depth >= 3)
+ mGreenDepth = kPipelineUnlimited;
+ }
+
+ nsAHttpTransaction::Classifier classification;
+ if (conn)
+ classification = conn->Classification();
+ else if (info == BadInsufficientFraming ||
+ info == BadUnexpectedLarge)
+ classification = (nsAHttpTransaction::Classifier) data;
+ else
+ classification = nsAHttpTransaction::CLASS_SOLO;
+
+ if (gHttpHandler->GetPipelineAggressive() &&
+ info & kPipelineInfoTypeBad &&
+ info != BadExplicitClose &&
+ info != RedVersionTooLow &&
+ info != RedBannedServer &&
+ info != RedCorruptedContent &&
+ info != BadInsufficientFraming) {
+ LOG(("minor negative feedback ignored "
+ "because of pipeline aggressive mode"));
+ }
+ else if (info & kPipelineInfoTypeBad) {
+ if ((info & kPipelineInfoTypeRed) && (mPipelineState != PS_RED)) {
+ LOG(("transition to red from %d. Host = %s.\n",
+ mPipelineState, mConnInfo->Origin()));
+ mPipelineState = PS_RED;
+ mPipeliningPenalty = 0;
+ }
+
+ if (mLastCreditTime.IsNull())
+ mLastCreditTime = TimeStamp::Now();
+
+ // Red* events impact the host globally via mPipeliningPenalty, while
+ // Bad* events impact the per class penalty.
+
+ // The individual penalties should be < 16bit-signed-maxint - 25000
+ // (approx 7500). Penalties are paid-off either when something promising
+ // happens (a successful transaction, or promising headers) or when
+ // time goes by at a rate of 1 penalty point every 16 seconds.
+
+ switch (info) {
+ case RedVersionTooLow:
+ mPipeliningPenalty += 1000;
+ break;
+ case RedBannedServer:
+ mPipeliningPenalty += 7000;
+ break;
+ case RedCorruptedContent:
+ mPipeliningPenalty += 7000;
+ break;
+ case RedCanceledPipeline:
+ mPipeliningPenalty += 60;
+ break;
+ case BadExplicitClose:
+ mPipeliningClassPenalty[classification] += 250;
+ break;
+ case BadSlowReadMinor:
+ mPipeliningClassPenalty[classification] += 5;
+ break;
+ case BadSlowReadMajor:
+ mPipeliningClassPenalty[classification] += 25;
+ break;
+ case BadInsufficientFraming:
+ mPipeliningClassPenalty[classification] += 7000;
+ break;
+ case BadUnexpectedLarge:
+ mPipeliningClassPenalty[classification] += 120;
+ break;
+
+ default:
+ MOZ_ASSERT(false, "Unknown Bad/Red Pipeline Feedback Event");
+ }
+
+ const int16_t kPenalty = 25000;
+ mPipeliningPenalty = std::min(mPipeliningPenalty, kPenalty);
+ mPipeliningClassPenalty[classification] =
+ std::min(mPipeliningClassPenalty[classification], kPenalty);
+
+ LOG(("Assessing red penalty to %s class %d for event %d. "
+ "Penalty now %d, throttle[%d] = %d\n", mConnInfo->Origin(),
+ classification, info, mPipeliningPenalty, classification,
+ mPipeliningClassPenalty[classification]));
+ }
+ else {
+ // hand out credits for neutral and good events such as
+ // "headers look ok" events
+
+ mPipeliningPenalty = std::max(mPipeliningPenalty - 1, 0);
+ mPipeliningClassPenalty[classification] = std::max(mPipeliningClassPenalty[classification] - 1, 0);
+ }
+
+ if (mPipelineState == PS_RED && !mPipeliningPenalty)
+ {
+ LOG(("transition %s to yellow\n", mConnInfo->Origin()));
+ mPipelineState = PS_YELLOW;
+ mYellowConnection = nullptr;
+ }
+}
+
+void
+nsHttpConnectionMgr::
+nsConnectionEntry::SetYellowConnection(nsHttpConnection *conn)
+{
+ MOZ_ASSERT(!mYellowConnection && mPipelineState == PS_YELLOW,
+ "yellow connection already set or state is not yellow");
+ mYellowConnection = conn;
+ mYellowGoodEvents = mYellowBadEvents = 0;
+}
+
+void
+nsHttpConnectionMgr::
+nsConnectionEntry::OnYellowComplete()
+{
+ if (mPipelineState == PS_YELLOW) {
+ if (mYellowGoodEvents && !mYellowBadEvents) {
+ LOG(("transition %s to green\n", mConnInfo->Origin()));
+ mPipelineState = PS_GREEN;
+ mGreenDepth = mInitialGreenDepth;
+ }
+ else {
+ // The purpose of the yellow state is to witness at least
+ // one successful pipelined transaction without seeing any
+ // kind of negative feedback before opening the flood gates.
+ // If we haven't confirmed that, then transfer back to red.
+ LOG(("transition %s to red from yellow return\n",
+ mConnInfo->Origin()));
+ mPipelineState = PS_RED;
+ }
+ }
+
+ mYellowConnection = nullptr;
+}
+
+void
+nsHttpConnectionMgr::
+nsConnectionEntry::CreditPenalty()
+{
+ if (mLastCreditTime.IsNull())
+ return;
+
+ // Decrease penalty values by 1 for every 16 seconds
+ // (i.e 3.7 per minute, or 1000 every 4h20m)
+
+ TimeStamp now = TimeStamp::Now();
+ TimeDuration elapsedTime = now - mLastCreditTime;
+ uint32_t creditsEarned =
+ static_cast<uint32_t>(elapsedTime.ToSeconds()) >> 4;
+
+ bool failed = false;
+ if (creditsEarned > 0) {
+ mPipeliningPenalty =
+ std::max(int32_t(mPipeliningPenalty - creditsEarned), 0);
+ if (mPipeliningPenalty > 0)
+ failed = true;
+
+ for (int32_t i = 0; i < nsAHttpTransaction::CLASS_MAX; ++i) {
+ mPipeliningClassPenalty[i] =
+ std::max(int32_t(mPipeliningClassPenalty[i] - creditsEarned), 0);
+ failed = failed || (mPipeliningClassPenalty[i] > 0);
+ }
+
+ // update last credit mark to reflect elapsed time
+ mLastCreditTime += TimeDuration::FromSeconds(creditsEarned << 4);
+ }
+ else {
+ failed = true; /* just assume this */
+ }
+
+ // If we are no longer red then clear the credit counter - you only
+ // get credits for time spent in the red state
+ if (!failed)
+ mLastCreditTime = TimeStamp(); /* reset to null timestamp */
+
+ if (mPipelineState == PS_RED && !mPipeliningPenalty)
+ {
+ LOG(("transition %s to yellow based on time credit\n",
+ mConnInfo->Origin()));
+ mPipelineState = PS_YELLOW;
+ mYellowConnection = nullptr;
+ }
+}
+
+uint32_t
+nsHttpConnectionMgr::
+nsConnectionEntry::MaxPipelineDepth(nsAHttpTransaction::Classifier aClass)
+{
+ // Still subject to configuration limit no matter return value
+
+ if ((mPipelineState == PS_RED) || (mPipeliningClassPenalty[aClass] > 0))
+ return 0;
+
+ if (mPipelineState == PS_YELLOW)
+ return kPipelineRestricted;
+
+ return mGreenDepth;
+}
+
+bool
+nsHttpConnectionMgr::GetConnectionData(nsTArray<HttpRetParams> *aArg)
+{
+ for (auto iter = mCT.Iter(); !iter.Done(); iter.Next()) {
+ nsAutoPtr<nsConnectionEntry>& ent = iter.Data();
+
+ if (ent->mConnInfo->GetPrivate()) {
+ continue;
+ }
+
+ HttpRetParams data;
+ data.host = ent->mConnInfo->Origin();
+ data.port = ent->mConnInfo->OriginPort();
+ for (uint32_t i = 0; i < ent->mActiveConns.Length(); i++) {
+ HttpConnInfo info;
+ info.ttl = ent->mActiveConns[i]->TimeToLive();
+ info.rtt = ent->mActiveConns[i]->Rtt();
+ if (ent->mActiveConns[i]->UsingSpdy()) {
+ info.SetHTTP2ProtocolVersion(
+ ent->mActiveConns[i]->GetSpdyVersion());
+ } else {
+ info.SetHTTP1ProtocolVersion(
+ ent->mActiveConns[i]->GetLastHttpResponseVersion());
+ }
+ data.active.AppendElement(info);
+ }
+ for (uint32_t i = 0; i < ent->mIdleConns.Length(); i++) {
+ HttpConnInfo info;
+ info.ttl = ent->mIdleConns[i]->TimeToLive();
+ info.rtt = ent->mIdleConns[i]->Rtt();
+ info.SetHTTP1ProtocolVersion(
+ ent->mIdleConns[i]->GetLastHttpResponseVersion());
+ data.idle.AppendElement(info);
+ }
+ for (uint32_t i = 0; i < ent->mHalfOpens.Length(); i++) {
+ HalfOpenSockets hSocket;
+ hSocket.speculative = ent->mHalfOpens[i]->IsSpeculative();
+ data.halfOpens.AppendElement(hSocket);
+ }
+ data.spdy = ent->mUsingSpdy;
+ data.ssl = ent->mConnInfo->EndToEndSSL();
+ aArg->AppendElement(data);
+ }
+
+ return true;
+}
+
+void
+nsHttpConnectionMgr::ResetIPFamilyPreference(nsHttpConnectionInfo *ci)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ nsConnectionEntry *ent = LookupConnectionEntry(ci, nullptr, nullptr);
+ if (ent)
+ ent->ResetIPFamilyPreference();
+}
+
+uint32_t
+nsHttpConnectionMgr::
+nsConnectionEntry::UnconnectedHalfOpens()
+{
+ uint32_t unconnectedHalfOpens = 0;
+ for (uint32_t i = 0; i < mHalfOpens.Length(); ++i) {
+ if (!mHalfOpens[i]->HasConnected())
+ ++unconnectedHalfOpens;
+ }
+ return unconnectedHalfOpens;
+}
+
+void
+nsHttpConnectionMgr::
+nsConnectionEntry::RemoveHalfOpen(nsHalfOpenSocket *halfOpen)
+{
+ // 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)) {
+
+ if (halfOpen->IsSpeculative()) {
+ Telemetry::AutoCounter<Telemetry::HTTPCONNMGR_UNUSED_SPECULATIVE_CONN> unusedSpeculativeConn;
+ ++unusedSpeculativeConn;
+
+ if (halfOpen->IsFromPredictor()) {
+ Telemetry::AutoCounter<Telemetry::PREDICTOR_TOTAL_PRECONNECTS_UNUSED> totalPreconnectsUnused;
+ ++totalPreconnectsUnused;
+ }
+ }
+
+ MOZ_ASSERT(gHttpHandler->ConnMgr()->mNumHalfOpenConns);
+ if (gHttpHandler->ConnMgr()->mNumHalfOpenConns) { // just in case
+ gHttpHandler->ConnMgr()->mNumHalfOpenConns--;
+ }
+ }
+
+ if (!UnconnectedHalfOpens())
+ // perhaps this reverted RestrictConnections()
+ // use the PostEvent version of processpendingq to avoid
+ // altering the pending q vector from an arbitrary stack
+ gHttpHandler->ConnMgr()->ProcessPendingQ(mConnInfo);
+}
+
+void
+nsHttpConnectionMgr::
+nsConnectionEntry::RecordIPFamilyPreference(uint16_t family)
+{
+ if (family == PR_AF_INET && !mPreferIPv6)
+ mPreferIPv4 = true;
+
+ if (family == PR_AF_INET6 && !mPreferIPv4)
+ mPreferIPv6 = true;
+}
+
+void
+nsHttpConnectionMgr::
+nsConnectionEntry::ResetIPFamilyPreference()
+{
+ mPreferIPv4 = false;
+ mPreferIPv6 = false;
+}
+
+void
+nsHttpConnectionMgr::MoveToWildCardConnEntry(nsHttpConnectionInfo *specificCI,
+ nsHttpConnectionInfo *wildCardCI,
+ nsHttpConnection *proxyConn)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ MOZ_ASSERT(specificCI->UsingHttpsProxy());
+
+ LOG(("nsHttpConnectionMgr::MakeConnEntryWildCard conn %p has requested to "
+ "change CI from %s to %s\n", proxyConn, specificCI->HashKey().get(),
+ wildCardCI->HashKey().get()));
+
+ nsConnectionEntry *ent = LookupConnectionEntry(specificCI, proxyConn, nullptr);
+ LOG(("nsHttpConnectionMgr::MakeConnEntryWildCard conn %p using ent %p (spdy %d)\n",
+ proxyConn, ent, ent ? ent->mUsingSpdy : 0));
+
+ if (!ent || !ent->mUsingSpdy) {
+ return;
+ }
+
+ nsConnectionEntry *wcEnt = GetOrCreateConnectionEntry(wildCardCI, true);
+ if (wcEnt == ent) {
+ // nothing to do!
+ return;
+ }
+ wcEnt->mUsingSpdy = true;
+
+ LOG(("nsHttpConnectionMgr::MakeConnEntryWildCard ent %p "
+ "idle=%d active=%d half=%d pending=%d\n", ent,
+ ent->mIdleConns.Length(), ent->mActiveConns.Length(),
+ ent->mHalfOpens.Length(), ent->mPendingQ.Length()));
+
+ LOG(("nsHttpConnectionMgr::MakeConnEntryWildCard wc-ent %p "
+ "idle=%d active=%d half=%d pending=%d\n", wcEnt,
+ wcEnt->mIdleConns.Length(), wcEnt->mActiveConns.Length(),
+ wcEnt->mHalfOpens.Length(), wcEnt->mPendingQ.Length()));
+
+ int32_t count = ent->mActiveConns.Length();
+ RefPtr<nsHttpConnection> deleteProtector(proxyConn);
+ for (int32_t i = 0; i < count; ++i) {
+ if (ent->mActiveConns[i] == proxyConn) {
+ ent->mActiveConns.RemoveElementAt(i);
+ wcEnt->mActiveConns.InsertElementAt(0, proxyConn);
+ return;
+ }
+ }
+
+ count = ent->mIdleConns.Length();
+ for (int32_t i = 0; i < count; ++i) {
+ if (ent->mIdleConns[i] == proxyConn) {
+ ent->mIdleConns.RemoveElementAt(i);
+ wcEnt->mIdleConns.InsertElementAt(0, proxyConn);
+ return;
+ }
+ }
+}
+
+} // namespace net
+} // namespace mozilla