tenfourfox/netwerk/protocol/http/nsHttpConnectionMgr.cpp

4083 lines
141 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/Services.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 "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()
{
nsCOMPtr<nsIEventTarget> sts;
nsCOMPtr<nsIIOService> ioService = services::GetIOService();
if (ioService) {
nsCOMPtr<nsISocketTransportService> realSTS =
services::GetSocketTransportService();
sts = do_QueryInterface(realSTS);
}
ReentrantMonitorAutoEnter mon(mReentrantMonitor);
// do nothing if already initialized or if we've shut down
if (mSocketThreadTarget || mIsShuttingDown)
return NS_OK;
mSocketThreadTarget = sts;
return sts ? NS_OK : NS_ERROR_NOT_AVAILABLE;
}
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) {
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)
{
RefPtr<nsHttpConnectionInfo> ci;
if (aCI) {
ci = aCI->Clone();
}
return PostEvent(&nsHttpConnectionMgr::OnMsgDoShiftReloadConnectionCleanup,
0, ci);
}
class SpeculativeConnectArgs : public ARefBase
{
public:
SpeculativeConnectArgs() { mOverridesOK = false; }
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SpeculativeConnectArgs)
public: // intentional!
RefPtr<NullHttpTransaction> mTrans;
bool mOverridesOK;
uint32_t mParallelSpeculativeConnectLimit;
bool mIgnoreIdle;
bool mIsFromPredictor;
bool mAllow1918;
private:
virtual ~SpeculativeConnectArgs() {}
NS_DECL_OWNINGTHREAD
};
nsresult
nsHttpConnectionMgr::SpeculativeConnect(nsHttpConnectionInfo *ci,
nsIInterfaceRequestor *callbacks,
uint32_t caps,
NullHttpTransaction *nullTransaction)
{
MOZ_ASSERT(NS_IsMainThread(), "nsHttpConnectionMgr::SpeculativeConnect called off main thread!");
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->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 *aCI)
{
LOG(("nsHttpConnectionMgr::ProcessPendingQ [ci=%s]\n", aCI->HashKey().get()));
RefPtr<nsHttpConnectionInfo> ci;
if (aCI) {
ci = aCI->Clone();
}
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);
}
PLDHashOperator
nsHttpConnectionMgr::RemoveDeadConnections(const nsACString &key,
nsAutoPtr<nsConnectionEntry> &ent,
void *aArg)
{
if (ent->mIdleConns.Length() == 0 &&
ent->mActiveConns.Length() == 0 &&
ent->mHalfOpens.Length() == 0 &&
ent->mPendingQ.Length() == 0) {
return PL_DHASH_REMOVE;
}
return PL_DHASH_NEXT;
}
nsresult
nsHttpConnectionMgr::ClearConnectionHistory()
{
MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
mCT.Enumerate(RemoveDeadConnections, nullptr);
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;
if (!usingSpdy)
return;
ent->mUsingSpdy = true;
mNumSpdyActiveConns++;
uint32_t ttl = conn->TimeToLive();
uint64_t timeOfExpire = NowInSeconds() + ttl;
if (!mTimer || timeOfExpire < mTimeOfNextWakeUp)
PruneDeadConnectionsAfter(ttl);
// Lookup preferred directly from the hash instead of using
// GetSpdyPreferredEnt() because we want to avoid the cert compatibility
// check at this point because the cert is never part of the hash
// lookup. Filtering on that has to be done at the time of use
// rather than the time of registration (i.e. now).
nsConnectionEntry *joinedConnection;
nsConnectionEntry *preferred = LookupPreferredHash(ent);
LOG(("ReportSpdyConnection %p,%s conn %p prefers %p,%s\n",
ent, ent->mConnInfo->Origin(), conn, preferred,
preferred ? preferred->mConnInfo->Origin() : ""));
if (!preferred) {
// this becomes the preferred entry
StorePreferredHash(ent);
preferred = ent;
} else if ((preferred != ent) &&
(joinedConnection = GetSpdyPreferredEnt(ent)) &&
(joinedConnection != ent)) {
//
// A connection entry (e.g. made with a different hostname) with
// the same IP address is preferred for future transactions over this
// connection entry. Gracefully close down the connection to help
// new transactions migrate over.
LOG(("ReportSpdyConnection graceful close of conn=%p ent=%p to "
"migrate to preferred (desharding)\n", conn, ent));
conn->DontReuse();
} else if (preferred != ent) {
LOG (("ReportSpdyConnection preferred host may be in false start or "
"may have insufficient cert. Leave mapping in place but do not "
"abandon this connection yet."));
}
if ((preferred == ent) && conn->CanDirectlyActivate()) {
// this is a new spdy connection to the preferred entry
// Cancel any other pending connections - their associated transactions
// are in the pending queue and will be dispatched onto this connection
for (int32_t index = ent->mHalfOpens.Length() - 1;
index >= 0; --index) {
LOG(("ReportSpdyConnection forcing halfopen abandon %p\n",
ent->mHalfOpens[index]));
ent->mHalfOpens[index]->Abandon();
}
if (ent->mActiveConns.Length() > 1) {
// this is a new connection to an established preferred spdy host.
// if there is more than 1 live and established spdy connection (e.g.
// some could still be handshaking, shutting down, etc..) then close
// this one down after any transactions that are on it are complete.
// This probably happened due to the parallel connection algorithm
// that is used only before the host is known to speak spdy.
for (uint32_t index = 0; index < ent->mActiveConns.Length(); ++index) {
nsHttpConnection *otherConn = ent->mActiveConns[index];
if (otherConn != conn) {
LOG(("ReportSpdyConnection shutting down connection (%p) because new "
"spdy connection (%p) takes precedence\n", otherConn, conn));
otherConn->DontReuse();
}
}
}
}
ProcessPendingQ(ent->mConnInfo);
PostEvent(&nsHttpConnectionMgr::OnMsgProcessAllSpdyPendingQ);
}
nsHttpConnectionMgr::nsConnectionEntry *
nsHttpConnectionMgr::GetSpdyPreferredEnt(nsConnectionEntry *aOriginalEntry)
{
if (!gHttpHandler->IsSpdyEnabled() ||
!gHttpHandler->CoalesceSpdy() ||
aOriginalEntry->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;
}
//-----------------------------------------------------------------------------
// enumeration callbacks
PLDHashOperator
nsHttpConnectionMgr::ProcessAllTransactionsCB(const nsACString &key,
nsAutoPtr<nsConnectionEntry> &ent,
void *closure)
{
nsHttpConnectionMgr *self = (nsHttpConnectionMgr *) closure;
self->ProcessPendingQForEntry(ent, true);
return PL_DHASH_NEXT;
}
PLDHashOperator
nsHttpConnectionMgr::PruneDeadConnectionsCB(const nsACString &key,
nsAutoPtr<nsConnectionEntry> &ent,
void *closure)
{
MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
nsHttpConnectionMgr *self = (nsHttpConnectionMgr *) closure;
LOG((" pruning [ci=%s]\n", ent->mConnInfo->HashKey().get()));
// Find out how long it will take for next idle connection to not be reusable
// anymore.
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);
self->mNumIdleConns--;
} else {
timeToNextExpire = std::min(timeToNextExpire, conn->TimeToLive());
}
}
}
if (ent->mUsingSpdy) {
for (uint32_t index = 0; index < ent->mActiveConns.Length(); ++index) {
nsHttpConnection *conn = ent->mActiveConns[index];
if (conn->UsingSpdy()) {
if (!conn->CanReuse()) {
// marking it dont reuse will create an active tear down if
// the spdy session is idle.
conn->DontReuse();
}
else {
timeToNextExpire = 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 (!self->mTimer || timeOfNextExpire < self->mTimeOfNextWakeUp) {
self->PruneDeadConnectionsAfter(timeToNextExpire);
}
} else {
self->ConditionallyStopPruneDeadConnectionsTimer();
}
// if this entry is empty, we have too many entries,
// and this doesn't represent some painfully determined
// red condition, then we can clean it up and restart from
// yellow
if (ent->PipelineState() != PS_RED &&
self->mCT.Count() > 125 &&
ent->mIdleConns.Length() == 0 &&
ent->mActiveConns.Length() == 0 &&
ent->mHalfOpens.Length() == 0 &&
ent->mPendingQ.Length() == 0 &&
(!ent->mUsingSpdy || self->mCT.Count() > 300)) {
LOG((" removing empty connection entry\n"));
return PL_DHASH_REMOVE;
}
// otherwise use this opportunity to compact our arrays...
ent->mIdleConns.Compact();
ent->mActiveConns.Compact();
ent->mPendingQ.Compact();
return PL_DHASH_NEXT;
}
PLDHashOperator
nsHttpConnectionMgr::VerifyTrafficCB(const nsACString &key,
nsAutoPtr<nsConnectionEntry> &ent,
void *closure)
{
// Iterate over all active connections and check them
for (uint32_t index = 0; index < ent->mActiveConns.Length(); ++index) {
nsHttpConnection *conn = ent->mActiveConns[index];
conn->CheckForTraffic(true);
}
// Iterate the idle connections and unmark them for traffic checks
for (uint32_t index = 0; index < ent->mIdleConns.Length(); ++index) {
nsHttpConnection *conn = ent->mIdleConns[index];
conn->CheckForTraffic(false);
}
return PL_DHASH_NEXT;
}
PLDHashOperator
nsHttpConnectionMgr::PruneNoTrafficCB(const nsACString &key,
nsAutoPtr<nsConnectionEntry> &ent,
void *closure)
{
// Close the connections with no registered traffic
nsHttpConnectionMgr *self = (nsHttpConnectionMgr *) closure;
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);
self->DecrementActiveConnCount(conn);
conn->Close(NS_ERROR_ABORT);
LOG((" closed active connection due to no traffic [conn=%p]\n",
conn.get()));
}
}
}
return PL_DHASH_NEXT;
}
PLDHashOperator
nsHttpConnectionMgr::ShutdownPassCB(const nsACString &key,
nsAutoPtr<nsConnectionEntry> &ent,
void *closure)
{
nsHttpConnectionMgr *self = (nsHttpConnectionMgr *) closure;
nsHttpTransaction *trans;
nsHttpConnection *conn;
// close all active connections
while (ent->mActiveConns.Length()) {
conn = ent->mActiveConns[0];
ent->mActiveConns.RemoveElementAt(0);
self->DecrementActiveConnCount(conn);
conn->Close(NS_ERROR_ABORT);
NS_RELEASE(conn);
}
// close all idle connections
while (ent->mIdleConns.Length()) {
conn = ent->mIdleConns[0];
ent->mIdleConns.RemoveElementAt(0);
self->mNumIdleConns--;
conn->Close(NS_ERROR_ABORT);
NS_RELEASE(conn);
}
// If all idle connections are removed,
// we can stop pruning dead connections.
self->ConditionallyStopPruneDeadConnectionsTimer();
// close all pending transactions
while (ent->mPendingQ.Length()) {
trans = ent->mPendingQ[0];
ent->mPendingQ.RemoveElementAt(0);
trans->Close(NS_ERROR_ABORT);
NS_RELEASE(trans);
}
// close all half open tcp connections
for (int32_t i = ((int32_t) ent->mHalfOpens.Length()) - 1; i >= 0; i--)
ent->mHalfOpens[i]->Abandon();
return PL_DHASH_REMOVE;
}
//-----------------------------------------------------------------------------
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();
}
PLDHashOperator
nsHttpConnectionMgr::ClosePersistentConnectionsCB(const nsACString &key,
nsAutoPtr<nsConnectionEntry> &ent,
void *closure)
{
nsHttpConnectionMgr *self = static_cast<nsHttpConnectionMgr *>(closure);
self->ClosePersistentConnections(ent);
return PL_DHASH_NEXT;
}
bool
nsHttpConnectionMgr::RestrictConnections(nsConnectionEntry *ent)
{
MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
// If this host is trying to negotiate a SPDY session right now,
// don't create any new ssl connections until the result of the
// negotiation is known.
bool doRestrict =
ent->mConnInfo->FirstHopSSL() && gHttpHandler->IsSpdyEnabled() &&
ent->mUsingSpdy && (ent->mHalfOpens.Length() || ent->mActiveConns.Length());
// If there are no restrictions, we are done
if (!doRestrict)
return false;
// If the restriction is based on a tcp handshake in progress
// let that connect and then see if it was SPDY or not
if (ent->UnconnectedHalfOpens()) {
return true;
}
// There is a concern that a host is using a mix of HTTP/1 and SPDY.
// In that case we don't want to restrict connections just because
// there is a single active HTTP/1 session in use.
if (ent->mUsingSpdy && ent->mActiveConns.Length()) {
bool confirmedRestrict = false;
for (uint32_t index = 0; index < ent->mActiveConns.Length(); ++index) {
nsHttpConnection *conn = ent->mActiveConns[index];
if (!conn->ReportedNPN() || conn->CanDirectlyActivate()) {
confirmedRestrict = true;
break;
}
}
doRestrict = confirmedRestrict;
if (!confirmedRestrict) {
LOG(("nsHttpConnectionMgr spdy connection restriction to "
"%s bypassed.\n", ent->mConnInfo->Origin()));
}
}
return doRestrict;
}
// returns NS_OK if a connection was started
// return NS_ERROR_NOT_AVAILABLE if a new connection cannot be made due to
// ephemeral limits
// returns other NS_ERROR on hard failure conditions
nsresult
nsHttpConnectionMgr::MakeNewConnection(nsConnectionEntry *ent,
nsHttpTransaction *trans)
{
LOG(("nsHttpConnectionMgr::MakeNewConnection %p ent=%p trans=%p",
this, ent, trans));
MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
uint32_t halfOpenLength = ent->mHalfOpens.Length();
for (uint32_t i = 0; i < halfOpenLength; i++) {
if (ent->mHalfOpens[i]->IsSpeculative()) {
// We've found a speculative connection in the half
// open list. Remove the speculative bit from it and that
// connection can later be used for this transaction
// (or another one in the pending queue) - we don't
// need to open a new connection here.
LOG(("nsHttpConnectionMgr::MakeNewConnection [ci = %s]\n"
"Found a speculative half open connection\n",
ent->mConnInfo->HashKey().get()));
uint32_t flags;
ent->mHalfOpens[i]->SetSpeculative(false);
nsISocketTransport *transport = ent->mHalfOpens[i]->SocketTransport();
if (transport && NS_SUCCEEDED(transport->GetConnectionFlags(&flags))) {
flags &= ~nsISocketTransport::DISABLE_RFC1918;
transport->SetConnectionFlags(flags);
}
#if(0)
Telemetry::AutoCounter<Telemetry::HTTPCONNMGR_USED_SPECULATIVE_CONN> usedSpeculativeConn;
++usedSpeculativeConn;
if (ent->mHalfOpens[i]->IsFromPredictor()) {
Telemetry::AutoCounter<Telemetry::PREDICTOR_TOTAL_PRECONNECTS_USED> totalPreconnectsUsed;
++totalPreconnectsUsed;
}
#endif
// 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)
{
#if(0)
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);
#endif
}
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();
RefPtr<Http2PushedStreamWrapper> pushedStreamWrapper =
trans->GetPushedStream();
if (pushedStreamWrapper) {
Http2PushedStream* pushedStream = pushedStreamWrapper->GetStream();
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();
}
PLDHashOperator
nsHttpConnectionMgr::ProcessSpdyPendingQCB(const nsACString &key,
nsAutoPtr<nsConnectionEntry> &ent,
void *closure)
{
nsHttpConnectionMgr *self = (nsHttpConnectionMgr *) closure;
self->ProcessSpdyPendingQ(ent);
return PL_DHASH_NEXT;
}
void
nsHttpConnectionMgr::OnMsgProcessAllSpdyPendingQ(int32_t, ARefBase *)
{
MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
LOG(("nsHttpConnectionMgr::OnMsgProcessAllSpdyPendingQ\n"));
mCT.Enumerate(ProcessSpdyPendingQCB, this);
}
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;
nsHttpConnection *conn = nullptr;
if (preferred->mUsingSpdy) {
for (uint32_t index = 0;
index < preferred->mActiveConns.Length();
++index) {
if (preferred->mActiveConns[index]->CanDirectlyActivate()) {
conn = preferred->mActiveConns[index];
break;
}
}
}
return conn;
}
//-----------------------------------------------------------------------------
void
nsHttpConnectionMgr::OnMsgShutdown(int32_t, ARefBase *param)
{
MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
LOG(("nsHttpConnectionMgr::OnMsgShutdown\n"));
gHttpHandler->StopRequestTokenBucket();
mCT.Enumerate(ShutdownPassCB, this);
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
mCT.Enumerate(ProcessAllTransactionsCB, this);
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()))
mCT.Enumerate(PruneDeadConnectionsCB, this);
}
void
nsHttpConnectionMgr::OnMsgPruneNoTraffic(int32_t, ARefBase *)
{
MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
LOG(("nsHttpConnectionMgr::OnMsgPruneNoTraffic\n"));
// Prune connections without traffic
mCT.Enumerate(PruneNoTrafficCB, this);
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
mCT.Enumerate(VerifyTrafficCB, this);
// 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);
mCT.Enumerate(ClosePersistentConnectionsCB, this);
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
mCT.Enumerate(TimeoutTickCB, this);
if (mTimeoutTick) {
mTimeoutTickNext = std::max(mTimeoutTickNext, 1U);
mTimeoutTick->SetDelay(mTimeoutTickNext * 1000);
}
}
PLDHashOperator
nsHttpConnectionMgr::TimeoutTickCB(const nsACString &key,
nsAutoPtr<nsConnectionEntry> &ent,
void *closure)
{
nsHttpConnectionMgr *self = (nsHttpConnectionMgr *) closure;
LOG(("nsHttpConnectionMgr::TimeoutTickCB() this=%p host=%s "
"idle=%d active=%d half-len=%d pending=%d\n",
self, 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);
self->mTimeoutTickNext = std::min(self->mTimeoutTickNext, connNextTimeout);
}
// now check for any stalled half open sockets
if (ent->mHalfOpens.Length()) {
TimeStamp now = TimeStamp::Now();
double maxConnectTime = gHttpHandler->ConnectTimeout(); /* in milliseconds */
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) {
LOG(("Force timeout of half open to %s after %.2fms.\n",
ent->mConnInfo->HashKey().get(), delta));
if (half->SocketTransport())
half->SocketTransport()->Close(NS_ERROR_NET_TIMEOUT);
if (half->BackupTransport())
half->BackupTransport()->Close(NS_ERROR_NET_TIMEOUT);
}
// If this half open hangs around for 5 seconds after we've closed() it
// then just abandon the socket.
if (delta > maxConnectTime + 5000) {
LOG(("Abandon half open to %s after %.2fms.\n",
ent->mConnInfo->HashKey().get(), delta));
half->Abandon();
}
}
}
if (ent->mHalfOpens.Length()) {
self->mTimeoutTickNext = 1;
}
return PL_DHASH_NEXT;
}
// GetOrCreateConnectionEntry finds a ent for a particular CI for use in
// dispatching a transaction according to these rules
// 1] use an ent that matches the ci that can be dispatched immediately
// 2] otherwise use an ent of wildcard(ci) than can be dispatched immediately
// 3] otherwise create an ent that matches ci and make new conn on it
nsHttpConnectionMgr::nsConnectionEntry *
nsHttpConnectionMgr::GetOrCreateConnectionEntry(nsHttpConnectionInfo *specificCI,
bool prohibitWildCard)
{
// step 1
nsConnectionEntry *specificEnt = mCT.Get(specificCI->HashKey());
if (specificEnt && specificEnt->AvailableForDispatchNow()) {
return specificEnt;
}
if (!specificCI->UsingHttpsProxy()) {
prohibitWildCard = true;
}
// step 2
if (!prohibitWildCard) {
RefPtr<nsHttpConnectionInfo> wildCardProxyCI;
specificCI->CreateWildCard(getter_AddRefs(wildCardProxyCI));
nsConnectionEntry *wildCardEnt = mCT.Get(wildCardProxyCI->HashKey());
if (wildCardEnt && wildCardEnt->AvailableForDispatchNow()) {
return wildCardEnt;
}
}
// step 3
if (!specificEnt) {
RefPtr<nsHttpConnectionInfo> clone(specificCI->Clone());
specificEnt = new nsConnectionEntry(clone);
mCT.Put(clone->HashKey(), specificEnt);
}
return specificEnt;
}
nsresult
ConnectionHandle::OnHeadersAvailable(nsAHttpTransaction *trans,
nsHttpRequestHead *req,
nsHttpResponseHead *resp,
bool *reset)
{
return mConn->OnHeadersAvailable(trans, req, resp, reset);
}
void
ConnectionHandle::CloseTransaction(nsAHttpTransaction *trans, nsresult reason)
{
mConn->CloseTransaction(trans, reason);
}
nsresult
ConnectionHandle::TakeTransport(nsISocketTransport **aTransport,
nsIAsyncInputStream **aInputStream,
nsIAsyncOutputStream **aOutputStream)
{
return mConn->TakeTransport(aTransport, aInputStream, aOutputStream);
}
void
nsHttpConnectionMgr::OnMsgSpeculativeConnect(int32_t, ARefBase *param)
{
MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
SpeculativeConnectArgs *args = static_cast<SpeculativeConnectArgs *>(param);
LOG(("nsHttpConnectionMgr::OnMsgSpeculativeConnect [ci=%s]\n",
args->mTrans->ConnectionInfo()->HashKey().get()));
nsConnectionEntry *ent =
GetOrCreateConnectionEntry(args->mTrans->ConnectionInfo(), false);
// If spdy has previously made a preferred entry for this host via
// the ip pooling rules. If so, connect to the preferred host instead of
// the one directly passed in here.
nsConnectionEntry *preferredEntry = GetSpdyPreferredEnt(ent);
if (preferredEntry)
ent = preferredEntry;
uint32_t parallelSpeculativeConnectLimit =
gHttpHandler->ParallelSpeculativeConnectLimit();
bool ignoreIdle = false;
bool isFromPredictor = false;
bool allow1918 = false;
if (args->mOverridesOK) {
parallelSpeculativeConnectLimit = args->mParallelSpeculativeConnectLimit;
ignoreIdle = args->mIgnoreIdle;
isFromPredictor = args->mIsFromPredictor;
allow1918 = args->mAllow1918;
}
bool keepAlive = args->mTrans->Caps() & NS_HTTP_ALLOW_KEEPALIVE;
if (mNumHalfOpenConns < parallelSpeculativeConnectLimit &&
((ignoreIdle && (ent->mIdleConns.Length() < parallelSpeculativeConnectLimit)) ||
!ent->mIdleConns.Length()) &&
!(keepAlive && RestrictConnections(ent)) &&
!AtActiveConnectionLimit(ent, args->mTrans->Caps())) {
CreateTransport(ent, args->mTrans, args->mTrans->Caps(), true, isFromPredictor, allow1918);
} else {
LOG((" 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 = services::GetSocketTransportService();
if (!sts) {
return NS_ERROR_NOT_AVAILABLE;
}
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);
MOZ_ASSERT(!mTransaction->IsNullTransaction(),
"null transactions dont have backup streams");
mBackupSynStarted = TimeStamp::Now();
nsresult rv = SetupStreams(getter_AddRefs(mBackupTransport),
getter_AddRefs(mBackupStreamIn),
getter_AddRefs(mBackupStreamOut),
true);
LOG(("nsHalfOpenSocket::SetupBackupStream [this=%p ent=%s rv=%x]",
this, mEnt->mConnInfo->Origin(), rv));
if (NS_FAILED(rv)) {
if (mBackupStreamOut)
mBackupStreamOut->AsyncWait(nullptr, 0, 0, nullptr);
mBackupStreamOut = nullptr;
mBackupStreamIn = nullptr;
mBackupTransport = nullptr;
}
return rv;
}
void
nsHttpConnectionMgr::nsHalfOpenSocket::SetupBackupTimer()
{
uint16_t timeout = gHttpHandler->GetIdleSynTimeout();
MOZ_ASSERT(!mSynTimer, "timer already initd");
if (timeout && !mTransaction->IsDone() && !mTransaction->IsNullTransaction()) {
// Setup the timer that will establish a backup socket
// if we do not get a writable event on the main one.
// We do this because a lost SYN takes a very long time
// to repair at the TCP level.
//
// Failure to setup the timer is something we can live with,
// so don't return an error in that case.
nsresult rv;
mSynTimer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv);
if (NS_SUCCEEDED(rv)) {
mSynTimer->InitWithCallback(this, timeout, nsITimer::TYPE_ONE_SHOT);
LOG(("nsHalfOpenSocket::SetupBackupTimer() [this=%p]", this));
}
} else if (timeout) {
LOG(("nsHalfOpenSocket::SetupBackupTimer() [this=%p], did not arm", this));
}
}
void
nsHttpConnectionMgr::nsHalfOpenSocket::CancelBackupTimer()
{
// If the syntimer is still armed, we can cancel it because no backup
// socket should be formed at this point
if (!mSynTimer)
return;
LOG(("nsHalfOpenSocket::CancelBackupTimer()"));
mSynTimer->Cancel();
mSynTimer = nullptr;
}
void
nsHttpConnectionMgr::nsHalfOpenSocket::Abandon()
{
LOG(("nsHalfOpenSocket::Abandon [this=%p ent=%s] %p %p %p %p",
this, mEnt->mConnInfo->Origin(),
mSocketTransport.get(), mBackupTransport.get(),
mStreamOut.get(), mBackupStreamOut.get()));
MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
RefPtr<nsHalfOpenSocket> deleteProtector(this);
// Tell socket (and backup socket) to forget the half open socket.
if (mSocketTransport) {
mSocketTransport->SetEventSink(nullptr, nullptr);
mSocketTransport->SetSecurityCallbacks(nullptr);
mSocketTransport = nullptr;
}
if (mBackupTransport) {
mBackupTransport->SetEventSink(nullptr, nullptr);
mBackupTransport->SetSecurityCallbacks(nullptr);
mBackupTransport = nullptr;
}
// Tell output stream (and backup) to forget the half open socket.
if (mStreamOut) {
gHttpHandler->ConnMgr()->RecvdConnect();
mStreamOut->AsyncWait(nullptr, 0, 0, nullptr);
mStreamOut = nullptr;
}
if (mBackupStreamOut) {
gHttpHandler->ConnMgr()->RecvdConnect();
mBackupStreamOut->AsyncWait(nullptr, 0, 0, nullptr);
mBackupStreamOut = nullptr;
}
// Lose references to input stream (and backup).
mStreamIn = mBackupStreamIn = nullptr;
// Stop the timer - we don't want any new backups.
CancelBackupTimer();
// Remove the half open from the connection entry.
if (mEnt)
mEnt->RemoveHalfOpen(this);
mEnt = nullptr;
}
double
nsHttpConnectionMgr::nsHalfOpenSocket::Duration(TimeStamp epoch)
{
if (mPrimarySynStarted.IsNull())
return 0;
return (epoch - mPrimarySynStarted).ToMilliseconds();
}
NS_IMETHODIMP // method for nsITimerCallback
nsHttpConnectionMgr::nsHalfOpenSocket::Notify(nsITimer *timer)
{
MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
MOZ_ASSERT(timer == mSynTimer, "wrong timer");
MOZ_ASSERT(mTransaction && !mTransaction->IsNullTransaction(),
"null transactions dont have backup streams");
SetupBackupStreams();
mSynTimer = nullptr;
return NS_OK;
}
// method for nsIAsyncOutputStreamCallback
NS_IMETHODIMP
nsHttpConnectionMgr::
nsHalfOpenSocket::OnOutputStreamReady(nsIAsyncOutputStream *out)
{
MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
MOZ_ASSERT(out == mStreamOut || out == mBackupStreamOut,
"stream mismatch");
LOG(("nsHalfOpenSocket::OnOutputStreamReady [this=%p ent=%s %s]\n",
this, mEnt->mConnInfo->Origin(),
out == mStreamOut ? "primary" : "backup"));
int32_t index;
nsresult rv;
gHttpHandler->ConnMgr()->RecvdConnect();
CancelBackupTimer();
// assign the new socket to the http connection
RefPtr<nsHttpConnection> conn = new nsHttpConnection();
LOG(("nsHalfOpenSocket::OnOutputStreamReady "
"Created new nshttpconnection %p\n", conn.get()));
// Some capabilities are needed before a transaciton actually gets
// scheduled (e.g. how to negotiate false start)
conn->SetTransactionCaps(mTransaction->Caps());
NetAddr peeraddr;
nsCOMPtr<nsIInterfaceRequestor> callbacks;
mTransaction->GetSecurityCallbacks(getter_AddRefs(callbacks));
if (out == mStreamOut) {
TimeDuration rtt = TimeStamp::Now() - mPrimarySynStarted;
rv = conn->Init(mEnt->mConnInfo,
gHttpHandler->ConnMgr()->mMaxRequestDelay,
mSocketTransport, mStreamIn, mStreamOut,
mPrimaryConnectedOK, callbacks,
PR_MillisecondsToInterval(
static_cast<uint32_t>(rtt.ToMilliseconds())));
if (NS_SUCCEEDED(mSocketTransport->GetPeerAddr(&peeraddr)))
mEnt->RecordIPFamilyPreference(peeraddr.raw.family);
// The nsHttpConnection object now owns these streams and sockets
mStreamOut = nullptr;
mStreamIn = nullptr;
mSocketTransport = nullptr;
} else if (out == mBackupStreamOut) {
MOZ_ASSERT(!mTransaction->IsNullTransaction(),
"null transactions dont have backup streams");
TimeDuration rtt = TimeStamp::Now() - mBackupSynStarted;
rv = conn->Init(mEnt->mConnInfo,
gHttpHandler->ConnMgr()->mMaxRequestDelay,
mBackupTransport, mBackupStreamIn, mBackupStreamOut,
mBackupConnectedOK, callbacks,
PR_MillisecondsToInterval(
static_cast<uint32_t>(rtt.ToMilliseconds())));
if (NS_SUCCEEDED(mBackupTransport->GetPeerAddr(&peeraddr)))
mEnt->RecordIPFamilyPreference(peeraddr.raw.family);
// The nsHttpConnection object now owns these streams and sockets
mBackupStreamOut = nullptr;
mBackupStreamIn = nullptr;
mBackupTransport = nullptr;
} else {
MOZ_ASSERT(false, "unexpected stream");
rv = NS_ERROR_UNEXPECTED;
}
if (NS_FAILED(rv)) {
LOG(("nsHalfOpenSocket::OnOutputStreamReady "
"conn->init (%p) failed %x\n", conn.get(), rv));
return rv;
}
// This half-open socket has created a connection. This flag excludes it
// from counter of actual connections used for checking limits.
mHasConnected = true;
// if this is still in the pending list, remove it and dispatch it
index = mEnt->mPendingQ.IndexOf(mTransaction);
if (index != -1) {
MOZ_ASSERT(!mSpeculative,
"Speculative Half Open found mTransaction");
RefPtr<nsHttpTransaction> temp = 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 abandons all half open socket instances and only
// after that the second stops the socket thread.
if (mEnt && !mBackupTransport && !mSynTimer)
SetupBackupTimer();
break;
case NS_NET_STATUS_CONNECTED_TO:
// TCP connection's up, now transfer or SSL negotiantion starts,
// no need for backup socket
CancelBackupTimer();
break;
default:
break;
}
return NS_OK;
}
// method for nsIInterfaceRequestor
NS_IMETHODIMP
nsHttpConnectionMgr::nsHalfOpenSocket::GetInterface(const nsIID &iid,
void **result)
{
if (mTransaction) {
nsCOMPtr<nsIInterfaceRequestor> callbacks;
mTransaction->GetSecurityCallbacks(getter_AddRefs(callbacks));
if (callbacks)
return callbacks->GetInterface(iid, result);
}
return NS_ERROR_NO_INTERFACE;
}
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)
, 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;
}
PLDHashOperator
nsHttpConnectionMgr::ReadConnectionEntry(const nsACString &key,
nsAutoPtr<nsConnectionEntry> &ent,
void *aArg)
{
if (ent->mConnInfo->GetPrivate())
return PL_DHASH_NEXT;
nsTArray<HttpRetParams> *args = static_cast<nsTArray<HttpRetParams> *> (aArg);
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();
args->AppendElement(data);
return PL_DHASH_NEXT;
}
bool
nsHttpConnectionMgr::GetConnectionData(nsTArray<HttpRetParams> *aArg)
{
mCT.Enumerate(ReadConnectionEntry, aArg);
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(0)
if (halfOpen->IsSpeculative()) {
Telemetry::AutoCounter<Telemetry::HTTPCONNMGR_UNUSED_SPECULATIVE_CONN> unusedSpeculativeConn;
++unusedSpeculativeConn;
if (halfOpen->IsFromPredictor()) {
Telemetry::AutoCounter<Telemetry::PREDICTOR_TOTAL_PRECONNECTS_UNUSED> totalPreconnectsUnused;
++totalPreconnectsUnused;
}
}
#endif
MOZ_ASSERT(gHttpHandler->ConnMgr()->mNumHalfOpenConns);
if (gHttpHandler->ConnMgr()->mNumHalfOpenConns) { // just in case
gHttpHandler->ConnMgr()->mNumHalfOpenConns--;
}
}
if (!UnconnectedHalfOpens())
// perhaps this reverted RestrictConnections()
// use the PostEvent version of processpendingq to avoid
// altering the pending q vector from an arbitrary stack
gHttpHandler->ConnMgr()->ProcessPendingQ(mConnInfo);
}
void
nsHttpConnectionMgr::
nsConnectionEntry::RecordIPFamilyPreference(uint16_t family)
{
if (family == PR_AF_INET && !mPreferIPv6)
mPreferIPv4 = true;
if (family == PR_AF_INET6 && !mPreferIPv4)
mPreferIPv6 = true;
}
void
nsHttpConnectionMgr::
nsConnectionEntry::ResetIPFamilyPreference()
{
mPreferIPv4 = false;
mPreferIPv6 = false;
}
void
nsHttpConnectionMgr::MoveToWildCardConnEntry(nsHttpConnectionInfo *specificCI,
nsHttpConnectionInfo *wildCardCI,
nsHttpConnection *proxyConn)
{
MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
MOZ_ASSERT(specificCI->UsingHttpsProxy());
LOG(("nsHttpConnectionMgr::MakeConnEntryWildCard conn %p has requested to "
"change CI from %s to %s\n", proxyConn, specificCI->HashKey().get(),
wildCardCI->HashKey().get()));
nsConnectionEntry *ent = LookupConnectionEntry(specificCI, proxyConn, nullptr);
LOG(("nsHttpConnectionMgr::MakeConnEntryWildCard conn %p using ent %p (spdy %d)\n",
proxyConn, ent, ent ? ent->mUsingSpdy : 0));
if (!ent || !ent->mUsingSpdy) {
return;
}
nsConnectionEntry *wcEnt = GetOrCreateConnectionEntry(wildCardCI, true);
if (wcEnt == ent) {
// nothing to do!
return;
}
wcEnt->mUsingSpdy = true;
LOG(("nsHttpConnectionMgr::MakeConnEntryWildCard ent %p "
"idle=%d active=%d half=%d pending=%d\n", ent,
ent->mIdleConns.Length(), ent->mActiveConns.Length(),
ent->mHalfOpens.Length(), ent->mPendingQ.Length()));
LOG(("nsHttpConnectionMgr::MakeConnEntryWildCard wc-ent %p "
"idle=%d active=%d half=%d pending=%d\n", wcEnt,
wcEnt->mIdleConns.Length(), wcEnt->mActiveConns.Length(),
wcEnt->mHalfOpens.Length(), wcEnt->mPendingQ.Length()));
int32_t count = ent->mActiveConns.Length();
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