fune/netwerk/protocol/http/nsHttpConnectionMgr.cpp
David Rajchenbach-Teller 48f2be74e2 Bug 1249590 - Bullet-proofing AsyncShutdown wrt exceptions;r=froydnj
While investigating bug 1248489, we discovered that some code paths in
AsyncShutdown could possibly be sensitive to exceptions being thrown
in unexpected places. This patch attempts to make AsyncShutdown more
robust to such exceptions.

MozReview-Commit-ID: 5ImL9YNVgQr

--HG--
extra : rebase_source : 823ee41406f7b341750e0339dfe3d5dbaa6d0cc9
2016-02-19 12:51:04 +01:00

4000 lines
138 KiB
C++

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