michael@0: /* vim:set ts=4 sw=4 sts=4 et cin: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: // HttpLog.h should generally be included first michael@0: #include "HttpLog.h" michael@0: michael@0: // Log on level :5, instead of default :4. michael@0: #undef LOG michael@0: #define LOG(args) LOG5(args) michael@0: #undef LOG_ENABLED michael@0: #define LOG_ENABLED() LOG5_ENABLED() michael@0: michael@0: #include "nsHttpConnectionMgr.h" michael@0: #include "nsHttpConnection.h" michael@0: #include "nsHttpPipeline.h" michael@0: #include "nsHttpHandler.h" michael@0: #include "nsIHttpChannelInternal.h" michael@0: #include "nsNetCID.h" michael@0: #include "nsCOMPtr.h" michael@0: #include "nsNetUtil.h" michael@0: #include "mozilla/net/DNS.h" michael@0: #include "nsISocketTransport.h" michael@0: #include "nsISSLSocketControl.h" michael@0: #include "mozilla/Telemetry.h" michael@0: #include "mozilla/net/DashboardTypes.h" michael@0: #include "NullHttpTransaction.h" michael@0: #include "nsITransport.h" michael@0: #include "nsISocketTransportService.h" michael@0: #include michael@0: #include "Http2Compression.h" michael@0: #include "mozilla/ChaosMode.h" michael@0: #include "mozilla/unused.h" michael@0: #include michael@0: #include "nsHttpRequestHead.h" michael@0: michael@0: // defined by the socket transport service while active michael@0: extern PRThread *gSocketThread; michael@0: michael@0: namespace mozilla { michael@0: namespace net { michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: NS_IMPL_ISUPPORTS(nsHttpConnectionMgr, nsIObserver) michael@0: michael@0: static void michael@0: InsertTransactionSorted(nsTArray &pendingQ, nsHttpTransaction *trans) michael@0: { michael@0: // insert into queue with smallest valued number first. search in reverse michael@0: // order under the assumption that many of the existing transactions will michael@0: // have the same priority (usually 0). michael@0: uint32_t len = pendingQ.Length(); michael@0: michael@0: if (pendingQ.IsEmpty()) { michael@0: pendingQ.InsertElementAt(0, trans); michael@0: return; michael@0: } michael@0: michael@0: pendingQ.InsertElementAt(0, trans); michael@0: michael@0: // FIXME: Refactor into standalone helper (for nsHttpPipeline) michael@0: // Or at least simplify this function if this shuffle ends up michael@0: // being an improvement. michael@0: uint32_t i = 0; michael@0: for (i=0; i < len; ++i) { michael@0: uint32_t ridx = rand() % len; michael@0: michael@0: nsHttpTransaction *tmp = pendingQ[i]; michael@0: pendingQ[i] = pendingQ[ridx]; michael@0: pendingQ[ridx] = tmp; michael@0: } michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: nsHttpConnectionMgr::nsHttpConnectionMgr() michael@0: : mReentrantMonitor("nsHttpConnectionMgr.mReentrantMonitor") michael@0: , mMaxConns(0) michael@0: , mMaxPersistConnsPerHost(0) michael@0: , mMaxPersistConnsPerProxy(0) michael@0: , mIsShuttingDown(false) michael@0: , mNumActiveConns(0) michael@0: , mNumIdleConns(0) michael@0: , mNumSpdyActiveConns(0) michael@0: , mNumHalfOpenConns(0) michael@0: , mTimeOfNextWakeUp(UINT64_MAX) michael@0: , mTimeoutTickArmed(false) michael@0: , mTimeoutTickNext(1) michael@0: { michael@0: LOG(("Creating nsHttpConnectionMgr @%x\n", this)); michael@0: } michael@0: michael@0: nsHttpConnectionMgr::~nsHttpConnectionMgr() michael@0: { michael@0: LOG(("Destroying nsHttpConnectionMgr @%x\n", this)); michael@0: if (mTimeoutTick) michael@0: mTimeoutTick->Cancel(); michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpConnectionMgr::EnsureSocketThreadTarget() michael@0: { michael@0: nsresult rv; michael@0: nsCOMPtr sts; michael@0: nsCOMPtr ioService = do_GetIOService(&rv); michael@0: if (NS_SUCCEEDED(rv)) michael@0: sts = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv); michael@0: michael@0: ReentrantMonitorAutoEnter mon(mReentrantMonitor); michael@0: michael@0: // do nothing if already initialized or if we've shut down michael@0: if (mSocketThreadTarget || mIsShuttingDown) michael@0: return NS_OK; michael@0: michael@0: mSocketThreadTarget = sts; michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpConnectionMgr::Init(uint16_t maxConns, michael@0: uint16_t maxPersistConnsPerHost, michael@0: uint16_t maxPersistConnsPerProxy, michael@0: uint16_t maxRequestDelay, michael@0: uint16_t maxPipelinedRequests, michael@0: uint16_t maxOptimisticPipelinedRequests) michael@0: { michael@0: LOG(("nsHttpConnectionMgr::Init\n")); michael@0: michael@0: { michael@0: ReentrantMonitorAutoEnter mon(mReentrantMonitor); michael@0: michael@0: mMaxConns = maxConns; michael@0: mMaxPersistConnsPerHost = maxPersistConnsPerHost; michael@0: mMaxPersistConnsPerProxy = maxPersistConnsPerProxy; michael@0: mMaxRequestDelay = maxRequestDelay; michael@0: mMaxPipelinedRequests = maxPipelinedRequests; michael@0: mMaxOptimisticPipelinedRequests = maxOptimisticPipelinedRequests; michael@0: michael@0: mIsShuttingDown = false; michael@0: } michael@0: michael@0: return EnsureSocketThreadTarget(); michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpConnectionMgr::Shutdown() michael@0: { michael@0: LOG(("nsHttpConnectionMgr::Shutdown\n")); michael@0: michael@0: bool shutdown = false; michael@0: { michael@0: ReentrantMonitorAutoEnter mon(mReentrantMonitor); michael@0: michael@0: // do nothing if already shutdown michael@0: if (!mSocketThreadTarget) michael@0: return NS_OK; michael@0: michael@0: nsresult rv = PostEvent(&nsHttpConnectionMgr::OnMsgShutdown, michael@0: 0, &shutdown); michael@0: michael@0: // release our reference to the STS to prevent further events michael@0: // from being posted. this is how we indicate that we are michael@0: // shutting down. michael@0: mIsShuttingDown = true; michael@0: mSocketThreadTarget = 0; michael@0: michael@0: if (NS_FAILED(rv)) { michael@0: NS_WARNING("unable to post SHUTDOWN message"); michael@0: return rv; michael@0: } michael@0: } michael@0: michael@0: // wait for shutdown event to complete michael@0: while (!shutdown) michael@0: NS_ProcessNextEvent(NS_GetCurrentThread()); michael@0: Http2CompressionCleanup(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpConnectionMgr::PostEvent(nsConnEventHandler handler, int32_t iparam, void *vparam) michael@0: { michael@0: EnsureSocketThreadTarget(); michael@0: michael@0: ReentrantMonitorAutoEnter mon(mReentrantMonitor); michael@0: michael@0: nsresult rv; michael@0: if (!mSocketThreadTarget) { michael@0: NS_WARNING("cannot post event if not initialized"); michael@0: rv = NS_ERROR_NOT_INITIALIZED; michael@0: } michael@0: else { michael@0: nsRefPtr event = new nsConnEvent(this, handler, iparam, vparam); michael@0: rv = mSocketThreadTarget->Dispatch(event, NS_DISPATCH_NORMAL); michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: void michael@0: nsHttpConnectionMgr::PruneDeadConnectionsAfter(uint32_t timeInSeconds) michael@0: { michael@0: LOG(("nsHttpConnectionMgr::PruneDeadConnectionsAfter\n")); michael@0: michael@0: if(!mTimer) michael@0: mTimer = do_CreateInstance("@mozilla.org/timer;1"); michael@0: michael@0: // failure to create a timer is not a fatal error, but idle connections michael@0: // will not be cleaned up until we try to use them. michael@0: if (mTimer) { michael@0: mTimeOfNextWakeUp = timeInSeconds + NowInSeconds(); michael@0: mTimer->Init(this, timeInSeconds*1000, nsITimer::TYPE_ONE_SHOT); michael@0: } else { michael@0: NS_WARNING("failed to create: timer for pruning the dead connections!"); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsHttpConnectionMgr::ConditionallyStopPruneDeadConnectionsTimer() michael@0: { michael@0: // Leave the timer in place if there are connections that potentially michael@0: // need management michael@0: if (mNumIdleConns || (mNumActiveConns && gHttpHandler->IsSpdyEnabled())) michael@0: return; michael@0: michael@0: LOG(("nsHttpConnectionMgr::StopPruneDeadConnectionsTimer\n")); michael@0: michael@0: // Reset mTimeOfNextWakeUp so that we can find a new shortest value. michael@0: mTimeOfNextWakeUp = UINT64_MAX; michael@0: if (mTimer) { michael@0: mTimer->Cancel(); michael@0: mTimer = nullptr; michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsHttpConnectionMgr::ConditionallyStopTimeoutTick() michael@0: { michael@0: LOG(("nsHttpConnectionMgr::ConditionallyStopTimeoutTick " michael@0: "armed=%d active=%d\n", mTimeoutTickArmed, mNumActiveConns)); michael@0: michael@0: if (!mTimeoutTickArmed) michael@0: return; michael@0: michael@0: if (mNumActiveConns) michael@0: return; michael@0: michael@0: LOG(("nsHttpConnectionMgr::ConditionallyStopTimeoutTick stop==true\n")); michael@0: michael@0: mTimeoutTick->Cancel(); michael@0: mTimeoutTickArmed = false; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsHttpConnectionMgr::nsIObserver michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: NS_IMETHODIMP michael@0: nsHttpConnectionMgr::Observe(nsISupports *subject, michael@0: const char *topic, michael@0: const char16_t *data) michael@0: { michael@0: LOG(("nsHttpConnectionMgr::Observe [topic=\"%s\"]\n", topic)); michael@0: michael@0: if (0 == strcmp(topic, NS_TIMER_CALLBACK_TOPIC)) { michael@0: nsCOMPtr timer = do_QueryInterface(subject); michael@0: if (timer == mTimer) { michael@0: PruneDeadConnections(); michael@0: } michael@0: else if (timer == mTimeoutTick) { michael@0: TimeoutTick(); michael@0: } michael@0: else { michael@0: MOZ_ASSERT(false, "unexpected timer-callback"); michael@0: LOG(("Unexpected timer object\n")); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: nsresult michael@0: nsHttpConnectionMgr::AddTransaction(nsHttpTransaction *trans, int32_t priority) michael@0: { michael@0: LOG(("nsHttpConnectionMgr::AddTransaction [trans=%x %d]\n", trans, priority)); michael@0: michael@0: NS_ADDREF(trans); michael@0: nsresult rv = PostEvent(&nsHttpConnectionMgr::OnMsgNewTransaction, priority, trans); michael@0: if (NS_FAILED(rv)) michael@0: NS_RELEASE(trans); michael@0: return rv; michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpConnectionMgr::RescheduleTransaction(nsHttpTransaction *trans, int32_t priority) michael@0: { michael@0: LOG(("nsHttpConnectionMgr::RescheduleTransaction [trans=%x %d]\n", trans, priority)); michael@0: michael@0: NS_ADDREF(trans); michael@0: nsresult rv = PostEvent(&nsHttpConnectionMgr::OnMsgReschedTransaction, priority, trans); michael@0: if (NS_FAILED(rv)) michael@0: NS_RELEASE(trans); michael@0: return rv; michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpConnectionMgr::CancelTransaction(nsHttpTransaction *trans, nsresult reason) michael@0: { michael@0: LOG(("nsHttpConnectionMgr::CancelTransaction [trans=%x reason=%x]\n", trans, reason)); michael@0: michael@0: NS_ADDREF(trans); michael@0: nsresult rv = PostEvent(&nsHttpConnectionMgr::OnMsgCancelTransaction, michael@0: static_cast(reason), trans); michael@0: if (NS_FAILED(rv)) michael@0: NS_RELEASE(trans); michael@0: return rv; michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpConnectionMgr::PruneDeadConnections() michael@0: { michael@0: return PostEvent(&nsHttpConnectionMgr::OnMsgPruneDeadConnections); michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpConnectionMgr::DoShiftReloadConnectionCleanup(nsHttpConnectionInfo *aCI) michael@0: { michael@0: nsRefPtr connInfo(aCI); michael@0: michael@0: nsresult rv = PostEvent(&nsHttpConnectionMgr::OnMsgDoShiftReloadConnectionCleanup, michael@0: 0, connInfo); michael@0: if (NS_SUCCEEDED(rv)) michael@0: unused << connInfo.forget(); michael@0: return rv; michael@0: } michael@0: michael@0: class SpeculativeConnectArgs michael@0: { michael@0: public: michael@0: SpeculativeConnectArgs() { mOverridesOK = false; } michael@0: virtual ~SpeculativeConnectArgs() {} michael@0: michael@0: // Added manually so we can use nsRefPtr without inheriting from michael@0: // nsISupports michael@0: NS_IMETHOD_(MozExternalRefCountType) AddRef(void); michael@0: NS_IMETHOD_(MozExternalRefCountType) Release(void); michael@0: michael@0: public: // intentional! michael@0: nsRefPtr mTrans; michael@0: michael@0: bool mOverridesOK; michael@0: uint32_t mParallelSpeculativeConnectLimit; michael@0: bool mIgnoreIdle; michael@0: bool mIgnorePossibleSpdyConnections; michael@0: michael@0: // As above, added manually so we can use nsRefPtr without inheriting from michael@0: // nsISupports michael@0: protected: michael@0: ThreadSafeAutoRefCnt mRefCnt; michael@0: NS_DECL_OWNINGTHREAD michael@0: }; michael@0: michael@0: NS_IMPL_ADDREF(SpeculativeConnectArgs) michael@0: NS_IMPL_RELEASE(SpeculativeConnectArgs) michael@0: michael@0: nsresult michael@0: nsHttpConnectionMgr::SpeculativeConnect(nsHttpConnectionInfo *ci, michael@0: nsIInterfaceRequestor *callbacks, michael@0: uint32_t caps) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread(), "nsHttpConnectionMgr::SpeculativeConnect called off main thread!"); michael@0: michael@0: LOG(("nsHttpConnectionMgr::SpeculativeConnect [ci=%s]\n", michael@0: ci->HashKey().get())); michael@0: michael@0: // Hosts that are Local IP Literals should not be speculatively michael@0: // connected - Bug 853423. michael@0: if (ci && ci->HostIsLocalIPLiteral()) { michael@0: LOG(("nsHttpConnectionMgr::SpeculativeConnect skipping RFC1918 " michael@0: "address [%s]", ci->Host())); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsRefPtr args = new SpeculativeConnectArgs(); michael@0: michael@0: // Wrap up the callbacks and the target to ensure they're released on the target michael@0: // thread properly. michael@0: nsCOMPtr wrappedCallbacks; michael@0: NS_NewInterfaceRequestorAggregation(callbacks, nullptr, getter_AddRefs(wrappedCallbacks)); michael@0: michael@0: caps |= ci->GetAnonymous() ? NS_HTTP_LOAD_ANONYMOUS : 0; michael@0: args->mTrans = new NullHttpTransaction(ci, wrappedCallbacks, caps); michael@0: michael@0: nsCOMPtr overrider = michael@0: do_GetInterface(callbacks); michael@0: if (overrider) { michael@0: args->mOverridesOK = true; michael@0: overrider->GetParallelSpeculativeConnectLimit( michael@0: &args->mParallelSpeculativeConnectLimit); michael@0: overrider->GetIgnoreIdle(&args->mIgnoreIdle); michael@0: overrider->GetIgnorePossibleSpdyConnections( michael@0: &args->mIgnorePossibleSpdyConnections); michael@0: } michael@0: michael@0: nsresult rv = michael@0: PostEvent(&nsHttpConnectionMgr::OnMsgSpeculativeConnect, 0, args); michael@0: if (NS_SUCCEEDED(rv)) michael@0: unused << args.forget(); michael@0: return rv; michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpConnectionMgr::GetSocketThreadTarget(nsIEventTarget **target) michael@0: { michael@0: EnsureSocketThreadTarget(); michael@0: michael@0: ReentrantMonitorAutoEnter mon(mReentrantMonitor); michael@0: NS_IF_ADDREF(*target = mSocketThreadTarget); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpConnectionMgr::ReclaimConnection(nsHttpConnection *conn) michael@0: { michael@0: LOG(("nsHttpConnectionMgr::ReclaimConnection [conn=%x]\n", conn)); michael@0: michael@0: NS_ADDREF(conn); michael@0: nsresult rv = PostEvent(&nsHttpConnectionMgr::OnMsgReclaimConnection, 0, conn); michael@0: if (NS_FAILED(rv)) michael@0: NS_RELEASE(conn); michael@0: return rv; michael@0: } michael@0: michael@0: // A structure used to marshall 2 pointers across the various necessary michael@0: // threads to complete an HTTP upgrade. michael@0: class nsCompleteUpgradeData michael@0: { michael@0: public: michael@0: nsCompleteUpgradeData(nsAHttpConnection *aConn, michael@0: nsIHttpUpgradeListener *aListener) michael@0: : mConn(aConn), mUpgradeListener(aListener) {} michael@0: michael@0: nsRefPtr mConn; michael@0: nsCOMPtr mUpgradeListener; michael@0: }; michael@0: michael@0: nsresult michael@0: nsHttpConnectionMgr::CompleteUpgrade(nsAHttpConnection *aConn, michael@0: nsIHttpUpgradeListener *aUpgradeListener) michael@0: { michael@0: nsCompleteUpgradeData *data = michael@0: new nsCompleteUpgradeData(aConn, aUpgradeListener); michael@0: nsresult rv; michael@0: rv = PostEvent(&nsHttpConnectionMgr::OnMsgCompleteUpgrade, 0, data); michael@0: if (NS_FAILED(rv)) michael@0: delete data; michael@0: return rv; michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpConnectionMgr::UpdateParam(nsParamName name, uint16_t value) michael@0: { michael@0: uint32_t param = (uint32_t(name) << 16) | uint32_t(value); michael@0: return PostEvent(&nsHttpConnectionMgr::OnMsgUpdateParam, 0, michael@0: (void *)(uintptr_t) param); michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpConnectionMgr::ProcessPendingQ(nsHttpConnectionInfo *ci) michael@0: { michael@0: LOG(("nsHttpConnectionMgr::ProcessPendingQ [ci=%s]\n", ci->HashKey().get())); michael@0: michael@0: NS_ADDREF(ci); michael@0: nsresult rv = PostEvent(&nsHttpConnectionMgr::OnMsgProcessPendingQ, 0, ci); michael@0: if (NS_FAILED(rv)) michael@0: NS_RELEASE(ci); michael@0: return rv; michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpConnectionMgr::ProcessPendingQ() michael@0: { michael@0: LOG(("nsHttpConnectionMgr::ProcessPendingQ [All CI]\n")); michael@0: return PostEvent(&nsHttpConnectionMgr::OnMsgProcessPendingQ, 0, nullptr); michael@0: } michael@0: michael@0: void michael@0: nsHttpConnectionMgr::OnMsgUpdateRequestTokenBucket(int32_t, void *param) michael@0: { michael@0: nsRefPtr tokenBucket = michael@0: dont_AddRef(static_cast(param)); michael@0: gHttpHandler->SetRequestTokenBucket(tokenBucket); michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpConnectionMgr::UpdateRequestTokenBucket(EventTokenBucket *aBucket) michael@0: { michael@0: nsRefPtr bucket(aBucket); michael@0: michael@0: // Call From main thread when a new EventTokenBucket has been made in order michael@0: // to post the new value to the socket thread. michael@0: nsresult rv = PostEvent(&nsHttpConnectionMgr::OnMsgUpdateRequestTokenBucket, michael@0: 0, bucket); michael@0: if (NS_SUCCEEDED(rv)) michael@0: unused << bucket.forget(); michael@0: return rv; michael@0: } michael@0: michael@0: // Given a nsHttpConnectionInfo find the connection entry object that michael@0: // contains either the nshttpconnection or nshttptransaction parameter. michael@0: // Normally this is done by the hashkey lookup of connectioninfo, michael@0: // but if spdy coalescing is in play it might be found in a redirected michael@0: // entry michael@0: nsHttpConnectionMgr::nsConnectionEntry * michael@0: nsHttpConnectionMgr::LookupConnectionEntry(nsHttpConnectionInfo *ci, michael@0: nsHttpConnection *conn, michael@0: nsHttpTransaction *trans) michael@0: { michael@0: MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); michael@0: if (!ci) michael@0: return nullptr; michael@0: michael@0: nsConnectionEntry *ent = mCT.Get(ci->HashKey()); michael@0: michael@0: // If there is no sign of coalescing (or it is disabled) then just michael@0: // return the primary hash lookup michael@0: if (!ent || !ent->mUsingSpdy || ent->mCoalescingKey.IsEmpty()) michael@0: return ent; michael@0: michael@0: // If there is no preferred coalescing entry for this host (or the michael@0: // preferred entry is the one that matched the mCT hash lookup) then michael@0: // there is only option michael@0: nsConnectionEntry *preferred = mSpdyPreferredHash.Get(ent->mCoalescingKey); michael@0: if (!preferred || (preferred == ent)) michael@0: return ent; michael@0: michael@0: if (conn) { michael@0: // The connection could be either in preferred or ent. It is most michael@0: // likely the only active connection in preferred - so start with that. michael@0: if (preferred->mActiveConns.Contains(conn)) michael@0: return preferred; michael@0: if (preferred->mIdleConns.Contains(conn)) michael@0: return preferred; michael@0: } michael@0: michael@0: if (trans && preferred->mPendingQ.Contains(trans)) michael@0: return preferred; michael@0: michael@0: // Neither conn nor trans found in preferred, use the default entry michael@0: return ent; michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpConnectionMgr::CloseIdleConnection(nsHttpConnection *conn) michael@0: { michael@0: MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); michael@0: LOG(("nsHttpConnectionMgr::CloseIdleConnection %p conn=%p", michael@0: this, conn)); michael@0: michael@0: if (!conn->ConnectionInfo()) michael@0: return NS_ERROR_UNEXPECTED; michael@0: michael@0: nsConnectionEntry *ent = LookupConnectionEntry(conn->ConnectionInfo(), michael@0: conn, nullptr); michael@0: michael@0: if (!ent || !ent->mIdleConns.RemoveElement(conn)) michael@0: return NS_ERROR_UNEXPECTED; michael@0: michael@0: conn->Close(NS_ERROR_ABORT); michael@0: NS_RELEASE(conn); michael@0: mNumIdleConns--; michael@0: ConditionallyStopPruneDeadConnectionsTimer(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // This function lets a connection, after completing the NPN phase, michael@0: // report whether or not it is using spdy through the usingSpdy michael@0: // argument. It would not be necessary if NPN were driven out of michael@0: // the connection manager. The connection entry associated with the michael@0: // connection is then updated to indicate whether or not we want to use michael@0: // spdy with that host and update the preliminary preferred host michael@0: // entries used for de-sharding hostsnames. michael@0: void michael@0: nsHttpConnectionMgr::ReportSpdyConnection(nsHttpConnection *conn, michael@0: bool usingSpdy) michael@0: { michael@0: MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); michael@0: michael@0: nsConnectionEntry *ent = LookupConnectionEntry(conn->ConnectionInfo(), michael@0: conn, nullptr); michael@0: michael@0: if (!ent) michael@0: return; michael@0: michael@0: ent->mTestedSpdy = true; michael@0: michael@0: if (!usingSpdy) michael@0: return; michael@0: michael@0: ent->mUsingSpdy = true; michael@0: mNumSpdyActiveConns++; michael@0: michael@0: uint32_t ttl = conn->TimeToLive(); michael@0: uint64_t timeOfExpire = NowInSeconds() + ttl; michael@0: if (!mTimer || timeOfExpire < mTimeOfNextWakeUp) michael@0: PruneDeadConnectionsAfter(ttl); michael@0: michael@0: // Lookup preferred directly from the hash instead of using michael@0: // GetSpdyPreferredEnt() because we want to avoid the cert compatibility michael@0: // check at this point because the cert is never part of the hash michael@0: // lookup. Filtering on that has to be done at the time of use michael@0: // rather than the time of registration (i.e. now). michael@0: nsConnectionEntry *joinedConnection; michael@0: nsConnectionEntry *preferred = michael@0: mSpdyPreferredHash.Get(ent->mCoalescingKey); michael@0: michael@0: LOG(("ReportSpdyConnection %s %s ent=%p preferred=%p\n", michael@0: ent->mConnInfo->Host(), ent->mCoalescingKey.get(), michael@0: ent, preferred)); michael@0: michael@0: if (!preferred) { michael@0: if (!ent->mCoalescingKey.IsEmpty()) { michael@0: mSpdyPreferredHash.Put(ent->mCoalescingKey, ent); michael@0: ent->mSpdyPreferred = true; michael@0: preferred = ent; michael@0: } michael@0: } else if ((preferred != ent) && michael@0: (joinedConnection = GetSpdyPreferredEnt(ent)) && michael@0: (joinedConnection != ent)) { michael@0: // michael@0: // A connection entry (e.g. made with a different hostname) with michael@0: // the same IP address is preferred for future transactions over this michael@0: // connection entry. Gracefully close down the connection to help michael@0: // new transactions migrate over. michael@0: michael@0: LOG(("ReportSpdyConnection graceful close of conn=%p ent=%p to " michael@0: "migrate to preferred\n", conn, ent)); michael@0: michael@0: conn->DontReuse(); michael@0: } else if (preferred != ent) { michael@0: LOG (("ReportSpdyConnection preferred host may be in false start or " michael@0: "may have insufficient cert. Leave mapping in place but do not " michael@0: "abandon this connection yet.")); michael@0: } michael@0: michael@0: PostEvent(&nsHttpConnectionMgr::OnMsgProcessAllSpdyPendingQ); michael@0: } michael@0: michael@0: void michael@0: nsHttpConnectionMgr::ReportSpdyCWNDSetting(nsHttpConnectionInfo *ci, michael@0: uint32_t cwndValue) michael@0: { michael@0: MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); michael@0: michael@0: if (!gHttpHandler->UseSpdyPersistentSettings()) michael@0: return; michael@0: michael@0: if (!ci) michael@0: return; michael@0: michael@0: nsConnectionEntry *ent = mCT.Get(ci->HashKey()); michael@0: if (!ent) michael@0: return; michael@0: michael@0: ent = GetSpdyPreferredEnt(ent); michael@0: if (!ent) // just to be thorough - but that map should always exist michael@0: return; michael@0: michael@0: cwndValue = std::max(2U, cwndValue); michael@0: cwndValue = std::min(128U, cwndValue); michael@0: michael@0: ent->mSpdyCWND = cwndValue; michael@0: ent->mSpdyCWNDTimeStamp = TimeStamp::Now(); michael@0: return; michael@0: } michael@0: michael@0: // a value of 0 means no setting is available michael@0: uint32_t michael@0: nsHttpConnectionMgr::GetSpdyCWNDSetting(nsHttpConnectionInfo *ci) michael@0: { michael@0: MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); michael@0: michael@0: if (!gHttpHandler->UseSpdyPersistentSettings()) michael@0: return 0; michael@0: michael@0: if (!ci) michael@0: return 0; michael@0: michael@0: nsConnectionEntry *ent = mCT.Get(ci->HashKey()); michael@0: if (!ent) michael@0: return 0; michael@0: michael@0: ent = GetSpdyPreferredEnt(ent); michael@0: if (!ent) // just to be thorough - but that map should always exist michael@0: return 0; michael@0: michael@0: if (ent->mSpdyCWNDTimeStamp.IsNull()) michael@0: return 0; michael@0: michael@0: // For privacy tracking reasons, and the fact that CWND is not michael@0: // meaningful after some time, we don't honor stored CWND after 8 michael@0: // hours. michael@0: TimeDuration age = TimeStamp::Now() - ent->mSpdyCWNDTimeStamp; michael@0: if (age.ToMilliseconds() > (1000 * 60 * 60 * 8)) michael@0: return 0; michael@0: michael@0: return ent->mSpdyCWND; michael@0: } michael@0: michael@0: nsHttpConnectionMgr::nsConnectionEntry * michael@0: nsHttpConnectionMgr::GetSpdyPreferredEnt(nsConnectionEntry *aOriginalEntry) michael@0: { michael@0: if (!gHttpHandler->IsSpdyEnabled() || michael@0: !gHttpHandler->CoalesceSpdy() || michael@0: aOriginalEntry->mCoalescingKey.IsEmpty()) michael@0: return nullptr; michael@0: michael@0: nsConnectionEntry *preferred = michael@0: mSpdyPreferredHash.Get(aOriginalEntry->mCoalescingKey); michael@0: michael@0: // if there is no redirection no cert validation is required michael@0: if (preferred == aOriginalEntry) michael@0: return aOriginalEntry; michael@0: michael@0: // if there is no preferred host or it is no longer using spdy michael@0: // then skip pooling michael@0: if (!preferred || !preferred->mUsingSpdy) michael@0: return nullptr; michael@0: michael@0: // if there is not an active spdy session in this entry then michael@0: // we cannot pool because the cert upon activation may not michael@0: // be the same as the old one. Active sessions are prohibited michael@0: // from changing certs. michael@0: michael@0: nsHttpConnection *activeSpdy = nullptr; michael@0: michael@0: for (uint32_t index = 0; index < preferred->mActiveConns.Length(); ++index) { michael@0: if (preferred->mActiveConns[index]->CanDirectlyActivate()) { michael@0: activeSpdy = preferred->mActiveConns[index]; michael@0: break; michael@0: } michael@0: } michael@0: michael@0: if (!activeSpdy) { michael@0: // remove the preferred status of this entry if it cannot be michael@0: // used for pooling. michael@0: preferred->mSpdyPreferred = false; michael@0: RemoveSpdyPreferredEnt(preferred->mCoalescingKey); michael@0: LOG(("nsHttpConnectionMgr::GetSpdyPreferredConnection " michael@0: "preferred host mapping %s to %s removed due to inactivity.\n", michael@0: aOriginalEntry->mConnInfo->Host(), michael@0: preferred->mConnInfo->Host())); michael@0: michael@0: return nullptr; michael@0: } michael@0: michael@0: // Check that the server cert supports redirection michael@0: nsresult rv; michael@0: bool isJoined = false; michael@0: michael@0: nsCOMPtr securityInfo; michael@0: nsCOMPtr sslSocketControl; michael@0: nsAutoCString negotiatedNPN; michael@0: michael@0: activeSpdy->GetSecurityInfo(getter_AddRefs(securityInfo)); michael@0: if (!securityInfo) { michael@0: NS_WARNING("cannot obtain spdy security info"); michael@0: return nullptr; michael@0: } michael@0: michael@0: sslSocketControl = do_QueryInterface(securityInfo, &rv); michael@0: if (NS_FAILED(rv)) { michael@0: NS_WARNING("sslSocketControl QI Failed"); michael@0: return nullptr; michael@0: } michael@0: michael@0: if (gHttpHandler->SpdyInfo()->ProtocolEnabled(0)) michael@0: rv = sslSocketControl->JoinConnection(gHttpHandler->SpdyInfo()->VersionString[0], michael@0: aOriginalEntry->mConnInfo->GetHost(), michael@0: aOriginalEntry->mConnInfo->Port(), michael@0: &isJoined); michael@0: else michael@0: rv = NS_OK; /* simulate failed join */ michael@0: michael@0: // JoinConnection() may have failed due to spdy version level. Try the other michael@0: // level we support (if any) michael@0: if (NS_SUCCEEDED(rv) && !isJoined && gHttpHandler->SpdyInfo()->ProtocolEnabled(1)) { michael@0: rv = sslSocketControl->JoinConnection(gHttpHandler->SpdyInfo()->VersionString[1], michael@0: aOriginalEntry->mConnInfo->GetHost(), michael@0: aOriginalEntry->mConnInfo->Port(), michael@0: &isJoined); michael@0: } michael@0: michael@0: if (NS_FAILED(rv) || !isJoined) { michael@0: LOG(("nsHttpConnectionMgr::GetSpdyPreferredConnection " michael@0: "Host %s cannot be confirmed to be joined " michael@0: "with %s connections. rv=%x isJoined=%d", michael@0: preferred->mConnInfo->Host(), aOriginalEntry->mConnInfo->Host(), michael@0: rv, isJoined)); michael@0: Telemetry::Accumulate(Telemetry::SPDY_NPN_JOIN, false); michael@0: return nullptr; michael@0: } michael@0: michael@0: // IP pooling confirmed michael@0: LOG(("nsHttpConnectionMgr::GetSpdyPreferredConnection " michael@0: "Host %s has cert valid for %s connections, " michael@0: "so %s will be coalesced with %s", michael@0: preferred->mConnInfo->Host(), aOriginalEntry->mConnInfo->Host(), michael@0: aOriginalEntry->mConnInfo->Host(), preferred->mConnInfo->Host())); michael@0: Telemetry::Accumulate(Telemetry::SPDY_NPN_JOIN, true); michael@0: return preferred; michael@0: } michael@0: michael@0: void michael@0: nsHttpConnectionMgr::RemoveSpdyPreferredEnt(nsACString &aHashKey) michael@0: { michael@0: if (aHashKey.IsEmpty()) michael@0: return; michael@0: michael@0: mSpdyPreferredHash.Remove(aHashKey); michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // enumeration callbacks michael@0: michael@0: PLDHashOperator michael@0: nsHttpConnectionMgr::ProcessOneTransactionCB(const nsACString &key, michael@0: nsAutoPtr &ent, michael@0: void *closure) michael@0: { michael@0: nsHttpConnectionMgr *self = (nsHttpConnectionMgr *) closure; michael@0: michael@0: if (self->ProcessPendingQForEntry(ent, false)) michael@0: return PL_DHASH_STOP; michael@0: michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: PLDHashOperator michael@0: nsHttpConnectionMgr::ProcessAllTransactionsCB(const nsACString &key, michael@0: nsAutoPtr &ent, michael@0: void *closure) michael@0: { michael@0: nsHttpConnectionMgr *self = (nsHttpConnectionMgr *) closure; michael@0: self->ProcessPendingQForEntry(ent, true); michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: // If the global number of connections is preventing the opening of michael@0: // new connections to a host without idle connections, then michael@0: // close them regardless of their TTL michael@0: PLDHashOperator michael@0: nsHttpConnectionMgr::PurgeExcessIdleConnectionsCB(const nsACString &key, michael@0: nsAutoPtr &ent, michael@0: void *closure) michael@0: { michael@0: nsHttpConnectionMgr *self = (nsHttpConnectionMgr *) closure; michael@0: michael@0: while (self->mNumIdleConns + self->mNumActiveConns + 1 >= self->mMaxConns) { michael@0: if (!ent->mIdleConns.Length()) { michael@0: // There are no idle conns left in this connection entry michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: nsHttpConnection *conn = ent->mIdleConns[0]; michael@0: ent->mIdleConns.RemoveElementAt(0); michael@0: conn->Close(NS_ERROR_ABORT); michael@0: NS_RELEASE(conn); michael@0: self->mNumIdleConns--; michael@0: self->ConditionallyStopPruneDeadConnectionsTimer(); michael@0: } michael@0: return PL_DHASH_STOP; michael@0: } michael@0: michael@0: // If the global number of connections is preventing the opening of michael@0: // new connections to a host without idle connections, then michael@0: // close any spdy asap michael@0: PLDHashOperator michael@0: nsHttpConnectionMgr::PurgeExcessSpdyConnectionsCB(const nsACString &key, michael@0: nsAutoPtr &ent, michael@0: void *closure) michael@0: { michael@0: if (!ent->mUsingSpdy) michael@0: return PL_DHASH_NEXT; michael@0: michael@0: nsHttpConnectionMgr *self = static_cast(closure); michael@0: for (uint32_t index = 0; index < ent->mActiveConns.Length(); ++index) { michael@0: nsHttpConnection *conn = ent->mActiveConns[index]; michael@0: if (conn->UsingSpdy() && conn->CanReuse()) { michael@0: conn->DontReuse(); michael@0: // stop on <= (particularly =) beacuse this dontreuse causes async close michael@0: if (self->mNumIdleConns + self->mNumActiveConns + 1 <= self->mMaxConns) michael@0: return PL_DHASH_STOP; michael@0: } michael@0: } michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: PLDHashOperator michael@0: nsHttpConnectionMgr::PruneDeadConnectionsCB(const nsACString &key, michael@0: nsAutoPtr &ent, michael@0: void *closure) michael@0: { michael@0: MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); michael@0: nsHttpConnectionMgr *self = (nsHttpConnectionMgr *) closure; michael@0: michael@0: LOG((" pruning [ci=%s]\n", ent->mConnInfo->HashKey().get())); michael@0: michael@0: // Find out how long it will take for next idle connection to not be reusable michael@0: // anymore. michael@0: uint32_t timeToNextExpire = UINT32_MAX; michael@0: int32_t count = ent->mIdleConns.Length(); michael@0: if (count > 0) { michael@0: for (int32_t i=count-1; i>=0; --i) { michael@0: nsHttpConnection *conn = ent->mIdleConns[i]; michael@0: if (!conn->CanReuse()) { michael@0: ent->mIdleConns.RemoveElementAt(i); michael@0: conn->Close(NS_ERROR_ABORT); michael@0: NS_RELEASE(conn); michael@0: self->mNumIdleConns--; michael@0: } else { michael@0: timeToNextExpire = std::min(timeToNextExpire, conn->TimeToLive()); michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (ent->mUsingSpdy) { michael@0: for (uint32_t index = 0; index < ent->mActiveConns.Length(); ++index) { michael@0: nsHttpConnection *conn = ent->mActiveConns[index]; michael@0: if (conn->UsingSpdy()) { michael@0: if (!conn->CanReuse()) { michael@0: // marking it dont reuse will create an active tear down if michael@0: // the spdy session is idle. michael@0: conn->DontReuse(); michael@0: } michael@0: else { michael@0: timeToNextExpire = std::min(timeToNextExpire, michael@0: conn->TimeToLive()); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: // If time to next expire found is shorter than time to next wake-up, we need to michael@0: // change the time for next wake-up. michael@0: if (timeToNextExpire != UINT32_MAX) { michael@0: uint32_t now = NowInSeconds(); michael@0: uint64_t timeOfNextExpire = now + timeToNextExpire; michael@0: // If pruning of dead connections is not already scheduled to happen michael@0: // or time found for next connection to expire is is before michael@0: // mTimeOfNextWakeUp, we need to schedule the pruning to happen michael@0: // after timeToNextExpire. michael@0: if (!self->mTimer || timeOfNextExpire < self->mTimeOfNextWakeUp) { michael@0: self->PruneDeadConnectionsAfter(timeToNextExpire); michael@0: } michael@0: } else { michael@0: self->ConditionallyStopPruneDeadConnectionsTimer(); michael@0: } michael@0: michael@0: // if this entry is empty, we have too many entries, michael@0: // and this doesn't represent some painfully determined michael@0: // red condition, then we can clean it up and restart from michael@0: // yellow michael@0: if (ent->PipelineState() != PS_RED && michael@0: self->mCT.Count() > 125 && michael@0: ent->mIdleConns.Length() == 0 && michael@0: ent->mActiveConns.Length() == 0 && michael@0: ent->mHalfOpens.Length() == 0 && michael@0: ent->mPendingQ.Length() == 0 && michael@0: ((!ent->mTestedSpdy && !ent->mUsingSpdy) || michael@0: !gHttpHandler->IsSpdyEnabled() || michael@0: self->mCT.Count() > 300)) { michael@0: LOG((" removing empty connection entry\n")); michael@0: return PL_DHASH_REMOVE; michael@0: } michael@0: michael@0: // otherwise use this opportunity to compact our arrays... michael@0: ent->mIdleConns.Compact(); michael@0: ent->mActiveConns.Compact(); michael@0: ent->mPendingQ.Compact(); michael@0: michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: PLDHashOperator michael@0: nsHttpConnectionMgr::ShutdownPassCB(const nsACString &key, michael@0: nsAutoPtr &ent, michael@0: void *closure) michael@0: { michael@0: nsHttpConnectionMgr *self = (nsHttpConnectionMgr *) closure; michael@0: michael@0: nsHttpTransaction *trans; michael@0: nsHttpConnection *conn; michael@0: michael@0: // close all active connections michael@0: while (ent->mActiveConns.Length()) { michael@0: conn = ent->mActiveConns[0]; michael@0: michael@0: ent->mActiveConns.RemoveElementAt(0); michael@0: self->DecrementActiveConnCount(conn); michael@0: michael@0: conn->Close(NS_ERROR_ABORT); michael@0: NS_RELEASE(conn); michael@0: } michael@0: michael@0: // close all idle connections michael@0: while (ent->mIdleConns.Length()) { michael@0: conn = ent->mIdleConns[0]; michael@0: michael@0: ent->mIdleConns.RemoveElementAt(0); michael@0: self->mNumIdleConns--; michael@0: michael@0: conn->Close(NS_ERROR_ABORT); michael@0: NS_RELEASE(conn); michael@0: } michael@0: // If all idle connections are removed, michael@0: // we can stop pruning dead connections. michael@0: self->ConditionallyStopPruneDeadConnectionsTimer(); michael@0: michael@0: // close all pending transactions michael@0: while (ent->mPendingQ.Length()) { michael@0: trans = ent->mPendingQ[0]; michael@0: michael@0: ent->mPendingQ.RemoveElementAt(0); michael@0: michael@0: trans->Close(NS_ERROR_ABORT); michael@0: NS_RELEASE(trans); michael@0: } michael@0: michael@0: // close all half open tcp connections michael@0: for (int32_t i = ((int32_t) ent->mHalfOpens.Length()) - 1; i >= 0; i--) michael@0: ent->mHalfOpens[i]->Abandon(); michael@0: michael@0: return PL_DHASH_REMOVE; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: bool michael@0: nsHttpConnectionMgr::ProcessPendingQForEntry(nsConnectionEntry *ent, bool considerAll) michael@0: { michael@0: MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); michael@0: michael@0: LOG(("nsHttpConnectionMgr::ProcessPendingQForEntry [ci=%s]\n", michael@0: ent->mConnInfo->HashKey().get())); michael@0: michael@0: ProcessSpdyPendingQ(ent); michael@0: michael@0: nsHttpTransaction *trans; michael@0: nsresult rv; michael@0: bool dispatchedSuccessfully = false; michael@0: int dispatchCount = 0; michael@0: #ifdef WTF_DEBUG michael@0: uint32_t total = ent->mPendingQ.Length(); michael@0: #endif michael@0: michael@0: // if !considerAll iterate the pending list until one is dispatched successfully. michael@0: // Keep iterating afterwards only until a transaction fails to dispatch. michael@0: // if considerAll == true then try and dispatch all items. michael@0: for (uint32_t i = 0; i < ent->mPendingQ.Length(); ) { michael@0: trans = ent->mPendingQ[i]; michael@0: michael@0: // When this entry has already established a half-open michael@0: // connection, we want to prevent any duplicate half-open michael@0: // connections from being established and bound to this michael@0: // transaction. michael@0: bool alreadyHalfOpen = false; michael@0: if (ent->SupportsPipelining()) { michael@0: alreadyHalfOpen = (ent->UnconnectedHalfOpens() > 0); michael@0: } else { michael@0: for (int32_t j = 0; j < ((int32_t) ent->mHalfOpens.Length()); ++j) { michael@0: if (ent->mHalfOpens[j]->Transaction() == trans) { michael@0: alreadyHalfOpen = true; michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: michael@0: rv = TryDispatchTransaction(ent, alreadyHalfOpen, trans); michael@0: if (NS_SUCCEEDED(rv) || (rv != NS_ERROR_NOT_AVAILABLE)) { michael@0: if (NS_SUCCEEDED(rv)) michael@0: LOG((" dispatching pending transaction...\n")); michael@0: else michael@0: LOG((" removing pending transaction based on " michael@0: "TryDispatchTransaction returning hard error %x\n", rv)); michael@0: michael@0: if (ent->mPendingQ.RemoveElement(trans)) { michael@0: dispatchedSuccessfully = true; michael@0: dispatchCount++; michael@0: NS_RELEASE(trans); michael@0: continue; // dont ++i as we just made the array shorter michael@0: } michael@0: michael@0: LOG((" transaction not found in pending queue\n")); michael@0: } michael@0: michael@0: // We want to keep walking the dispatch table to ensure requests michael@0: // get combined properly. michael@0: //if (dispatchedSuccessfully && !considerAll) michael@0: // break; michael@0: michael@0: ++i; michael@0: } michael@0: michael@0: #ifdef WTF_DEBUG michael@0: if (dispatchedSuccessfully) { michael@0: fprintf(stderr, "WTF-queue: Dispatched %d/%d pending transactions for %s\n", michael@0: dispatchCount, total, ent->mConnInfo->Host()); michael@0: return true; michael@0: } michael@0: #endif michael@0: michael@0: return dispatchedSuccessfully; michael@0: } michael@0: michael@0: bool michael@0: nsHttpConnectionMgr::ProcessPendingQForEntry(nsHttpConnectionInfo *ci) michael@0: { michael@0: MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); michael@0: michael@0: nsConnectionEntry *ent = mCT.Get(ci->HashKey()); michael@0: if (ent) michael@0: return ProcessPendingQForEntry(ent, false); michael@0: return false; michael@0: } michael@0: michael@0: bool michael@0: nsHttpConnectionMgr::SupportsPipelining(nsHttpConnectionInfo *ci) michael@0: { michael@0: MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); michael@0: michael@0: nsConnectionEntry *ent = mCT.Get(ci->HashKey()); michael@0: if (ent) michael@0: return ent->SupportsPipelining(); michael@0: return false; michael@0: } michael@0: michael@0: // nsHttpPipelineFeedback used to hold references across events michael@0: michael@0: class nsHttpPipelineFeedback michael@0: { michael@0: public: michael@0: nsHttpPipelineFeedback(nsHttpConnectionInfo *ci, michael@0: nsHttpConnectionMgr::PipelineFeedbackInfoType info, michael@0: nsHttpConnection *conn, uint32_t data) michael@0: : mConnInfo(ci) michael@0: , mConn(conn) michael@0: , mInfo(info) michael@0: , mData(data) michael@0: { michael@0: } michael@0: michael@0: ~nsHttpPipelineFeedback() michael@0: { michael@0: } michael@0: michael@0: nsRefPtr mConnInfo; michael@0: nsRefPtr mConn; michael@0: nsHttpConnectionMgr::PipelineFeedbackInfoType mInfo; michael@0: uint32_t mData; michael@0: }; michael@0: michael@0: void michael@0: nsHttpConnectionMgr::PipelineFeedbackInfo(nsHttpConnectionInfo *ci, michael@0: PipelineFeedbackInfoType info, michael@0: nsHttpConnection *conn, michael@0: uint32_t data) michael@0: { michael@0: if (!ci) michael@0: return; michael@0: michael@0: // Post this to the socket thread if we are not running there already michael@0: if (PR_GetCurrentThread() != gSocketThread) { michael@0: nsHttpPipelineFeedback *fb = new nsHttpPipelineFeedback(ci, info, michael@0: conn, data); michael@0: michael@0: nsresult rv = PostEvent(&nsHttpConnectionMgr::OnMsgProcessFeedback, michael@0: 0, fb); michael@0: if (NS_FAILED(rv)) michael@0: delete fb; michael@0: return; michael@0: } michael@0: michael@0: nsConnectionEntry *ent = mCT.Get(ci->HashKey()); michael@0: michael@0: if (ent) michael@0: ent->OnPipelineFeedbackInfo(info, conn, data); michael@0: } michael@0: michael@0: void michael@0: nsHttpConnectionMgr::ReportFailedToProcess(nsIURI *uri) michael@0: { michael@0: MOZ_ASSERT(uri); michael@0: michael@0: nsAutoCString host; michael@0: int32_t port = -1; michael@0: nsAutoCString username; michael@0: bool usingSSL = false; michael@0: bool isHttp = false; michael@0: michael@0: nsresult rv = uri->SchemeIs("https", &usingSSL); michael@0: if (NS_SUCCEEDED(rv) && usingSSL) michael@0: isHttp = true; michael@0: if (NS_SUCCEEDED(rv) && !isHttp) michael@0: rv = uri->SchemeIs("http", &isHttp); michael@0: if (NS_SUCCEEDED(rv)) michael@0: rv = uri->GetAsciiHost(host); michael@0: if (NS_SUCCEEDED(rv)) michael@0: rv = uri->GetPort(&port); michael@0: if (NS_SUCCEEDED(rv)) michael@0: uri->GetUsername(username); michael@0: if (NS_FAILED(rv) || !isHttp || host.IsEmpty()) michael@0: return; michael@0: michael@0: // report the event for all the permutations of anonymous and michael@0: // private versions of this host michael@0: nsRefPtr ci = michael@0: new nsHttpConnectionInfo(host, port, username, nullptr, usingSSL); michael@0: ci->SetAnonymous(false); michael@0: ci->SetPrivate(false); michael@0: PipelineFeedbackInfo(ci, RedCorruptedContent, nullptr, 0); michael@0: michael@0: ci = ci->Clone(); michael@0: ci->SetAnonymous(false); michael@0: ci->SetPrivate(true); michael@0: PipelineFeedbackInfo(ci, RedCorruptedContent, nullptr, 0); michael@0: michael@0: ci = ci->Clone(); michael@0: ci->SetAnonymous(true); michael@0: ci->SetPrivate(false); michael@0: PipelineFeedbackInfo(ci, RedCorruptedContent, nullptr, 0); michael@0: michael@0: ci = ci->Clone(); michael@0: ci->SetAnonymous(true); michael@0: ci->SetPrivate(true); michael@0: PipelineFeedbackInfo(ci, RedCorruptedContent, nullptr, 0); michael@0: } michael@0: michael@0: // we're at the active connection limit if any one of the following conditions is true: michael@0: // (1) at max-connections michael@0: // (2) keep-alive enabled and at max-persistent-connections-per-server/proxy michael@0: // (3) keep-alive disabled and at max-connections-per-server michael@0: bool michael@0: nsHttpConnectionMgr::AtActiveConnectionLimit(nsConnectionEntry *ent, uint32_t caps) michael@0: { michael@0: nsHttpConnectionInfo *ci = ent->mConnInfo; michael@0: michael@0: LOG(("nsHttpConnectionMgr::AtActiveConnectionLimit [ci=%s caps=%x]\n", michael@0: ci->HashKey().get(), caps)); michael@0: michael@0: // update maxconns if potentially limited by the max socket count michael@0: // this requires a dynamic reduction in the max socket count to a point michael@0: // lower than the max-connections pref. michael@0: uint32_t maxSocketCount = gHttpHandler->MaxSocketCount(); michael@0: if (mMaxConns > maxSocketCount) { michael@0: mMaxConns = maxSocketCount; michael@0: LOG(("nsHttpConnectionMgr %p mMaxConns dynamically reduced to %u", michael@0: this, mMaxConns)); michael@0: } michael@0: michael@0: // If there are more active connections than the global limit, then we're michael@0: // done. Purging idle connections won't get us below it. michael@0: if (mNumActiveConns >= mMaxConns) { michael@0: LOG((" num active conns == max conns\n")); michael@0: return true; michael@0: } michael@0: michael@0: // Add in the in-progress tcp connections, we will assume they are michael@0: // keepalive enabled. michael@0: // Exclude half-open's that has already created a usable connection. michael@0: // This prevents the limit being stuck on ipv6 connections that michael@0: // eventually time out after typical 21 seconds of no ACK+SYN reply. michael@0: uint32_t totalCount = michael@0: ent->mActiveConns.Length() + ent->UnconnectedHalfOpens(); michael@0: michael@0: uint16_t maxPersistConns; michael@0: michael@0: if (ci->UsingHttpProxy() && !ci->UsingConnect()) michael@0: maxPersistConns = mMaxPersistConnsPerProxy; michael@0: else michael@0: maxPersistConns = mMaxPersistConnsPerHost; michael@0: michael@0: LOG((" connection count = %d, limit %d\n", totalCount, maxPersistConns)); michael@0: michael@0: // use >= just to be safe michael@0: bool result = (totalCount >= maxPersistConns); michael@0: LOG((" result: %s", result ? "true" : "false")); michael@0: return result; michael@0: } michael@0: michael@0: void michael@0: nsHttpConnectionMgr::ClosePersistentConnections(nsConnectionEntry *ent) michael@0: { michael@0: LOG(("nsHttpConnectionMgr::ClosePersistentConnections [ci=%s]\n", michael@0: ent->mConnInfo->HashKey().get())); michael@0: while (ent->mIdleConns.Length()) { michael@0: nsHttpConnection *conn = ent->mIdleConns[0]; michael@0: ent->mIdleConns.RemoveElementAt(0); michael@0: mNumIdleConns--; michael@0: conn->Close(NS_ERROR_ABORT); michael@0: NS_RELEASE(conn); michael@0: } michael@0: michael@0: int32_t activeCount = ent->mActiveConns.Length(); michael@0: for (int32_t i=0; i < activeCount; i++) michael@0: ent->mActiveConns[i]->DontReuse(); michael@0: } michael@0: michael@0: PLDHashOperator michael@0: nsHttpConnectionMgr::ClosePersistentConnectionsCB(const nsACString &key, michael@0: nsAutoPtr &ent, michael@0: void *closure) michael@0: { michael@0: nsHttpConnectionMgr *self = static_cast(closure); michael@0: self->ClosePersistentConnections(ent); michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: bool michael@0: nsHttpConnectionMgr::RestrictConnections(nsConnectionEntry *ent, michael@0: bool ignorePossibleSpdyConnections) michael@0: { michael@0: MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); michael@0: michael@0: // If this host is trying to negotiate a SPDY session right now, michael@0: // don't create any new ssl connections until the result of the michael@0: // negotiation is known. michael@0: michael@0: bool doRestrict = ent->mConnInfo->UsingSSL() && michael@0: gHttpHandler->IsSpdyEnabled() && michael@0: ((!ent->mTestedSpdy && !ignorePossibleSpdyConnections) || michael@0: ent->mUsingSpdy) && michael@0: (ent->mHalfOpens.Length() || ent->mActiveConns.Length()); michael@0: michael@0: // If there are no restrictions, we are done michael@0: if (!doRestrict) michael@0: return false; michael@0: michael@0: // If the restriction is based on a tcp handshake in progress michael@0: // let that connect and then see if it was SPDY or not michael@0: if (ent->UnconnectedHalfOpens() && !ignorePossibleSpdyConnections) michael@0: return true; michael@0: michael@0: // There is a concern that a host is using a mix of HTTP/1 and SPDY. michael@0: // In that case we don't want to restrict connections just because michael@0: // there is a single active HTTP/1 session in use. michael@0: if (ent->mUsingSpdy && ent->mActiveConns.Length()) { michael@0: bool confirmedRestrict = false; michael@0: for (uint32_t index = 0; index < ent->mActiveConns.Length(); ++index) { michael@0: nsHttpConnection *conn = ent->mActiveConns[index]; michael@0: if (!conn->ReportedNPN() || conn->CanDirectlyActivate()) { michael@0: confirmedRestrict = true; michael@0: break; michael@0: } michael@0: } michael@0: doRestrict = confirmedRestrict; michael@0: if (!confirmedRestrict) { michael@0: LOG(("nsHttpConnectionMgr spdy connection restriction to " michael@0: "%s bypassed.\n", ent->mConnInfo->Host())); michael@0: } michael@0: } michael@0: return doRestrict; michael@0: } michael@0: michael@0: // returns NS_OK if a connection was started michael@0: // return NS_ERROR_NOT_AVAILABLE if a new connection cannot be made due to michael@0: // ephemeral limits michael@0: // returns other NS_ERROR on hard failure conditions michael@0: nsresult michael@0: nsHttpConnectionMgr::MakeNewConnection(nsConnectionEntry *ent, michael@0: nsHttpTransaction *trans) michael@0: { michael@0: LOG(("nsHttpConnectionMgr::MakeNewConnection %p ent=%p trans=%p", michael@0: this, ent, trans)); michael@0: MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); michael@0: michael@0: uint32_t halfOpenLength = ent->mHalfOpens.Length(); michael@0: for (uint32_t i = 0; i < halfOpenLength; i++) { michael@0: if (ent->mHalfOpens[i]->IsSpeculative()) { michael@0: // We've found a speculative connection in the half michael@0: // open list. Remove the speculative bit from it and that michael@0: // connection can later be used for this transaction michael@0: // (or another one in the pending queue) - we don't michael@0: // need to open a new connection here. michael@0: LOG(("nsHttpConnectionMgr::MakeNewConnection [ci = %s]\n" michael@0: "Found a speculative half open connection\n", michael@0: ent->mConnInfo->HashKey().get())); michael@0: ent->mHalfOpens[i]->SetSpeculative(false); michael@0: michael@0: // return OK because we have essentially opened a new connection michael@0: // by converting a speculative half-open to general use michael@0: return NS_OK; michael@0: } michael@0: } michael@0: michael@0: // If this host is trying to negotiate a SPDY session right now, michael@0: // don't create any new connections until the result of the michael@0: // negotiation is known. michael@0: if (!(trans->Caps() & NS_HTTP_DISALLOW_SPDY) && michael@0: (trans->Caps() & NS_HTTP_ALLOW_KEEPALIVE) && michael@0: RestrictConnections(ent)) { michael@0: LOG(("nsHttpConnectionMgr::MakeNewConnection [ci = %s] " michael@0: "Not Available Due to RestrictConnections()\n", michael@0: ent->mConnInfo->HashKey().get())); michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: // We need to make a new connection. If that is going to exceed the michael@0: // global connection limit then try and free up some room by closing michael@0: // an idle connection to another host. We know it won't select "ent" michael@0: // beacuse we have already determined there are no idle connections michael@0: // to our destination michael@0: michael@0: if ((mNumIdleConns + mNumActiveConns + 1 >= mMaxConns) && mNumIdleConns) michael@0: mCT.Enumerate(PurgeExcessIdleConnectionsCB, this); michael@0: michael@0: if ((mNumIdleConns + mNumActiveConns + 1 >= mMaxConns) && michael@0: mNumActiveConns && gHttpHandler->IsSpdyEnabled()) michael@0: mCT.Enumerate(PurgeExcessSpdyConnectionsCB, this); michael@0: michael@0: if (AtActiveConnectionLimit(ent, trans->Caps())) michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: #ifdef WTF_DEBUG michael@0: fprintf(stderr, "WTF: MakeNewConnection() is creating a transport (pipelines %d) for host %s\n", michael@0: ent->SupportsPipelining(), ent->mConnInfo->Host()); michael@0: #endif michael@0: nsresult rv = CreateTransport(ent, trans, trans->Caps(), false); michael@0: if (NS_FAILED(rv)) { michael@0: /* hard failure */ michael@0: LOG(("nsHttpConnectionMgr::MakeNewConnection [ci = %s trans = %p] " michael@0: "CreateTransport() hard failure.\n", michael@0: ent->mConnInfo->HashKey().get(), trans)); michael@0: trans->Close(rv); michael@0: if (rv == NS_ERROR_NOT_AVAILABLE) michael@0: rv = NS_ERROR_FAILURE; michael@0: return rv; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool michael@0: nsHttpConnectionMgr::AddToBestPipeline(nsConnectionEntry *ent, michael@0: nsHttpTransaction *trans, michael@0: nsHttpTransaction::Classifier classification, michael@0: uint16_t depthLimit) michael@0: { michael@0: if (classification == nsAHttpTransaction::CLASS_SOLO) michael@0: return false; michael@0: michael@0: uint32_t maxdepth = ent->MaxPipelineDepth(classification); michael@0: if (maxdepth == 0) { michael@0: ent->CreditPenalty(); michael@0: maxdepth = ent->MaxPipelineDepth(classification); michael@0: } michael@0: michael@0: if (ent->PipelineState() == PS_RED) michael@0: return false; michael@0: michael@0: if (ent->PipelineState() == PS_YELLOW && ent->mYellowConnection) michael@0: return false; michael@0: michael@0: // The maximum depth of a pipeline in yellow is 1 pipeline of michael@0: // depth 2 for entire CI. When that transaction completes successfully michael@0: // we transition to green and that expands the allowed depth michael@0: // to any number of pipelines of up to depth 4. When a transaction michael@0: // queued at position 3 or deeper succeeds we open it all the way michael@0: // up to depths limited only by configuration. The staggered start michael@0: // in green is simply because a successful yellow test of depth=2 michael@0: // might really just be a race condition (i.e. depth=1 from the michael@0: // server's point of view), while depth=3 is a stronger indicator - michael@0: // keeping the pipelines to a modest depth during that period limits michael@0: // the damage if something is going to go wrong. michael@0: michael@0: maxdepth = std::min(maxdepth, depthLimit); michael@0: michael@0: if (maxdepth < 2) michael@0: return false; michael@0: michael@0: // Find out how many requests of this class we have michael@0: uint32_t sameClass = 0; michael@0: uint32_t allClasses = ent->mPendingQ.Length(); michael@0: for (uint32_t i = 0; i < allClasses; ++i) { michael@0: if (trans != ent->mPendingQ[i] && michael@0: classification == ent->mPendingQ[i]->Classification()) { michael@0: sameClass++; michael@0: } michael@0: } michael@0: michael@0: nsAHttpTransaction *activeTrans; michael@0: nsHttpPipeline *pipeline; michael@0: nsHttpConnection *bestConn = nullptr; michael@0: uint32_t activeCount = ent->mActiveConns.Length(); michael@0: uint32_t pipelineDepth; michael@0: uint32_t requestLen; michael@0: uint32_t totalDepth = 0; michael@0: michael@0: // Now, try to find the best pipeline michael@0: nsTArray validConns; michael@0: nsTArray betterConns; michael@0: nsTArray bestConns; michael@0: uint32_t numPipelines = 0; michael@0: michael@0: for (uint32_t i = 0; i < activeCount; ++i) { michael@0: nsHttpConnection *conn = ent->mActiveConns[i]; michael@0: michael@0: if (!conn->SupportsPipelining()) michael@0: continue; michael@0: michael@0: activeTrans = conn->Transaction(); michael@0: michael@0: if (!activeTrans || michael@0: activeTrans->IsDone() || michael@0: NS_FAILED(activeTrans->Status())) michael@0: continue; michael@0: michael@0: pipeline = activeTrans->QueryPipeline(); michael@0: if (!pipeline) michael@0: continue; michael@0: michael@0: numPipelines++; michael@0: michael@0: pipelineDepth = activeTrans->PipelineDepth(); michael@0: requestLen = pipeline->RequestDepth(); michael@0: michael@0: totalDepth += pipelineDepth; michael@0: michael@0: // If we're within striking distance of our pipeline michael@0: // packaging goal, give a little slack on the depth michael@0: // limit to allow us to try to get there. Don't give michael@0: // too much slack, though, or we'll tend to have michael@0: // request packages of the same size when we have michael@0: // many content elements appear at once. michael@0: if (maxdepth + michael@0: PR_MIN(mMaxOptimisticPipelinedRequests, michael@0: requestLen + allClasses) michael@0: <= pipelineDepth) michael@0: continue; michael@0: michael@0: validConns.AppendElement(conn); michael@0: michael@0: // Prefer a pipeline that either has at least two requests michael@0: // queued already, or for which we can add multiple requests michael@0: if (requestLen + allClasses < mMaxOptimisticPipelinedRequests) michael@0: continue; michael@0: michael@0: betterConns.AppendElement(conn); michael@0: michael@0: // Prefer a pipeline with the same classification if michael@0: // our current classes will put it over the line michael@0: if (conn->Classification() != classification) michael@0: continue; michael@0: if (requestLen + sameClass < mMaxOptimisticPipelinedRequests) michael@0: continue; michael@0: michael@0: bestConns.AppendElement(conn); michael@0: } michael@0: michael@0: const char *type; michael@0: if (bestConns.Length()) { michael@0: type = "best"; michael@0: bestConn = bestConns[rand()%bestConns.Length()]; michael@0: } else if (betterConns.Length()) { michael@0: type = "better"; michael@0: bestConn = betterConns[rand()%betterConns.Length()]; michael@0: } else if (validConns.Length() && totalDepth == 0) { michael@0: // We only use valid conns if it's a last resort michael@0: // (No other requests are pending or in flight) michael@0: type = "valid"; michael@0: bestConn = validConns[rand()%validConns.Length()]; michael@0: } else { michael@0: return false; michael@0: } michael@0: michael@0: activeTrans = bestConn->Transaction(); michael@0: nsresult rv = activeTrans->AddTransaction(trans); michael@0: if (NS_FAILED(rv)) michael@0: return false; michael@0: michael@0: LOG((" scheduling trans %p on pipeline at position %d, type %s\n", michael@0: trans, trans->PipelinePosition(), type)); michael@0: michael@0: #ifdef WTF_DEBUG michael@0: pipeline = activeTrans->QueryPipeline(); michael@0: fprintf(stderr, michael@0: "WTF-depth: Added trans to %s of %d/%d/%d/%d pipelines. Request len %d/%d/%d for %s\n", michael@0: type, bestConns.Length(), betterConns.Length(), validConns.Length(), michael@0: numPipelines, pipeline->RequestDepth(), activeTrans->PipelineDepth(), michael@0: maxdepth, ent->mConnInfo->Host()); michael@0: #endif michael@0: michael@0: if ((ent->PipelineState() == PS_YELLOW) && (trans->PipelinePosition() > 1)) michael@0: ent->SetYellowConnection(bestConn); michael@0: michael@0: if (!trans->GetPendingTime().IsNull()) { michael@0: if (trans->UsesPipelining()) michael@0: AccumulateTimeDelta( michael@0: Telemetry::TRANSACTION_WAIT_TIME_HTTP_PIPELINES, michael@0: trans->GetPendingTime(), TimeStamp::Now()); michael@0: else michael@0: AccumulateTimeDelta( michael@0: Telemetry::TRANSACTION_WAIT_TIME_HTTP, michael@0: trans->GetPendingTime(), TimeStamp::Now()); michael@0: trans->SetPendingTime(false); michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: nsHttpConnectionMgr::IsUnderPressure(nsConnectionEntry *ent, michael@0: nsHttpTransaction::Classifier classification) michael@0: { michael@0: // A connection entry is declared to be "under pressure" if most of the michael@0: // allowed parallel connections are already used up. In that case we want to michael@0: // favor existing pipelines over more parallelism so as to reserve any michael@0: // unused parallel connections for types that don't have existing pipelines. michael@0: // michael@0: // The definition of connection pressure is a pretty liberal one here - that michael@0: // is why we are using the more restrictive maxPersist* counters. michael@0: // michael@0: // Pipelines are also favored when the requested classification is already michael@0: // using 3 or more of the connections. Failure to do this could result in michael@0: // one class (e.g. images) establishing self replenishing queues on all the michael@0: // connections that would starve the other transaction types. michael@0: michael@0: int32_t currentConns = ent->mActiveConns.Length(); michael@0: int32_t maxConns = michael@0: (ent->mConnInfo->UsingHttpProxy() && !ent->mConnInfo->UsingConnect()) ? michael@0: mMaxPersistConnsPerProxy : mMaxPersistConnsPerHost; michael@0: michael@0: // Leave room for at least 3 distinct types to operate concurrently, michael@0: // this satisfies the typical {html, js/css, img} page. michael@0: if (currentConns >= (maxConns - 2)) michael@0: return true; /* prefer pipeline */ michael@0: michael@0: int32_t sameClass = 0; michael@0: for (int32_t i = 0; i < currentConns; ++i) michael@0: if (classification == ent->mActiveConns[i]->Classification()) michael@0: if (++sameClass == 3) michael@0: return true; /* prefer pipeline */ michael@0: michael@0: return false; /* normal behavior */ michael@0: } michael@0: michael@0: // returns OK if a connection is found for the transaction michael@0: // and the transaction is started. michael@0: // returns ERROR_NOT_AVAILABLE if no connection can be found and it michael@0: // should be queued until circumstances change michael@0: // returns other ERROR when transaction has a hard failure and should michael@0: // not remain in the pending queue michael@0: nsresult michael@0: nsHttpConnectionMgr::TryDispatchTransaction(nsConnectionEntry *ent, michael@0: bool onlyReusedConnection, michael@0: nsHttpTransaction *trans) michael@0: { michael@0: MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); michael@0: LOG(("nsHttpConnectionMgr::TryDispatchTransaction without conn " michael@0: "[ci=%s caps=%x]\n", michael@0: ent->mConnInfo->HashKey().get(), uint32_t(trans->Caps()))); michael@0: michael@0: nsHttpTransaction::Classifier classification = trans->Classification(); michael@0: uint32_t caps = trans->Caps(); michael@0: michael@0: bool allowNewPipelines = true; michael@0: michael@0: // no keep-alive means no pipelines either michael@0: if (!(caps & NS_HTTP_ALLOW_KEEPALIVE)) michael@0: caps = caps & ~NS_HTTP_ALLOW_PIPELINING; michael@0: michael@0: nsRefPtr unusedSpdyPersistentConnection; michael@0: michael@0: // step 0 michael@0: // look for existing spdy connection - that's always best because it is michael@0: // essentially pipelining without head of line blocking michael@0: michael@0: if (!(caps & NS_HTTP_DISALLOW_SPDY) && gHttpHandler->IsSpdyEnabled()) { michael@0: nsRefPtr conn = GetSpdyPreferredConn(ent); michael@0: if (conn) { michael@0: if ((caps & NS_HTTP_ALLOW_KEEPALIVE) || !conn->IsExperienced()) { michael@0: LOG((" dispatch to spdy: [conn=%x]\n", conn.get())); michael@0: trans->RemoveDispatchedAsBlocking(); /* just in case */ michael@0: DispatchTransaction(ent, trans, conn); michael@0: return NS_OK; michael@0: } michael@0: unusedSpdyPersistentConnection = conn; michael@0: } michael@0: } michael@0: michael@0: // If this is not a blocking transaction and the loadgroup for it is michael@0: // currently processing one or more blocking transactions then we michael@0: // need to just leave it in the queue until those are complete unless it is michael@0: // explicitly marked as unblocked. michael@0: if (!(caps & NS_HTTP_LOAD_AS_BLOCKING)) { michael@0: if (!(caps & NS_HTTP_LOAD_UNBLOCKED)) { michael@0: nsILoadGroupConnectionInfo *loadGroupCI = trans->LoadGroupConnectionInfo(); michael@0: if (loadGroupCI) { michael@0: uint32_t blockers = 0; michael@0: if (NS_SUCCEEDED(loadGroupCI->GetBlockingTransactionCount(&blockers)) && michael@0: blockers) { michael@0: // need to wait for blockers to clear michael@0: LOG((" blocked by load group: [blockers=%d]\n", blockers)); michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: else { michael@0: // Mark the transaction and its load group as blocking right now to prevent michael@0: // other transactions from being reordered in the queue due to slow syns. michael@0: trans->DispatchedAsBlocking(); michael@0: } michael@0: michael@0: // step 1: Try a pipeline michael@0: if (caps & NS_HTTP_ALLOW_PIPELINING && michael@0: AddToBestPipeline(ent, trans, classification, michael@0: mMaxPipelinedRequests)) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: // XXX: Kill this block? It's new.. but it may be needed for SPDY michael@0: // Subject most transactions at high parallelism to rate pacing. michael@0: // It will only be actually submitted to the michael@0: // token bucket once, and if possible it is granted admission synchronously. michael@0: // It is important to leave a transaction in the pending queue when blocked by michael@0: // pacing so it can be found on cancel if necessary. michael@0: // Transactions that cause blocking or bypass it (e.g. js/css) are not rate michael@0: // limited. michael@0: if (gHttpHandler->UseRequestTokenBucket() && michael@0: (mNumActiveConns >= mNumSpdyActiveConns) && // just check for robustness sake michael@0: ((mNumActiveConns - mNumSpdyActiveConns) >= gHttpHandler->RequestTokenBucketMinParallelism()) && michael@0: !(caps & (NS_HTTP_LOAD_AS_BLOCKING | NS_HTTP_LOAD_UNBLOCKED))) { michael@0: if (!trans->TryToRunPacedRequest()) { michael@0: LOG((" blocked due to rate pacing\n")); michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: } michael@0: michael@0: // Step 2: Decide if we should forbid new pipeline creation. michael@0: // michael@0: // FIXME: We repurposed mMaxOptimisticPipelinedRequests here to mean: michael@0: // "Don't make a new pipeline until you have this many requests pending and michael@0: // no potential connections to put them on". It might be nice to give this michael@0: // its own pref.. michael@0: if (HasPipelines(ent) && michael@0: ent->mPendingQ.Length() < mMaxOptimisticPipelinedRequests && michael@0: trans->Classification() != nsAHttpTransaction::CLASS_SOLO && michael@0: caps & NS_HTTP_ALLOW_PIPELINING) michael@0: allowNewPipelines = false; michael@0: michael@0: // step 3: consider an idle persistent connection michael@0: if (allowNewPipelines && (caps & NS_HTTP_ALLOW_KEEPALIVE)) { michael@0: nsRefPtr conn; michael@0: while (!conn && (ent->mIdleConns.Length() > 0)) { michael@0: conn = ent->mIdleConns[0]; michael@0: ent->mIdleConns.RemoveElementAt(0); michael@0: mNumIdleConns--; michael@0: nsHttpConnection *temp = conn; michael@0: NS_RELEASE(temp); michael@0: michael@0: // we check if the connection can be reused before even checking if michael@0: // it is a "matching" connection. michael@0: if (!conn->CanReuse()) { michael@0: LOG((" dropping stale connection: [conn=%x]\n", conn.get())); michael@0: conn->Close(NS_ERROR_ABORT); michael@0: conn = nullptr; michael@0: } michael@0: else { michael@0: LOG((" reusing connection [conn=%x]\n", conn.get())); michael@0: conn->EndIdleMonitoring(); michael@0: } michael@0: michael@0: // If there are no idle connections left at all, we need to make michael@0: // sure that we are not pruning dead connections anymore. michael@0: ConditionallyStopPruneDeadConnectionsTimer(); michael@0: } michael@0: if (conn) { michael@0: // This will update the class of the connection to be the class of michael@0: // the transaction dispatched on it. michael@0: AddActiveConn(conn, ent); michael@0: DispatchTransaction(ent, trans, conn); michael@0: return NS_OK; michael@0: } michael@0: } michael@0: michael@0: // step 4: Maybe make a connection? michael@0: if (!onlyReusedConnection && allowNewPipelines) { michael@0: nsresult rv = MakeNewConnection(ent, trans); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: // this function returns NOT_AVAILABLE for asynchronous connects michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: if (rv != NS_ERROR_NOT_AVAILABLE) { michael@0: // not available return codes should try next step as they are michael@0: // not hard errors. Other codes should stop now michael@0: return rv; michael@0: } michael@0: } michael@0: michael@0: // step 5 michael@0: if (unusedSpdyPersistentConnection) { michael@0: // to avoid deadlocks, we need to throw away this perfectly valid SPDY michael@0: // connection to make room for a new one that can service a no KEEPALIVE michael@0: // request michael@0: unusedSpdyPersistentConnection->DontReuse(); michael@0: } michael@0: michael@0: // XXX: We dequeue and queue the same url here sometimes.. michael@0: #ifdef WTF_DEBUG michael@0: nsHttpRequestHead *head = trans->RequestHead(); michael@0: fprintf(stderr, "WTF: Queuing url %s%s\n", michael@0: ent->mConnInfo->Host(), michael@0: head ? head->RequestURI().BeginReading() : ""); michael@0: #endif michael@0: michael@0: michael@0: return NS_ERROR_NOT_AVAILABLE; /* queue it */ michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpConnectionMgr::DispatchTransaction(nsConnectionEntry *ent, michael@0: nsHttpTransaction *trans, michael@0: nsHttpConnection *conn) michael@0: { michael@0: uint32_t caps = trans->Caps(); michael@0: int32_t priority = trans->Priority(); michael@0: nsresult rv; michael@0: michael@0: LOG(("nsHttpConnectionMgr::DispatchTransaction " michael@0: "[ci=%s trans=%x caps=%x conn=%x priority=%d]\n", michael@0: ent->mConnInfo->HashKey().get(), trans, caps, conn, priority)); michael@0: michael@0: // It is possible for a rate-paced transaction to be dispatched independent michael@0: // of the token bucket when the amount of parallelization has changed or michael@0: // when a muxed connection (e.g. spdy or pipelines) becomes available. michael@0: trans->CancelPacing(NS_OK); michael@0: michael@0: if (conn->UsingSpdy()) { michael@0: LOG(("Spdy Dispatch Transaction via Activate(). Transaction host = %s," michael@0: "Connection host = %s\n", michael@0: trans->ConnectionInfo()->Host(), michael@0: conn->ConnectionInfo()->Host())); michael@0: rv = conn->Activate(trans, caps, priority); michael@0: MOZ_ASSERT(NS_SUCCEEDED(rv), "SPDY Cannot Fail Dispatch"); michael@0: if (NS_SUCCEEDED(rv) && !trans->GetPendingTime().IsNull()) { michael@0: AccumulateTimeDelta(Telemetry::TRANSACTION_WAIT_TIME_SPDY, michael@0: trans->GetPendingTime(), TimeStamp::Now()); michael@0: trans->SetPendingTime(false); michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: MOZ_ASSERT(conn && !conn->Transaction(), michael@0: "DispatchTranaction() on non spdy active connection"); michael@0: michael@0: if (!(caps & NS_HTTP_ALLOW_PIPELINING)) michael@0: conn->Classify(nsAHttpTransaction::CLASS_SOLO); michael@0: else michael@0: conn->Classify(trans->Classification()); michael@0: michael@0: rv = DispatchAbstractTransaction(ent, trans, caps, conn, priority); michael@0: michael@0: if (NS_SUCCEEDED(rv) && !trans->GetPendingTime().IsNull()) { michael@0: if (trans->UsesPipelining()) michael@0: AccumulateTimeDelta(Telemetry::TRANSACTION_WAIT_TIME_HTTP_PIPELINES, michael@0: trans->GetPendingTime(), TimeStamp::Now()); michael@0: else michael@0: AccumulateTimeDelta(Telemetry::TRANSACTION_WAIT_TIME_HTTP, michael@0: trans->GetPendingTime(), TimeStamp::Now()); michael@0: trans->SetPendingTime(false); michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: michael@0: // Use this method for dispatching nsAHttpTransction's. It can only safely be michael@0: // used upon first use of a connection when NPN has not negotiated SPDY vs michael@0: // HTTP/1 yet as multiplexing onto an existing SPDY session requires a michael@0: // concrete nsHttpTransaction michael@0: nsresult michael@0: nsHttpConnectionMgr::DispatchAbstractTransaction(nsConnectionEntry *ent, michael@0: nsAHttpTransaction *aTrans, michael@0: uint32_t caps, michael@0: nsHttpConnection *conn, michael@0: int32_t priority) michael@0: { michael@0: MOZ_ASSERT(!conn->UsingSpdy(), michael@0: "Spdy Must Not Use DispatchAbstractTransaction"); michael@0: LOG(("nsHttpConnectionMgr::DispatchAbstractTransaction " michael@0: "[ci=%s trans=%x caps=%x conn=%x]\n", michael@0: ent->mConnInfo->HashKey().get(), aTrans, caps, conn)); michael@0: michael@0: /* Use pipeline datastructure even if connection does not currently qualify michael@0: to pipeline this transaction because a different pipeline-eligible michael@0: transaction might be placed on the active connection. Make an exception michael@0: for CLASS_SOLO as that connection will never pipeline until it goes michael@0: quiescent */ michael@0: michael@0: nsRefPtr transaction; michael@0: nsresult rv; michael@0: if (conn->Classification() != nsAHttpTransaction::CLASS_SOLO) { michael@0: LOG((" using pipeline datastructure.\n")); michael@0: nsRefPtr pipeline; michael@0: rv = BuildPipeline(ent, aTrans, getter_AddRefs(pipeline)); michael@0: if (!NS_SUCCEEDED(rv)) michael@0: return rv; michael@0: transaction = pipeline; michael@0: #ifdef WTF_DEBUG michael@0: if (HasPipelines(ent) && michael@0: ent->mPendingQ.Length()+1 < mMaxOptimisticPipelinedRequests) { michael@0: fprintf(stderr, "WTF-new-bug: New pipeline created from %d idle conns for host %s with %d/%d pending\n", michael@0: ent->mIdleConns.Length(), ent->mConnInfo->Host(), ent->mPendingQ.Length(), michael@0: mMaxOptimisticPipelinedRequests); michael@0: } else { michael@0: fprintf(stderr, "WTF-new: New pipeline created from %d idle conns for host %s with %d/%d pending\n", michael@0: ent->mIdleConns.Length(), ent->mConnInfo->Host(), ent->mPendingQ.Length(), michael@0: mMaxOptimisticPipelinedRequests); michael@0: } michael@0: #endif michael@0: } michael@0: else { michael@0: LOG((" not using pipeline datastructure due to class solo.\n")); michael@0: transaction = aTrans; michael@0: #ifdef WTF_TEST michael@0: nsHttpRequestHead *head = transaction->RequestHead(); michael@0: fprintf(stderr, "WTF-order: Pipeline forbidden for url %s%s\n", michael@0: ent->mConnInfo->Host(), michael@0: head ? head->RequestURI().BeginReading() : ""); michael@0: #endif michael@0: } michael@0: michael@0: nsRefPtr handle = new nsConnectionHandle(conn); michael@0: michael@0: // give the transaction the indirect reference to the connection. michael@0: transaction->SetConnection(handle); michael@0: michael@0: rv = conn->Activate(transaction, caps, priority); michael@0: if (NS_FAILED(rv)) { michael@0: LOG((" conn->Activate failed [rv=%x]\n", rv)); michael@0: ent->mActiveConns.RemoveElement(conn); michael@0: if (conn == ent->mYellowConnection) michael@0: ent->OnYellowComplete(); michael@0: DecrementActiveConnCount(conn); michael@0: ConditionallyStopTimeoutTick(); michael@0: michael@0: // sever back references to connection, and do so without triggering michael@0: // a call to ReclaimConnection ;-) michael@0: transaction->SetConnection(nullptr); michael@0: NS_RELEASE(handle->mConn); michael@0: // destroy the connection michael@0: NS_RELEASE(conn); michael@0: } michael@0: michael@0: // As transaction goes out of scope it will drop the last refernece to the michael@0: // pipeline if activation failed, in which case this will destroy michael@0: // the pipeline, which will cause each the transactions owned by the michael@0: // pipeline to be restarted. michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpConnectionMgr::BuildPipeline(nsConnectionEntry *ent, michael@0: nsAHttpTransaction *firstTrans, michael@0: nsHttpPipeline **result) michael@0: { michael@0: MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); michael@0: michael@0: /* form a pipeline here even if nothing is pending so that we michael@0: can stream-feed it as new transactions arrive */ michael@0: michael@0: /* the first transaction can go in unconditionally - 1 transaction michael@0: on a nsHttpPipeline object is not a real HTTP pipeline */ michael@0: michael@0: nsRefPtr pipeline = new nsHttpPipeline(); michael@0: pipeline->AddTransaction(firstTrans); michael@0: NS_ADDREF(*result = pipeline); michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsHttpConnectionMgr::ReportProxyTelemetry(nsConnectionEntry *ent) michael@0: { michael@0: enum { PROXY_NONE = 1, PROXY_HTTP = 2, PROXY_SOCKS = 3 }; michael@0: michael@0: if (!ent->mConnInfo->UsingProxy()) michael@0: Telemetry::Accumulate(Telemetry::HTTP_PROXY_TYPE, PROXY_NONE); michael@0: else if (ent->mConnInfo->UsingHttpProxy()) michael@0: Telemetry::Accumulate(Telemetry::HTTP_PROXY_TYPE, PROXY_HTTP); michael@0: else michael@0: Telemetry::Accumulate(Telemetry::HTTP_PROXY_TYPE, PROXY_SOCKS); michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpConnectionMgr::ProcessNewTransaction(nsHttpTransaction *trans) michael@0: { michael@0: MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); michael@0: michael@0: // since "adds" and "cancels" are processed asynchronously and because michael@0: // various events might trigger an "add" directly on the socket thread, michael@0: // we must take care to avoid dispatching a transaction that has already michael@0: // been canceled (see bug 190001). michael@0: if (NS_FAILED(trans->Status())) { michael@0: LOG((" transaction was canceled... dropping event!\n")); michael@0: return NS_OK; michael@0: } michael@0: michael@0: trans->SetPendingTime(); michael@0: michael@0: nsresult rv = NS_OK; michael@0: nsHttpConnectionInfo *ci = trans->ConnectionInfo(); michael@0: MOZ_ASSERT(ci); michael@0: michael@0: nsConnectionEntry *ent = GetOrCreateConnectionEntry(ci); michael@0: michael@0: // SPDY coalescing of hostnames means we might redirect from this michael@0: // connection entry onto the preferred one. michael@0: nsConnectionEntry *preferredEntry = GetSpdyPreferredEnt(ent); michael@0: if (preferredEntry && (preferredEntry != ent)) { michael@0: LOG(("nsHttpConnectionMgr::ProcessNewTransaction trans=%p " michael@0: "redirected via coalescing from %s to %s\n", trans, michael@0: ent->mConnInfo->Host(), preferredEntry->mConnInfo->Host())); michael@0: michael@0: ent = preferredEntry; michael@0: } michael@0: michael@0: ReportProxyTelemetry(ent); michael@0: michael@0: // Check if the transaction already has a sticky reference to a connection. michael@0: // If so, then we can just use it directly by transferring its reference michael@0: // to the new connection variable instead of searching for a new one michael@0: michael@0: nsAHttpConnection *wrappedConnection = trans->Connection(); michael@0: nsRefPtr conn; michael@0: if (wrappedConnection) michael@0: conn = dont_AddRef(wrappedConnection->TakeHttpConnection()); michael@0: michael@0: if (conn) { michael@0: MOZ_ASSERT(trans->Caps() & NS_HTTP_STICKY_CONNECTION); michael@0: MOZ_ASSERT(((int32_t)ent->mActiveConns.IndexOf(conn)) != -1, michael@0: "Sticky Connection Not In Active List"); michael@0: LOG(("nsHttpConnectionMgr::ProcessNewTransaction trans=%p " michael@0: "sticky connection=%p\n", trans, conn.get())); michael@0: trans->SetConnection(nullptr); michael@0: #ifdef WTF_TEST michael@0: fprintf(stderr, "WTF-bad: Sticky connection status on 1 transaction to host %s\n", michael@0: ent->mConnInfo->Host()); michael@0: #endif michael@0: rv = DispatchTransaction(ent, trans, conn); michael@0: return rv; michael@0: } else { michael@0: // XXX: maybe check the queue first and directly call TryDispatch? michael@0: InsertTransactionSorted(ent->mPendingQ, trans); michael@0: NS_ADDREF(trans); michael@0: ProcessPendingQForEntry(ent, true); michael@0: return NS_OK; michael@0: } michael@0: } michael@0: michael@0: michael@0: void michael@0: nsHttpConnectionMgr::AddActiveConn(nsHttpConnection *conn, michael@0: nsConnectionEntry *ent) michael@0: { michael@0: NS_ADDREF(conn); michael@0: ent->mActiveConns.AppendElement(conn); michael@0: mNumActiveConns++; michael@0: ActivateTimeoutTick(); michael@0: } michael@0: michael@0: void michael@0: nsHttpConnectionMgr::DecrementActiveConnCount(nsHttpConnection *conn) michael@0: { michael@0: mNumActiveConns--; michael@0: if (conn->EverUsedSpdy()) michael@0: mNumSpdyActiveConns--; michael@0: } michael@0: michael@0: void michael@0: nsHttpConnectionMgr::StartedConnect() michael@0: { michael@0: mNumActiveConns++; michael@0: ActivateTimeoutTick(); // likely disabled by RecvdConnect() michael@0: } michael@0: michael@0: void michael@0: nsHttpConnectionMgr::RecvdConnect() michael@0: { michael@0: mNumActiveConns--; michael@0: ConditionallyStopTimeoutTick(); michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpConnectionMgr::CreateTransport(nsConnectionEntry *ent, michael@0: nsAHttpTransaction *trans, michael@0: uint32_t caps, michael@0: bool speculative) michael@0: { michael@0: MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); michael@0: michael@0: nsRefPtr sock = new nsHalfOpenSocket(ent, trans, caps); michael@0: if (speculative) michael@0: sock->SetSpeculative(true); michael@0: nsresult rv = sock->SetupPrimaryStreams(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: ent->mHalfOpens.AppendElement(sock); michael@0: mNumHalfOpenConns++; michael@0: return NS_OK; michael@0: } michael@0: michael@0: // This function tries to dispatch the pending spdy transactions on michael@0: // the connection entry sent in as an argument. It will do so on the michael@0: // active spdy connection either in that same entry or in the michael@0: // redirected 'preferred' entry for the same coalescing hash key if michael@0: // coalescing is enabled. michael@0: michael@0: void michael@0: nsHttpConnectionMgr::ProcessSpdyPendingQ(nsConnectionEntry *ent) michael@0: { michael@0: nsHttpConnection *conn = GetSpdyPreferredConn(ent); michael@0: if (!conn || !conn->CanDirectlyActivate()) michael@0: return; michael@0: michael@0: nsTArray leftovers; michael@0: uint32_t index; michael@0: michael@0: // Dispatch all the transactions we can michael@0: for (index = 0; michael@0: index < ent->mPendingQ.Length() && conn->CanDirectlyActivate(); michael@0: ++index) { michael@0: nsHttpTransaction *trans = ent->mPendingQ[index]; michael@0: michael@0: if (!(trans->Caps() & NS_HTTP_ALLOW_KEEPALIVE) || michael@0: trans->Caps() & NS_HTTP_DISALLOW_SPDY) { michael@0: leftovers.AppendElement(trans); michael@0: continue; michael@0: } michael@0: michael@0: nsresult rv = DispatchTransaction(ent, trans, conn); michael@0: if (NS_FAILED(rv)) { michael@0: // this cannot happen, but if due to some bug it does then michael@0: // close the transaction michael@0: MOZ_ASSERT(false, "Dispatch SPDY Transaction"); michael@0: LOG(("ProcessSpdyPendingQ Dispatch Transaction failed trans=%p\n", michael@0: trans)); michael@0: trans->Close(rv); michael@0: } michael@0: NS_RELEASE(trans); michael@0: } michael@0: michael@0: // Slurp up the rest of the pending queue into our leftovers bucket (we michael@0: // might have some left if conn->CanDirectlyActivate returned false) michael@0: for (; index < ent->mPendingQ.Length(); ++index) { michael@0: nsHttpTransaction *trans = ent->mPendingQ[index]; michael@0: leftovers.AppendElement(trans); michael@0: } michael@0: michael@0: // Put the leftovers back in the pending queue and get rid of the michael@0: // transactions we dispatched michael@0: leftovers.SwapElements(ent->mPendingQ); michael@0: leftovers.Clear(); michael@0: } michael@0: michael@0: PLDHashOperator michael@0: nsHttpConnectionMgr::ProcessSpdyPendingQCB(const nsACString &key, michael@0: nsAutoPtr &ent, michael@0: void *closure) michael@0: { michael@0: nsHttpConnectionMgr *self = (nsHttpConnectionMgr *) closure; michael@0: self->ProcessSpdyPendingQ(ent); michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: void michael@0: nsHttpConnectionMgr::OnMsgProcessAllSpdyPendingQ(int32_t, void *) michael@0: { michael@0: MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); michael@0: LOG(("nsHttpConnectionMgr::OnMsgProcessAllSpdyPendingQ\n")); michael@0: mCT.Enumerate(ProcessSpdyPendingQCB, this); michael@0: } michael@0: michael@0: nsHttpConnection * michael@0: nsHttpConnectionMgr::GetSpdyPreferredConn(nsConnectionEntry *ent) michael@0: { michael@0: MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); michael@0: MOZ_ASSERT(ent); michael@0: michael@0: nsConnectionEntry *preferred = GetSpdyPreferredEnt(ent); michael@0: michael@0: // this entry is spdy-enabled if it is involved in a redirect michael@0: if (preferred) michael@0: // all new connections for this entry will use spdy too michael@0: ent->mUsingSpdy = true; michael@0: else michael@0: preferred = ent; michael@0: michael@0: nsHttpConnection *conn = nullptr; michael@0: michael@0: if (preferred->mUsingSpdy) { michael@0: for (uint32_t index = 0; michael@0: index < preferred->mActiveConns.Length(); michael@0: ++index) { michael@0: if (preferred->mActiveConns[index]->CanDirectlyActivate()) { michael@0: conn = preferred->mActiveConns[index]; michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: michael@0: return conn; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: void michael@0: nsHttpConnectionMgr::OnMsgShutdown(int32_t, void *param) michael@0: { michael@0: MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); michael@0: LOG(("nsHttpConnectionMgr::OnMsgShutdown\n")); michael@0: michael@0: mCT.Enumerate(ShutdownPassCB, this); michael@0: michael@0: if (mTimeoutTick) { michael@0: mTimeoutTick->Cancel(); michael@0: mTimeoutTick = nullptr; michael@0: mTimeoutTickArmed = false; michael@0: } michael@0: if (mTimer) { michael@0: mTimer->Cancel(); michael@0: mTimer = nullptr; michael@0: } michael@0: michael@0: // signal shutdown complete michael@0: nsRefPtr runnable = michael@0: new nsConnEvent(this, &nsHttpConnectionMgr::OnMsgShutdownConfirm, michael@0: 0, param); michael@0: NS_DispatchToMainThread(runnable); michael@0: } michael@0: michael@0: void michael@0: nsHttpConnectionMgr::OnMsgShutdownConfirm(int32_t priority, void *param) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: LOG(("nsHttpConnectionMgr::OnMsgShutdownConfirm\n")); michael@0: michael@0: bool *shutdown = static_cast(param); michael@0: *shutdown = true; michael@0: } michael@0: michael@0: void michael@0: nsHttpConnectionMgr::OnMsgNewTransaction(int32_t priority, void *param) michael@0: { michael@0: LOG(("nsHttpConnectionMgr::OnMsgNewTransaction [trans=%p]\n", param)); michael@0: michael@0: nsHttpTransaction *trans = (nsHttpTransaction *) param; michael@0: trans->SetPriority(priority); michael@0: nsresult rv = ProcessNewTransaction(trans); michael@0: if (NS_FAILED(rv)) michael@0: trans->Close(rv); // for whatever its worth michael@0: NS_RELEASE(trans); michael@0: } michael@0: michael@0: void michael@0: nsHttpConnectionMgr::OnMsgReschedTransaction(int32_t priority, void *param) michael@0: { michael@0: MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); michael@0: LOG(("nsHttpConnectionMgr::OnMsgReschedTransaction [trans=%p]\n", param)); michael@0: michael@0: nsHttpTransaction *trans = (nsHttpTransaction *) param; michael@0: trans->SetPriority(priority); michael@0: michael@0: nsConnectionEntry *ent = LookupConnectionEntry(trans->ConnectionInfo(), michael@0: nullptr, trans); michael@0: michael@0: if (ent) { michael@0: int32_t index = ent->mPendingQ.IndexOf(trans); michael@0: if (index >= 0) { michael@0: ent->mPendingQ.RemoveElementAt(index); michael@0: InsertTransactionSorted(ent->mPendingQ, trans); michael@0: } michael@0: } michael@0: michael@0: NS_RELEASE(trans); michael@0: } michael@0: michael@0: void michael@0: nsHttpConnectionMgr::OnMsgCancelTransaction(int32_t reason, void *param) michael@0: { michael@0: MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); michael@0: LOG(("nsHttpConnectionMgr::OnMsgCancelTransaction [trans=%p]\n", param)); michael@0: michael@0: nsresult closeCode = static_cast(reason); michael@0: nsHttpTransaction *trans = (nsHttpTransaction *) param; michael@0: // michael@0: // if the transaction owns a connection and the transaction is not done, michael@0: // then ask the connection to close the transaction. otherwise, close the michael@0: // transaction directly (removing it from the pending queue first). michael@0: // michael@0: nsAHttpConnection *conn = trans->Connection(); michael@0: if (conn && !trans->IsDone()) { michael@0: conn->CloseTransaction(trans, closeCode); michael@0: } else { michael@0: nsConnectionEntry *ent = michael@0: LookupConnectionEntry(trans->ConnectionInfo(), nullptr, trans); michael@0: michael@0: if (ent) { michael@0: int32_t index = ent->mPendingQ.IndexOf(trans); michael@0: if (index >= 0) { michael@0: LOG(("nsHttpConnectionMgr::OnMsgCancelTransaction [trans=%p]" michael@0: " found in pending queue\n", trans)); michael@0: ent->mPendingQ.RemoveElementAt(index); michael@0: nsHttpTransaction *temp = trans; michael@0: NS_RELEASE(temp); // b/c NS_RELEASE nulls its argument! michael@0: } michael@0: } michael@0: trans->Close(closeCode); michael@0: michael@0: // Cancel is a pretty strong signal that things might be hanging michael@0: // so we want to cancel any null transactions related to this connection michael@0: // entry. They are just optimizations, but they aren't hooked up to michael@0: // anything that might get canceled from the rest of gecko, so best michael@0: // to assume that's what was meant by the cancel we did receive if michael@0: // it only applied to something in the queue. michael@0: for (uint32_t index = 0; michael@0: ent && (index < ent->mActiveConns.Length()); michael@0: ++index) { michael@0: nsHttpConnection *activeConn = ent->mActiveConns[index]; michael@0: nsAHttpTransaction *liveTransaction = activeConn->Transaction(); michael@0: if (liveTransaction && liveTransaction->IsNullTransaction()) { michael@0: LOG(("nsHttpConnectionMgr::OnMsgCancelTransaction [trans=%p] " michael@0: "also canceling Null Transaction %p on conn %p\n", michael@0: trans, liveTransaction, activeConn)); michael@0: activeConn->CloseTransaction(liveTransaction, closeCode); michael@0: } michael@0: } michael@0: } michael@0: NS_RELEASE(trans); michael@0: } michael@0: michael@0: void michael@0: nsHttpConnectionMgr::OnMsgProcessPendingQ(int32_t, void *param) michael@0: { michael@0: MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); michael@0: nsHttpConnectionInfo *ci = (nsHttpConnectionInfo *) param; michael@0: michael@0: if (!ci) { michael@0: LOG(("nsHttpConnectionMgr::OnMsgProcessPendingQ [ci=nullptr]\n")); michael@0: // Try and dispatch everything michael@0: mCT.Enumerate(ProcessAllTransactionsCB, this); michael@0: return; michael@0: } michael@0: michael@0: LOG(("nsHttpConnectionMgr::OnMsgProcessPendingQ [ci=%s]\n", michael@0: ci->HashKey().get())); michael@0: michael@0: // start by processing the queue identified by the given connection info. michael@0: nsConnectionEntry *ent = mCT.Get(ci->HashKey()); michael@0: if (!(ent && ProcessPendingQForEntry(ent, false))) { michael@0: // if we reach here, it means that we couldn't dispatch a transaction michael@0: // for the specified connection info. walk the connection table... michael@0: mCT.Enumerate(ProcessOneTransactionCB, this); michael@0: } michael@0: michael@0: NS_RELEASE(ci); michael@0: } michael@0: michael@0: void michael@0: nsHttpConnectionMgr::OnMsgPruneDeadConnections(int32_t, void *) michael@0: { michael@0: MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); michael@0: LOG(("nsHttpConnectionMgr::OnMsgPruneDeadConnections\n")); michael@0: michael@0: // Reset mTimeOfNextWakeUp so that we can find a new shortest value. michael@0: mTimeOfNextWakeUp = UINT64_MAX; michael@0: michael@0: // check canreuse() for all idle connections plus any active connections on michael@0: // connection entries that are using spdy. michael@0: if (mNumIdleConns || (mNumActiveConns && gHttpHandler->IsSpdyEnabled())) michael@0: mCT.Enumerate(PruneDeadConnectionsCB, this); michael@0: } michael@0: michael@0: void michael@0: nsHttpConnectionMgr::OnMsgDoShiftReloadConnectionCleanup(int32_t, void *param) michael@0: { michael@0: LOG(("nsHttpConnectionMgr::OnMsgDoShiftReloadConnectionCleanup\n")); michael@0: MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); michael@0: michael@0: nsRefPtr ci = michael@0: dont_AddRef(static_cast(param)); michael@0: michael@0: mCT.Enumerate(ClosePersistentConnectionsCB, this); michael@0: if (ci) michael@0: ResetIPFamilyPreference(ci); michael@0: } michael@0: michael@0: void michael@0: nsHttpConnectionMgr::OnMsgReclaimConnection(int32_t, void *param) michael@0: { michael@0: MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); michael@0: LOG(("nsHttpConnectionMgr::OnMsgReclaimConnection [conn=%p]\n", param)); michael@0: michael@0: nsHttpConnection *conn = (nsHttpConnection *) param; michael@0: michael@0: // michael@0: // 1) remove the connection from the active list michael@0: // 2) if keep-alive, add connection to idle list michael@0: // 3) post event to process the pending transaction queue michael@0: // michael@0: michael@0: nsConnectionEntry *ent = LookupConnectionEntry(conn->ConnectionInfo(), michael@0: conn, nullptr); michael@0: nsHttpConnectionInfo *ci = nullptr; michael@0: michael@0: if (!ent) { michael@0: // this should never happen michael@0: LOG(("nsHttpConnectionMgr::OnMsgReclaimConnection ent == null\n")); michael@0: MOZ_ASSERT(false, "no connection entry"); michael@0: NS_ADDREF(ci = conn->ConnectionInfo()); michael@0: } michael@0: else { michael@0: NS_ADDREF(ci = ent->mConnInfo); michael@0: michael@0: // If the connection is in the active list, remove that entry michael@0: // and the reference held by the mActiveConns list. michael@0: // This is never the final reference on conn as the event context michael@0: // is also holding one that is released at the end of this function. michael@0: michael@0: if (ent->mUsingSpdy) { michael@0: // Spdy connections aren't reused in the traditional HTTP way in michael@0: // the idleconns list, they are actively multplexed as active michael@0: // conns. Even when they have 0 transactions on them they are michael@0: // considered active connections. So when one is reclaimed it michael@0: // is really complete and is meant to be shut down and not michael@0: // reused. michael@0: conn->DontReuse(); michael@0: } michael@0: michael@0: if (ent->mActiveConns.RemoveElement(conn)) { michael@0: if (conn == ent->mYellowConnection) michael@0: ent->OnYellowComplete(); michael@0: nsHttpConnection *temp = conn; michael@0: NS_RELEASE(temp); michael@0: DecrementActiveConnCount(conn); michael@0: ConditionallyStopTimeoutTick(); michael@0: } michael@0: michael@0: if (conn->CanReuse()) { michael@0: LOG((" adding connection to idle list\n")); michael@0: // Keep The idle connection list sorted with the connections that michael@0: // have moved the largest data pipelines at the front because these michael@0: // connections have the largest cwnds on the server. michael@0: michael@0: // The linear search is ok here because the number of idleconns michael@0: // in a single entry is generally limited to a small number (i.e. 6) michael@0: michael@0: uint32_t idx; michael@0: for (idx = 0; idx < ent->mIdleConns.Length(); idx++) { michael@0: nsHttpConnection *idleConn = ent->mIdleConns[idx]; michael@0: if (idleConn->MaxBytesRead() < conn->MaxBytesRead()) michael@0: break; michael@0: } michael@0: michael@0: NS_ADDREF(conn); michael@0: ent->mIdleConns.InsertElementAt(idx, conn); michael@0: mNumIdleConns++; michael@0: conn->BeginIdleMonitoring(); michael@0: michael@0: // If the added connection was first idle connection or has shortest michael@0: // time to live among the watched connections, pruning dead michael@0: // connections needs to be done when it can't be reused anymore. michael@0: uint32_t timeToLive = conn->TimeToLive(); michael@0: if(!mTimer || NowInSeconds() + timeToLive < mTimeOfNextWakeUp) michael@0: PruneDeadConnectionsAfter(timeToLive); michael@0: } michael@0: else { michael@0: LOG((" connection cannot be reused; closing connection\n")); michael@0: conn->Close(NS_ERROR_ABORT); michael@0: } michael@0: } michael@0: michael@0: OnMsgProcessPendingQ(0, ci); // releases |ci| michael@0: NS_RELEASE(conn); michael@0: } michael@0: michael@0: void michael@0: nsHttpConnectionMgr::OnMsgCompleteUpgrade(int32_t, void *param) michael@0: { michael@0: MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); michael@0: nsCompleteUpgradeData *data = (nsCompleteUpgradeData *) param; michael@0: LOG(("nsHttpConnectionMgr::OnMsgCompleteUpgrade " michael@0: "this=%p conn=%p listener=%p\n", this, data->mConn.get(), michael@0: data->mUpgradeListener.get())); michael@0: michael@0: nsCOMPtr socketTransport; michael@0: nsCOMPtr socketIn; michael@0: nsCOMPtr socketOut; michael@0: michael@0: nsresult rv; michael@0: rv = data->mConn->TakeTransport(getter_AddRefs(socketTransport), michael@0: getter_AddRefs(socketIn), michael@0: getter_AddRefs(socketOut)); michael@0: michael@0: if (NS_SUCCEEDED(rv)) michael@0: data->mUpgradeListener->OnTransportAvailable(socketTransport, michael@0: socketIn, michael@0: socketOut); michael@0: delete data; michael@0: } michael@0: michael@0: void michael@0: nsHttpConnectionMgr::OnMsgUpdateParam(int32_t, void *param) michael@0: { michael@0: uint16_t name = (NS_PTR_TO_INT32(param) & 0xFFFF0000) >> 16; michael@0: uint16_t value = NS_PTR_TO_INT32(param) & 0x0000FFFF; michael@0: michael@0: switch (name) { michael@0: case MAX_CONNECTIONS: michael@0: mMaxConns = value; michael@0: break; michael@0: case MAX_PERSISTENT_CONNECTIONS_PER_HOST: michael@0: mMaxPersistConnsPerHost = value; michael@0: break; michael@0: case MAX_PERSISTENT_CONNECTIONS_PER_PROXY: michael@0: mMaxPersistConnsPerProxy = value; michael@0: break; michael@0: case MAX_REQUEST_DELAY: michael@0: mMaxRequestDelay = value; michael@0: break; michael@0: case MAX_PIPELINED_REQUESTS: michael@0: mMaxPipelinedRequests = value; michael@0: break; michael@0: case MAX_OPTIMISTIC_PIPELINED_REQUESTS: michael@0: mMaxOptimisticPipelinedRequests = value; michael@0: break; michael@0: default: michael@0: NS_NOTREACHED("unexpected parameter name"); michael@0: } michael@0: } michael@0: michael@0: // nsHttpConnectionMgr::nsConnectionEntry michael@0: nsHttpConnectionMgr::nsConnectionEntry::~nsConnectionEntry() michael@0: { michael@0: if (mSpdyPreferred) michael@0: gHttpHandler->ConnMgr()->RemoveSpdyPreferredEnt(mCoalescingKey); michael@0: michael@0: NS_RELEASE(mConnInfo); michael@0: } michael@0: michael@0: void michael@0: nsHttpConnectionMgr::OnMsgProcessFeedback(int32_t, void *param) michael@0: { michael@0: MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); michael@0: nsHttpPipelineFeedback *fb = (nsHttpPipelineFeedback *)param; michael@0: michael@0: PipelineFeedbackInfo(fb->mConnInfo, fb->mInfo, fb->mConn, fb->mData); michael@0: delete fb; michael@0: } michael@0: michael@0: // Read Timeout Tick handlers michael@0: michael@0: void michael@0: nsHttpConnectionMgr::ActivateTimeoutTick() michael@0: { michael@0: MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); michael@0: LOG(("nsHttpConnectionMgr::ActivateTimeoutTick() " michael@0: "this=%p mTimeoutTick=%p\n")); michael@0: michael@0: // The timer tick should be enabled if it is not already pending. michael@0: // Upon running the tick will rearm itself if there are active michael@0: // connections available. michael@0: michael@0: if (mTimeoutTick && mTimeoutTickArmed) { michael@0: // make sure we get one iteration on a quick tick michael@0: if (mTimeoutTickNext > 1) { michael@0: mTimeoutTickNext = 1; michael@0: mTimeoutTick->SetDelay(1000); michael@0: } michael@0: return; michael@0: } michael@0: michael@0: if (!mTimeoutTick) { michael@0: mTimeoutTick = do_CreateInstance(NS_TIMER_CONTRACTID); michael@0: if (!mTimeoutTick) { michael@0: NS_WARNING("failed to create timer for http timeout management"); michael@0: return; michael@0: } michael@0: mTimeoutTick->SetTarget(mSocketThreadTarget); michael@0: } michael@0: michael@0: MOZ_ASSERT(!mTimeoutTickArmed, "timer tick armed"); michael@0: mTimeoutTickArmed = true; michael@0: mTimeoutTick->Init(this, 1000, nsITimer::TYPE_REPEATING_SLACK); michael@0: } michael@0: michael@0: void michael@0: nsHttpConnectionMgr::TimeoutTick() michael@0: { michael@0: MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); michael@0: MOZ_ASSERT(mTimeoutTick, "no readtimeout tick"); michael@0: michael@0: LOG(("nsHttpConnectionMgr::TimeoutTick active=%d\n", mNumActiveConns)); michael@0: // The next tick will be between 1 second and 1 hr michael@0: // Set it to the max value here, and the TimeoutTickCB()s can michael@0: // reduce it to their local needs. michael@0: mTimeoutTickNext = 3600; // 1hr michael@0: mCT.Enumerate(TimeoutTickCB, this); michael@0: if (mTimeoutTick) { michael@0: mTimeoutTickNext = std::max(mTimeoutTickNext, 1U); michael@0: mTimeoutTick->SetDelay(mTimeoutTickNext * 1000); michael@0: } michael@0: } michael@0: michael@0: PLDHashOperator michael@0: nsHttpConnectionMgr::TimeoutTickCB(const nsACString &key, michael@0: nsAutoPtr &ent, michael@0: void *closure) michael@0: { michael@0: nsHttpConnectionMgr *self = (nsHttpConnectionMgr *) closure; michael@0: michael@0: LOG(("nsHttpConnectionMgr::TimeoutTickCB() this=%p host=%s " michael@0: "idle=%d active=%d half-len=%d pending=%d\n", michael@0: self, ent->mConnInfo->Host(), ent->mIdleConns.Length(), michael@0: ent->mActiveConns.Length(), ent->mHalfOpens.Length(), michael@0: ent->mPendingQ.Length())); michael@0: michael@0: // first call the tick handler for each active connection michael@0: PRIntervalTime now = PR_IntervalNow(); michael@0: for (uint32_t index = 0; index < ent->mActiveConns.Length(); ++index) { michael@0: uint32_t connNextTimeout = ent->mActiveConns[index]->ReadTimeoutTick(now); michael@0: self->mTimeoutTickNext = std::min(self->mTimeoutTickNext, connNextTimeout); michael@0: } michael@0: michael@0: // now check for any stalled half open sockets michael@0: if (ent->mHalfOpens.Length()) { michael@0: TimeStamp now = TimeStamp::Now(); michael@0: double maxConnectTime = gHttpHandler->ConnectTimeout(); /* in milliseconds */ michael@0: michael@0: for (uint32_t index = ent->mHalfOpens.Length(); index > 0; ) { michael@0: index--; michael@0: michael@0: nsHalfOpenSocket *half = ent->mHalfOpens[index]; michael@0: double delta = half->Duration(now); michael@0: // If the socket has timed out, close it so the waiting transaction michael@0: // will get the proper signal michael@0: if (delta > maxConnectTime) { michael@0: LOG(("Force timeout of half open to %s after %.2fms.\n", michael@0: ent->mConnInfo->HashKey().get(), delta)); michael@0: if (half->SocketTransport()) michael@0: half->SocketTransport()->Close(NS_ERROR_ABORT); michael@0: if (half->BackupTransport()) michael@0: half->BackupTransport()->Close(NS_ERROR_ABORT); michael@0: } michael@0: michael@0: // If this half open hangs around for 5 seconds after we've closed() it michael@0: // then just abandon the socket. michael@0: if (delta > maxConnectTime + 5000) { michael@0: LOG(("Abandon half open to %s after %.2fms.\n", michael@0: ent->mConnInfo->HashKey().get(), delta)); michael@0: half->Abandon(); michael@0: } michael@0: } michael@0: } michael@0: if (ent->mHalfOpens.Length()) { michael@0: self->mTimeoutTickNext = 1; michael@0: } michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsHttpConnectionMgr::nsConnectionHandle michael@0: michael@0: nsHttpConnectionMgr::nsConnectionHandle::~nsConnectionHandle() michael@0: { michael@0: if (mConn) { michael@0: gHttpHandler->ReclaimConnection(mConn); michael@0: NS_RELEASE(mConn); michael@0: } michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS0(nsHttpConnectionMgr::nsConnectionHandle) michael@0: michael@0: nsHttpConnectionMgr::nsConnectionEntry * michael@0: nsHttpConnectionMgr::GetOrCreateConnectionEntry(nsHttpConnectionInfo *ci) michael@0: { michael@0: nsConnectionEntry *ent = mCT.Get(ci->HashKey()); michael@0: if (ent) michael@0: return ent; michael@0: michael@0: nsHttpConnectionInfo *clone = ci->Clone(); michael@0: ent = new nsConnectionEntry(clone); michael@0: mCT.Put(ci->HashKey(), ent); michael@0: return ent; michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpConnectionMgr::nsConnectionHandle::OnHeadersAvailable(nsAHttpTransaction *trans, michael@0: nsHttpRequestHead *req, michael@0: nsHttpResponseHead *resp, michael@0: bool *reset) michael@0: { michael@0: return mConn->OnHeadersAvailable(trans, req, resp, reset); michael@0: } michael@0: michael@0: void michael@0: nsHttpConnectionMgr::nsConnectionHandle::CloseTransaction(nsAHttpTransaction *trans, nsresult reason) michael@0: { michael@0: mConn->CloseTransaction(trans, reason); michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpConnectionMgr:: michael@0: nsConnectionHandle::TakeTransport(nsISocketTransport **aTransport, michael@0: nsIAsyncInputStream **aInputStream, michael@0: nsIAsyncOutputStream **aOutputStream) michael@0: { michael@0: return mConn->TakeTransport(aTransport, aInputStream, aOutputStream); michael@0: } michael@0: michael@0: void michael@0: nsHttpConnectionMgr::OnMsgSpeculativeConnect(int32_t, void *param) michael@0: { michael@0: MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); michael@0: michael@0: nsRefPtr args = michael@0: dont_AddRef(static_cast(param)); michael@0: michael@0: LOG(("nsHttpConnectionMgr::OnMsgSpeculativeConnect [ci=%s]\n", michael@0: args->mTrans->ConnectionInfo()->HashKey().get())); michael@0: michael@0: nsConnectionEntry *ent = michael@0: GetOrCreateConnectionEntry(args->mTrans->ConnectionInfo()); michael@0: michael@0: // If spdy has previously made a preferred entry for this host via michael@0: // the ip pooling rules. If so, connect to the preferred host instead of michael@0: // the one directly passed in here. michael@0: nsConnectionEntry *preferredEntry = GetSpdyPreferredEnt(ent); michael@0: if (preferredEntry) michael@0: ent = preferredEntry; michael@0: michael@0: uint32_t parallelSpeculativeConnectLimit = michael@0: gHttpHandler->ParallelSpeculativeConnectLimit(); michael@0: bool ignorePossibleSpdyConnections = false; michael@0: bool ignoreIdle = false; michael@0: michael@0: if (args->mOverridesOK) { michael@0: parallelSpeculativeConnectLimit = args->mParallelSpeculativeConnectLimit; michael@0: ignorePossibleSpdyConnections = args->mIgnorePossibleSpdyConnections; michael@0: ignoreIdle = args->mIgnoreIdle; michael@0: } michael@0: michael@0: if (ent->SupportsPipelining()) { michael@0: /* Only speculative connect if we're not pipelining and have no other pending michael@0: * unconnected half-opens.. */ michael@0: if (ent->UnconnectedHalfOpens() == 0 && ent->mIdleConns.Length() == 0 michael@0: && !RestrictConnections(ent) && !HasPipelines(ent) michael@0: && !AtActiveConnectionLimit(ent, args->mTrans->Caps())) { michael@0: #ifdef WTF_DEBUG michael@0: fprintf(stderr, "WTF: Creating speculative connection because we have no pipelines\n"); michael@0: #endif michael@0: CreateTransport(ent, args->mTrans, args->mTrans->Caps(), true); michael@0: } michael@0: } else if (mNumHalfOpenConns < parallelSpeculativeConnectLimit && michael@0: ((ignoreIdle && (ent->mIdleConns.Length() < parallelSpeculativeConnectLimit)) || michael@0: !ent->mIdleConns.Length()) && michael@0: !RestrictConnections(ent, ignorePossibleSpdyConnections) && michael@0: !AtActiveConnectionLimit(ent, args->mTrans->Caps())) { michael@0: #ifdef WTF_DEBUG michael@0: fprintf(stderr, "WTF: Creating speculative connection because we can't pipeline\n"); michael@0: #endif michael@0: CreateTransport(ent, args->mTrans, args->mTrans->Caps(), true); michael@0: } else { michael@0: LOG((" Transport not created due to existing connection count\n")); michael@0: } michael@0: } michael@0: michael@0: bool michael@0: nsHttpConnectionMgr::HasPipelines(nsConnectionEntry *ent) michael@0: { michael@0: uint32_t activeCount = ent->mActiveConns.Length(); michael@0: michael@0: if (!ent->SupportsPipelining()) { michael@0: return false; michael@0: } michael@0: michael@0: for (uint32_t i = 0; i < activeCount; ++i) { michael@0: nsHttpConnection *conn = ent->mActiveConns[i]; michael@0: if (!conn->SupportsPipelining()) michael@0: continue; michael@0: michael@0: nsAHttpTransaction *activeTrans = conn->Transaction(); michael@0: michael@0: if (activeTrans && !activeTrans->IsDone() && michael@0: !NS_FAILED(activeTrans->Status())) michael@0: return true; michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: bool michael@0: nsHttpConnectionMgr::nsConnectionHandle::IsPersistent() michael@0: { michael@0: return mConn->IsPersistent(); michael@0: } michael@0: michael@0: bool michael@0: nsHttpConnectionMgr::nsConnectionHandle::IsReused() michael@0: { michael@0: return mConn->IsReused(); michael@0: } michael@0: michael@0: void michael@0: nsHttpConnectionMgr::nsConnectionHandle::DontReuse() michael@0: { michael@0: mConn->DontReuse(); michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpConnectionMgr::nsConnectionHandle::PushBack(const char *buf, uint32_t bufLen) michael@0: { michael@0: return mConn->PushBack(buf, bufLen); michael@0: } michael@0: michael@0: michael@0: //////////////////////// nsHalfOpenSocket michael@0: michael@0: michael@0: NS_IMPL_ISUPPORTS(nsHttpConnectionMgr::nsHalfOpenSocket, michael@0: nsIOutputStreamCallback, michael@0: nsITransportEventSink, michael@0: nsIInterfaceRequestor, michael@0: nsITimerCallback) michael@0: michael@0: nsHttpConnectionMgr:: michael@0: nsHalfOpenSocket::nsHalfOpenSocket(nsConnectionEntry *ent, michael@0: nsAHttpTransaction *trans, michael@0: uint32_t caps) michael@0: : mEnt(ent), michael@0: mTransaction(trans), michael@0: mCaps(caps), michael@0: mSpeculative(false), michael@0: mHasConnected(false) michael@0: { michael@0: MOZ_ASSERT(ent && trans, "constructor with null arguments"); michael@0: LOG(("Creating nsHalfOpenSocket [this=%p trans=%p ent=%s]\n", michael@0: this, trans, ent->mConnInfo->Host())); michael@0: } michael@0: michael@0: nsHttpConnectionMgr::nsHalfOpenSocket::~nsHalfOpenSocket() michael@0: { michael@0: MOZ_ASSERT(!mStreamOut); michael@0: MOZ_ASSERT(!mBackupStreamOut); michael@0: MOZ_ASSERT(!mSynTimer); michael@0: LOG(("Destroying nsHalfOpenSocket [this=%p]\n", this)); michael@0: michael@0: if (mEnt) michael@0: mEnt->RemoveHalfOpen(this); michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpConnectionMgr:: michael@0: nsHalfOpenSocket::SetupStreams(nsISocketTransport **transport, michael@0: nsIAsyncInputStream **instream, michael@0: nsIAsyncOutputStream **outstream, michael@0: bool isBackup) michael@0: { michael@0: nsresult rv; michael@0: michael@0: const char* types[1]; michael@0: types[0] = (mEnt->mConnInfo->UsingSSL()) ? michael@0: "ssl" : gHttpHandler->DefaultSocketType(); michael@0: uint32_t typeCount = (types[0] != nullptr); michael@0: michael@0: nsCOMPtr socketTransport; michael@0: nsCOMPtr sts; michael@0: michael@0: sts = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = sts->CreateTransport(types, typeCount, michael@0: nsDependentCString(mEnt->mConnInfo->Host()), michael@0: mEnt->mConnInfo->Port(), michael@0: mEnt->mConnInfo->ProxyInfo(), michael@0: getter_AddRefs(socketTransport)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: uint32_t tmpFlags = 0; michael@0: if (mCaps & NS_HTTP_REFRESH_DNS) michael@0: tmpFlags = nsISocketTransport::BYPASS_CACHE; michael@0: michael@0: if (mCaps & NS_HTTP_LOAD_ANONYMOUS) michael@0: tmpFlags |= nsISocketTransport::ANONYMOUS_CONNECT; michael@0: michael@0: if (mEnt->mConnInfo->GetPrivate()) michael@0: tmpFlags |= nsISocketTransport::NO_PERMANENT_STORAGE; michael@0: michael@0: // For backup connections, we disable IPv6. That's because some users have michael@0: // broken IPv6 connectivity (leading to very long timeouts), and disabling michael@0: // IPv6 on the backup connection gives them a much better user experience michael@0: // with dual-stack hosts, though they still pay the 250ms delay for each new michael@0: // connection. This strategy is also known as "happy eyeballs". michael@0: if (mEnt->mPreferIPv6) { michael@0: tmpFlags |= nsISocketTransport::DISABLE_IPV4; michael@0: } michael@0: else if (mEnt->mPreferIPv4 || michael@0: (isBackup && gHttpHandler->FastFallbackToIPv4())) { michael@0: tmpFlags |= nsISocketTransport::DISABLE_IPV6; michael@0: } michael@0: michael@0: if (IsSpeculative()) { michael@0: tmpFlags |= nsISocketTransport::DISABLE_RFC1918; michael@0: } michael@0: michael@0: socketTransport->SetConnectionFlags(tmpFlags); michael@0: michael@0: socketTransport->SetQoSBits(gHttpHandler->GetQoSBits()); michael@0: michael@0: rv = socketTransport->SetEventSink(this, nullptr); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = socketTransport->SetSecurityCallbacks(this); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr sout; michael@0: rv = socketTransport->OpenOutputStream(nsITransport::OPEN_UNBUFFERED, michael@0: 0, 0, michael@0: getter_AddRefs(sout)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr sin; michael@0: rv = socketTransport->OpenInputStream(nsITransport::OPEN_UNBUFFERED, michael@0: 0, 0, michael@0: getter_AddRefs(sin)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: socketTransport.forget(transport); michael@0: CallQueryInterface(sin, instream); michael@0: CallQueryInterface(sout, outstream); michael@0: michael@0: rv = (*outstream)->AsyncWait(this, 0, 0, nullptr); michael@0: if (NS_SUCCEEDED(rv)) michael@0: gHttpHandler->ConnMgr()->StartedConnect(); michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpConnectionMgr::nsHalfOpenSocket::SetupPrimaryStreams() michael@0: { michael@0: MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); michael@0: michael@0: nsresult rv; michael@0: michael@0: mPrimarySynStarted = TimeStamp::Now(); michael@0: rv = SetupStreams(getter_AddRefs(mSocketTransport), michael@0: getter_AddRefs(mStreamIn), michael@0: getter_AddRefs(mStreamOut), michael@0: false); michael@0: LOG(("nsHalfOpenSocket::SetupPrimaryStream [this=%p ent=%s rv=%x]", michael@0: this, mEnt->mConnInfo->Host(), rv)); michael@0: if (NS_FAILED(rv)) { michael@0: if (mStreamOut) michael@0: mStreamOut->AsyncWait(nullptr, 0, 0, nullptr); michael@0: mStreamOut = nullptr; michael@0: mStreamIn = nullptr; michael@0: mSocketTransport = nullptr; michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpConnectionMgr::nsHalfOpenSocket::SetupBackupStreams() michael@0: { michael@0: mBackupSynStarted = TimeStamp::Now(); michael@0: nsresult rv = SetupStreams(getter_AddRefs(mBackupTransport), michael@0: getter_AddRefs(mBackupStreamIn), michael@0: getter_AddRefs(mBackupStreamOut), michael@0: true); michael@0: LOG(("nsHalfOpenSocket::SetupBackupStream [this=%p ent=%s rv=%x]", michael@0: this, mEnt->mConnInfo->Host(), rv)); michael@0: if (NS_FAILED(rv)) { michael@0: if (mBackupStreamOut) michael@0: mBackupStreamOut->AsyncWait(nullptr, 0, 0, nullptr); michael@0: mBackupStreamOut = nullptr; michael@0: mBackupStreamIn = nullptr; michael@0: mBackupTransport = nullptr; michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: void michael@0: nsHttpConnectionMgr::nsHalfOpenSocket::SetupBackupTimer() michael@0: { michael@0: uint16_t timeout = gHttpHandler->GetIdleSynTimeout(); michael@0: MOZ_ASSERT(!mSynTimer, "timer already initd"); michael@0: michael@0: if (timeout && !mTransaction->IsDone()) { michael@0: // Setup the timer that will establish a backup socket michael@0: // if we do not get a writable event on the main one. michael@0: // We do this because a lost SYN takes a very long time michael@0: // to repair at the TCP level. michael@0: // michael@0: // Failure to setup the timer is something we can live with, michael@0: // so don't return an error in that case. michael@0: nsresult rv; michael@0: mSynTimer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: mSynTimer->InitWithCallback(this, timeout, nsITimer::TYPE_ONE_SHOT); michael@0: LOG(("nsHalfOpenSocket::SetupBackupTimer() [this=%p]", this)); michael@0: } michael@0: } michael@0: else if (timeout) { michael@0: LOG(("nsHalfOpenSocket::SetupBackupTimer() [this=%p]," michael@0: " transaction already done!", this)); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsHttpConnectionMgr::nsHalfOpenSocket::CancelBackupTimer() michael@0: { michael@0: // If the syntimer is still armed, we can cancel it because no backup michael@0: // socket should be formed at this point michael@0: if (!mSynTimer) michael@0: return; michael@0: michael@0: LOG(("nsHalfOpenSocket::CancelBackupTimer()")); michael@0: mSynTimer->Cancel(); michael@0: mSynTimer = nullptr; michael@0: } michael@0: michael@0: void michael@0: nsHttpConnectionMgr::nsHalfOpenSocket::Abandon() michael@0: { michael@0: LOG(("nsHalfOpenSocket::Abandon [this=%p ent=%s]", michael@0: this, mEnt->mConnInfo->Host())); michael@0: michael@0: MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); michael@0: michael@0: nsRefPtr deleteProtector(this); michael@0: michael@0: // Tell socket (and backup socket) to forget the half open socket. michael@0: if (mSocketTransport) { michael@0: mSocketTransport->SetEventSink(nullptr, nullptr); michael@0: mSocketTransport->SetSecurityCallbacks(nullptr); michael@0: mSocketTransport = nullptr; michael@0: } michael@0: if (mBackupTransport) { michael@0: mBackupTransport->SetEventSink(nullptr, nullptr); michael@0: mBackupTransport->SetSecurityCallbacks(nullptr); michael@0: mBackupTransport = nullptr; michael@0: } michael@0: michael@0: // Tell output stream (and backup) to forget the half open socket. michael@0: if (mStreamOut) { michael@0: gHttpHandler->ConnMgr()->RecvdConnect(); michael@0: mStreamOut->AsyncWait(nullptr, 0, 0, nullptr); michael@0: mStreamOut = nullptr; michael@0: } michael@0: if (mBackupStreamOut) { michael@0: gHttpHandler->ConnMgr()->RecvdConnect(); michael@0: mBackupStreamOut->AsyncWait(nullptr, 0, 0, nullptr); michael@0: mBackupStreamOut = nullptr; michael@0: } michael@0: michael@0: // Lose references to input stream (and backup). michael@0: mStreamIn = mBackupStreamIn = nullptr; michael@0: michael@0: // Stop the timer - we don't want any new backups. michael@0: CancelBackupTimer(); michael@0: michael@0: // Remove the half open from the connection entry. michael@0: if (mEnt) michael@0: mEnt->RemoveHalfOpen(this); michael@0: mEnt = nullptr; michael@0: } michael@0: michael@0: double michael@0: nsHttpConnectionMgr::nsHalfOpenSocket::Duration(TimeStamp epoch) michael@0: { michael@0: if (mPrimarySynStarted.IsNull()) michael@0: return 0; michael@0: michael@0: return (epoch - mPrimarySynStarted).ToMilliseconds(); michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP // method for nsITimerCallback michael@0: nsHttpConnectionMgr::nsHalfOpenSocket::Notify(nsITimer *timer) michael@0: { michael@0: MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); michael@0: MOZ_ASSERT(timer == mSynTimer, "wrong timer"); michael@0: michael@0: SetupBackupStreams(); michael@0: michael@0: mSynTimer = nullptr; michael@0: return NS_OK; michael@0: } michael@0: michael@0: // method for nsIAsyncOutputStreamCallback michael@0: NS_IMETHODIMP michael@0: nsHttpConnectionMgr:: michael@0: nsHalfOpenSocket::OnOutputStreamReady(nsIAsyncOutputStream *out) michael@0: { michael@0: MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); michael@0: MOZ_ASSERT(out == mStreamOut || out == mBackupStreamOut, michael@0: "stream mismatch"); michael@0: LOG(("nsHalfOpenSocket::OnOutputStreamReady [this=%p ent=%s %s]\n", michael@0: this, mEnt->mConnInfo->Host(), michael@0: out == mStreamOut ? "primary" : "backup")); michael@0: int32_t index; michael@0: nsresult rv; michael@0: michael@0: gHttpHandler->ConnMgr()->RecvdConnect(); michael@0: michael@0: CancelBackupTimer(); michael@0: michael@0: // assign the new socket to the http connection michael@0: nsRefPtr conn = new nsHttpConnection(); michael@0: LOG(("nsHalfOpenSocket::OnOutputStreamReady " michael@0: "Created new nshttpconnection %p\n", conn.get())); michael@0: michael@0: // Some capabilities are needed before a transaciton actually gets michael@0: // scheduled (e.g. how to negotiate false start) michael@0: conn->SetTransactionCaps(mTransaction->Caps()); michael@0: michael@0: NetAddr peeraddr; michael@0: nsCOMPtr callbacks; michael@0: mTransaction->GetSecurityCallbacks(getter_AddRefs(callbacks)); michael@0: if (out == mStreamOut) { michael@0: TimeDuration rtt = TimeStamp::Now() - mPrimarySynStarted; michael@0: rv = conn->Init(mEnt->mConnInfo, michael@0: gHttpHandler->ConnMgr()->mMaxRequestDelay, michael@0: mSocketTransport, mStreamIn, mStreamOut, michael@0: callbacks, michael@0: PR_MillisecondsToInterval( michael@0: static_cast(rtt.ToMilliseconds()))); michael@0: michael@0: if (NS_SUCCEEDED(mSocketTransport->GetPeerAddr(&peeraddr))) michael@0: mEnt->RecordIPFamilyPreference(peeraddr.raw.family); michael@0: michael@0: // The nsHttpConnection object now owns these streams and sockets michael@0: mStreamOut = nullptr; michael@0: mStreamIn = nullptr; michael@0: mSocketTransport = nullptr; michael@0: } michael@0: else { michael@0: TimeDuration rtt = TimeStamp::Now() - mBackupSynStarted; michael@0: rv = conn->Init(mEnt->mConnInfo, michael@0: gHttpHandler->ConnMgr()->mMaxRequestDelay, michael@0: mBackupTransport, mBackupStreamIn, mBackupStreamOut, michael@0: callbacks, michael@0: PR_MillisecondsToInterval( michael@0: static_cast(rtt.ToMilliseconds()))); michael@0: michael@0: if (NS_SUCCEEDED(mBackupTransport->GetPeerAddr(&peeraddr))) michael@0: mEnt->RecordIPFamilyPreference(peeraddr.raw.family); michael@0: michael@0: // The nsHttpConnection object now owns these streams and sockets michael@0: mBackupStreamOut = nullptr; michael@0: mBackupStreamIn = nullptr; michael@0: mBackupTransport = nullptr; michael@0: } michael@0: michael@0: if (NS_FAILED(rv)) { michael@0: LOG(("nsHalfOpenSocket::OnOutputStreamReady " michael@0: "conn->init (%p) failed %x\n", conn.get(), rv)); michael@0: return rv; michael@0: } michael@0: michael@0: // This half-open socket has created a connection. This flag excludes it michael@0: // from counter of actual connections used for checking limits. michael@0: mHasConnected = true; michael@0: michael@0: // if this is still in the pending list, remove it and dispatch it michael@0: index = mEnt->mPendingQ.IndexOf(mTransaction); michael@0: if (index != -1) { michael@0: MOZ_ASSERT(!mSpeculative, michael@0: "Speculative Half Open found mTranscation"); michael@0: nsRefPtr temp = dont_AddRef(mEnt->mPendingQ[index]); michael@0: mEnt->mPendingQ.RemoveElementAt(index); michael@0: gHttpHandler->ConnMgr()->AddActiveConn(conn, mEnt); michael@0: #ifdef WTF_DEBUG michael@0: fprintf(stderr, "WTF: Speculative half-opened connection is now ready for %s (pipelines %d)\n", michael@0: mEnt->mConnInfo->Host(), mEnt->SupportsPipelining()); michael@0: #endif michael@0: rv = gHttpHandler->ConnMgr()->DispatchTransaction(mEnt, temp, conn); michael@0: } michael@0: else { michael@0: // this transaction was dispatched off the pending q before all the michael@0: // sockets established themselves. michael@0: michael@0: // After about 1 second allow for the possibility of restarting a michael@0: // transaction due to server close. Keep at sub 1 second as that is the michael@0: // minimum granularity we can expect a server to be timing out with. michael@0: conn->SetIsReusedAfter(950); michael@0: michael@0: // if we are using ssl and no other transactions are waiting right now, michael@0: // then form a null transaction to drive the SSL handshake to michael@0: // completion. Afterwards the connection will be 100% ready for the next michael@0: // transaction to use it. Make an exception for SSL over HTTP proxy as the michael@0: // NullHttpTransaction does not know how to drive CONNECT. michael@0: if (mEnt->mConnInfo->UsingSSL() && !mEnt->mPendingQ.Length() && michael@0: !mEnt->mConnInfo->UsingHttpProxy()) { michael@0: LOG(("nsHalfOpenSocket::OnOutputStreamReady null transaction will " michael@0: "be used to finish SSL handshake on conn %p\n", conn.get())); michael@0: nsRefPtr trans = michael@0: new NullHttpTransaction(mEnt->mConnInfo, michael@0: callbacks, michael@0: mCaps & ~NS_HTTP_ALLOW_PIPELINING); michael@0: michael@0: gHttpHandler->ConnMgr()->AddActiveConn(conn, mEnt); michael@0: conn->Classify(nsAHttpTransaction::CLASS_SOLO); michael@0: rv = gHttpHandler->ConnMgr()-> michael@0: DispatchAbstractTransaction(mEnt, trans, mCaps, conn, 0); michael@0: } michael@0: else { michael@0: // otherwise just put this in the persistent connection pool michael@0: LOG(("nsHalfOpenSocket::OnOutputStreamReady no transaction match " michael@0: "returning conn %p to pool\n", conn.get())); michael@0: nsRefPtr copy(conn); michael@0: // forget() to effectively addref because onmsg*() will drop a ref michael@0: gHttpHandler->ConnMgr()->OnMsgReclaimConnection( michael@0: 0, conn.forget().take()); michael@0: } michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: // method for nsITransportEventSink michael@0: NS_IMETHODIMP michael@0: nsHttpConnectionMgr::nsHalfOpenSocket::OnTransportStatus(nsITransport *trans, michael@0: nsresult status, michael@0: uint64_t progress, michael@0: uint64_t progressMax) michael@0: { michael@0: MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); michael@0: michael@0: if (mTransaction) michael@0: mTransaction->OnTransportStatus(trans, status, progress); michael@0: michael@0: if (trans != mSocketTransport) michael@0: return NS_OK; michael@0: michael@0: // if we are doing spdy coalescing and haven't recorded the ip address michael@0: // for this entry before then make the hash key if our dns lookup michael@0: // just completed. We can't do coalescing if using a proxy because the michael@0: // ip addresses are not available to the client. michael@0: michael@0: if (status == NS_NET_STATUS_CONNECTED_TO && michael@0: gHttpHandler->IsSpdyEnabled() && michael@0: gHttpHandler->CoalesceSpdy() && michael@0: mEnt && mEnt->mConnInfo && mEnt->mConnInfo->UsingSSL() && michael@0: !mEnt->mConnInfo->UsingProxy() && michael@0: mEnt->mCoalescingKey.IsEmpty()) { michael@0: michael@0: NetAddr addr; michael@0: nsresult rv = mSocketTransport->GetPeerAddr(&addr); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: mEnt->mCoalescingKey.SetCapacity(kIPv6CStrBufSize + 26); michael@0: NetAddrToString(&addr, mEnt->mCoalescingKey.BeginWriting(), kIPv6CStrBufSize); michael@0: mEnt->mCoalescingKey.SetLength( michael@0: strlen(mEnt->mCoalescingKey.BeginReading())); michael@0: michael@0: if (mEnt->mConnInfo->GetAnonymous()) michael@0: mEnt->mCoalescingKey.AppendLiteral("~A:"); michael@0: else michael@0: mEnt->mCoalescingKey.AppendLiteral("~.:"); michael@0: mEnt->mCoalescingKey.AppendInt(mEnt->mConnInfo->Port()); michael@0: michael@0: LOG(("nsHttpConnectionMgr::nsHalfOpenSocket::OnTransportStatus " michael@0: "STATUS_CONNECTED_TO Established New Coalescing Key for host " michael@0: "%s [%s]", mEnt->mConnInfo->Host(), michael@0: mEnt->mCoalescingKey.get())); michael@0: michael@0: gHttpHandler->ConnMgr()->ProcessSpdyPendingQ(mEnt); michael@0: } michael@0: } michael@0: michael@0: switch (status) { michael@0: case NS_NET_STATUS_CONNECTING_TO: michael@0: // Passed DNS resolution, now trying to connect, start the backup timer michael@0: // only prevent creating another backup transport. michael@0: // We also check for mEnt presence to not instantiate the timer after michael@0: // this half open socket has already been abandoned. It may happen michael@0: // when we get this notification right between main-thread calls to michael@0: // nsHttpConnectionMgr::Shutdown and nsSocketTransportService::Shutdown michael@0: // where the first abandones all half open socket instances and only michael@0: // after that the second stops the socket thread. michael@0: if (mEnt && !mBackupTransport && !mSynTimer) michael@0: SetupBackupTimer(); michael@0: break; michael@0: michael@0: case NS_NET_STATUS_CONNECTED_TO: michael@0: // TCP connection's up, now transfer or SSL negotiantion starts, michael@0: // no need for backup socket michael@0: CancelBackupTimer(); michael@0: break; michael@0: michael@0: default: michael@0: break; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // method for nsIInterfaceRequestor michael@0: NS_IMETHODIMP michael@0: nsHttpConnectionMgr::nsHalfOpenSocket::GetInterface(const nsIID &iid, michael@0: void **result) michael@0: { michael@0: if (mTransaction) { michael@0: nsCOMPtr callbacks; michael@0: mTransaction->GetSecurityCallbacks(getter_AddRefs(callbacks)); michael@0: if (callbacks) michael@0: return callbacks->GetInterface(iid, result); michael@0: } michael@0: return NS_ERROR_NO_INTERFACE; michael@0: } michael@0: michael@0: michael@0: nsHttpConnection * michael@0: nsHttpConnectionMgr::nsConnectionHandle::TakeHttpConnection() michael@0: { michael@0: // return our connection object to the caller and clear it internally michael@0: // do not drop our reference - the caller now owns it. michael@0: michael@0: MOZ_ASSERT(mConn); michael@0: nsHttpConnection *conn = mConn; michael@0: mConn = nullptr; michael@0: return conn; michael@0: } michael@0: michael@0: uint32_t michael@0: nsHttpConnectionMgr::nsConnectionHandle::CancelPipeline(nsresult reason) michael@0: { michael@0: // no pipeline to cancel michael@0: return 0; michael@0: } michael@0: michael@0: nsAHttpTransaction::Classifier michael@0: nsHttpConnectionMgr::nsConnectionHandle::Classification() michael@0: { michael@0: if (mConn) michael@0: return mConn->Classification(); michael@0: michael@0: LOG(("nsConnectionHandle::Classification this=%p " michael@0: "has null mConn using CLASS_SOLO default", this)); michael@0: return nsAHttpTransaction::CLASS_SOLO; michael@0: } michael@0: michael@0: // nsConnectionEntry michael@0: michael@0: nsHttpConnectionMgr:: michael@0: nsConnectionEntry::nsConnectionEntry(nsHttpConnectionInfo *ci) michael@0: : mConnInfo(ci) michael@0: , mPipelineState(PS_YELLOW) michael@0: , mYellowGoodEvents(0) michael@0: , mYellowBadEvents(0) michael@0: , mYellowConnection(nullptr) michael@0: , mGreenDepth(kPipelineOpen) michael@0: , mPipeliningPenalty(0) michael@0: , mSpdyCWND(0) michael@0: , mUsingSpdy(false) michael@0: , mTestedSpdy(false) michael@0: , mSpdyPreferred(false) michael@0: , mPreferIPv4(false) michael@0: , mPreferIPv6(false) michael@0: { michael@0: NS_ADDREF(mConnInfo); michael@0: michael@0: // Randomize the pipeline depth (3..12) michael@0: mGreenDepth = gHttpHandler->GetMaxOptimisticPipelinedRequests() michael@0: + rand() % (gHttpHandler->GetMaxPipelinedRequests() michael@0: - gHttpHandler->GetMaxOptimisticPipelinedRequests()); michael@0: michael@0: if (gHttpHandler->GetPipelineAggressive()) { michael@0: mPipelineState = PS_GREEN; michael@0: } michael@0: michael@0: mInitialGreenDepth = mGreenDepth; michael@0: memset(mPipeliningClassPenalty, 0, sizeof(int16_t) * nsAHttpTransaction::CLASS_MAX); michael@0: } michael@0: michael@0: bool michael@0: nsHttpConnectionMgr::nsConnectionEntry::SupportsPipelining() michael@0: { michael@0: return mPipelineState != nsHttpConnectionMgr::PS_RED; michael@0: } michael@0: michael@0: nsHttpConnectionMgr::PipeliningState michael@0: nsHttpConnectionMgr::nsConnectionEntry::PipelineState() michael@0: { michael@0: return mPipelineState; michael@0: } michael@0: michael@0: void michael@0: nsHttpConnectionMgr:: michael@0: nsConnectionEntry::OnPipelineFeedbackInfo( michael@0: nsHttpConnectionMgr::PipelineFeedbackInfoType info, michael@0: nsHttpConnection *conn, michael@0: uint32_t data) michael@0: { michael@0: MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); michael@0: michael@0: if (mPipelineState == PS_YELLOW) { michael@0: if (info & kPipelineInfoTypeBad) michael@0: mYellowBadEvents++; michael@0: else if (info & (kPipelineInfoTypeNeutral | kPipelineInfoTypeGood)) michael@0: mYellowGoodEvents++; michael@0: } michael@0: michael@0: if (mPipelineState == PS_GREEN && info == GoodCompletedOK) { michael@0: int32_t depth = data; michael@0: LOG(("Transaction completed at pipeline depth of %d. Host = %s\n", michael@0: depth, mConnInfo->Host())); michael@0: michael@0: // Don't set this. We want to keep our initial random value.. michael@0: //if (depth >= 3) michael@0: // mGreenDepth = kPipelineUnlimited; michael@0: } michael@0: michael@0: nsAHttpTransaction::Classifier classification; michael@0: if (conn) michael@0: classification = conn->Classification(); michael@0: else if (info == BadInsufficientFraming || michael@0: info == BadUnexpectedLarge) michael@0: classification = (nsAHttpTransaction::Classifier) data; michael@0: else michael@0: classification = nsAHttpTransaction::CLASS_SOLO; michael@0: michael@0: if (gHttpHandler->GetPipelineAggressive() && michael@0: info & kPipelineInfoTypeBad && michael@0: info != BadExplicitClose && michael@0: info != RedVersionTooLow && michael@0: info != RedBannedServer && michael@0: info != RedCorruptedContent && michael@0: info != BadInsufficientFraming) { michael@0: LOG(("minor negative feedback ignored " michael@0: "because of pipeline aggressive mode")); michael@0: } michael@0: else if (info & kPipelineInfoTypeBad) { michael@0: if ((info & kPipelineInfoTypeRed) && (mPipelineState != PS_RED)) { michael@0: LOG(("transition to red from %d. Host = %s.\n", michael@0: mPipelineState, mConnInfo->Host())); michael@0: mPipelineState = PS_RED; michael@0: mPipeliningPenalty = 0; michael@0: #ifdef WTF_TEST michael@0: fprintf(stderr, "WTF-bad: Red pipeline status disabled host %s\n", michael@0: mConnInfo->Host()); michael@0: #endif michael@0: michael@0: } michael@0: michael@0: if (mLastCreditTime.IsNull()) michael@0: mLastCreditTime = TimeStamp::Now(); michael@0: michael@0: // Red* events impact the host globally via mPipeliningPenalty, while michael@0: // Bad* events impact the per class penalty. michael@0: michael@0: // The individual penalties should be < 16bit-signed-maxint - 25000 michael@0: // (approx 7500). Penalties are paid-off either when something promising michael@0: // happens (a successful transaction, or promising headers) or when michael@0: // time goes by at a rate of 1 penalty point every 16 seconds. michael@0: michael@0: switch (info) { michael@0: case RedVersionTooLow: michael@0: mPipeliningPenalty += 1000; michael@0: break; michael@0: case RedBannedServer: michael@0: mPipeliningPenalty += 7000; michael@0: break; michael@0: case RedCorruptedContent: michael@0: mPipeliningPenalty += 7000; michael@0: break; michael@0: case RedCanceledPipeline: michael@0: mPipeliningPenalty += 60; michael@0: break; michael@0: case BadExplicitClose: michael@0: mPipeliningClassPenalty[classification] += 250; michael@0: break; michael@0: case BadSlowReadMinor: michael@0: mPipeliningClassPenalty[classification] += 5; michael@0: break; michael@0: case BadSlowReadMajor: michael@0: mPipeliningClassPenalty[classification] += 25; michael@0: break; michael@0: case BadInsufficientFraming: michael@0: mPipeliningClassPenalty[classification] += 7000; michael@0: break; michael@0: case BadUnexpectedLarge: michael@0: mPipeliningClassPenalty[classification] += 120; michael@0: break; michael@0: michael@0: default: michael@0: MOZ_ASSERT(false, "Unknown Bad/Red Pipeline Feedback Event"); michael@0: } michael@0: michael@0: const int16_t kPenalty = 25000; michael@0: mPipeliningPenalty = std::min(mPipeliningPenalty, kPenalty); michael@0: mPipeliningClassPenalty[classification] = michael@0: std::min(mPipeliningClassPenalty[classification], kPenalty); michael@0: michael@0: LOG(("Assessing red penalty to %s class %d for event %d. " michael@0: "Penalty now %d, throttle[%d] = %d\n", mConnInfo->Host(), michael@0: classification, info, mPipeliningPenalty, classification, michael@0: mPipeliningClassPenalty[classification])); michael@0: } michael@0: else { michael@0: // hand out credits for neutral and good events such as michael@0: // "headers look ok" events michael@0: michael@0: mPipeliningPenalty = std::max(mPipeliningPenalty - 1, 0); michael@0: mPipeliningClassPenalty[classification] = std::max(mPipeliningClassPenalty[classification] - 1, 0); michael@0: } michael@0: michael@0: if (mPipelineState == PS_RED && !mPipeliningPenalty) michael@0: { michael@0: LOG(("transition %s to yellow\n", mConnInfo->Host())); michael@0: mPipelineState = PS_YELLOW; michael@0: mYellowConnection = nullptr; michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsHttpConnectionMgr:: michael@0: nsConnectionEntry::SetYellowConnection(nsHttpConnection *conn) michael@0: { michael@0: MOZ_ASSERT(!mYellowConnection && mPipelineState == PS_YELLOW, michael@0: "yellow connection already set or state is not yellow"); michael@0: mYellowConnection = conn; michael@0: mYellowGoodEvents = mYellowBadEvents = 0; michael@0: } michael@0: michael@0: void michael@0: nsHttpConnectionMgr:: michael@0: nsConnectionEntry::OnYellowComplete() michael@0: { michael@0: if (mPipelineState == PS_YELLOW) { michael@0: if (mYellowGoodEvents && !mYellowBadEvents) { michael@0: LOG(("transition %s to green\n", mConnInfo->Host())); michael@0: mPipelineState = PS_GREEN; michael@0: mGreenDepth = mInitialGreenDepth; michael@0: } michael@0: else { michael@0: // The purpose of the yellow state is to witness at least michael@0: // one successful pipelined transaction without seeing any michael@0: // kind of negative feedback before opening the flood gates. michael@0: // If we haven't confirmed that, then transfer back to red. michael@0: LOG(("transition %s to red from yellow return\n", michael@0: mConnInfo->Host())); michael@0: mPipelineState = PS_RED; michael@0: } michael@0: } michael@0: michael@0: mYellowConnection = nullptr; michael@0: } michael@0: michael@0: void michael@0: nsHttpConnectionMgr:: michael@0: nsConnectionEntry::CreditPenalty() michael@0: { michael@0: if (mLastCreditTime.IsNull()) michael@0: return; michael@0: michael@0: // Decrease penalty values by 1 for every 16 seconds michael@0: // (i.e 3.7 per minute, or 1000 every 4h20m) michael@0: michael@0: TimeStamp now = TimeStamp::Now(); michael@0: TimeDuration elapsedTime = now - mLastCreditTime; michael@0: uint32_t creditsEarned = michael@0: static_cast(elapsedTime.ToSeconds()) >> 4; michael@0: michael@0: bool failed = false; michael@0: if (creditsEarned > 0) { michael@0: mPipeliningPenalty = michael@0: std::max(int32_t(mPipeliningPenalty - creditsEarned), 0); michael@0: if (mPipeliningPenalty > 0) michael@0: failed = true; michael@0: michael@0: for (int32_t i = 0; i < nsAHttpTransaction::CLASS_MAX; ++i) { michael@0: mPipeliningClassPenalty[i] = michael@0: std::max(int32_t(mPipeliningClassPenalty[i] - creditsEarned), 0); michael@0: failed = failed || (mPipeliningClassPenalty[i] > 0); michael@0: } michael@0: michael@0: // update last credit mark to reflect elapsed time michael@0: mLastCreditTime += TimeDuration::FromSeconds(creditsEarned << 4); michael@0: } michael@0: else { michael@0: failed = true; /* just assume this */ michael@0: } michael@0: michael@0: // If we are no longer red then clear the credit counter - you only michael@0: // get credits for time spent in the red state michael@0: if (!failed) michael@0: mLastCreditTime = TimeStamp(); /* reset to null timestamp */ michael@0: michael@0: if (mPipelineState == PS_RED && !mPipeliningPenalty) michael@0: { michael@0: LOG(("transition %s to yellow based on time credit\n", michael@0: mConnInfo->Host())); michael@0: mPipelineState = PS_YELLOW; michael@0: mYellowConnection = nullptr; michael@0: } michael@0: } michael@0: michael@0: uint32_t michael@0: nsHttpConnectionMgr:: michael@0: nsConnectionEntry::MaxPipelineDepth(nsAHttpTransaction::Classifier aClass) michael@0: { michael@0: // Still subject to configuration limit no matter return value michael@0: michael@0: if ((mPipelineState == PS_RED) || (mPipeliningClassPenalty[aClass] > 0)) michael@0: return 0; michael@0: michael@0: if (mPipelineState == PS_YELLOW) michael@0: return kPipelineRestricted; michael@0: michael@0: return mGreenDepth; michael@0: } michael@0: michael@0: PLDHashOperator michael@0: nsHttpConnectionMgr::ReadConnectionEntry(const nsACString &key, michael@0: nsAutoPtr &ent, michael@0: void *aArg) michael@0: { michael@0: if (ent->mConnInfo->GetPrivate()) michael@0: return PL_DHASH_NEXT; michael@0: michael@0: nsTArray *args = static_cast *> (aArg); michael@0: HttpRetParams data; michael@0: data.host = ent->mConnInfo->Host(); michael@0: data.port = ent->mConnInfo->Port(); michael@0: for (uint32_t i = 0; i < ent->mActiveConns.Length(); i++) { michael@0: HttpConnInfo info; michael@0: info.ttl = ent->mActiveConns[i]->TimeToLive(); michael@0: info.rtt = ent->mActiveConns[i]->Rtt(); michael@0: if (ent->mActiveConns[i]->UsingSpdy()) michael@0: info.SetHTTP2ProtocolVersion(ent->mActiveConns[i]->GetSpdyVersion()); michael@0: else michael@0: info.SetHTTP1ProtocolVersion(ent->mActiveConns[i]->GetLastHttpResponseVersion()); michael@0: michael@0: data.active.AppendElement(info); michael@0: } michael@0: for (uint32_t i = 0; i < ent->mIdleConns.Length(); i++) { michael@0: HttpConnInfo info; michael@0: info.ttl = ent->mIdleConns[i]->TimeToLive(); michael@0: info.rtt = ent->mIdleConns[i]->Rtt(); michael@0: info.SetHTTP1ProtocolVersion(ent->mIdleConns[i]->GetLastHttpResponseVersion()); michael@0: data.idle.AppendElement(info); michael@0: } michael@0: for(uint32_t i = 0; i < ent->mHalfOpens.Length(); i++) { michael@0: HalfOpenSockets hSocket; michael@0: hSocket.speculative = ent->mHalfOpens[i]->IsSpeculative(); michael@0: data.halfOpens.AppendElement(hSocket); michael@0: } michael@0: data.spdy = ent->mUsingSpdy; michael@0: data.ssl = ent->mConnInfo->UsingSSL(); michael@0: args->AppendElement(data); michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: bool michael@0: nsHttpConnectionMgr::GetConnectionData(nsTArray *aArg) michael@0: { michael@0: mCT.Enumerate(ReadConnectionEntry, aArg); michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: nsHttpConnectionMgr::ResetIPFamilyPreference(nsHttpConnectionInfo *ci) michael@0: { michael@0: MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); michael@0: nsConnectionEntry *ent = LookupConnectionEntry(ci, nullptr, nullptr); michael@0: if (ent) michael@0: ent->ResetIPFamilyPreference(); michael@0: } michael@0: michael@0: uint32_t michael@0: nsHttpConnectionMgr:: michael@0: nsConnectionEntry::UnconnectedHalfOpens() michael@0: { michael@0: uint32_t unconnectedHalfOpens = 0; michael@0: for (uint32_t i = 0; i < mHalfOpens.Length(); ++i) { michael@0: if (!mHalfOpens[i]->HasConnected()) michael@0: ++unconnectedHalfOpens; michael@0: } michael@0: return unconnectedHalfOpens; michael@0: } michael@0: michael@0: void michael@0: nsHttpConnectionMgr:: michael@0: nsConnectionEntry::RemoveHalfOpen(nsHalfOpenSocket *halfOpen) michael@0: { michael@0: // A failure to create the transport object at all michael@0: // will result in it not being present in the halfopen table michael@0: // so ignore failures of RemoveElement() michael@0: mHalfOpens.RemoveElement(halfOpen); michael@0: gHttpHandler->ConnMgr()->mNumHalfOpenConns--; michael@0: michael@0: if (!UnconnectedHalfOpens()) michael@0: // perhaps this reverted RestrictConnections() michael@0: // use the PostEvent version of processpendingq to avoid michael@0: // altering the pending q vector from an arbitrary stack michael@0: gHttpHandler->ConnMgr()->ProcessPendingQ(mConnInfo); michael@0: } michael@0: michael@0: void michael@0: nsHttpConnectionMgr:: michael@0: nsConnectionEntry::RecordIPFamilyPreference(uint16_t family) michael@0: { michael@0: if (family == PR_AF_INET && !mPreferIPv6) michael@0: mPreferIPv4 = true; michael@0: michael@0: if (family == PR_AF_INET6 && !mPreferIPv4) michael@0: mPreferIPv6 = true; michael@0: } michael@0: michael@0: void michael@0: nsHttpConnectionMgr:: michael@0: nsConnectionEntry::ResetIPFamilyPreference() michael@0: { michael@0: mPreferIPv4 = false; michael@0: mPreferIPv6 = false; michael@0: } michael@0: michael@0: } // namespace mozilla::net michael@0: } // namespace mozilla