gecko/netwerk/protocol/http/nsHttpConnectionMgr.cpp
Patrick McManus f04f8c8cf1 bug 603512 - large objects block pipelines r=honzab
the type and state patch tries hard not to form pipelines behind resources that
could become head of line blockers. But of course that requires the ability to
predict the future, and won't be perfect.

This patch reacts to a transaction that has a large response body (defined by
either a large content-length header or actually reading a large number of
chunked bytes) by cancelling any transactions that have been pipelined down the
same connection and rescheduling them elsewhere. It also changes the type of
the connection to "solo", which prevents new transactions from being pipelined
onto this one and provides class-specific negative feedback to the pipeline
manager so that near-future requests to the same host of the same type (e.g.
general) will not be pipelined but other types (e.g. img or js/css) can still
do that.

Content-Length is ideal, because it allows us to identify the problem so early.
But even actually reading the document for a fairly long time gives it a fairly
high probability of not ending soon. (i.e. long document sizes are spread over
a larger range than small ones. duh.)

The pref network.http.pipelining.maxsize controls the threshold. I set the
default at 300KB, which is roughly the bandwidth delay product of a 2mbit 120ms
rtt connection and 1 rtt is mostly what you are giving up by canceling it on
one connection and sending it on another. (modulo maybe needing a handshake).
2012-03-22 19:39:31 -04:00

2825 lines
94 KiB
C++

/* 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 <darin@netscape.com>
*
* 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<nsHttpTransaction*> &pendingQ, nsHttpTransaction *trans)
{
// insert into queue with smallest valued number first. search in reverse
// order under the assumption that many of the existing transactions will
// have the same priority (usually 0).
for (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<nsIEventTarget> sts;
nsCOMPtr<nsIIOService> 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<nsIRunnable> 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<nsITimer> 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<nsISupports> securityInfo;
nsCOMPtr<nsISSLSocketControl> 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<nsConnectionEntry> &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<nsConnectionEntry> &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<nsConnectionEntry> &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<nsConnectionEntry> &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<nsHttpConnectionInfo> mConnInfo;
nsRefPtr<nsHttpConnection> 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; i<totalCount; ++i) {
conn = ent->mActiveConns[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<nsConnectionEntry> &ent,
void *closure)
{
nsHttpConnectionMgr *self = static_cast<nsHttpConnectionMgr *>(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<nsHttpConnection> 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<nsHttpConnection> 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<nsHttpPipeline> pipeline;
nsresult rv = BuildPipeline(ent, trans, getter_AddRefs(pipeline));
if (!NS_SUCCEEDED(rv))
return rv;
nsRefPtr<nsConnectionHandle> 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<nsHttpPipeline> 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<nsHttpConnection> 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<nsHalfOpenSocket> 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<nsConnectionEntry> &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<nsConnectionEntry> &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<nsISocketTransport> socketTransport;
nsCOMPtr<nsISocketTransportService> 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<nsIOutputStream> sout;
rv = socketTransport->OpenOutputStream(nsITransport::OPEN_UNBUFFERED,
0, 0,
getter_AddRefs(sout));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIInputStream> sin;
rv = socketTransport->OpenInputStream(nsITransport::OPEN_UNBUFFERED,
0, 0,
getter_AddRefs(sin));
NS_ENSURE_SUCCESS(rv, rv);
socketTransport.forget(transport);
CallQueryInterface(sin, instream);
CallQueryInterface(sout, outstream);
rv = (*outstream)->AsyncWait(this, 0, 0, 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<nsHalfOpenSocket> 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<nsHttpConnection> conn = new nsHttpConnection();
LOG(("nsHalfOpenSocket::OnOutputStreamReady "
"Created new nshttpconnection %p\n", conn.get()));
nsCOMPtr<nsIInterfaceRequestor> callbacks;
nsCOMPtr<nsIEventTarget> 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<nsHttpConnection> 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<nsIInterfaceRequestor> 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<PRUint32>(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();
}