/* vim:set ts=4 sw=4 sts=4 et cin: */ /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Mozilla. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 2002 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Darin Fisher * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #include "nsHttpConnectionMgr.h" #include "nsHttpConnection.h" #include "nsHttpPipeline.h" #include "nsHttpHandler.h" #include "nsNetCID.h" #include "nsCOMPtr.h" #include "nsNetUtil.h" #include "nsIServiceManager.h" #include "nsIObserverService.h" #include "nsISSLSocketControl.h" #include "prnetdb.h" #include "mozilla/Telemetry.h" using namespace mozilla; // defined by the socket transport service while active extern PRThread *gSocketThread; static NS_DEFINE_CID(kSocketTransportServiceCID, NS_SOCKETTRANSPORTSERVICE_CID); //----------------------------------------------------------------------------- NS_IMPL_THREADSAFE_ISUPPORTS1(nsHttpConnectionMgr, nsIObserver) static void InsertTransactionSorted(nsTArray &pendingQ, nsHttpTransaction *trans) { // insert into queue with smallest valued number first. search in reverse // order under the assumption that many of the existing transactions will // have the same priority (usually 0). for (PRInt32 i=pendingQ.Length()-1; i>=0; --i) { nsHttpTransaction *t = pendingQ[i]; if (trans->Priority() >= t->Priority()) { pendingQ.InsertElementAt(i+1, trans); return; } } pendingQ.InsertElementAt(0, trans); } //----------------------------------------------------------------------------- nsHttpConnectionMgr::nsHttpConnectionMgr() : mRef(0) , mReentrantMonitor("nsHttpConnectionMgr.mReentrantMonitor") , mMaxConns(0) , mMaxConnsPerHost(0) , mMaxConnsPerProxy(0) , mMaxPersistConnsPerHost(0) , mMaxPersistConnsPerProxy(0) , mIsShuttingDown(false) , mNumActiveConns(0) , mNumIdleConns(0) , mTimeOfNextWakeUp(LL_MAXUINT) , mReadTimeoutTickArmed(false) { LOG(("Creating nsHttpConnectionMgr @%x\n", this)); mCT.Init(); mAlternateProtocolHash.Init(16); mSpdyPreferredHash.Init(); } nsHttpConnectionMgr::~nsHttpConnectionMgr() { LOG(("Destroying nsHttpConnectionMgr @%x\n", this)); if (mReadTimeoutTick) mReadTimeoutTick->Cancel(); } nsresult nsHttpConnectionMgr::EnsureSocketThreadTargetIfOnline() { nsresult rv; nsCOMPtr sts; nsCOMPtr ioService = do_GetIOService(&rv); if (NS_SUCCEEDED(rv)) { bool offline = true; ioService->GetOffline(&offline); if (!offline) { sts = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv); } } ReentrantMonitorAutoEnter mon(mReentrantMonitor); // do nothing if already initialized or if we've shut down if (mSocketThreadTarget || mIsShuttingDown) return NS_OK; mSocketThreadTarget = sts; return rv; } nsresult nsHttpConnectionMgr::Init(PRUint16 maxConns, PRUint16 maxConnsPerHost, PRUint16 maxConnsPerProxy, PRUint16 maxPersistConnsPerHost, PRUint16 maxPersistConnsPerProxy, PRUint16 maxRequestDelay, PRUint16 maxPipelinedRequests, PRUint16 maxOptimisticPipelinedRequests) { LOG(("nsHttpConnectionMgr::Init\n")); { ReentrantMonitorAutoEnter mon(mReentrantMonitor); mMaxConns = maxConns; mMaxConnsPerHost = maxConnsPerHost; mMaxConnsPerProxy = maxConnsPerProxy; mMaxPersistConnsPerHost = maxPersistConnsPerHost; mMaxPersistConnsPerProxy = maxPersistConnsPerProxy; mMaxRequestDelay = maxRequestDelay; mMaxPipelinedRequests = maxPipelinedRequests; mMaxOptimisticPipelinedRequests = maxOptimisticPipelinedRequests; mIsShuttingDown = false; } return EnsureSocketThreadTargetIfOnline(); } nsresult nsHttpConnectionMgr::Shutdown() { LOG(("nsHttpConnectionMgr::Shutdown\n")); ReentrantMonitorAutoEnter mon(mReentrantMonitor); // do nothing if already shutdown if (!mSocketThreadTarget) return NS_OK; nsresult rv = PostEvent(&nsHttpConnectionMgr::OnMsgShutdown); // release our reference to the STS to prevent further events // from being posted. this is how we indicate that we are // shutting down. mIsShuttingDown = true; mSocketThreadTarget = 0; if (NS_FAILED(rv)) { NS_WARNING("unable to post SHUTDOWN message"); return rv; } // wait for shutdown event to complete mon.Wait(); return NS_OK; } nsresult nsHttpConnectionMgr::PostEvent(nsConnEventHandler handler, PRInt32 iparam, void *vparam) { // This object doesn't get reinitialized if the offline state changes, so our // socket thread target might be uninitialized if we were offline when this // object was being initialized, and we go online later on. This call takes // care of initializing the socket thread target if that's the case. EnsureSocketThreadTargetIfOnline(); ReentrantMonitorAutoEnter mon(mReentrantMonitor); nsresult rv; if (!mSocketThreadTarget) { NS_WARNING("cannot post event if not initialized"); rv = NS_ERROR_NOT_INITIALIZED; } else { nsRefPtr event = new nsConnEvent(this, handler, iparam, vparam); rv = mSocketThreadTarget->Dispatch(event, NS_DISPATCH_NORMAL); } return rv; } void nsHttpConnectionMgr::PruneDeadConnectionsAfter(PRUint32 timeInSeconds) { LOG(("nsHttpConnectionMgr::PruneDeadConnectionsAfter\n")); if(!mTimer) mTimer = do_CreateInstance("@mozilla.org/timer;1"); // failure to create a timer is not a fatal error, but idle connections // will not be cleaned up until we try to use them. if (mTimer) { mTimeOfNextWakeUp = timeInSeconds + NowInSeconds(); mTimer->Init(this, timeInSeconds*1000, nsITimer::TYPE_ONE_SHOT); } else { NS_WARNING("failed to create: timer for pruning the dead connections!"); } } void nsHttpConnectionMgr::ConditionallyStopPruneDeadConnectionsTimer() { // Leave the timer in place if there are connections that potentially // need management if (mNumIdleConns || (mNumActiveConns && gHttpHandler->IsSpdyEnabled())) return; LOG(("nsHttpConnectionMgr::StopPruneDeadConnectionsTimer\n")); // Reset mTimeOfNextWakeUp so that we can find a new shortest value. mTimeOfNextWakeUp = LL_MAXUINT; if (mTimer) { mTimer->Cancel(); mTimer = NULL; } } //----------------------------------------------------------------------------- // nsHttpConnectionMgr::nsIObserver //----------------------------------------------------------------------------- NS_IMETHODIMP nsHttpConnectionMgr::Observe(nsISupports *subject, const char *topic, const PRUnichar *data) { LOG(("nsHttpConnectionMgr::Observe [topic=\"%s\"]\n", topic)); if (0 == strcmp(topic, NS_TIMER_CALLBACK_TOPIC)) { nsCOMPtr timer = do_QueryInterface(subject); if (timer == mTimer) { PruneDeadConnections(); } else if (timer == mReadTimeoutTick) { ReadTimeoutTick(); } else { NS_ABORT_IF_FALSE(false, "unexpected timer-callback"); LOG(("Unexpected timer object\n")); return NS_ERROR_UNEXPECTED; } } return NS_OK; } //----------------------------------------------------------------------------- nsresult nsHttpConnectionMgr::AddTransaction(nsHttpTransaction *trans, PRInt32 priority) { LOG(("nsHttpConnectionMgr::AddTransaction [trans=%x %d]\n", trans, priority)); NS_ADDREF(trans); nsresult rv = PostEvent(&nsHttpConnectionMgr::OnMsgNewTransaction, priority, trans); if (NS_FAILED(rv)) NS_RELEASE(trans); return rv; } nsresult nsHttpConnectionMgr::RescheduleTransaction(nsHttpTransaction *trans, PRInt32 priority) { LOG(("nsHttpConnectionMgr::RescheduleTransaction [trans=%x %d]\n", trans, priority)); NS_ADDREF(trans); nsresult rv = PostEvent(&nsHttpConnectionMgr::OnMsgReschedTransaction, priority, trans); if (NS_FAILED(rv)) NS_RELEASE(trans); return rv; } nsresult nsHttpConnectionMgr::CancelTransaction(nsHttpTransaction *trans, nsresult reason) { LOG(("nsHttpConnectionMgr::CancelTransaction [trans=%x reason=%x]\n", trans, reason)); NS_ADDREF(trans); nsresult rv = PostEvent(&nsHttpConnectionMgr::OnMsgCancelTransaction, reason, trans); if (NS_FAILED(rv)) NS_RELEASE(trans); return rv; } nsresult nsHttpConnectionMgr::PruneDeadConnections() { return PostEvent(&nsHttpConnectionMgr::OnMsgPruneDeadConnections); } nsresult nsHttpConnectionMgr::ClosePersistentConnections() { return PostEvent(&nsHttpConnectionMgr::OnMsgClosePersistentConnections); } nsresult nsHttpConnectionMgr::GetSocketThreadTarget(nsIEventTarget **target) { // This object doesn't get reinitialized if the offline state changes, so our // socket thread target might be uninitialized if we were offline when this // object was being initialized, and we go online later on. This call takes // care of initializing the socket thread target if that's the case. EnsureSocketThreadTargetIfOnline(); ReentrantMonitorAutoEnter mon(mReentrantMonitor); NS_IF_ADDREF(*target = mSocketThreadTarget); return NS_OK; } nsresult nsHttpConnectionMgr::ReclaimConnection(nsHttpConnection *conn) { LOG(("nsHttpConnectionMgr::ReclaimConnection [conn=%x]\n", conn)); NS_ADDREF(conn); nsresult rv = PostEvent(&nsHttpConnectionMgr::OnMsgReclaimConnection, 0, conn); if (NS_FAILED(rv)) NS_RELEASE(conn); return rv; } nsresult nsHttpConnectionMgr::UpdateParam(nsParamName name, PRUint16 value) { PRUint32 param = (PRUint32(name) << 16) | PRUint32(value); return PostEvent(&nsHttpConnectionMgr::OnMsgUpdateParam, 0, (void *) param); } nsresult nsHttpConnectionMgr::ProcessPendingQ(nsHttpConnectionInfo *ci) { LOG(("nsHttpConnectionMgr::ProcessPendingQ [ci=%s]\n", ci->HashKey().get())); NS_ADDREF(ci); nsresult rv = PostEvent(&nsHttpConnectionMgr::OnMsgProcessPendingQ, 0, ci); if (NS_FAILED(rv)) NS_RELEASE(ci); return rv; } // Given a nsHttpConnectionInfo find the connection entry object that // contains either the nshttpconnection or nshttptransaction parameter. // Normally this is done by the hashkey lookup of connectioninfo, // but if spdy coalescing is in play it might be found in a redirected // entry nsHttpConnectionMgr::nsConnectionEntry * nsHttpConnectionMgr::LookupConnectionEntry(nsHttpConnectionInfo *ci, nsHttpConnection *conn, nsHttpTransaction *trans) { if (!ci) return nsnull; nsConnectionEntry *ent = mCT.Get(ci->HashKey()); // If there is no sign of coalescing (or it is disabled) then just // return the primary hash lookup if (!ent || !ent->mUsingSpdy || ent->mCoalescingKey.IsEmpty()) return ent; // If there is no preferred coalescing entry for this host (or the // preferred entry is the one that matched the mCT hash lookup) then // there is only option nsConnectionEntry *preferred = mSpdyPreferredHash.Get(ent->mCoalescingKey); if (!preferred || (preferred == ent)) return ent; if (conn) { // The connection could be either in preferred or ent. It is most // likely the only active connection in preferred - so start with that. if (preferred->mActiveConns.Contains(conn)) return preferred; if (preferred->mIdleConns.Contains(conn)) return preferred; } if (trans && preferred->mPendingQ.Contains(trans)) return preferred; // Neither conn nor trans found in preferred, use the default entry return ent; } nsresult nsHttpConnectionMgr::CloseIdleConnection(nsHttpConnection *conn) { NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); LOG(("nsHttpConnectionMgr::CloseIdleConnection %p conn=%p", this, conn)); if (!conn->ConnectionInfo()) return NS_ERROR_UNEXPECTED; nsConnectionEntry *ent = LookupConnectionEntry(conn->ConnectionInfo(), conn, nsnull); if (!ent || !ent->mIdleConns.RemoveElement(conn)) return NS_ERROR_UNEXPECTED; conn->Close(NS_ERROR_ABORT); NS_RELEASE(conn); mNumIdleConns--; ConditionallyStopPruneDeadConnectionsTimer(); return NS_OK; } // This function lets a connection, after completing the NPN phase, // report whether or not it is using spdy through the usingSpdy // argument. It would not be necessary if NPN were driven out of // the connection manager. The connection entry associated with the // connection is then updated to indicate whether or not we want to use // spdy with that host and update the preliminary preferred host // entries used for de-sharding hostsnames. void nsHttpConnectionMgr::ReportSpdyConnection(nsHttpConnection *conn, bool usingSpdy) { NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); nsConnectionEntry *ent = LookupConnectionEntry(conn->ConnectionInfo(), conn, nsnull); if (!ent) return; ent->mTestedSpdy = true; if (!usingSpdy) return; ent->mUsingSpdy = true; PRUint32 ttl = conn->TimeToLive(); PRUint64 timeOfExpire = NowInSeconds() + ttl; if (!mTimer || timeOfExpire < mTimeOfNextWakeUp) PruneDeadConnectionsAfter(ttl); // Lookup preferred directly from the hash instead of using // GetSpdyPreferredEnt() because we want to avoid the cert compatibility // check at this point because the cert is never part of the hash // lookup. Filtering on that has to be done at the time of use // rather than the time of registration (i.e. now). nsConnectionEntry *preferred = mSpdyPreferredHash.Get(ent->mCoalescingKey); LOG(("ReportSpdyConnection %s %s ent=%p preferred=%p\n", ent->mConnInfo->Host(), ent->mCoalescingKey.get(), ent, preferred)); if (!preferred) { if (!ent->mCoalescingKey.IsEmpty()) { mSpdyPreferredHash.Put(ent->mCoalescingKey, ent); ent->mSpdyPreferred = true; preferred = ent; } } else if (preferred != ent) { // A different hostname is the preferred spdy host for this // IP address. That preferred mapping must have been setup while // this connection was negotiating NPN. // Call don't reuse on the current connection to shut it down as soon // as possible without causing any errors. // i.e. the current transaction(s) on this connection will be processed // normally, but then it will go away and future connections will be // coalesced through the preferred entry. conn->DontReuse(); } ProcessAllSpdyPendingQ(); } bool nsHttpConnectionMgr::GetSpdyAlternateProtocol(nsACString &hostPortKey) { if (!gHttpHandler->UseAlternateProtocol()) return false; // The Alternate Protocol hash is protected under the monitor because // it is read from both the main and the network thread. ReentrantMonitorAutoEnter mon(mReentrantMonitor); return mAlternateProtocolHash.Contains(hostPortKey); } void nsHttpConnectionMgr::ReportSpdyAlternateProtocol(nsHttpConnection *conn) { // Check network.http.spdy.use-alternate-protocol pref if (!gHttpHandler->UseAlternateProtocol()) return; // For now lets not bypass proxies due to the alternate-protocol header if (conn->ConnectionInfo()->UsingHttpProxy()) return; nsCString hostPortKey(conn->ConnectionInfo()->Host()); if (conn->ConnectionInfo()->Port() != 80) { hostPortKey.Append(NS_LITERAL_CSTRING(":")); hostPortKey.AppendInt(conn->ConnectionInfo()->Port()); } // The Alternate Protocol hash is protected under the monitor because // it is read from both the main and the network thread. ReentrantMonitorAutoEnter mon(mReentrantMonitor); // Check to see if this is already present if (mAlternateProtocolHash.Contains(hostPortKey)) return; if (mAlternateProtocolHash.Count() > 2000) mAlternateProtocolHash.EnumerateEntries(&TrimAlternateProtocolHash, this); mAlternateProtocolHash.PutEntry(hostPortKey); } void nsHttpConnectionMgr::RemoveSpdyAlternateProtocol(nsACString &hostPortKey) { // The Alternate Protocol hash is protected under the monitor because // it is read from both the main and the network thread. ReentrantMonitorAutoEnter mon(mReentrantMonitor); return mAlternateProtocolHash.RemoveEntry(hostPortKey); } PLDHashOperator nsHttpConnectionMgr::TrimAlternateProtocolHash(nsCStringHashKey *entry, void *closure) { nsHttpConnectionMgr *self = (nsHttpConnectionMgr *) closure; if (self->mAlternateProtocolHash.Count() > 2000) return PL_DHASH_REMOVE; return PL_DHASH_STOP; } nsHttpConnectionMgr::nsConnectionEntry * nsHttpConnectionMgr::GetSpdyPreferredEnt(nsConnectionEntry *aOriginalEntry) { if (!gHttpHandler->IsSpdyEnabled() || !gHttpHandler->CoalesceSpdy() || aOriginalEntry->mCoalescingKey.IsEmpty()) return nsnull; nsConnectionEntry *preferred = mSpdyPreferredHash.Get(aOriginalEntry->mCoalescingKey); // if there is no redirection no cert validation is required if (preferred == aOriginalEntry) return aOriginalEntry; // if there is no preferred host or it is no longer using spdy // then skip pooling if (!preferred || !preferred->mUsingSpdy) return nsnull; // if there is not an active spdy session in this entry then // we cannot pool because the cert upon activation may not // be the same as the old one. Active sessions are prohibited // from changing certs. nsHttpConnection *activeSpdy = nsnull; for (PRUint32 index = 0; index < preferred->mActiveConns.Length(); ++index) { if (preferred->mActiveConns[index]->CanDirectlyActivate()) { activeSpdy = preferred->mActiveConns[index]; break; } } if (!activeSpdy) { // remove the preferred status of this entry if it cannot be // used for pooling. preferred->mSpdyPreferred = false; RemoveSpdyPreferredEnt(preferred->mCoalescingKey); LOG(("nsHttpConnectionMgr::GetSpdyPreferredConnection " "preferred host mapping %s to %s removed due to inactivity.\n", aOriginalEntry->mConnInfo->Host(), preferred->mConnInfo->Host())); return nsnull; } // Check that the server cert supports redirection nsresult rv; bool isJoined = false; nsCOMPtr securityInfo; nsCOMPtr sslSocketControl; nsCAutoString negotiatedNPN; activeSpdy->GetSecurityInfo(getter_AddRefs(securityInfo)); if (!securityInfo) { NS_WARNING("cannot obtain spdy security info"); return nsnull; } sslSocketControl = do_QueryInterface(securityInfo, &rv); if (NS_FAILED(rv)) { NS_WARNING("sslSocketControl QI Failed"); return nsnull; } rv = sslSocketControl->JoinConnection(NS_LITERAL_CSTRING("spdy/2"), aOriginalEntry->mConnInfo->GetHost(), aOriginalEntry->mConnInfo->Port(), &isJoined); if (NS_FAILED(rv) || !isJoined) { LOG(("nsHttpConnectionMgr::GetSpdyPreferredConnection " "Host %s cannot be confirmed to be joined " "with %s connections. rv=%x isJoined=%d", preferred->mConnInfo->Host(), aOriginalEntry->mConnInfo->Host(), rv, isJoined)); mozilla::Telemetry::Accumulate(mozilla::Telemetry::SPDY_NPN_JOIN, false); return nsnull; } // IP pooling confirmed LOG(("nsHttpConnectionMgr::GetSpdyPreferredConnection " "Host %s has cert valid for %s connections, " "so %s will be coalesced with %s", preferred->mConnInfo->Host(), aOriginalEntry->mConnInfo->Host(), aOriginalEntry->mConnInfo->Host(), preferred->mConnInfo->Host())); mozilla::Telemetry::Accumulate(mozilla::Telemetry::SPDY_NPN_JOIN, true); return preferred; } void nsHttpConnectionMgr::RemoveSpdyPreferredEnt(nsACString &aHashKey) { if (aHashKey.IsEmpty()) return; mSpdyPreferredHash.Remove(aHashKey); } //----------------------------------------------------------------------------- // enumeration callbacks PLDHashOperator nsHttpConnectionMgr::ProcessOneTransactionCB(const nsACString &key, nsAutoPtr &ent, void *closure) { nsHttpConnectionMgr *self = (nsHttpConnectionMgr *) closure; if (self->ProcessPendingQForEntry(ent)) return PL_DHASH_STOP; return PL_DHASH_NEXT; } // If the global number of idle connections is preventing the opening of // new connections to a host without idle connections, then // close them regardless of their TTL PLDHashOperator nsHttpConnectionMgr::PurgeExcessIdleConnectionsCB(const nsACString &key, nsAutoPtr &ent, void *closure) { nsHttpConnectionMgr *self = (nsHttpConnectionMgr *) closure; while (self->mNumIdleConns + self->mNumActiveConns + 1 >= self->mMaxConns) { if (!ent->mIdleConns.Length()) { // There are no idle conns left in this connection entry return PL_DHASH_NEXT; } nsHttpConnection *conn = ent->mIdleConns[0]; ent->mIdleConns.RemoveElementAt(0); conn->Close(NS_ERROR_ABORT); NS_RELEASE(conn); self->mNumIdleConns--; self->ConditionallyStopPruneDeadConnectionsTimer(); } return PL_DHASH_STOP; } PLDHashOperator nsHttpConnectionMgr::PruneDeadConnectionsCB(const nsACString &key, nsAutoPtr &ent, void *closure) { nsHttpConnectionMgr *self = (nsHttpConnectionMgr *) closure; LOG((" pruning [ci=%s]\n", ent->mConnInfo->HashKey().get())); // Find out how long it will take for next idle connection to not be reusable // anymore. PRUint32 timeToNextExpire = PR_UINT32_MAX; PRInt32 count = ent->mIdleConns.Length(); if (count > 0) { for (PRInt32 i=count-1; i>=0; --i) { nsHttpConnection *conn = ent->mIdleConns[i]; if (!conn->CanReuse()) { ent->mIdleConns.RemoveElementAt(i); conn->Close(NS_ERROR_ABORT); NS_RELEASE(conn); self->mNumIdleConns--; } else { timeToNextExpire = NS_MIN(timeToNextExpire, conn->TimeToLive()); } } } if (ent->mUsingSpdy) { for (PRUint32 index = 0; index < ent->mActiveConns.Length(); ++index) { nsHttpConnection *conn = ent->mActiveConns[index]; if (conn->UsingSpdy()) { if (!conn->CanReuse()) { // marking it dont reuse will create an active tear down if // the spdy session is idle. conn->DontReuse(); } else { timeToNextExpire = NS_MIN(timeToNextExpire, conn->TimeToLive()); } } } } // If time to next expire found is shorter than time to next wake-up, we need to // change the time for next wake-up. if (timeToNextExpire != PR_UINT32_MAX) { PRUint32 now = NowInSeconds(); PRUint64 timeOfNextExpire = now + timeToNextExpire; // If pruning of dead connections is not already scheduled to happen // or time found for next connection to expire is is before // mTimeOfNextWakeUp, we need to schedule the pruning to happen // after timeToNextExpire. if (!self->mTimer || timeOfNextExpire < self->mTimeOfNextWakeUp) { self->PruneDeadConnectionsAfter(timeToNextExpire); } } else { self->ConditionallyStopPruneDeadConnectionsTimer(); } // if this entry is empty, we have too many entries, // and this doesn't represent some painfully determined // red condition, then we can clean it up and restart from // yellow if (ent->PipelineState() != PS_RED && self->mCT.Count() > 125 && ent->mIdleConns.Length() == 0 && ent->mActiveConns.Length() == 0 && ent->mHalfOpens.Length() == 0 && ent->mPendingQ.Length() == 0 && ((!ent->mTestedSpdy && !ent->mUsingSpdy) || !gHttpHandler->IsSpdyEnabled() || self->mCT.Count() > 300)) { LOG((" removing empty connection entry\n")); return PL_DHASH_REMOVE; } // otherwise use this opportunity to compact our arrays... ent->mIdleConns.Compact(); ent->mActiveConns.Compact(); ent->mPendingQ.Compact(); return PL_DHASH_NEXT; } PLDHashOperator nsHttpConnectionMgr::ShutdownPassCB(const nsACString &key, nsAutoPtr &ent, void *closure) { nsHttpConnectionMgr *self = (nsHttpConnectionMgr *) closure; nsHttpTransaction *trans; nsHttpConnection *conn; // close all active connections while (ent->mActiveConns.Length()) { conn = ent->mActiveConns[0]; ent->mActiveConns.RemoveElementAt(0); self->mNumActiveConns--; conn->Close(NS_ERROR_ABORT); NS_RELEASE(conn); } // close all idle connections while (ent->mIdleConns.Length()) { conn = ent->mIdleConns[0]; ent->mIdleConns.RemoveElementAt(0); self->mNumIdleConns--; conn->Close(NS_ERROR_ABORT); NS_RELEASE(conn); } // If all idle connections are removed, // we can stop pruning dead connections. self->ConditionallyStopPruneDeadConnectionsTimer(); // close all pending transactions while (ent->mPendingQ.Length()) { trans = ent->mPendingQ[0]; ent->mPendingQ.RemoveElementAt(0); trans->Close(NS_ERROR_ABORT); NS_RELEASE(trans); } // close all half open tcp connections for (PRInt32 i = ((PRInt32) ent->mHalfOpens.Length()) - 1; i >= 0; i--) ent->mHalfOpens[i]->Abandon(); return PL_DHASH_REMOVE; } //----------------------------------------------------------------------------- bool nsHttpConnectionMgr::ProcessPendingQForEntry(nsConnectionEntry *ent) { NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); LOG(("nsHttpConnectionMgr::ProcessPendingQForEntry [ci=%s]\n", ent->mConnInfo->HashKey().get())); ProcessSpdyPendingQ(ent); PRUint32 count = ent->mPendingQ.Length(); nsHttpTransaction *trans; nsresult rv; bool dispatchedSuccessfully = false; // iterate the pending list until one is dispatched successfully. Keep // iterating afterwards only until a transaction fails to dispatch. for (PRUint32 i = 0; i < count; ++i) { trans = ent->mPendingQ[i]; // When this transaction has already established a half-open // connection, we want to prevent any duplicate half-open // connections from being established and bound to this // transaction. Allow only use of an idle persistent connection // (if found) for transactions referred by a half-open connection. bool alreadyHalfOpen = false; for (PRInt32 j = 0; j < ((PRInt32) ent->mHalfOpens.Length()); ++j) { if (ent->mHalfOpens[j]->Transaction() == trans) { alreadyHalfOpen = true; break; } } rv = TryDispatchTransaction(ent, alreadyHalfOpen, trans); if (NS_SUCCEEDED(rv)) { LOG((" dispatching pending transaction...\n")); ent->mPendingQ.RemoveElementAt(i); NS_RELEASE(trans); // reset index and array length after RemoveElelmentAt() dispatchedSuccessfully = true; count = ent->mPendingQ.Length(); --i; continue; } if (dispatchedSuccessfully) return true; NS_ABORT_IF_FALSE(count == ((PRInt32) ent->mPendingQ.Length()), "something mutated pending queue from " "GetConnection()"); } return false; } bool nsHttpConnectionMgr::ProcessPendingQForEntry(nsHttpConnectionInfo *ci) { NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); nsConnectionEntry *ent = mCT.Get(ci->HashKey()); if (ent) return ProcessPendingQForEntry(ent); return false; } bool nsHttpConnectionMgr::SupportsPipelining(nsHttpConnectionInfo *ci) { NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); nsConnectionEntry *ent = mCT.Get(ci->HashKey()); if (ent) return ent->SupportsPipelining(); return false; } // nsHttpPipelineFeedback used to hold references across events class nsHttpPipelineFeedback { public: nsHttpPipelineFeedback(nsHttpConnectionInfo *ci, nsHttpConnectionMgr::PipelineFeedbackInfoType info, nsHttpConnection *conn, PRUint32 data) : mConnInfo(ci) , mConn(conn) , mInfo(info) , mData(data) { } ~nsHttpPipelineFeedback() { } nsRefPtr mConnInfo; nsRefPtr mConn; nsHttpConnectionMgr::PipelineFeedbackInfoType mInfo; PRUint32 mData; }; void nsHttpConnectionMgr::PipelineFeedbackInfo(nsHttpConnectionInfo *ci, PipelineFeedbackInfoType info, nsHttpConnection *conn, PRUint32 data) { if (!ci) return; // Post this to the socket thread if we are not running there already if (PR_GetCurrentThread() != gSocketThread) { nsHttpPipelineFeedback *fb = new nsHttpPipelineFeedback(ci, info, conn, data); nsresult rv = PostEvent(&nsHttpConnectionMgr::OnMsgProcessFeedback, 0, fb); if (NS_FAILED(rv)) delete fb; return; } nsConnectionEntry *ent = mCT.Get(ci->HashKey()); if (ent) ent->OnPipelineFeedbackInfo(info, conn, data); } // we're at the active connection limit if any one of the following conditions is true: // (1) at max-connections // (2) keep-alive enabled and at max-persistent-connections-per-server/proxy // (3) keep-alive disabled and at max-connections-per-server bool nsHttpConnectionMgr::AtActiveConnectionLimit(nsConnectionEntry *ent, PRUint8 caps) { nsHttpConnectionInfo *ci = ent->mConnInfo; LOG(("nsHttpConnectionMgr::AtActiveConnectionLimit [ci=%s caps=%x]\n", ci->HashKey().get(), caps)); // update maxconns if potentially limited by the max socket count // this requires a dynamic reduction in the max socket count to a point // lower than the max-connections pref. PRUint32 maxSocketCount = gHttpHandler->MaxSocketCount(); if (mMaxConns > maxSocketCount) { mMaxConns = maxSocketCount; LOG(("nsHttpConnectionMgr %p mMaxConns dynamically reduced to %u", this, mMaxConns)); } // If there are more active connections than the global limit, then we're // done. Purging idle connections won't get us below it. if (mNumActiveConns >= mMaxConns) { LOG((" num active conns == max conns\n")); return true; } nsHttpConnection *conn; PRInt32 i, totalCount, persistCount = 0; totalCount = ent->mActiveConns.Length(); // count the number of persistent connections for (i=0; imActiveConns[i]; if (conn->IsKeepAlive()) // XXX make sure this is thread-safe persistCount++; } // Add in the in-progress tcp connections, we will assume they are // keepalive enabled. totalCount += ent->mHalfOpens.Length(); persistCount += ent->mHalfOpens.Length(); LOG((" total=%d, persist=%d\n", totalCount, persistCount)); PRUint16 maxConns; PRUint16 maxPersistConns; if (ci->UsingHttpProxy() && !ci->UsingSSL()) { maxConns = mMaxConnsPerProxy; maxPersistConns = mMaxPersistConnsPerProxy; } else { maxConns = mMaxConnsPerHost; maxPersistConns = mMaxPersistConnsPerHost; } // use >= just to be safe return (totalCount >= maxConns) || ( (caps & NS_HTTP_ALLOW_KEEPALIVE) && (persistCount >= maxPersistConns) ); } void nsHttpConnectionMgr::ClosePersistentConnections(nsConnectionEntry *ent) { LOG(("nsHttpConnectionMgr::ClosePersistentConnections [ci=%s]\n", ent->mConnInfo->HashKey().get())); while (ent->mIdleConns.Length()) { nsHttpConnection *conn = ent->mIdleConns[0]; ent->mIdleConns.RemoveElementAt(0); mNumIdleConns--; conn->Close(NS_ERROR_ABORT); NS_RELEASE(conn); } PRInt32 activeCount = ent->mActiveConns.Length(); for (PRInt32 i=0; i < activeCount; i++) ent->mActiveConns[i]->DontReuse(); } PLDHashOperator nsHttpConnectionMgr::ClosePersistentConnectionsCB(const nsACString &key, nsAutoPtr &ent, void *closure) { nsHttpConnectionMgr *self = static_cast(closure); self->ClosePersistentConnections(ent); return PL_DHASH_NEXT; } bool nsHttpConnectionMgr::MakeNewConnection(nsConnectionEntry *ent, nsHttpTransaction *trans) { LOG(("nsHttpConnectionMgr::MakeNewConnection %p ent=%p trans=%p", this, ent, trans)); NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); // If this host is trying to negotiate a SPDY session right now, // don't create any new connections until the result of the // negotiation is known. if (gHttpHandler->IsSpdyEnabled() && ent->mConnInfo->UsingSSL() && !ent->mConnInfo->UsingHttpProxy() && !(trans->Caps() & NS_HTTP_DISALLOW_SPDY) && (!ent->mTestedSpdy || ent->mUsingSpdy) && (ent->mHalfOpens.Length() || ent->mActiveConns.Length())) { return false; } // We need to make a new connection. If that is going to exceed the // global connection limit then try and free up some room by closing // an idle connection to another host. We know it won't select "ent" // beacuse we have already determined there are no idle connections // to our destination if ((mNumIdleConns + mNumActiveConns + 1 >= mMaxConns) && mNumIdleConns) mCT.Enumerate(PurgeExcessIdleConnectionsCB, this); if (AtActiveConnectionLimit(ent, trans->Caps())) return false; nsresult rv = CreateTransport(ent, trans); if (NS_FAILED(rv)) /* hard failure */ trans->Close(rv); return true; } bool nsHttpConnectionMgr::AddToShortestPipeline(nsConnectionEntry *ent, nsHttpTransaction *trans, nsHttpTransaction::Classifier classification, PRUint16 depthLimit) { if (classification == nsAHttpTransaction::CLASS_SOLO) return false; PRUint32 maxdepth = ent->MaxPipelineDepth(classification); if (maxdepth == 0) { ent->CreditPenalty(); maxdepth = ent->MaxPipelineDepth(classification); } if (ent->PipelineState() == PS_RED) return false; if (ent->PipelineState() == PS_YELLOW && ent->mYellowConnection) return false; // The maximum depth of a pipeline in yellow is 1 pipeline of // depth 2 for entire CI. When that transaction completes successfully // we transition to green and that expands the allowed depth // to any number of pipelines of up to depth 4. When a transaction // queued at position 3 or deeper succeeds we open it all the way // up to depths limited only by configuration. The staggered start // in green is simply because a successful yellow test of depth=2 // might really just be a race condition (i.e. depth=1 from the // server's point of view), while depth=3 is a stronger indicator - // keeping the pipelines to a modest depth during that period limits // the damage if something is going to go wrong. maxdepth = PR_MIN(maxdepth, depthLimit); if (maxdepth < 2) return false; nsAHttpTransaction *activeTrans; nsHttpConnection *bestConn = nsnull; PRUint32 activeCount = ent->mActiveConns.Length(); PRUint32 bestConnLength = 0; PRUint32 connLength; for (PRUint32 i = 0; i < activeCount; ++i) { nsHttpConnection *conn = ent->mActiveConns[i]; if (!conn->SupportsPipelining()) continue; if (conn->Classification() != classification) continue; activeTrans = conn->Transaction(); if (!activeTrans || activeTrans->IsDone() || NS_FAILED(activeTrans->Status())) continue; connLength = activeTrans->PipelineDepth(); if (maxdepth <= connLength) continue; if (!bestConn || (connLength < bestConnLength)) { bestConn = conn; bestConnLength = connLength; } } if (!bestConn) return false; activeTrans = bestConn->Transaction(); nsresult rv = activeTrans->AddTransaction(trans); if (NS_FAILED(rv)) return false; LOG((" scheduling trans %p on pipeline at position %d\n", trans, trans->PipelinePosition())); if ((ent->PipelineState() == PS_YELLOW) && (trans->PipelinePosition() > 1)) ent->SetYellowConnection(bestConn); return true; } bool nsHttpConnectionMgr::IsUnderPressure(nsConnectionEntry *ent, nsHttpTransaction::Classifier classification) { // A connection entry is declared to be "under pressure" if most of the // allowed parallel connections are already used up. In that case we want to // favor existing pipelines over more parallelism so as to reserve any // unused parallel connections for types that don't have existing pipelines. // // The defintion of connection pressure is a pretty liberal one here - that // is why we are using the more restrictive maxPersist* counters. // // Pipelines are also favored when the requested classification is already // using 3 or more of the connections. Failure to do this could result in // one class (e.g. images) establishing self replenishing queues on all the // connections that would starve the other transaction types. PRInt32 currentConns = ent->mActiveConns.Length(); PRInt32 maxConns = (ent->mConnInfo->UsingHttpProxy() && !ent->mConnInfo->UsingSSL()) ? mMaxPersistConnsPerProxy : mMaxPersistConnsPerHost; // Leave room for at least 3 distinct types to operate concurrently, // this satisfies the typical {html, js/css, img} page. if (currentConns >= (maxConns - 2)) return true; /* prefer pipeline */ PRInt32 sameClass = 0; for (PRInt32 i = 0; i < currentConns; ++i) if (classification == ent->mActiveConns[i]->Classification()) if (++sameClass == 3) return true; /* prefer pipeline */ return false; /* normal behavior */ } // returns OK if a connection is found for the transaction // and the transaction is started. // returns ERROR_NOT_AVAILABLE if no connection can be found and it // should be queued nsresult nsHttpConnectionMgr::TryDispatchTransaction(nsConnectionEntry *ent, bool onlyReusedConnection, nsHttpTransaction *trans) { NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); LOG(("nsHttpConnectionMgr::TryDispatchTransaction without conn " "[ci=%s caps=%x]\n", ent->mConnInfo->HashKey().get(), PRUint32(trans->Caps()))); nsHttpTransaction::Classifier classification = trans->Classification(); PRUint8 caps = trans->Caps(); // no keep-alive means no pipelines either if (!(caps & NS_HTTP_ALLOW_KEEPALIVE)) caps = caps & ~NS_HTTP_ALLOW_PIPELINING; // 0 - If this should use spdy then dispatch it post haste. // 1 - If there is connection pressure then see if we can pipeline this on // a connection of a matching type instead of using a new conn // 2 - If there is an idle connection, use it! // 3 - if class == reval or script and there is an open conn of that type // then pipeline onto shortest pipeline of that class if limits allow // 4 - If we aren't up against our connection limit, // then open a new one // 5 - Try a pipeline if we haven't already - this will be unusual because // it implies a low connection pressure situation where // MakeNewConnection() failed.. that is possible, but unlikely, due to // global limits // 6 - no connection is available - queue it bool attemptedOptimisticPipeline = !(caps & NS_HTTP_ALLOW_PIPELINING); // step 0 // look for existing spdy connection - that's always best because it is // essentially pipelining without head of line blocking if (!(caps & NS_HTTP_DISALLOW_SPDY) && gHttpHandler->IsSpdyEnabled()) { nsRefPtr conn = GetSpdyPreferredConn(ent); if (conn) { LOG((" dispatch to spdy: [conn=%x]\n", conn.get())); DispatchTransaction(ent, trans, conn); return NS_OK; } } // step 1 // If connection pressure, then we want to favor pipelining of any kind if (IsUnderPressure(ent, classification) && !attemptedOptimisticPipeline) { attemptedOptimisticPipeline = true; if (AddToShortestPipeline(ent, trans, classification, mMaxOptimisticPipelinedRequests)) { return NS_OK; } } // step 2 // consider an idle persistent connection if (caps & NS_HTTP_ALLOW_KEEPALIVE) { nsRefPtr conn; while (!conn && (ent->mIdleConns.Length() > 0)) { conn = ent->mIdleConns[0]; ent->mIdleConns.RemoveElementAt(0); mNumIdleConns--; nsHttpConnection *temp = conn; NS_RELEASE(temp); // we check if the connection can be reused before even checking if // it is a "matching" connection. if (!conn->CanReuse()) { LOG((" dropping stale connection: [conn=%x]\n", conn.get())); conn->Close(NS_ERROR_ABORT); conn = nsnull; } else { LOG((" reusing connection [conn=%x]\n", conn.get())); conn->EndIdleMonitoring(); } // If there are no idle connections left at all, we need to make // sure that we are not pruning dead connections anymore. ConditionallyStopPruneDeadConnectionsTimer(); } if (conn) { // This will update the class of the connection to be the class of // the transaction dispatched on it. AddActiveConn(conn, ent); DispatchTransaction(ent, trans, conn); return NS_OK; } } // step 3 // consider pipelining scripts and revalidations if (!attemptedOptimisticPipeline && (classification == nsHttpTransaction::CLASS_REVALIDATION || classification == nsHttpTransaction::CLASS_SCRIPT)) { attemptedOptimisticPipeline = true; if (AddToShortestPipeline(ent, trans, classification, mMaxOptimisticPipelinedRequests)) { return NS_OK; } } // step 4 if (!onlyReusedConnection && MakeNewConnection(ent, trans)) { return NS_ERROR_IN_PROGRESS; } // step 5 if (caps & NS_HTTP_ALLOW_PIPELINING) { if (AddToShortestPipeline(ent, trans, classification, mMaxPipelinedRequests)) { return NS_OK; } } // step 6 return NS_ERROR_NOT_AVAILABLE; /* queue it */ } nsresult nsHttpConnectionMgr::DispatchTransaction(nsConnectionEntry *ent, nsHttpTransaction *trans, nsHttpConnection *conn) { PRUint8 caps = trans->Caps(); PRInt32 priority = trans->Priority(); LOG(("nsHttpConnectionMgr::DispatchTransaction " "[ci=%s trans=%x caps=%x conn=%x priority=%d]\n", ent->mConnInfo->HashKey().get(), trans, caps, conn, priority)); if (conn->UsingSpdy()) { LOG(("Spdy Dispatch Transaction via Activate(). Transaction host = %s," "Connection host = %s\n", trans->ConnectionInfo()->Host(), conn->ConnectionInfo()->Host())); nsresult rv = conn->Activate(trans, caps, priority); NS_ABORT_IF_FALSE(NS_SUCCEEDED(rv), "SPDY Cannot Fail Dispatch"); return rv; } NS_ABORT_IF_FALSE(conn && !conn->Transaction(), "DispatchTranaction() on non spdy active connection"); /* Use pipeline datastructure even if connection does not currently qualify to pipeline this transaction because a different pipeline-eligible transaction might be placed on the active connection */ nsRefPtr pipeline; nsresult rv = BuildPipeline(ent, trans, getter_AddRefs(pipeline)); if (!NS_SUCCEEDED(rv)) return rv; nsRefPtr handle = new nsConnectionHandle(conn); // give the transaction the indirect reference to the connection. pipeline->SetConnection(handle); if (!(caps & NS_HTTP_ALLOW_PIPELINING)) conn->Classify(nsAHttpTransaction::CLASS_SOLO); else conn->Classify(trans->Classification()); rv = conn->Activate(pipeline, caps, priority); if (NS_FAILED(rv)) { LOG((" conn->Activate failed [rv=%x]\n", rv)); ent->mActiveConns.RemoveElement(conn); if (conn == ent->mYellowConnection) ent->OnYellowComplete(); mNumActiveConns--; // sever back references to connection, and do so without triggering // a call to ReclaimConnection ;-) pipeline->SetConnection(nsnull); NS_RELEASE(handle->mConn); // destroy the connection NS_RELEASE(conn); } // As pipeline goes out of scope it will drop the last refernece to the // pipeline if activation failed, in which case this will destroy // the pipeline, which will cause each the transactions owned by the // pipeline to be restarted. return rv; } nsresult nsHttpConnectionMgr::BuildPipeline(nsConnectionEntry *ent, nsAHttpTransaction *firstTrans, nsHttpPipeline **result) { NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); /* form a pipeline here even if nothing is pending so that we can stream-feed it as new transactions arrive */ /* the first transaction can go in unconditionally - 1 transaction on a nsHttpPipeline object is not a real HTTP pipeline */ nsRefPtr pipeline = new nsHttpPipeline(); pipeline->AddTransaction(firstTrans); NS_ADDREF(*result = pipeline); return NS_OK; } nsresult nsHttpConnectionMgr::ProcessNewTransaction(nsHttpTransaction *trans) { NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); // since "adds" and "cancels" are processed asynchronously and because // various events might trigger an "add" directly on the socket thread, // we must take care to avoid dispatching a transaction that has already // been canceled (see bug 190001). if (NS_FAILED(trans->Status())) { LOG((" transaction was canceled... dropping event!\n")); return NS_OK; } nsresult rv = NS_OK; nsHttpConnectionInfo *ci = trans->ConnectionInfo(); NS_ASSERTION(ci, "no connection info"); nsConnectionEntry *ent = mCT.Get(ci->HashKey()); if (!ent) { nsHttpConnectionInfo *clone = ci->Clone(); if (!clone) return NS_ERROR_OUT_OF_MEMORY; ent = new nsConnectionEntry(clone); mCT.Put(ci->HashKey(), ent); } // SPDY coalescing of hostnames means we might redirect from this // connection entry onto the preferred one. nsConnectionEntry *preferredEntry = GetSpdyPreferredEnt(ent); if (preferredEntry && (preferredEntry != ent)) { LOG(("nsHttpConnectionMgr::ProcessNewTransaction trans=%p " "redirected via coalescing from %s to %s\n", trans, ent->mConnInfo->Host(), preferredEntry->mConnInfo->Host())); ent = preferredEntry; } // If we are doing a force reload then close out any existing conns // to this host so that changes in DNS, LBs, etc.. are reflected if (trans->Caps() & NS_HTTP_CLEAR_KEEPALIVES) ClosePersistentConnections(ent); // Check if the transaction already has a sticky reference to a connection. // If so, then we can just use it directly by transferring its reference // to the new connection variable instead of searching for a new one nsAHttpConnection *wrappedConnection = trans->Connection(); nsRefPtr conn; if (wrappedConnection) conn = dont_AddRef(wrappedConnection->TakeHttpConnection()); if (conn) { NS_ASSERTION(trans->Caps() & NS_HTTP_STICKY_CONNECTION, "unexpected caps"); NS_ABORT_IF_FALSE(((PRInt32)ent->mActiveConns.IndexOf(conn)) != -1, "Sticky Connection Not In Active List"); trans->SetConnection(nsnull); rv = DispatchTransaction(ent, trans, conn); } else rv = TryDispatchTransaction(ent, false, trans); if (NS_FAILED(rv)) { LOG((" adding transaction to pending queue " "[trans=%p pending-count=%u]\n", trans, ent->mPendingQ.Length()+1)); // put this transaction on the pending queue... InsertTransactionSorted(ent->mPendingQ, trans); NS_ADDREF(trans); } return NS_OK; } void nsHttpConnectionMgr::AddActiveConn(nsHttpConnection *conn, nsConnectionEntry *ent) { NS_ADDREF(conn); ent->mActiveConns.AppendElement(conn); mNumActiveConns++; ActivateTimeoutTick(); } void nsHttpConnectionMgr::StartedConnect() { mNumActiveConns++; } void nsHttpConnectionMgr::RecvdConnect() { mNumActiveConns--; } nsresult nsHttpConnectionMgr::CreateTransport(nsConnectionEntry *ent, nsHttpTransaction *trans) { NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); nsRefPtr sock = new nsHalfOpenSocket(ent, trans); nsresult rv = sock->SetupPrimaryStreams(); NS_ENSURE_SUCCESS(rv, rv); ent->mHalfOpens.AppendElement(sock); return NS_OK; } // This function tries to dispatch the pending spdy transactions on // the connection entry sent in as an argument. It will do so on the // active spdy connection either in that same entry or in the // redirected 'preferred' entry for the same coalescing hash key if // coalescing is enabled. void nsHttpConnectionMgr::ProcessSpdyPendingQ(nsConnectionEntry *ent) { nsHttpConnection *conn = GetSpdyPreferredConn(ent); if (!conn) return; for (PRInt32 index = ent->mPendingQ.Length() - 1; index >= 0 && conn->CanDirectlyActivate(); --index) { nsHttpTransaction *trans = ent->mPendingQ[index]; if (!(trans->Caps() & NS_HTTP_ALLOW_KEEPALIVE) || trans->Caps() & NS_HTTP_DISALLOW_SPDY) continue; ent->mPendingQ.RemoveElementAt(index); nsresult rv = DispatchTransaction(ent, trans, conn); if (NS_FAILED(rv)) { // this cannot happen, but if due to some bug it does then // close the transaction NS_ABORT_IF_FALSE(false, "Dispatch SPDY Transaction"); LOG(("ProcessSpdyPendingQ Dispatch Transaction failed trans=%p\n", trans)); trans->Close(rv); } NS_RELEASE(trans); } } PLDHashOperator nsHttpConnectionMgr::ProcessSpdyPendingQCB(const nsACString &key, nsAutoPtr &ent, void *closure) { nsHttpConnectionMgr *self = (nsHttpConnectionMgr *) closure; self->ProcessSpdyPendingQ(ent); return PL_DHASH_NEXT; } void nsHttpConnectionMgr::ProcessAllSpdyPendingQ() { mCT.Enumerate(ProcessSpdyPendingQCB, this); } nsHttpConnection * nsHttpConnectionMgr::GetSpdyPreferredConn(nsConnectionEntry *ent) { NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); NS_ABORT_IF_FALSE(ent, "no connection entry"); nsConnectionEntry *preferred = GetSpdyPreferredEnt(ent); // this entry is spdy-enabled if it is involved in a redirect if (preferred) // all new connections for this entry will use spdy too ent->mUsingSpdy = true; else preferred = ent; nsHttpConnection *conn = nsnull; if (preferred->mUsingSpdy) { for (PRUint32 index = 0; index < preferred->mActiveConns.Length(); ++index) { if (preferred->mActiveConns[index]->CanDirectlyActivate()) { conn = preferred->mActiveConns[index]; break; } } } return conn; } //----------------------------------------------------------------------------- void nsHttpConnectionMgr::OnMsgShutdown(PRInt32, void *) { NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); LOG(("nsHttpConnectionMgr::OnMsgShutdown\n")); mCT.Enumerate(ShutdownPassCB, this); if (mReadTimeoutTick) { mReadTimeoutTick->Cancel(); mReadTimeoutTick = nsnull; mReadTimeoutTickArmed = false; } // signal shutdown complete ReentrantMonitorAutoEnter mon(mReentrantMonitor); mon.Notify(); } void nsHttpConnectionMgr::OnMsgNewTransaction(PRInt32 priority, void *param) { LOG(("nsHttpConnectionMgr::OnMsgNewTransaction [trans=%p]\n", param)); nsHttpTransaction *trans = (nsHttpTransaction *) param; trans->SetPriority(priority); nsresult rv = ProcessNewTransaction(trans); if (NS_FAILED(rv)) trans->Close(rv); // for whatever its worth NS_RELEASE(trans); } void nsHttpConnectionMgr::OnMsgReschedTransaction(PRInt32 priority, void *param) { NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); LOG(("nsHttpConnectionMgr::OnMsgReschedTransaction [trans=%p]\n", param)); nsHttpTransaction *trans = (nsHttpTransaction *) param; trans->SetPriority(priority); nsConnectionEntry *ent = LookupConnectionEntry(trans->ConnectionInfo(), nsnull, trans); if (ent) { PRInt32 index = ent->mPendingQ.IndexOf(trans); if (index >= 0) { ent->mPendingQ.RemoveElementAt(index); InsertTransactionSorted(ent->mPendingQ, trans); } } NS_RELEASE(trans); } void nsHttpConnectionMgr::OnMsgCancelTransaction(PRInt32 reason, void *param) { NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); LOG(("nsHttpConnectionMgr::OnMsgCancelTransaction [trans=%p]\n", param)); nsHttpTransaction *trans = (nsHttpTransaction *) param; // // if the transaction owns a connection and the transaction is not done, // then ask the connection to close the transaction. otherwise, close the // transaction directly (removing it from the pending queue first). // nsAHttpConnection *conn = trans->Connection(); if (conn && !trans->IsDone()) conn->CloseTransaction(trans, reason); else { nsConnectionEntry *ent = LookupConnectionEntry(trans->ConnectionInfo(), nsnull, trans); if (ent) { PRInt32 index = ent->mPendingQ.IndexOf(trans); if (index >= 0) { ent->mPendingQ.RemoveElementAt(index); nsHttpTransaction *temp = trans; NS_RELEASE(temp); // b/c NS_RELEASE nulls its argument! } } trans->Close(reason); } NS_RELEASE(trans); } void nsHttpConnectionMgr::OnMsgProcessPendingQ(PRInt32, void *param) { NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); nsHttpConnectionInfo *ci = (nsHttpConnectionInfo *) param; LOG(("nsHttpConnectionMgr::OnMsgProcessPendingQ [ci=%s]\n", ci->HashKey().get())); // start by processing the queue identified by the given connection info. nsConnectionEntry *ent = mCT.Get(ci->HashKey()); if (!(ent && ProcessPendingQForEntry(ent))) { // if we reach here, it means that we couldn't dispatch a transaction // for the specified connection info. walk the connection table... mCT.Enumerate(ProcessOneTransactionCB, this); } NS_RELEASE(ci); } void nsHttpConnectionMgr::OnMsgPruneDeadConnections(PRInt32, void *) { NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); LOG(("nsHttpConnectionMgr::OnMsgPruneDeadConnections\n")); // Reset mTimeOfNextWakeUp so that we can find a new shortest value. mTimeOfNextWakeUp = LL_MAXUINT; // check canreuse() for all idle connections plus any active connections on // connection entries that are using spdy. if (mNumIdleConns || (mNumActiveConns && gHttpHandler->IsSpdyEnabled())) mCT.Enumerate(PruneDeadConnectionsCB, this); } void nsHttpConnectionMgr::OnMsgClosePersistentConnections(PRInt32, void *) { LOG(("nsHttpConnectionMgr::OnMsgClosePersistentConnections\n")); mCT.Enumerate(ClosePersistentConnectionsCB, this); } void nsHttpConnectionMgr::OnMsgReclaimConnection(PRInt32, void *param) { NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); LOG(("nsHttpConnectionMgr::OnMsgReclaimConnection [conn=%p]\n", param)); nsHttpConnection *conn = (nsHttpConnection *) param; // // 1) remove the connection from the active list // 2) if keep-alive, add connection to idle list // 3) post event to process the pending transaction queue // nsConnectionEntry *ent = LookupConnectionEntry(conn->ConnectionInfo(), conn, nsnull); nsHttpConnectionInfo *ci = nsnull; if (!ent) { // this should never happen LOG(("nsHttpConnectionMgr::OnMsgReclaimConnection ent == null\n")); NS_ABORT_IF_FALSE(false, "no connection entry"); NS_ADDREF(ci = conn->ConnectionInfo()); } else { NS_ADDREF(ci = ent->mConnInfo); // If the connection is in the active list, remove that entry // and the reference held by the mActiveConns list. // This is never the final reference on conn as the event context // is also holding one that is released at the end of this function. if (ent->mUsingSpdy) { // Spdy connections aren't reused in the traditional HTTP way in // the idleconns list, they are actively multplexed as active // conns. Even when they have 0 transactions on them they are // considered active connections. So when one is reclaimed it // is really complete and is meant to be shut down and not // reused. conn->DontReuse(); } if (ent->mActiveConns.RemoveElement(conn)) { if (conn == ent->mYellowConnection) ent->OnYellowComplete(); nsHttpConnection *temp = conn; NS_RELEASE(temp); mNumActiveConns--; } if (conn->CanReuse()) { LOG((" adding connection to idle list\n")); // Keep The idle connection list sorted with the connections that // have moved the largest data pipelines at the front because these // connections have the largest cwnds on the server. // The linear search is ok here because the number of idleconns // in a single entry is generally limited to a small number (i.e. 6) PRUint32 idx; for (idx = 0; idx < ent->mIdleConns.Length(); idx++) { nsHttpConnection *idleConn = ent->mIdleConns[idx]; if (idleConn->MaxBytesRead() < conn->MaxBytesRead()) break; } NS_ADDREF(conn); ent->mIdleConns.InsertElementAt(idx, conn); mNumIdleConns++; conn->BeginIdleMonitoring(); // If the added connection was first idle connection or has shortest // time to live among the watched connections, pruning dead // connections needs to be done when it can't be reused anymore. PRUint32 timeToLive = conn->TimeToLive(); if(!mTimer || NowInSeconds() + timeToLive < mTimeOfNextWakeUp) PruneDeadConnectionsAfter(timeToLive); } else { LOG((" connection cannot be reused; closing connection\n")); conn->Close(NS_ERROR_ABORT); } } OnMsgProcessPendingQ(NS_OK, ci); // releases |ci| NS_RELEASE(conn); } void nsHttpConnectionMgr::OnMsgUpdateParam(PRInt32, void *param) { PRUint16 name = (NS_PTR_TO_INT32(param) & 0xFFFF0000) >> 16; PRUint16 value = NS_PTR_TO_INT32(param) & 0x0000FFFF; switch (name) { case MAX_CONNECTIONS: mMaxConns = value; break; case MAX_CONNECTIONS_PER_HOST: mMaxConnsPerHost = value; break; case MAX_CONNECTIONS_PER_PROXY: mMaxConnsPerProxy = value; break; case MAX_PERSISTENT_CONNECTIONS_PER_HOST: mMaxPersistConnsPerHost = value; break; case MAX_PERSISTENT_CONNECTIONS_PER_PROXY: mMaxPersistConnsPerProxy = value; break; case MAX_REQUEST_DELAY: mMaxRequestDelay = value; break; case MAX_PIPELINED_REQUESTS: mMaxPipelinedRequests = value; break; case MAX_OPTIMISTIC_PIPELINED_REQUESTS: mMaxOptimisticPipelinedRequests = value; break; default: NS_NOTREACHED("unexpected parameter name"); } } // nsHttpConnectionMgr::nsConnectionEntry nsHttpConnectionMgr::nsConnectionEntry::~nsConnectionEntry() { if (mSpdyPreferred) gHttpHandler->ConnMgr()->RemoveSpdyPreferredEnt(mCoalescingKey); NS_RELEASE(mConnInfo); } void nsHttpConnectionMgr::OnMsgProcessFeedback(PRInt32, void *param) { NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); nsHttpPipelineFeedback *fb = (nsHttpPipelineFeedback *)param; PipelineFeedbackInfo(fb->mConnInfo, fb->mInfo, fb->mConn, fb->mData); delete fb; } // Read Timeout Tick handlers void nsHttpConnectionMgr::ActivateTimeoutTick() { NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); LOG(("nsHttpConnectionMgr::ActivateTimeoutTick() " "this=%p mReadTimeoutTick=%p\n")); // right now the spdy timeout code is the only thing hooked to the timeout // tick, so disable it if spdy is not being used. However pipelining code // will also want this functionality soon. if (!gHttpHandler->IsSpdyEnabled()) return; // The timer tick should be enabled if it is not already pending. // Upon running the tick will rearm itself if there are active // connections available. if (mReadTimeoutTick && mReadTimeoutTickArmed) return; if (!mReadTimeoutTick) { mReadTimeoutTick = do_CreateInstance(NS_TIMER_CONTRACTID); if (!mReadTimeoutTick) { NS_WARNING("failed to create timer for http timeout management"); return; } } NS_ABORT_IF_FALSE(!mReadTimeoutTickArmed, "timer tick armed"); mReadTimeoutTickArmed = true; // pipeline will expect a 1000ms granuality mReadTimeoutTick->Init(this, 15000, nsITimer::TYPE_REPEATING_SLACK); } void nsHttpConnectionMgr::ReadTimeoutTick() { NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); NS_ABORT_IF_FALSE(mReadTimeoutTick, "no readtimeout tick"); LOG(("nsHttpConnectionMgr::ReadTimeoutTick active=%d\n", mNumActiveConns)); if (!mNumActiveConns && mReadTimeoutTickArmed) { mReadTimeoutTick->Cancel(); mReadTimeoutTickArmed = false; return; } mCT.Enumerate(ReadTimeoutTickCB, this); } PLDHashOperator nsHttpConnectionMgr::ReadTimeoutTickCB(const nsACString &key, nsAutoPtr &ent, void *closure) { nsHttpConnectionMgr *self = (nsHttpConnectionMgr *) closure; LOG(("nsHttpConnectionMgr::ReadTimeoutTickCB() this=%p host=%s\n", self, ent->mConnInfo->Host())); PRIntervalTime now = PR_IntervalNow(); for (PRUint32 index = 0; index < ent->mActiveConns.Length(); ++index) ent->mActiveConns[index]->ReadTimeoutTick(now); return PL_DHASH_NEXT; } //----------------------------------------------------------------------------- // nsHttpConnectionMgr::nsConnectionHandle nsHttpConnectionMgr::nsConnectionHandle::~nsConnectionHandle() { if (mConn) { gHttpHandler->ReclaimConnection(mConn); NS_RELEASE(mConn); } } NS_IMPL_THREADSAFE_ISUPPORTS0(nsHttpConnectionMgr::nsConnectionHandle) nsresult nsHttpConnectionMgr::nsConnectionHandle::OnHeadersAvailable(nsAHttpTransaction *trans, nsHttpRequestHead *req, nsHttpResponseHead *resp, bool *reset) { return mConn->OnHeadersAvailable(trans, req, resp, reset); } nsresult nsHttpConnectionMgr::nsConnectionHandle::ResumeSend() { return mConn->ResumeSend(); } nsresult nsHttpConnectionMgr::nsConnectionHandle::ResumeRecv() { return mConn->ResumeRecv(); } void nsHttpConnectionMgr::nsConnectionHandle::CloseTransaction(nsAHttpTransaction *trans, nsresult reason) { mConn->CloseTransaction(trans, reason); } void nsHttpConnectionMgr::nsConnectionHandle::GetConnectionInfo(nsHttpConnectionInfo **result) { mConn->GetConnectionInfo(result); } nsresult nsHttpConnectionMgr:: nsConnectionHandle::TakeTransport(nsISocketTransport **aTransport, nsIAsyncInputStream **aInputStream, nsIAsyncOutputStream **aOutputStream) { return mConn->TakeTransport(aTransport, aInputStream, aOutputStream); } void nsHttpConnectionMgr::nsConnectionHandle::GetSecurityInfo(nsISupports **result) { mConn->GetSecurityInfo(result); } bool nsHttpConnectionMgr::nsConnectionHandle::IsPersistent() { return mConn->IsPersistent(); } bool nsHttpConnectionMgr::nsConnectionHandle::IsReused() { return mConn->IsReused(); } void nsHttpConnectionMgr::nsConnectionHandle::DontReuse() { mConn->DontReuse(); } nsresult nsHttpConnectionMgr::nsConnectionHandle::PushBack(const char *buf, PRUint32 bufLen) { return mConn->PushBack(buf, bufLen); } //////////////////////// nsHalfOpenSocket NS_IMPL_THREADSAFE_ISUPPORTS4(nsHttpConnectionMgr::nsHalfOpenSocket, nsIOutputStreamCallback, nsITransportEventSink, nsIInterfaceRequestor, nsITimerCallback) nsHttpConnectionMgr:: nsHalfOpenSocket::nsHalfOpenSocket(nsConnectionEntry *ent, nsHttpTransaction *trans) : mEnt(ent), mTransaction(trans) { NS_ABORT_IF_FALSE(ent && trans, "constructor with null arguments"); LOG(("Creating nsHalfOpenSocket [this=%p trans=%p ent=%s]\n", this, trans, ent->mConnInfo->Host())); } nsHttpConnectionMgr::nsHalfOpenSocket::~nsHalfOpenSocket() { NS_ABORT_IF_FALSE(!mStreamOut, "streamout not null"); NS_ABORT_IF_FALSE(!mBackupStreamOut, "backupstreamout not null"); NS_ABORT_IF_FALSE(!mSynTimer, "syntimer not null"); LOG(("Destroying nsHalfOpenSocket [this=%p]\n", this)); if (mEnt) { // A failure to create the transport object at all // will result in this not being present in the halfopen table // so ignore failures of RemoveElement() mEnt->mHalfOpens.RemoveElement(this); } } nsresult nsHttpConnectionMgr:: nsHalfOpenSocket::SetupStreams(nsISocketTransport **transport, nsIAsyncInputStream **instream, nsIAsyncOutputStream **outstream, bool isBackup) { nsresult rv; const char* types[1]; types[0] = (mEnt->mConnInfo->UsingSSL()) ? "ssl" : gHttpHandler->DefaultSocketType(); PRUint32 typeCount = (types[0] != nsnull); nsCOMPtr socketTransport; nsCOMPtr sts; sts = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv); NS_ENSURE_SUCCESS(rv, rv); rv = sts->CreateTransport(types, typeCount, nsDependentCString(mEnt->mConnInfo->Host()), mEnt->mConnInfo->Port(), mEnt->mConnInfo->ProxyInfo(), getter_AddRefs(socketTransport)); NS_ENSURE_SUCCESS(rv, rv); PRUint32 tmpFlags = 0; if (mTransaction->Caps() & NS_HTTP_REFRESH_DNS) tmpFlags = nsISocketTransport::BYPASS_CACHE; if (mTransaction->Caps() & NS_HTTP_LOAD_ANONYMOUS) tmpFlags |= nsISocketTransport::ANONYMOUS_CONNECT; // For backup connections, we disable IPv6. That's because some users have // broken IPv6 connectivity (leading to very long timeouts), and disabling // IPv6 on the backup connection gives them a much better user experience // with dual-stack hosts, though they still pay the 250ms delay for each new // connection. This strategy is also known as "happy eyeballs". if (isBackup && gHttpHandler->FastFallbackToIPv4()) tmpFlags |= nsISocketTransport::DISABLE_IPV6; socketTransport->SetConnectionFlags(tmpFlags); socketTransport->SetQoSBits(gHttpHandler->GetQoSBits()); rv = socketTransport->SetEventSink(this, nsnull); NS_ENSURE_SUCCESS(rv, rv); rv = socketTransport->SetSecurityCallbacks(this); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr sout; rv = socketTransport->OpenOutputStream(nsITransport::OPEN_UNBUFFERED, 0, 0, getter_AddRefs(sout)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr sin; rv = socketTransport->OpenInputStream(nsITransport::OPEN_UNBUFFERED, 0, 0, getter_AddRefs(sin)); NS_ENSURE_SUCCESS(rv, rv); socketTransport.forget(transport); CallQueryInterface(sin, instream); CallQueryInterface(sout, outstream); rv = (*outstream)->AsyncWait(this, 0, 0, nsnull); if (NS_SUCCEEDED(rv)) gHttpHandler->ConnMgr()->StartedConnect(); return rv; } nsresult nsHttpConnectionMgr::nsHalfOpenSocket::SetupPrimaryStreams() { NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); nsresult rv; mPrimarySynStarted = mozilla::TimeStamp::Now(); rv = SetupStreams(getter_AddRefs(mSocketTransport), getter_AddRefs(mStreamIn), getter_AddRefs(mStreamOut), false); LOG(("nsHalfOpenSocket::SetupPrimaryStream [this=%p ent=%s rv=%x]", this, mEnt->mConnInfo->Host(), rv)); if (NS_FAILED(rv)) { if (mStreamOut) mStreamOut->AsyncWait(nsnull, 0, 0, nsnull); mStreamOut = nsnull; mStreamIn = nsnull; mSocketTransport = nsnull; } return rv; } nsresult nsHttpConnectionMgr::nsHalfOpenSocket::SetupBackupStreams() { mBackupSynStarted = mozilla::TimeStamp::Now(); nsresult rv = SetupStreams(getter_AddRefs(mBackupTransport), getter_AddRefs(mBackupStreamIn), getter_AddRefs(mBackupStreamOut), true); LOG(("nsHalfOpenSocket::SetupBackupStream [this=%p ent=%s rv=%x]", this, mEnt->mConnInfo->Host(), rv)); if (NS_FAILED(rv)) { if (mBackupStreamOut) mBackupStreamOut->AsyncWait(nsnull, 0, 0, nsnull); mBackupStreamOut = nsnull; mBackupStreamIn = nsnull; mBackupTransport = nsnull; } return rv; } void nsHttpConnectionMgr::nsHalfOpenSocket::SetupBackupTimer() { PRUint16 timeout = gHttpHandler->GetIdleSynTimeout(); NS_ABORT_IF_FALSE(!mSynTimer, "timer already initd"); if (timeout) { // Setup the timer that will establish a backup socket // if we do not get a writable event on the main one. // We do this because a lost SYN takes a very long time // to repair at the TCP level. // // Failure to setup the timer is something we can live with, // so don't return an error in that case. nsresult rv; mSynTimer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv); if (NS_SUCCEEDED(rv)) { mSynTimer->InitWithCallback(this, timeout, nsITimer::TYPE_ONE_SHOT); LOG(("nsHalfOpenSocket::SetupBackupTimer()")); } } } void nsHttpConnectionMgr::nsHalfOpenSocket::CancelBackupTimer() { // If the syntimer is still armed, we can cancel it because no backup // socket should be formed at this point if (!mSynTimer) return; LOG(("nsHalfOpenSocket::CancelBackupTimer()")); mSynTimer->Cancel(); mSynTimer = nsnull; } void nsHttpConnectionMgr::nsHalfOpenSocket::Abandon() { LOG(("nsHalfOpenSocket::Abandon [this=%p ent=%s]", this, mEnt->mConnInfo->Host())); NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); nsRefPtr deleteProtector(this); if (mStreamOut) { gHttpHandler->ConnMgr()->RecvdConnect(); mStreamOut->AsyncWait(nsnull, 0, 0, nsnull); mStreamOut = nsnull; } if (mBackupStreamOut) { gHttpHandler->ConnMgr()->RecvdConnect(); mBackupStreamOut->AsyncWait(nsnull, 0, 0, nsnull); mBackupStreamOut = nsnull; } CancelBackupTimer(); mEnt = nsnull; } NS_IMETHODIMP // method for nsITimerCallback nsHttpConnectionMgr::nsHalfOpenSocket::Notify(nsITimer *timer) { NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); NS_ABORT_IF_FALSE(timer == mSynTimer, "wrong timer"); if (!gHttpHandler->ConnMgr()-> AtActiveConnectionLimit(mEnt, mTransaction->Caps())) { SetupBackupStreams(); } mSynTimer = nsnull; return NS_OK; } // method for nsIAsyncOutputStreamCallback NS_IMETHODIMP nsHttpConnectionMgr:: nsHalfOpenSocket::OnOutputStreamReady(nsIAsyncOutputStream *out) { NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); NS_ABORT_IF_FALSE(out == mStreamOut || out == mBackupStreamOut, "stream mismatch"); LOG(("nsHalfOpenSocket::OnOutputStreamReady [this=%p ent=%s %s]\n", this, mEnt->mConnInfo->Host(), out == mStreamOut ? "primary" : "backup")); PRInt32 index; nsresult rv; gHttpHandler->ConnMgr()->RecvdConnect(); CancelBackupTimer(); // assign the new socket to the http connection nsRefPtr conn = new nsHttpConnection(); LOG(("nsHalfOpenSocket::OnOutputStreamReady " "Created new nshttpconnection %p\n", conn.get())); nsCOMPtr callbacks; nsCOMPtr callbackTarget; mTransaction->GetSecurityCallbacks(getter_AddRefs(callbacks), getter_AddRefs(callbackTarget)); if (out == mStreamOut) { mozilla::TimeDuration rtt = mozilla::TimeStamp::Now() - mPrimarySynStarted; rv = conn->Init(mEnt->mConnInfo, gHttpHandler->ConnMgr()->mMaxRequestDelay, mSocketTransport, mStreamIn, mStreamOut, callbacks, callbackTarget, PR_MillisecondsToInterval(rtt.ToMilliseconds())); // The nsHttpConnection object now owns these streams and sockets mStreamOut = nsnull; mStreamIn = nsnull; mSocketTransport = nsnull; } else { mozilla::TimeDuration rtt = mozilla::TimeStamp::Now() - mBackupSynStarted; rv = conn->Init(mEnt->mConnInfo, gHttpHandler->ConnMgr()->mMaxRequestDelay, mBackupTransport, mBackupStreamIn, mBackupStreamOut, callbacks, callbackTarget, PR_MillisecondsToInterval(rtt.ToMilliseconds())); // The nsHttpConnection object now owns these streams and sockets mBackupStreamOut = nsnull; mBackupStreamIn = nsnull; mBackupTransport = nsnull; } if (NS_FAILED(rv)) { LOG(("nsHalfOpenSocket::OnOutputStreamReady " "conn->init (%p) failed %x\n", conn.get(), rv)); return rv; } // if this is still in the pending list, remove it and dispatch it index = mEnt->mPendingQ.IndexOf(mTransaction); if (index != -1) { mEnt->mPendingQ.RemoveElementAt(index); nsHttpTransaction *temp = mTransaction; NS_RELEASE(temp); gHttpHandler->ConnMgr()->AddActiveConn(conn, mEnt); rv = gHttpHandler->ConnMgr()->DispatchTransaction(mEnt, mTransaction, conn); } else { // this transaction was dispatched off the pending q before all the // sockets established themselves. // We need to establish a small non-zero idle timeout so the connection // mgr perceives this socket as suitable for persistent connection reuse const PRIntervalTime k5Sec = PR_SecondsToInterval(5); if (k5Sec < gHttpHandler->IdleTimeout()) conn->SetIdleTimeout(k5Sec); else conn->SetIdleTimeout(gHttpHandler->IdleTimeout()); // After about 1 second allow for the possibility of restarting a // transaction due to server close. Keep at sub 1 second as that is the // minimum granularity we can expect a server to be timing out with. conn->SetIsReusedAfter(950); nsRefPtr copy(conn); // because onmsg*() expects to drop a reference gHttpHandler->ConnMgr()->OnMsgReclaimConnection(NS_OK, conn.forget().get()); } return rv; } // method for nsITransportEventSink NS_IMETHODIMP nsHttpConnectionMgr::nsHalfOpenSocket::OnTransportStatus(nsITransport *trans, nsresult status, PRUint64 progress, PRUint64 progressMax) { NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread"); if (mTransaction) mTransaction->OnTransportStatus(trans, status, progress); if (trans != mSocketTransport) return NS_OK; // if we are doing spdy coalescing and haven't recorded the ip address // for this entry before then make the hash key if our dns lookup // just completed if (status == nsISocketTransport::STATUS_CONNECTED_TO && gHttpHandler->IsSpdyEnabled() && gHttpHandler->CoalesceSpdy() && mEnt && mEnt->mConnInfo && mEnt->mConnInfo->UsingSSL() && !mEnt->mConnInfo->UsingHttpProxy() && mEnt->mCoalescingKey.IsEmpty()) { PRNetAddr addr; nsresult rv = mSocketTransport->GetPeerAddr(&addr); if (NS_SUCCEEDED(rv)) { mEnt->mCoalescingKey.SetCapacity(72); PR_NetAddrToString(&addr, mEnt->mCoalescingKey.BeginWriting(), 64); mEnt->mCoalescingKey.SetLength( strlen(mEnt->mCoalescingKey.BeginReading())); if (mEnt->mConnInfo->GetAnonymous()) mEnt->mCoalescingKey.AppendLiteral("~A:"); else mEnt->mCoalescingKey.AppendLiteral("~.:"); mEnt->mCoalescingKey.AppendInt(mEnt->mConnInfo->Port()); LOG(("nsHttpConnectionMgr::nsHalfOpenSocket::OnTransportStatus " "STATUS_CONNECTED_TO Established New Coalescing Key for host " "%s [%s]", mEnt->mConnInfo->Host(), mEnt->mCoalescingKey.get())); gHttpHandler->ConnMgr()->ProcessSpdyPendingQ(mEnt); } } switch (status) { case nsISocketTransport::STATUS_CONNECTING_TO: // Passed DNS resolution, now trying to connect, start the backup timer // only prevent creating another backup transport. // We also check for mEnt presence to not instantiate the timer after // this half open socket has already been abandoned. It may happen // when we get this notification right between main-thread calls to // nsHttpConnectionMgr::Shutdown and nsSocketTransportService::Shutdown // where the first abandones all half open socket instances and only // after that the second stops the socket thread. if (mEnt && !mBackupTransport && !mSynTimer) SetupBackupTimer(); break; case nsISocketTransport::STATUS_CONNECTED_TO: // TCP connection's up, now transfer or SSL negotiantion starts, // no need for backup socket CancelBackupTimer(); break; default: break; } return NS_OK; } // method for nsIInterfaceRequestor NS_IMETHODIMP nsHttpConnectionMgr::nsHalfOpenSocket::GetInterface(const nsIID &iid, void **result) { if (mTransaction) { nsCOMPtr callbacks; mTransaction->GetSecurityCallbacks(getter_AddRefs(callbacks), nsnull); if (callbacks) return callbacks->GetInterface(iid, result); } return NS_ERROR_NO_INTERFACE; } nsHttpConnection * nsHttpConnectionMgr::nsConnectionHandle::TakeHttpConnection() { // return our connection object to the caller and clear it internally // do not drop our reference - the caller now owns it. NS_ASSERTION(mConn, "no connection"); nsHttpConnection *conn = mConn; mConn = nsnull; return conn; } bool nsHttpConnectionMgr::nsConnectionHandle::IsProxyConnectInProgress() { return mConn->IsProxyConnectInProgress(); } PRUint32 nsHttpConnectionMgr::nsConnectionHandle::CancelPipeline(nsresult reason) { // no pipeline to cancel return 0; } nsAHttpTransaction::Classifier nsHttpConnectionMgr::nsConnectionHandle::Classification() { if (mConn) return mConn->Classification(); LOG(("nsConnectionHandle::Classification this=%p " "has null mConn using CLASS_SOLO default", this)); return nsAHttpTransaction::CLASS_SOLO; } void nsHttpConnectionMgr:: nsConnectionHandle::Classify(nsAHttpTransaction::Classifier newclass) { if (mConn) mConn->Classify(newclass); } // nsConnectionEntry nsHttpConnectionMgr:: nsConnectionEntry::nsConnectionEntry(nsHttpConnectionInfo *ci) : mConnInfo(ci) , mPipelineState(PS_YELLOW) , mYellowGoodEvents(0) , mYellowBadEvents(0) , mYellowConnection(nsnull) , mGreenDepth(kPipelineOpen) , mPipeliningPenalty(0) , mUsingSpdy(false) , mTestedSpdy(false) , mSpdyPreferred(false) { NS_ADDREF(mConnInfo); if (gHttpHandler->GetPipelineAggressive()) { mGreenDepth = kPipelineUnlimited; mPipelineState = PS_GREEN; } mInitialGreenDepth = mGreenDepth; memset(mPipeliningClassPenalty, 0, sizeof(PRInt16) * nsAHttpTransaction::CLASS_MAX); } bool nsHttpConnectionMgr::nsConnectionEntry::SupportsPipelining() { return mPipelineState != nsHttpConnectionMgr::PS_RED; } nsHttpConnectionMgr::PipeliningState nsHttpConnectionMgr::nsConnectionEntry::PipelineState() { return mPipelineState; } void nsHttpConnectionMgr:: nsConnectionEntry::OnPipelineFeedbackInfo( nsHttpConnectionMgr::PipelineFeedbackInfoType info, nsHttpConnection *conn, PRUint32 data) { NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); if (mPipelineState == PS_YELLOW) { if (info & kPipelineInfoTypeBad) mYellowBadEvents++; else if (info & (kPipelineInfoTypeNeutral | kPipelineInfoTypeGood)) mYellowGoodEvents++; } if (mPipelineState == PS_GREEN && info == GoodCompletedOK) { PRInt32 depth = data; LOG(("Transaction completed at pipeline depty of %d. Host = %s\n", depth, mConnInfo->Host())); if (depth >= 3) mGreenDepth = kPipelineUnlimited; } nsAHttpTransaction::Classifier classification; if (conn) classification = conn->Classification(); else if (info == BadInsufficientFraming || info == BadUnexpectedLarge) classification = (nsAHttpTransaction::Classifier) data; else classification = nsAHttpTransaction::CLASS_SOLO; if (gHttpHandler->GetPipelineAggressive() && info & kPipelineInfoTypeBad && info != BadExplicitClose && info != RedVersionTooLow && info != RedBannedServer && info != RedCorruptedContent && info != BadInsufficientFraming) { LOG(("minor negative feedback ignored " "because of pipeline aggressive mode")); } else if (info & kPipelineInfoTypeBad) { if ((info & kPipelineInfoTypeRed) && (mPipelineState != PS_RED)) { LOG(("transition to red from %d. Host = %s.\n", mPipelineState, mConnInfo->Host())); mPipelineState = PS_RED; mPipeliningPenalty = 0; } if (mLastCreditTime.IsNull()) mLastCreditTime = mozilla::TimeStamp::Now(); // Red* events impact the host globally via mPipeliningPenalty, while // Bad* events impact the per class penalty. // The individual penalties should be < 16bit-signed-maxint - 25000 // (approx 7500). Penalties are paid-off either when something promising // happens (a successful transaction, or promising headers) or when // time goes by at a rate of 1 penalty point every 16 seconds. switch (info) { case RedVersionTooLow: mPipeliningPenalty += 1000; break; case RedBannedServer: mPipeliningPenalty += 7000; break; case RedCorruptedContent: mPipeliningPenalty += 7000; break; case RedCanceledPipeline: mPipeliningPenalty += 60; break; case BadExplicitClose: mPipeliningClassPenalty[classification] += 250; break; case BadSlowReadMinor: mPipeliningClassPenalty[classification] += 5; break; case BadSlowReadMajor: mPipeliningClassPenalty[classification] += 25; break; case BadInsufficientFraming: mPipeliningClassPenalty[classification] += 7000; break; case BadUnexpectedLarge: mPipeliningClassPenalty[classification] += 120; break; default: NS_ABORT_IF_FALSE(0, "Unknown Bad/Red Pipeline Feedback Event"); } mPipeliningPenalty = PR_MIN(mPipeliningPenalty, 25000); mPipeliningClassPenalty[classification] = PR_MIN(mPipeliningClassPenalty[classification], 25000); LOG(("Assessing red penalty to %s class %d for event %d. " "Penalty now %d, throttle[%d] = %d\n", mConnInfo->Host(), classification, info, mPipeliningPenalty, classification, mPipeliningClassPenalty[classification])); } else { // hand out credits for neutral and good events such as // "headers look ok" events mPipeliningPenalty = PR_MAX(mPipeliningPenalty - 1, 0); mPipeliningClassPenalty[classification] = PR_MAX(mPipeliningClassPenalty[classification] - 1, 0); } if (mPipelineState == PS_RED && !mPipeliningPenalty) { LOG(("transition %s to yellow\n", mConnInfo->Host())); mPipelineState = PS_YELLOW; mYellowConnection = nsnull; } } void nsHttpConnectionMgr:: nsConnectionEntry::SetYellowConnection(nsHttpConnection *conn) { NS_ABORT_IF_FALSE(!mYellowConnection && mPipelineState == PS_YELLOW, "yellow connection already set or state is not yellow"); mYellowConnection = conn; mYellowGoodEvents = mYellowBadEvents = 0; } void nsHttpConnectionMgr::nsConnectionEntry::OnYellowComplete() { if (mPipelineState == PS_YELLOW) { if (mYellowGoodEvents && !mYellowBadEvents) { LOG(("transition %s to green\n", mConnInfo->Host())); mPipelineState = PS_GREEN; mGreenDepth = mInitialGreenDepth; } else { // The purpose of the yellow state is to witness at least // one successful pipelined transaction without seeing any // kind of negative feedback before opening the flood gates. // If we haven't confirmed that, then transfer back to red. LOG(("transition %s to red from yellow return\n", mConnInfo->Host())); mPipelineState = PS_RED; } } mYellowConnection = nsnull; } void nsHttpConnectionMgr::nsConnectionEntry::CreditPenalty() { if (mLastCreditTime.IsNull()) return; // Decrease penalty values by 1 for every 16 seconds // (i.e 3.7 per minute, or 1000 every 4h20m) mozilla::TimeStamp now = mozilla::TimeStamp::Now(); mozilla::TimeDuration elapsedTime = now - mLastCreditTime; PRUint32 creditsEarned = static_cast(elapsedTime.ToSeconds()) >> 4; bool failed = false; if (creditsEarned > 0) { mPipeliningPenalty = PR_MAX(PRInt32(mPipeliningPenalty - creditsEarned), 0); if (mPipeliningPenalty > 0) failed = true; for (PRInt32 i = 0; i < nsAHttpTransaction::CLASS_MAX; ++i) { mPipeliningClassPenalty[i] = PR_MAX(PRInt32(mPipeliningClassPenalty[i] - creditsEarned), 0); failed = failed || (mPipeliningClassPenalty[i] > 0); } // update last credit mark to reflect elapsed time mLastCreditTime += mozilla::TimeDuration::FromSeconds(creditsEarned << 4); } else { failed = true; /* just assume this */ } // If we are no longer red then clear the credit counter - you only // get credits for time spent in the red state if (!failed) mLastCreditTime = mozilla::TimeStamp(); /* reset to null timestamp */ if (mPipelineState == PS_RED && !mPipeliningPenalty) { LOG(("transition %s to yellow based on time credit\n", mConnInfo->Host())); mPipelineState = PS_YELLOW; mYellowConnection = nsnull; } } PRUint32 nsHttpConnectionMgr:: nsConnectionEntry::MaxPipelineDepth(nsAHttpTransaction::Classifier aClass) { // Still subject to configuration limit no matter return value if ((mPipelineState == PS_RED) || (mPipeliningClassPenalty[aClass] > 0)) return 0; if (mPipelineState == PS_YELLOW) return kPipelineRestricted; return mGreenDepth; } bool nsHttpConnectionMgr::nsConnectionHandle::LastTransactionExpectedNoContent() { return mConn->LastTransactionExpectedNoContent(); } void nsHttpConnectionMgr:: nsConnectionHandle::SetLastTransactionExpectedNoContent(bool val) { mConn->SetLastTransactionExpectedNoContent(val); } nsISocketTransport * nsHttpConnectionMgr::nsConnectionHandle::Transport() { if (!mConn) return nsnull; return mConn->Transport(); }