diff options
Diffstat (limited to 'netwerk/protocol/http/nsHttpConnectionMgr.cpp')
-rw-r--r-- | netwerk/protocol/http/nsHttpConnectionMgr.cpp | 4006 |
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 |