gecko-dev/netwerk/protocol/http/nsHttpConnectionMgr.cpp
2018-08-24 14:05:13 +00:00

5609 lines
199 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 "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 "nsIRequestContext.h"
#include "nsISocketTransportService.h"
#include <algorithm>
#include "mozilla/ChaosMode.h"
#include "mozilla/Unused.h"
#include "nsIURI.h"
#include "mozilla/Move.h"
#include "mozilla/Telemetry.h"
namespace mozilla {
namespace net {
//-----------------------------------------------------------------------------
NS_IMPL_ISUPPORTS(nsHttpConnectionMgr, nsIObserver)
// This function decides the transaction's order in the pending queue.
// Given two transactions t1 and t2, returning true means that t2 is
// more important than t1 and thus should be dispatched first.
static bool
TransactionComparator(nsHttpTransaction *t1, nsHttpTransaction *t2)
{
bool t1Blocking =
t1->Caps() & (NS_HTTP_LOAD_AS_BLOCKING | NS_HTTP_LOAD_UNBLOCKED);
bool t2Blocking =
t2->Caps() & (NS_HTTP_LOAD_AS_BLOCKING | NS_HTTP_LOAD_UNBLOCKED);
if (t1Blocking > t2Blocking) {
return false;
}
if (t2Blocking > t1Blocking) {
return true;
}
return t1->Priority() >= t2->Priority();
}
void
nsHttpConnectionMgr::InsertTransactionSorted(nsTArray<RefPtr<nsHttpConnectionMgr::PendingTransactionInfo> > &pendingQ,
nsHttpConnectionMgr::PendingTransactionInfo *pendingTransInfo,
bool aInsertAsFirstForTheSamePriority /*= false*/)
{
// insert the transaction into the front of the queue based on following rules:
// 1. The transaction has NS_HTTP_LOAD_AS_BLOCKING or NS_HTTP_LOAD_UNBLOCKED.
// 2. The transaction's priority is higher.
//
// search in reverse order under the assumption that many of the
// existing transactions will have the same priority (usually 0).
nsHttpTransaction *trans = pendingTransInfo->mTransaction;
for (int32_t i = pendingQ.Length() - 1; i >= 0; --i) {
nsHttpTransaction *t = pendingQ[i]->mTransaction;
if (TransactionComparator(trans, t)) {
if (ChaosMode::isActive(ChaosFeature::NetworkScheduling) || aInsertAsFirstForTheSamePriority) {
int32_t samePriorityCount;
for (samePriorityCount = 0; i - samePriorityCount >= 0; ++samePriorityCount) {
if (pendingQ[i - samePriorityCount]->mTransaction->Priority() != trans->Priority()) {
break;
}
}
if (aInsertAsFirstForTheSamePriority) {
i -= samePriorityCount;
} else {
// skip over 0...all of the elements with the same priority.
i -= ChaosMode::randomUint32LessThan(samePriorityCount + 1);
}
}
pendingQ.InsertElementAt(i+1, pendingTransInfo);
return;
}
}
pendingQ.InsertElementAt(0, pendingTransInfo);
}
//-----------------------------------------------------------------------------
nsHttpConnectionMgr::nsHttpConnectionMgr()
: mReentrantMonitor("nsHttpConnectionMgr.mReentrantMonitor")
, mMaxUrgentExcessiveConns(0)
, mMaxConns(0)
, mMaxPersistConnsPerHost(0)
, mMaxPersistConnsPerProxy(0)
, mMaxRequestDelay(0)
, mThrottleEnabled(false)
, mThrottleVersion(2)
, mThrottleSuspendFor(0)
, mThrottleResumeFor(0)
, mThrottleReadLimit(0)
, mThrottleReadInterval(0)
, mThrottleHoldTime(0)
, mThrottleMaxTime(0)
, mIsShuttingDown(false)
, mNumActiveConns(0)
, mNumIdleConns(0)
, mNumSpdyActiveConns(0)
, mNumHalfOpenConns(0)
, mTimeOfNextWakeUp(UINT64_MAX)
, mPruningNoTraffic(false)
, mTimeoutTickArmed(false)
, mTimeoutTickNext(1)
, mCurrentTopLevelOuterContentWindowId(0)
, mThrottlingInhibitsReading(false)
, mActiveTabTransactionsExist(false)
, mActiveTabUnthrottledTransactionsExist(false)
{
LOG(("Creating nsHttpConnectionMgr @%p\n", this));
}
nsHttpConnectionMgr::~nsHttpConnectionMgr()
{
LOG(("Destroying nsHttpConnectionMgr @%p\n", this));
MOZ_ASSERT(mCoalescingHash.Count() == 0);
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 maxUrgentExcessiveConns,
uint16_t maxConns,
uint16_t maxPersistConnsPerHost,
uint16_t maxPersistConnsPerProxy,
uint16_t maxRequestDelay,
bool throttleEnabled,
uint32_t throttleVersion,
uint32_t throttleSuspendFor,
uint32_t throttleResumeFor,
uint32_t throttleReadLimit,
uint32_t throttleReadInterval,
uint32_t throttleHoldTime,
uint32_t throttleMaxTime)
{
LOG(("nsHttpConnectionMgr::Init\n"));
{
ReentrantMonitorAutoEnter mon(mReentrantMonitor);
mMaxUrgentExcessiveConns = maxUrgentExcessiveConns;
mMaxConns = maxConns;
mMaxPersistConnsPerHost = maxPersistConnsPerHost;
mMaxPersistConnsPerProxy = maxPersistConnsPerProxy;
mMaxRequestDelay = maxRequestDelay;
mThrottleEnabled = throttleEnabled;
mThrottleVersion = throttleVersion;
mThrottleSuspendFor = throttleSuspendFor;
mThrottleResumeFor = throttleResumeFor;
mThrottleReadLimit = throttleReadLimit;
mThrottleReadInterval = throttleReadInterval;
mThrottleHoldTime = throttleHoldTime;
mThrottleMaxTime = TimeDuration::FromMilliseconds(throttleMaxTime);
mIsShuttingDown = false;
}
return EnsureSocketThreadTarget();
}
class BoolWrapper : public ARefBase
{
public:
BoolWrapper() : mBool(false) {}
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(BoolWrapper, override)
public: // intentional!
bool mBool;
private:
virtual ~BoolWrapper() = default;
};
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 = nullptr;
if (NS_FAILED(rv)) {
NS_WARNING("unable to post SHUTDOWN message");
return rv;
}
}
// wait for shutdown event to complete
SpinEventLoopUntil([&, shutdownWrapper]() { return shutdownWrapper->mBool; });
return NS_OK;
}
class ConnEvent : public Runnable
{
public:
ConnEvent(nsHttpConnectionMgr* mgr,
nsConnEventHandler handler,
int32_t iparam,
ARefBase* vparam)
: Runnable("net::ConnEvent")
, mMgr(mgr)
, mHandler(handler)
, mIParam(iparam)
, mVParam(vparam)
{
}
NS_IMETHOD Run() override
{
(mMgr->*mHandler)(mIParam, mVParam);
return NS_OK;
}
private:
virtual ~ConnEvent() = default;
RefPtr<nsHttpConnectionMgr> mMgr;
nsConnEventHandler mHandler;
int32_t mIParam;
RefPtr<ARefBase> mVParam;
};
nsresult
nsHttpConnectionMgr::PostEvent(nsConnEventHandler handler,
int32_t iparam, ARefBase *vparam)
{
Unused << 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 = NS_NewTimer();
// 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) {
Unused << PruneDeadConnections();
} else if (timer == mTimeoutTick) {
TimeoutTick();
} else if (timer == mTrafficTimer) {
Unused << PruneNoTraffic();
} else if (timer == mThrottleTicker) {
ThrottlerTick();
} else if (timer == mDelayedResumeReadTimer) {
ResumeBackgroundThrottledTransactions();
} 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);
}
void
nsHttpConnectionMgr::UpdateClassOfServiceOnTransaction(nsHttpTransaction *trans, uint32_t classOfService)
{
LOG(("nsHttpConnectionMgr::UpdateClassOfServiceOnTransaction [trans=%p classOfService=%" PRIu32 "]\n",
trans, static_cast<uint32_t>(classOfService)));
Unused << PostEvent(&nsHttpConnectionMgr::OnMsgUpdateClassOfServiceOnTransaction,
static_cast<int32_t>(classOfService), trans);
}
nsresult
nsHttpConnectionMgr::CancelTransaction(nsHttpTransaction *trans, nsresult reason)
{
LOG(("nsHttpConnectionMgr::CancelTransaction [trans=%p reason=%" PRIx32 "]\n",
trans, static_cast<uint32_t>(reason)));
return PostEvent(&nsHttpConnectionMgr::OnMsgCancelTransaction,
static_cast<int32_t>(reason), trans);
}
nsresult
nsHttpConnectionMgr::PruneDeadConnections()
{
return PostEvent(&nsHttpConnectionMgr::OnMsgPruneDeadConnections);
}
//
// Called after a timeout. Check for active connections that have had no
// traffic since they were "marked" and nuke them.
nsresult
nsHttpConnectionMgr::PruneNoTraffic()
{
LOG(("nsHttpConnectionMgr::PruneNoTraffic\n"));
mPruningNoTraffic = true;
return PostEvent(&nsHttpConnectionMgr::OnMsgPruneNoTraffic);
}
nsresult
nsHttpConnectionMgr::VerifyTraffic()
{
LOG(("nsHttpConnectionMgr::VerifyTraffic\n"));
return PostEvent(&nsHttpConnectionMgr::OnMsgVerifyTraffic);
}
nsresult
nsHttpConnectionMgr::DoShiftReloadConnectionCleanup(nsHttpConnectionInfo *aCI)
{
return PostEvent(&nsHttpConnectionMgr::OnMsgDoShiftReloadConnectionCleanup,
0, aCI);
}
class SpeculativeConnectArgs : public ARefBase
{
public:
SpeculativeConnectArgs()
: mParallelSpeculativeConnectLimit(0)
, mIgnoreIdle(false)
, mIsFromPredictor(false)
, mAllow1918(false)
{
mOverridesOK = false;
}
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SpeculativeConnectArgs, override)
public: // intentional!
RefPtr<NullHttpTransaction> mTrans;
bool mOverridesOK;
uint32_t mParallelSpeculativeConnectLimit;
bool mIgnoreIdle;
bool mIsFromPredictor;
bool mAllow1918;
private:
virtual ~SpeculativeConnectArgs() = default;
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!");
if (!IsNeckoChild()) {
// HACK: make sure PSM gets initialized on the main thread.
net_EnsurePSMInit();
}
LOG(("nsHttpConnectionMgr::SpeculativeConnect [ci=%s]\n",
ci->HashKey().get()));
nsCOMPtr<nsISpeculativeConnectionOverrider> overrider =
do_GetInterface(callbacks);
bool allow1918 = overrider ? overrider->GetAllow1918() : false;
// 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;
caps |= NS_HTTP_ERROR_SOFTLY;
args->mTrans =
nullTransaction ? nullTransaction : new NullHttpTransaction(ci, wrappedCallbacks, caps);
if (overrider) {
args->mOverridesOK = true;
args->mParallelSpeculativeConnectLimit =
overrider->GetParallelSpeculativeConnectLimit();
args->mIgnoreIdle = overrider->GetIgnoreIdle();
args->mIsFromPredictor = overrider->GetIsFromPredictor();
args->mAllow1918 = overrider->GetAllow1918();
}
return PostEvent(&nsHttpConnectionMgr::OnMsgSpeculativeConnect, 0, args);
}
nsresult
nsHttpConnectionMgr::GetSocketThreadTarget(nsIEventTarget **target)
{
Unused << EnsureSocketThreadTarget();
ReentrantMonitorAutoEnter mon(mReentrantMonitor);
nsCOMPtr<nsIEventTarget> temp(mSocketThreadTarget);
temp.forget(target);
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, override)
RefPtr<nsAHttpConnection> mConn;
nsCOMPtr<nsIHttpUpgradeListener> mUpgradeListener;
private:
virtual ~nsCompleteUpgradeData() = default;
};
nsresult
nsHttpConnectionMgr::CompleteUpgrade(nsAHttpConnection *aConn,
nsIHttpUpgradeListener *aUpgradeListener)
{
RefPtr<nsCompleteUpgradeData> data =
new nsCompleteUpgradeData(aConn, aUpgradeListener);
return PostEvent(&nsHttpConnectionMgr::OnMsgCompleteUpgrade, 0, data);
}
nsresult
nsHttpConnectionMgr::UpdateParam(nsParamName name, uint16_t value)
{
uint32_t param = (uint32_t(name) << 16) | uint32_t(value);
return PostEvent(&nsHttpConnectionMgr::OnMsgUpdateParam,
static_cast<int32_t>(param), nullptr);
}
nsresult
nsHttpConnectionMgr::ProcessPendingQ(nsHttpConnectionInfo *ci)
{
LOG(("nsHttpConnectionMgr::ProcessPendingQ [ci=%s]\n", ci->HashKey().get()));
return PostEvent(&nsHttpConnectionMgr::OnMsgProcessPendingQ, 0, ci);
}
nsresult
nsHttpConnectionMgr::ProcessPendingQ()
{
LOG(("nsHttpConnectionMgr::ProcessPendingQ [All CI]\n"));
return PostEvent(&nsHttpConnectionMgr::OnMsgProcessPendingQ, 0, nullptr);
}
void
nsHttpConnectionMgr::OnMsgUpdateRequestTokenBucket(int32_t, ARefBase *param)
{
EventTokenBucket *tokenBucket = static_cast<EventTokenBucket *>(param);
gHttpHandler->SetRequestTokenBucket(tokenBucket);
}
nsresult
nsHttpConnectionMgr::UpdateRequestTokenBucket(EventTokenBucket *aBucket)
{
// Call From main thread when a new EventTokenBucket has been made in order
// to post the new value to the socket thread.
return PostEvent(&nsHttpConnectionMgr::OnMsgUpdateRequestTokenBucket,
0, aBucket);
}
nsresult
nsHttpConnectionMgr::ClearConnectionHistory()
{
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
for (auto iter = mCT.Iter(); !iter.Done(); iter.Next()) {
RefPtr<nsConnectionEntry> ent = iter.Data();
if (ent->mIdleConns.Length() == 0 &&
ent->mActiveConns.Length() == 0 &&
ent->mHalfOpens.Length() == 0 &&
ent->mUrgentStartQ.Length() == 0 &&
ent->PendingQLength() == 0 &&
ent->mHalfOpenFastOpenBackups.Length() == 0 &&
!ent->mDoNotDestroy) {
iter.Remove();
}
}
return NS_OK;
}
nsresult
nsHttpConnectionMgr::CloseIdleConnection(nsHttpConnection *conn)
{
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
LOG(("nsHttpConnectionMgr::CloseIdleConnection %p conn=%p",
this, conn));
if (!conn->ConnectionInfo()) {
return NS_ERROR_UNEXPECTED;
}
nsConnectionEntry *ent = mCT.GetWeak(conn->ConnectionInfo()->HashKey());
RefPtr<nsHttpConnection> deleteProtector(conn);
if (!ent || !ent->mIdleConns.RemoveElement(conn))
return NS_ERROR_UNEXPECTED;
conn->Close(NS_ERROR_ABORT);
mNumIdleConns--;
ConditionallyStopPruneDeadConnectionsTimer();
return NS_OK;
}
nsresult
nsHttpConnectionMgr::RemoveIdleConnection(nsHttpConnection *conn)
{
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
LOG(("nsHttpConnectionMgr::RemoveIdleConnection %p conn=%p",
this, conn));
if (!conn->ConnectionInfo()) {
return NS_ERROR_UNEXPECTED;
}
nsConnectionEntry *ent = mCT.GetWeak(conn->ConnectionInfo()->HashKey());
if (!ent || !ent->mIdleConns.RemoveElement(conn)) {
return NS_ERROR_UNEXPECTED;
}
mNumIdleConns--;
ConditionallyStopPruneDeadConnectionsTimer();
return NS_OK;
}
nsHttpConnection *
nsHttpConnectionMgr::FindCoalescableConnectionByHashKey(nsConnectionEntry *ent,
const nsCString &key,
bool justKidding)
{
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
MOZ_ASSERT(ent->mConnInfo);
nsHttpConnectionInfo *ci = ent->mConnInfo;
nsTArray<nsWeakPtr> *listOfWeakConns = mCoalescingHash.Get(key);
if (!listOfWeakConns) {
return nullptr;
}
uint32_t listLen = listOfWeakConns->Length();
for (uint32_t j = 0; j < listLen; ) {
RefPtr<nsHttpConnection> potentialMatch = do_QueryReferent(listOfWeakConns->ElementAt(j));
if (!potentialMatch) {
// This is a connection that needs to be removed from the list
LOG(("FindCoalescableConnectionByHashKey() found old conn %p that has null weak ptr - removing\n",
listOfWeakConns->ElementAt(j).get()));
if (j != listLen - 1) {
listOfWeakConns->Elements()[j] = listOfWeakConns->Elements()[listLen - 1];
}
listOfWeakConns->RemoveElementAt(listLen - 1);
MOZ_ASSERT(listOfWeakConns->Length() == listLen - 1);
listLen--;
continue; // without adjusting iterator
}
bool couldJoin;
if (justKidding) {
couldJoin = potentialMatch->TestJoinConnection(ci->GetOrigin(), ci->OriginPort());
} else {
couldJoin = potentialMatch->JoinConnection(ci->GetOrigin(), ci->OriginPort());
}
if (couldJoin) {
LOG(("FindCoalescableConnectionByHashKey() found match conn=%p key=%s newCI=%s matchedCI=%s join ok\n",
potentialMatch.get(), key.get(), ci->HashKey().get(), potentialMatch->ConnectionInfo()->HashKey().get()));
return potentialMatch.get();
}
LOG(("FindCoalescableConnectionByHashKey() found match conn=%p key=%s newCI=%s matchedCI=%s join failed\n",
potentialMatch.get(), key.get(), ci->HashKey().get(), potentialMatch->ConnectionInfo()->HashKey().get()));
++j; // bypassed by continue when weakptr fails
}
if (!listLen) { // shrunk to 0 while iterating
LOG(("FindCoalescableConnectionByHashKey() removing empty list element\n"));
mCoalescingHash.Remove(key);
}
return nullptr;
}
static void
BuildOriginFrameHashKey(nsACString &newKey, nsHttpConnectionInfo *ci,
const nsACString &host, int32_t port)
{
newKey.Assign(host);
if (ci->GetAnonymous()) {
newKey.AppendLiteral("~A:");
} else {
newKey.AppendLiteral("~.:");
}
newKey.AppendInt(port);
newKey.AppendLiteral("/[");
nsAutoCString suffix;
ci->GetOriginAttributes().CreateSuffix(suffix);
newKey.Append(suffix);
newKey.AppendLiteral("]viaORIGIN.FRAME");
}
nsHttpConnection *
nsHttpConnectionMgr::FindCoalescableConnection(nsConnectionEntry *ent,
bool justKidding)
{
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
MOZ_ASSERT(ent->mConnInfo);
nsHttpConnectionInfo *ci = ent->mConnInfo;
LOG(("FindCoalescableConnection %s\n", ci->HashKey().get()));
// First try and look it up by origin frame
nsCString newKey;
BuildOriginFrameHashKey(newKey, ci, ci->GetOrigin(), ci->OriginPort());
nsHttpConnection *conn =
FindCoalescableConnectionByHashKey(ent, newKey, justKidding);
if (conn) {
LOG(("FindCoalescableConnection(%s) match conn %p on frame key %s\n",
ci->HashKey().get(), conn, newKey.get()));
return conn;
}
// now check for DNS based keys
// deleted conns (null weak pointers) are removed from list
uint32_t keyLen = ent->mCoalescingKeys.Length();
for (uint32_t i = 0; i < keyLen; ++i) {
conn = FindCoalescableConnectionByHashKey(ent, ent->mCoalescingKeys[i], justKidding);
if (conn) {
LOG(("FindCoalescableConnection(%s) match conn %p on dns key %s\n",
ci->HashKey().get(), conn, ent->mCoalescingKeys[i].get()));
return conn;
}
}
LOG(("FindCoalescableConnection(%s) no matching conn\n", ci->HashKey().get()));
return nullptr;
}
void
nsHttpConnectionMgr::UpdateCoalescingForNewConn(nsHttpConnection *newConn,
nsConnectionEntry *ent)
{
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
MOZ_ASSERT(newConn);
MOZ_ASSERT(newConn->ConnectionInfo());
MOZ_ASSERT(ent);
MOZ_ASSERT(mCT.GetWeak(newConn->ConnectionInfo()->HashKey()) == ent);
nsHttpConnection *existingConn = FindCoalescableConnection(ent, true);
if (existingConn) {
LOG(("UpdateCoalescingForNewConn() found existing active conn that could have served newConn "
"graceful close of newConn=%p to migrate to existingConn %p\n", newConn, existingConn));
newConn->DontReuse();
return;
}
// This connection might go into the mCoalescingHash for new transactions to be coalesced onto
// if it can accept new transactions
if (!newConn->CanDirectlyActivate()) {
return;
}
uint32_t keyLen = ent->mCoalescingKeys.Length();
for (uint32_t i = 0;i < keyLen; ++i) {
LOG(("UpdateCoalescingForNewConn() registering newConn %p %s under key %s\n",
newConn, newConn->ConnectionInfo()->HashKey().get(), ent->mCoalescingKeys[i].get()));
nsTArray<nsWeakPtr> *listOfWeakConns = mCoalescingHash.Get(ent->mCoalescingKeys[i]);
if (!listOfWeakConns) {
LOG(("UpdateCoalescingForNewConn() need new list element\n"));
listOfWeakConns = new nsTArray<nsWeakPtr>(1);
mCoalescingHash.Put(ent->mCoalescingKeys[i], listOfWeakConns);
}
listOfWeakConns->AppendElement(
do_GetWeakReference(static_cast<nsISupportsWeakReference*>(newConn)));
}
// Cancel any other pending connections - their associated transactions
// are in the pending queue and will be dispatched onto this new connection
for (int32_t index = ent->mHalfOpens.Length() - 1; index >= 0; --index) {
RefPtr<nsHalfOpenSocket> half = ent->mHalfOpens[index];
LOG(("UpdateCoalescingForNewConn() forcing halfopen abandon %p\n",
half.get()));
ent->mHalfOpens[index]->Abandon();
}
if (ent->mActiveConns.Length() > 1) {
// this is a new connection that can be coalesced onto. hooray!
// if there are other connection to this entry (e.g.
// some could still be handshaking, shutting down, etc..) then close
// them down after any transactions that are on them are complete.
// This probably happened due to the parallel connection algorithm
// that is used only before the host is known to speak h2.
for (uint32_t index = 0; index < ent->mActiveConns.Length(); ++index) {
nsHttpConnection *otherConn = ent->mActiveConns[index];
if (otherConn != newConn) {
LOG(("UpdateCoalescingForNewConn() shutting down old connection (%p) because new "
"spdy connection (%p) takes precedence\n", otherConn, newConn));
otherConn->DontReuse();
}
}
}
for (int32_t index = ent->mHalfOpenFastOpenBackups.Length() - 1; index >= 0; --index) {
LOG(("UpdateCoalescingForNewConn() shutting down connection in fast "
"open state (%p) because new spdy connection (%p) takes "
"precedence\n", ent->mHalfOpenFastOpenBackups[index].get(), newConn));
RefPtr<nsHalfOpenSocket> half = ent->mHalfOpenFastOpenBackups[index];
half->CancelFastOpenConnection();
}
}
// 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 coalescing hash
// entries used for de-sharding hostsnames.
void
nsHttpConnectionMgr::ReportSpdyConnection(nsHttpConnection *conn,
bool usingSpdy)
{
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
if (!conn->ConnectionInfo()) {
return;
}
nsConnectionEntry *ent = mCT.GetWeak(conn->ConnectionInfo()->HashKey());
if (!ent || !usingSpdy) {
return;
}
ent->mUsingSpdy = true;
mNumSpdyActiveConns++;
// adjust timeout timer
uint32_t ttl = conn->TimeToLive();
uint64_t timeOfExpire = NowInSeconds() + ttl;
if (!mTimer || timeOfExpire < mTimeOfNextWakeUp) {
PruneDeadConnectionsAfter(ttl);
}
UpdateCoalescingForNewConn(conn, ent);
nsresult rv = ProcessPendingQ(ent->mConnInfo);
if (NS_FAILED(rv)) {
LOG(("ReportSpdyConnection conn=%p ent=%p "
"failed to process pending queue (%08x)\n", conn, ent,
static_cast<uint32_t>(rv)));
}
rv = PostEvent(&nsHttpConnectionMgr::OnMsgProcessAllSpdyPendingQ);
if (NS_FAILED(rv)) {
LOG(("ReportSpdyConnection conn=%p ent=%p "
"failed to post event (%08x)\n", conn, ent,
static_cast<uint32_t>(rv)));
}
}
//-----------------------------------------------------------------------------
bool
nsHttpConnectionMgr::DispatchPendingQ(nsTArray<RefPtr<nsHttpConnectionMgr::PendingTransactionInfo> > &pendingQ,
nsConnectionEntry *ent,
bool considerAll)
{
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
PendingTransactionInfo *pendingTransInfo = nullptr;
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 < pendingQ.Length(); ) {
pendingTransInfo = pendingQ[i];
LOG(("nsHttpConnectionMgr::DispatchPendingQ "
"[trans=%p, halfOpen=%p, activeConn=%p]\n",
pendingTransInfo->mTransaction.get(),
pendingTransInfo->mHalfOpen.get(),
pendingTransInfo->mActiveConn.get()));
// 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 alreadyHalfOpenOrWaitingForTLS = false;
if (pendingTransInfo->mHalfOpen) {
MOZ_ASSERT(!pendingTransInfo->mActiveConn);
RefPtr<nsHalfOpenSocket> halfOpen =
do_QueryReferent(pendingTransInfo->mHalfOpen);
LOG(("nsHttpConnectionMgr::DispatchPendingQ "
"[trans=%p, halfOpen=%p]\n",
pendingTransInfo->mTransaction.get(), halfOpen.get()));
if (halfOpen) {
alreadyHalfOpenOrWaitingForTLS = true;
} else {
// If we have not found the halfOpen socket, remove the pointer.
pendingTransInfo->mHalfOpen = nullptr;
}
} else if (pendingTransInfo->mActiveConn) {
MOZ_ASSERT(!pendingTransInfo->mHalfOpen);
RefPtr<nsHttpConnection> activeConn =
do_QueryReferent(pendingTransInfo->mActiveConn);
LOG(("nsHttpConnectionMgr::DispatchPendingQ "
"[trans=%p, activeConn=%p]\n",
pendingTransInfo->mTransaction.get(), activeConn.get()));
// Check if this transaction claimed a connection that is still
// performing tls handshake with a NullHttpTransaction or it is between
// finishing tls and reclaiming (When nullTrans finishes tls handshake,
// httpConnection does not have a transaction any more and a
// ReclaimConnection is dispatched). But if an error occurred the
// connection will be closed, it will exist but CanReused will be
// false.
if (activeConn &&
((activeConn->Transaction() &&
activeConn->Transaction()->IsNullTransaction()) ||
(!activeConn->Transaction() && activeConn->CanReuse()))) {
alreadyHalfOpenOrWaitingForTLS = true;
} else {
// If we have not found the connection, remove the pointer.
pendingTransInfo->mActiveConn = nullptr;
}
}
rv = TryDispatchTransaction(ent,
alreadyHalfOpenOrWaitingForTLS || !!pendingTransInfo->mTransaction->TunnelProvider(),
pendingTransInfo);
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 %" PRIx32 "\n",
static_cast<uint32_t>(rv)));
}
ReleaseClaimedSockets(ent, pendingTransInfo);
if (pendingQ.RemoveElement(pendingTransInfo)) {
// pendingTransInfo is now potentially destroyed
dispatchedSuccessfully = true;
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;
}
uint32_t
nsHttpConnectionMgr::TotalActiveConnections(nsConnectionEntry *ent) const
{
// 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.
return ent->mActiveConns.Length() + ent->UnconnectedHalfOpens();
}
uint32_t
nsHttpConnectionMgr::MaxPersistConnections(nsConnectionEntry *ent) const
{
if (ent->mConnInfo->UsingHttpProxy() && !ent->mConnInfo->UsingConnect()) {
return static_cast<uint32_t>(mMaxPersistConnsPerProxy);
}
return static_cast<uint32_t>(mMaxPersistConnsPerHost);
}
void
nsHttpConnectionMgr::PreparePendingQForDispatching(
nsConnectionEntry *ent,
nsTArray<RefPtr<PendingTransactionInfo>> &pendingQ,
bool considerAll)
{
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
pendingQ.Clear();
uint32_t totalCount = TotalActiveConnections(ent);
uint32_t maxPersistConns = MaxPersistConnections(ent);
uint32_t availableConnections = maxPersistConns > totalCount
? maxPersistConns - totalCount
: 0;
// No need to try dispatching if we reach the active connection limit.
if (!availableConnections) {
return;
}
// Only have to get transactions from the queue whose window id is 0.
if (!gHttpHandler->ActiveTabPriority()) {
ent->AppendPendingQForFocusedWindow(0, pendingQ, availableConnections);
return;
}
uint32_t maxFocusedWindowConnections =
availableConnections * gHttpHandler->FocusedWindowTransactionRatio();
MOZ_ASSERT(maxFocusedWindowConnections < availableConnections);
if (!maxFocusedWindowConnections) {
maxFocusedWindowConnections = 1;
}
// Only need to dispatch transactions for either focused or
// non-focused window because considerAll is false.
if (!considerAll) {
ent->AppendPendingQForFocusedWindow(
mCurrentTopLevelOuterContentWindowId,
pendingQ,
maxFocusedWindowConnections);
if (pendingQ.IsEmpty()) {
ent->AppendPendingQForNonFocusedWindows(
mCurrentTopLevelOuterContentWindowId,
pendingQ,
availableConnections);
}
return;
}
uint32_t maxNonFocusedWindowConnections =
availableConnections - maxFocusedWindowConnections;
nsTArray<RefPtr<PendingTransactionInfo>> remainingPendingQ;
ent->AppendPendingQForFocusedWindow(
mCurrentTopLevelOuterContentWindowId,
pendingQ,
maxFocusedWindowConnections);
if (maxNonFocusedWindowConnections) {
ent->AppendPendingQForNonFocusedWindows(
mCurrentTopLevelOuterContentWindowId,
remainingPendingQ,
maxNonFocusedWindowConnections);
}
// If the slots for either focused or non-focused window are not filled up
// to the availability, try to use the remaining available connections
// for the other slot (with preference for the focused window).
if (remainingPendingQ.Length() < maxNonFocusedWindowConnections) {
ent->AppendPendingQForFocusedWindow(
mCurrentTopLevelOuterContentWindowId,
pendingQ,
maxNonFocusedWindowConnections - remainingPendingQ.Length());
} else if (pendingQ.Length() < maxFocusedWindowConnections) {
ent->AppendPendingQForNonFocusedWindows(
mCurrentTopLevelOuterContentWindowId,
remainingPendingQ,
maxFocusedWindowConnections - pendingQ.Length());
}
MOZ_ASSERT(pendingQ.Length() + remainingPendingQ.Length() <=
availableConnections);
LOG(("nsHttpConnectionMgr::PreparePendingQForDispatching "
"focused window pendingQ.Length()=%zu"
", remainingPendingQ.Length()=%zu\n",
pendingQ.Length(), remainingPendingQ.Length()));
// Append elements in |remainingPendingQ| to |pendingQ|. The order in
// |pendingQ| is like: [focusedWindowTrans...nonFocusedWindowTrans].
pendingQ.AppendElements(std::move(remainingPendingQ));
}
bool
nsHttpConnectionMgr::ProcessPendingQForEntry(nsConnectionEntry *ent, bool considerAll)
{
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
LOG(("nsHttpConnectionMgr::ProcessPendingQForEntry "
"[ci=%s ent=%p active=%zu idle=%zu urgent-start-queue=%zu"
" queued=%zu]\n",
ent->mConnInfo->HashKey().get(), ent, ent->mActiveConns.Length(),
ent->mIdleConns.Length(), ent->mUrgentStartQ.Length(),
ent->PendingQLength()));
if (LOG_ENABLED()) {
LOG(("urgent queue ["));
for (const auto& info : ent->mUrgentStartQ) {
LOG((" %p", info->mTransaction.get()));
}
for (auto it = ent->mPendingTransactionTable.Iter(); !it.Done(); it.Next()) {
LOG(("] window id = %" PRIx64 " queue [", it.Key()));
for (const auto& info : *it.UserData()) {
LOG((" %p", info->mTransaction.get()));
}
}
LOG(("] active urgent conns ["));
for (nsHttpConnection* conn : ent->mActiveConns) {
if (conn->IsUrgentStartPreferred()) {
LOG((" %p", conn));
}
}
LOG(("] active regular conns ["));
for (nsHttpConnection* conn : ent->mActiveConns) {
if (!conn->IsUrgentStartPreferred()) {
LOG((" %p", conn));
}
}
LOG(("] idle urgent conns ["));
for (nsHttpConnection* conn : ent->mIdleConns) {
if (conn->IsUrgentStartPreferred()) {
LOG((" %p", conn));
}
}
LOG(("] idle regular conns ["));
for (nsHttpConnection* conn : ent->mIdleConns) {
if (!conn->IsUrgentStartPreferred()) {
LOG((" %p", conn));
}
}
LOG(("]"));
}
if (!ent->mUrgentStartQ.Length() && !ent->PendingQLength()) {
return false;
}
ProcessSpdyPendingQ(ent);
bool dispatchedSuccessfully = false;
if (!ent->mUrgentStartQ.IsEmpty()) {
dispatchedSuccessfully = DispatchPendingQ(ent->mUrgentStartQ,
ent,
considerAll);
}
if (dispatchedSuccessfully && !considerAll) {
return dispatchedSuccessfully;
}
nsTArray<RefPtr<PendingTransactionInfo>> pendingQ;
PreparePendingQForDispatching(ent, pendingQ, considerAll);
// The only case that |pendingQ| is empty is when there is no
// connection available for dispatching.
if (pendingQ.IsEmpty()) {
return dispatchedSuccessfully;
}
dispatchedSuccessfully |=
DispatchPendingQ(pendingQ, ent, considerAll);
// Put the leftovers into connection entry, in the same order as they
// were before to keep the natural ordering.
for (const auto& transactionInfo : Reversed(pendingQ)) {
ent->InsertTransaction(transactionInfo, true);
}
// Only remove empty pendingQ when considerAll is true.
if (considerAll) {
ent->RemoveEmptyPendingQ();
}
return dispatchedSuccessfully;
}
bool
nsHttpConnectionMgr::ProcessPendingQForEntry(nsHttpConnectionInfo *ci)
{
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
nsConnectionEntry *ent = mCT.GetWeak(ci->HashKey());
if (ent)
return ProcessPendingQForEntry(ent, false);
return false;
}
// 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;
uint32_t totalCount = TotalActiveConnections(ent);
uint32_t maxPersistConns = MaxPersistConnections(ent);
LOG(("nsHttpConnectionMgr::AtActiveConnectionLimit [ci=%s caps=%x,"
"totalCount=%u, maxPersistConns=%u]\n",
ci->HashKey().get(), caps, totalCount, maxPersistConns));
if (caps & NS_HTTP_URGENT_START) {
if (totalCount >= (mMaxUrgentExcessiveConns + maxPersistConns)) {
LOG(("The number of total connections are greater than or equal to sum of "
"max urgent-start queue length and the number of max persistent connections.\n"));
return true;
}
return false;
}
// 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;
}
bool result = (totalCount >= maxPersistConns);
LOG(("AtActiveConnectionLimit 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()) {
RefPtr<nsHttpConnection> conn(ent->mIdleConns[0]);
ent->mIdleConns.RemoveElementAt(0);
mNumIdleConns--;
conn->Close(NS_ERROR_ABORT);
}
int32_t activeCount = ent->mActiveConns.Length();
for (int32_t i=0; i < activeCount; i++)
ent->mActiveConns[i]->DontReuse();
for (int32_t index = ent->mHalfOpenFastOpenBackups.Length() - 1; index >= 0; --index) {
RefPtr<nsHalfOpenSocket> half = ent->mHalfOpenFastOpenBackups[index];
half->CancelFastOpenConnection();
}
}
bool
nsHttpConnectionMgr::RestrictConnections(nsConnectionEntry *ent)
{
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
if (ent->AvailableForDispatchNow()) {
// this might be a h2/spdy connection in this connection entry that
// is able to be immediately muxxed, or it might be one that
// was found in the same state through a coalescing hash
LOG(("nsHttpConnectionMgr::RestrictConnections %p %s restricted due to active >=h2\n",
ent, ent->mConnInfo->HashKey().get()));
return true;
}
// 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,
PendingTransactionInfo *pendingTransInfo)
{
nsHttpTransaction *trans = pendingTransInfo->mTransaction;
LOG(("nsHttpConnectionMgr::MakeNewConnection %p ent=%p trans=%p",
this, ent, trans));
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
uint32_t halfOpenLength = ent->mHalfOpens.Length();
for (uint32_t i = 0; i < halfOpenLength; i++) {
auto halfOpen = ent->mHalfOpens[i];
if (halfOpen->AcceptsTransaction(trans) && halfOpen->Claim()) {
// We've found a speculative connection or a connection that
// is free to be used in the half open list.
// A free to be used connection is a connection that was
// open for a concrete transaction, but that trunsaction
// ended up using another connection.
LOG(("nsHttpConnectionMgr::MakeNewConnection [ci = %s]\n"
"Found a speculative or a free-to-use half open connection\n",
ent->mConnInfo->HashKey().get()));
pendingTransInfo->mHalfOpen =
do_GetWeakReference(static_cast<nsISupportsWeakReference*>(ent->mHalfOpens[i]));
// 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()));
pendingTransInfo->mActiveConn =
do_GetWeakReference(static_cast<nsISupportsWeakReference*>(ent->mActiveConns[i]));
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"
// because 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()) {
RefPtr<nsConnectionEntry> entry = iter.Data();
if (!entry->mIdleConns.Length()) {
iter.Next();
continue;
}
RefPtr<nsHttpConnection> conn(entry->mIdleConns[0]);
entry->mIdleConns.RemoveElementAt(0);
conn->Close(NS_ERROR_ABORT);
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()) {
RefPtr<nsConnectionEntry> entry = iter.Data();
if (!entry->mUsingSpdy) {
continue;
}
for (uint32_t index = 0;
index < entry->mActiveConns.Length();
++index) {
nsHttpConnection *conn = entry->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,
trans->ClassOfService() & nsIClassOfService::UrgentStart,
true, pendingTransInfo);
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;
}
// 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,
PendingTransactionInfo *pendingTransInfo)
{
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
nsHttpTransaction *trans = pendingTransInfo->mTransaction;
LOG(("nsHttpConnectionMgr::TryDispatchTransaction without conn "
"[trans=%p halfOpen=%p conn=%p ci=%p ci=%s caps=%x tunnelprovider=%p "
"onlyreused=%d active=%zu idle=%zu]\n", trans,
pendingTransInfo->mHalfOpen.get(),
pendingTransInfo->mActiveConn.get(), ent->mConnInfo.get(),
ent->mConnInfo->HashKey().get(),
uint32_t(trans->Caps()), trans->TunnelProvider(),
onlyReusedConnection, ent->mActiveConns.Length(),
ent->mIdleConns.Length()));
uint32_t caps = trans->Caps();
// 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
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 = GetSpdyActiveConn(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 */
nsresult rv = DispatchTransaction(ent, trans, conn);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
unusedSpdyPersistentConnection = conn;
}
}
// If this is not a blocking transaction and the request 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)) {
nsIRequestContext *requestContext = trans->RequestContext();
if (requestContext) {
uint32_t blockers = 0;
if (NS_SUCCEEDED(requestContext->GetBlockingTransactionCount(&blockers)) &&
blockers) {
// need to wait for blockers to clear
LOG((" blocked by request context: [rc=%p trans=%p blockers=%d]\n",
requestContext, 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
// h1 pipelining has been removed
// 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
bool idleConnsAllUrgent = false;
if (caps & NS_HTTP_ALLOW_KEEPALIVE) {
nsresult rv = TryDispatchTransactionOnIdleConn(ent, pendingTransInfo,
true, &idleConnsAllUrgent);
if (NS_SUCCEEDED(rv)) {
LOG((" dispatched step 2 (idle) trans=%p\n", trans));
return NS_OK;
}
}
// step 3
// consider pipelining scripts and revalidations
// h1 pipelining has been removed
// step 4
if (!onlyReusedConnection) {
nsresult rv = MakeNewConnection(ent, pendingTransInfo);
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 (%" PRIx32 ") trans=%p\n",
static_cast<uint32_t>(rv), trans));
return rv;
}
// repeat step 2 when there are only idle connections and all are urgent,
// don't respect urgency so that non-urgent transaction will be allowed
// to dispatch on an urgent-start-only marked connection to avoid
// dispatch deadlocks
if (!(trans->ClassOfService() & nsIClassOfService::UrgentStart) &&
idleConnsAllUrgent &&
ent->mActiveConns.Length() < MaxPersistConnections(ent))
{
rv = TryDispatchTransactionOnIdleConn(ent, pendingTransInfo, false);
if (NS_SUCCEEDED(rv)) {
LOG((" dispatched step 2a (idle, reuse urgent) trans=%p\n", trans));
return NS_OK;
}
}
} 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
// previously pipelined anything here if allowed but h1 pipelining has been removed
// 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::TryDispatchTransactionOnIdleConn(
nsConnectionEntry * ent, PendingTransactionInfo * pendingTransInfo,
bool respectUrgency, bool *allUrgent)
{
bool onlyUrgent = !!ent->mIdleConns.Length();
nsHttpTransaction *trans = pendingTransInfo->mTransaction;
bool urgentTrans = trans->ClassOfService() & nsIClassOfService::UrgentStart;
LOG(("nsHttpConnectionMgr::TryDispatchTransactionOnIdleConn, ent=%p, trans=%p, urgent=%d",
ent, trans, urgentTrans));
RefPtr<nsHttpConnection> conn;
size_t index = 0;
while (!conn && (ent->mIdleConns.Length() > index)) {
conn = ent->mIdleConns[index];
// non-urgent transactions can only be dispatched on non-urgent
// started or used connections.
if (respectUrgency && conn->IsUrgentStartPreferred() && !urgentTrans) {
LOG((" skipping urgent: [conn=%p]", conn.get()));
conn = nullptr;
++index;
continue;
}
onlyUrgent = false;
ent->mIdleConns.RemoveElementAt(index);
mNumIdleConns--;
// 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 (allUrgent) {
*allUrgent = onlyUrgent;
}
if (conn) {
// This will update the class of the connection to be the class of
// the transaction dispatched on it.
AddActiveConn(conn, ent);
nsresult rv = DispatchTransaction(ent, trans, conn);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
return NS_ERROR_NOT_AVAILABLE;
}
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. h2) 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");
rv = DispatchAbstractTransaction(ent, trans, caps, conn, priority);
if (NS_SUCCEEDED(rv) && !trans->GetPendingTime().IsNull()) {
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 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) : mConn(conn) { }
void Reset() { mConn = nullptr; }
private:
virtual ~ConnectionHandle();
RefPtr<nsHttpConnection> mConn;
};
nsAHttpConnection *
nsHttpConnectionMgr::MakeConnectionHandle(nsHttpConnection *aWrapped)
{
return new ConnectionHandle(aWrapped);
}
ConnectionHandle::~ConnectionHandle()
{
if (mConn) {
nsresult rv = gHttpHandler->ReclaimConnection(mConn);
if (NS_FAILED(rv)) {
LOG(("ConnectionHandle::~ConnectionHandle\n"
" failed to reclaim connection\n"));
}
}
}
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(ent);
nsresult rv;
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));
RefPtr<nsAHttpTransaction> 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=%" PRIx32 "]\n", static_cast<uint32_t>(rv)));
ent->mActiveConns.RemoveElement(conn);
DecrementActiveConnCount(conn);
ConditionallyStopTimeoutTick();
// sever back references to connection, and do so without triggering
// a call to ReclaimConnection ;-)
transaction->SetConnection(nullptr);
handle->Reset(); // destroy the connection
}
return rv;
}
void
nsHttpConnectionMgr::ReportProxyTelemetry(nsConnectionEntry *ent)
{
enum { PROXY_NONE = 1, PROXY_HTTP = 2, PROXY_SOCKS = 3, PROXY_HTTPS = 4 };
if (!ent->mConnInfo->UsingProxy())
Telemetry::Accumulate(Telemetry::HTTP_PROXY_TYPE, PROXY_NONE);
else if (ent->mConnInfo->UsingHttpsProxy())
Telemetry::Accumulate(Telemetry::HTTP_PROXY_TYPE, PROXY_HTTPS);
else if (ent->mConnInfo->UsingHttpProxy())
Telemetry::Accumulate(Telemetry::HTTP_PROXY_TYPE, PROXY_HTTP);
else
Telemetry::Accumulate(Telemetry::HTTP_PROXY_TYPE, PROXY_SOCKS);
}
nsresult
nsHttpConnectionMgr::ProcessNewTransaction(nsHttpTransaction *trans)
{
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
// since "adds" and "cancels" are processed asynchronously and because
// various events might trigger an "add" directly on the socket thread,
// we must take care to avoid dispatching a transaction that has already
// been canceled (see bug 190001).
if (NS_FAILED(trans->Status())) {
LOG((" transaction was canceled... dropping event!\n"));
return NS_OK;
}
trans->SetPendingTime();
Http2PushedStream *pushedStream = trans->GetPushedStream();
if (pushedStream) {
LOG((" ProcessNewTransaction %p tied to h2 session push %p\n",
trans, pushedStream->Session()));
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());
MOZ_ASSERT(ent);
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;
RefPtr<PendingTransactionInfo> pendingTransInfo;
if (wrappedConnection)
conn = 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 {
pendingTransInfo = new PendingTransactionInfo(trans);
rv = TryDispatchTransaction(ent, !!trans->TunnelProvider(), pendingTransInfo);
}
if (NS_SUCCEEDED(rv)) {
LOG((" ProcessNewTransaction Dispatch Immediately trans=%p\n", trans));
return rv;
}
if (rv == NS_ERROR_NOT_AVAILABLE) {
if (!pendingTransInfo) {
pendingTransInfo = new PendingTransactionInfo(trans);
}
if (trans->Caps() & NS_HTTP_URGENT_START) {
LOG((" adding transaction to pending queue "
"[trans=%p urgent-start-count=%zu]\n",
trans, ent->mUrgentStartQ.Length() + 1));
// put this transaction on the urgent-start queue...
InsertTransactionSorted(ent->mUrgentStartQ, pendingTransInfo);
} else {
LOG((" adding transaction to pending queue "
"[trans=%p pending-count=%zu]\n",
trans, ent->PendingQLength() + 1));
// put this transaction on the pending queue...
ent->InsertTransaction(pendingTransInfo);
}
return NS_OK;
}
LOG((" ProcessNewTransaction Hard Error trans=%p rv=%" PRIx32 "\n",
trans, static_cast<uint32_t>(rv)));
return rv;
}
void
nsHttpConnectionMgr::AddActiveConn(nsHttpConnection *conn,
nsConnectionEntry *ent)
{
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();
}
void
nsHttpConnectionMgr::ReleaseClaimedSockets(nsConnectionEntry *ent,
PendingTransactionInfo * pendingTransInfo)
{
if (pendingTransInfo->mHalfOpen) {
RefPtr<nsHalfOpenSocket> halfOpen =
do_QueryReferent(pendingTransInfo->mHalfOpen);
LOG(("nsHttpConnectionMgr::ReleaseClaimedSockets "
"[trans=%p halfOpen=%p]",
pendingTransInfo->mTransaction.get(),
halfOpen.get()));
if (halfOpen) {
halfOpen->Unclaim();
}
pendingTransInfo->mHalfOpen = nullptr;
} else if (pendingTransInfo->mActiveConn) {
RefPtr<nsHttpConnection> activeConn =
do_QueryReferent(pendingTransInfo->mActiveConn);
if (activeConn && activeConn->Transaction() &&
activeConn->Transaction()->IsNullTransaction()) {
NullHttpTransaction *nullTrans = activeConn->Transaction()->QueryNullTransaction();
nullTrans->Unclaim();
LOG(("nsHttpConnectionMgr::ReleaseClaimedSockets - mark %p unclaimed.",
activeConn.get()));
}
}
}
nsresult
nsHttpConnectionMgr::CreateTransport(nsConnectionEntry *ent,
nsAHttpTransaction *trans,
uint32_t caps,
bool speculative,
bool isFromPredictor,
bool urgentStart,
bool allow1918,
PendingTransactionInfo *pendingTransInfo)
{
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
MOZ_ASSERT((speculative && !pendingTransInfo) ||
(!speculative && pendingTransInfo));
RefPtr<nsHalfOpenSocket> sock = new nsHalfOpenSocket(ent, trans, caps,
speculative,
isFromPredictor,
urgentStart);
if (speculative) {
sock->SetAllow1918(allow1918);
}
// 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);
if (pendingTransInfo) {
pendingTransInfo->mHalfOpen =
do_GetWeakReference(static_cast<nsISupportsWeakReference*>(sock));
DebugOnly<bool> claimed = sock->Claim();
MOZ_ASSERT(claimed);
}
ent->mHalfOpens.AppendElement(sock);
mNumHalfOpenConns++;
return NS_OK;
}
void
nsHttpConnectionMgr::DispatchSpdyPendingQ(nsTArray<RefPtr<PendingTransactionInfo>> &pendingQ,
nsConnectionEntry *ent,
nsHttpConnection *conn)
{
if (pendingQ.Length() == 0) {
return;
}
nsTArray<RefPtr<PendingTransactionInfo>> leftovers;
uint32_t index;
// Dispatch all the transactions we can
for (index = 0;
index < pendingQ.Length() && conn->CanDirectlyActivate();
++index) {
PendingTransactionInfo *pendingTransInfo = pendingQ[index];
if (!(pendingTransInfo->mTransaction->Caps() & NS_HTTP_ALLOW_KEEPALIVE) ||
pendingTransInfo->mTransaction->Caps() & NS_HTTP_DISALLOW_SPDY) {
leftovers.AppendElement(pendingTransInfo);
continue;
}
nsresult rv = DispatchTransaction(ent, pendingTransInfo->mTransaction,
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",
pendingTransInfo->mTransaction.get()));
pendingTransInfo->mTransaction->Close(rv);
}
ReleaseClaimedSockets(ent, pendingTransInfo);
}
// Slurp up the rest of the pending queue into our leftovers bucket (we
// might have some left if conn->CanDirectlyActivate returned false)
for (; index < pendingQ.Length(); ++index) {
PendingTransactionInfo *pendingTransInfo = pendingQ[index];
leftovers.AppendElement(pendingTransInfo);
}
// Put the leftovers back in the pending queue and get rid of the
// transactions we dispatched
leftovers.SwapElements(pendingQ);
leftovers.Clear();
}
// 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 from the
// coalescing hash table
void
nsHttpConnectionMgr::ProcessSpdyPendingQ(nsConnectionEntry *ent)
{
nsHttpConnection *conn = GetSpdyActiveConn(ent);
if (!conn || !conn->CanDirectlyActivate()) {
return;
}
DispatchSpdyPendingQ(ent->mUrgentStartQ, ent, conn);
if (!conn->CanDirectlyActivate()) {
return;
}
nsTArray<RefPtr<PendingTransactionInfo>> pendingQ;
// XXX Get all transactions for SPDY currently.
ent->AppendPendingQForNonFocusedWindows(0, pendingQ);
DispatchSpdyPendingQ(pendingQ, ent, conn);
// Put the leftovers back in the pending queue.
for (const auto& transactionInfo : pendingQ) {
ent->InsertTransaction(transactionInfo);
}
}
void
nsHttpConnectionMgr::OnMsgProcessAllSpdyPendingQ(int32_t, ARefBase *)
{
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
LOG(("nsHttpConnectionMgr::OnMsgProcessAllSpdyPendingQ\n"));
for (auto iter = mCT.Iter(); !iter.Done(); iter.Next()) {
ProcessSpdyPendingQ(iter.Data().get());
}
}
// Given a connection entry, return an active h2 connection
// that can be directly activated or null
nsHttpConnection *
nsHttpConnectionMgr::GetSpdyActiveConn(nsConnectionEntry *ent)
{
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
MOZ_ASSERT(ent);
nsHttpConnection *experienced = nullptr;
nsHttpConnection *noExperience = nullptr;
uint32_t activeLen = ent->mActiveConns.Length();
nsHttpConnectionInfo *ci = ent->mConnInfo;
uint32_t index;
// activeLen should generally be 1.. this is a setup race being resolved
// take a conn who can activate and is experienced
for (index = 0; index < activeLen; ++index) {
nsHttpConnection *tmp = ent->mActiveConns[index];
if (tmp->CanDirectlyActivate()) {
if (tmp->IsExperienced()) {
experienced = tmp;
break;
}
noExperience = tmp; // keep looking for a better option
}
}
// if that worked, cleanup anything else and exit
if (experienced) {
for (index = 0; index < activeLen; ++index) {
nsHttpConnection *tmp = ent->mActiveConns[index];
// in the case where there is a functional h2 session, drop the others
if (tmp != experienced) {
tmp->DontReuse();
}
}
for (int32_t index = ent->mHalfOpenFastOpenBackups.Length() - 1; index >= 0; --index) {
LOG(("GetSpdyActiveConn() shutting down connection in fast "
"open state (%p) because we have an experienced spdy "
"connection (%p).\n",
ent->mHalfOpenFastOpenBackups[index].get(), experienced));
RefPtr<nsHalfOpenSocket> half = ent->mHalfOpenFastOpenBackups[index];
half->CancelFastOpenConnection();
}
LOG(("GetSpdyActiveConn() request for ent %p %s "
"found an active experienced connection %p in native connection entry\n",
ent, ci->HashKey().get(), experienced));
return experienced;
}
if (noExperience) {
LOG(("GetSpdyActiveConn() request for ent %p %s "
"found an active but inexperienced connection %p in native connection entry\n",
ent, ci->HashKey().get(), noExperience));
return noExperience;
}
// there was no active spdy connection in the connection entry, but
// there might be one in the hash table for coalescing
nsHttpConnection *existingConn = FindCoalescableConnection(ent, false);
if (existingConn) {
LOG(("GetSpdyActiveConn() request for ent %p %s "
"found an active connection %p in the coalescing hashtable\n",
ent, ci->HashKey().get(), existingConn));
return existingConn;
}
LOG(("GetSpdyActiveConn() request for ent %p %s "
"did not find an active connection\n", ent, ci->HashKey().get()));
return nullptr;
}
//-----------------------------------------------------------------------------
void
nsHttpConnectionMgr::AbortAndCloseAllConnections(int32_t, ARefBase *)
{
if (!OnSocketThread()) {
Unused << PostEvent(&nsHttpConnectionMgr::AbortAndCloseAllConnections);
return;
}
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
LOG(("nsHttpConnectionMgr::AbortAndCloseAllConnections\n"));
for (auto iter = mCT.Iter(); !iter.Done(); iter.Next()) {
RefPtr<nsConnectionEntry> ent = iter.Data();
// Close all active connections.
while (ent->mActiveConns.Length()) {
RefPtr<nsHttpConnection> conn(ent->mActiveConns[0]);
ent->mActiveConns.RemoveElementAt(0);
DecrementActiveConnCount(conn);
// Since nsHttpConnection::Close doesn't break the bond with
// the connection's transaction, we must explicitely tell it
// to close its transaction and not just self.
conn->CloseTransaction(conn->Transaction(), NS_ERROR_ABORT, true);
}
// Close all idle connections.
while (ent->mIdleConns.Length()) {
RefPtr<nsHttpConnection> conn(ent->mIdleConns[0]);
ent->mIdleConns.RemoveElementAt(0);
mNumIdleConns--;
conn->Close(NS_ERROR_ABORT);
}
// If all idle connections are removed we can stop pruning dead
// connections.
ConditionallyStopPruneDeadConnectionsTimer();
// Close all urgentStart transactions.
while (ent->mUrgentStartQ.Length()) {
PendingTransactionInfo *pendingTransInfo = ent->mUrgentStartQ[0];
pendingTransInfo->mTransaction->Close(NS_ERROR_ABORT);
ent->mUrgentStartQ.RemoveElementAt(0);
}
// Close all pending transactions.
for (auto it = ent->mPendingTransactionTable.Iter();
!it.Done();
it.Next()) {
while (it.UserData()->Length()) {
PendingTransactionInfo *pendingTransInfo = (*it.UserData())[0];
pendingTransInfo->mTransaction->Close(NS_ERROR_ABORT);
it.UserData()->RemoveElementAt(0);
}
}
ent->mPendingTransactionTable.Clear();
// Close all half open tcp connections.
for (int32_t i = int32_t(ent->mHalfOpens.Length()) - 1; i >= 0; i--) {
ent->mHalfOpens[i]->Abandon();
}
MOZ_ASSERT(ent->mHalfOpenFastOpenBackups.Length() == 0 &&
!ent->mDoNotDestroy);
iter.Remove();
}
mActiveTransactions[false].Clear();
mActiveTransactions[true].Clear();
}
void
nsHttpConnectionMgr::OnMsgShutdown(int32_t, ARefBase *param)
{
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
LOG(("nsHttpConnectionMgr::OnMsgShutdown\n"));
gHttpHandler->StopRequestTokenBucket();
AbortAndCloseAllConnections(0, nullptr);
// If all idle connections are removed we can stop pruning dead
// connections.
ConditionallyStopPruneDeadConnectionsTimer();
if (mTimeoutTick) {
mTimeoutTick->Cancel();
mTimeoutTick = nullptr;
mTimeoutTickArmed = false;
}
if (mTimer) {
mTimer->Cancel();
mTimer = nullptr;
}
if (mTrafficTimer) {
mTrafficTimer->Cancel();
mTrafficTimer = nullptr;
}
DestroyThrottleTicker();
mCoalescingHash.Clear();
// 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
}
static uint64_t TabIdForQueuing(nsAHttpTransaction *transaction)
{
return gHttpHandler->ActiveTabPriority()
? transaction->TopLevelOuterContentWindowId()
: 0;
}
nsTArray<RefPtr<nsHttpConnectionMgr::PendingTransactionInfo>>*
nsHttpConnectionMgr::GetTransactionPendingQHelper(nsConnectionEntry *ent,
nsAHttpTransaction *trans)
{
nsTArray<RefPtr<PendingTransactionInfo>> *pendingQ = nullptr;
int32_t caps = trans->Caps();
if (caps & NS_HTTP_URGENT_START) {
pendingQ = &(ent->mUrgentStartQ);
} else {
pendingQ =
ent->mPendingTransactionTable.Get(TabIdForQueuing(trans));
}
return pendingQ;
}
void
nsHttpConnectionMgr::OnMsgReschedTransaction(int32_t priority, ARefBase *param)
{
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
LOG(("nsHttpConnectionMgr::OnMsgReschedTransaction [trans=%p]\n", param));
RefPtr<nsHttpTransaction> trans = static_cast<nsHttpTransaction *>(param);
trans->SetPriority(priority);
if (!trans->ConnectionInfo()) {
return;
}
nsConnectionEntry *ent = mCT.GetWeak(trans->ConnectionInfo()->HashKey());
if (ent) {
nsTArray<RefPtr<PendingTransactionInfo>> *pendingQ =
GetTransactionPendingQHelper(ent, trans);
int32_t index = pendingQ
? pendingQ->IndexOf(trans, 0, PendingComparator())
: -1;
if (index >= 0) {
RefPtr<PendingTransactionInfo> pendingTransInfo = (*pendingQ)[index];
pendingQ->RemoveElementAt(index);
InsertTransactionSorted(*pendingQ, pendingTransInfo);
}
}
}
void nsHttpConnectionMgr::OnMsgUpdateClassOfServiceOnTransaction(int32_t arg, ARefBase *param)
{
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
LOG(("nsHttpConnectionMgr::OnMsgUpdateClassOfServiceOnTransaction [trans=%p]\n", param));
uint32_t cos = static_cast<uint32_t>(arg);
nsHttpTransaction *trans = static_cast<nsHttpTransaction *>(param);
uint32_t previous = trans->ClassOfService();
trans->SetClassOfService(cos);
if ((previous ^ cos) & (NS_HTTP_LOAD_AS_BLOCKING | NS_HTTP_LOAD_UNBLOCKED)) {
Unused << RescheduleTransaction(trans, trans->Priority());
}
}
void
nsHttpConnectionMgr::OnMsgCancelTransaction(int32_t reason, ARefBase *param)
{
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
LOG(("nsHttpConnectionMgr::OnMsgCancelTransaction [trans=%p]\n", param));
nsresult closeCode = static_cast<nsresult>(reason);
// caller holds a ref to param/trans on stack
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 = nullptr;
if (trans->ConnectionInfo()) {
ent = mCT.GetWeak(trans->ConnectionInfo()->HashKey());
}
if (ent) {
int32_t transIndex;
// We will abandon all half-open sockets belonging to the given
// transaction.
nsTArray<RefPtr<PendingTransactionInfo>> *infoArray =
GetTransactionPendingQHelper(ent, trans);
RefPtr<PendingTransactionInfo> pendingTransInfo;
transIndex = infoArray
? infoArray->IndexOf(trans, 0, PendingComparator())
: -1;
if (transIndex >=0) {
LOG(("nsHttpConnectionMgr::OnMsgCancelTransaction [trans=%p]"
" found in urgentStart queue\n", trans));
pendingTransInfo = (*infoArray)[transIndex];
// We do not need to ReleaseClaimedSockets while we are
// going to close them all any way!
infoArray->RemoveElementAt(transIndex);
}
// Abandon all half-open sockets belonging to the given transaction.
if (pendingTransInfo) {
RefPtr<nsHalfOpenSocket> half =
do_QueryReferent(pendingTransInfo->mHalfOpen);
if (half) {
half->Abandon();
}
pendingTransInfo->mHalfOpen = nullptr;
}
}
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(OnSocketThread(), "not on socket thread");
nsHttpConnectionInfo *ci = static_cast<nsHttpConnectionInfo *>(param);
if (!ci) {
LOG(("nsHttpConnectionMgr::OnMsgProcessPendingQ [ci=nullptr]\n"));
// Try and dispatch everything
for (auto iter = mCT.Iter(); !iter.Done(); iter.Next()) {
Unused << ProcessPendingQForEntry(iter.Data().get(), true);
}
return;
}
LOG(("nsHttpConnectionMgr::OnMsgProcessPendingQ [ci=%s]\n",
ci->HashKey().get()));
// start by processing the queue identified by the given connection info.
nsConnectionEntry *ent = mCT.GetWeak(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().get(), 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::CancelTransactionsHelper(
nsTArray<RefPtr<nsHttpConnectionMgr::PendingTransactionInfo>> &pendingQ,
const nsHttpConnectionInfo *ci,
const nsHttpConnectionMgr::nsConnectionEntry *ent,
nsresult reason)
{
for (const auto& pendingTransInfo : pendingQ) {
LOG(("nsHttpConnectionMgr::OnMsgCancelTransactions %s %p %p\n",
ci->HashKey().get(), ent, pendingTransInfo->mTransaction.get()));
pendingTransInfo->mTransaction->Close(reason);
}
pendingQ.Clear();
}
void
nsHttpConnectionMgr::OnMsgCancelTransactions(int32_t code, ARefBase *param)
{
nsresult reason = static_cast<nsresult>(code);
nsHttpConnectionInfo *ci = static_cast<nsHttpConnectionInfo *>(param);
nsConnectionEntry *ent = mCT.GetWeak(ci->HashKey());
LOG(("nsHttpConnectionMgr::OnMsgCancelTransactions %s %p\n",
ci->HashKey().get(), ent));
if (!ent) {
return;
}
CancelTransactionsHelper(ent->mUrgentStartQ, ci, ent, reason);
for (auto it = ent->mPendingTransactionTable.Iter();
!it.Done();
it.Next()) {
CancelTransactionsHelper(*it.UserData(), ci, ent, reason);
}
ent->mPendingTransactionTable.Clear();
}
void
nsHttpConnectionMgr::OnMsgPruneDeadConnections(int32_t, ARefBase *)
{
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
LOG(("nsHttpConnectionMgr::OnMsgPruneDeadConnections\n"));
// Reset mTimeOfNextWakeUp so that we can find a new shortest value.
mTimeOfNextWakeUp = UINT64_MAX;
// check canreuse() for all idle connections plus any active connections on
// connection entries that are using spdy.
if (mNumIdleConns || (mNumActiveConns && gHttpHandler->IsSpdyEnabled())) {
for (auto iter = mCT.Iter(); !iter.Done(); iter.Next()) {
RefPtr<nsConnectionEntry> ent = iter.Data();
LOG((" pruning [ci=%s]\n", ent->mConnInfo->HashKey().get()));
// Find out how long it will take for next idle connection to not
// be reusable anymore.
uint32_t timeToNextExpire = UINT32_MAX;
int32_t count = ent->mIdleConns.Length();
if (count > 0) {
for (int32_t i = count - 1; i >= 0; --i) {
RefPtr<nsHttpConnection> conn(ent->mIdleConns[i]);
if (!conn->CanReuse()) {
ent->mIdleConns.RemoveElementAt(i);
conn->Close(NS_ERROR_ABORT);
mNumIdleConns--;
} else {
timeToNextExpire =
std::min(timeToNextExpire, conn->TimeToLive());
}
}
}
if (ent->mUsingSpdy) {
for (uint32_t i = 0; i < ent->mActiveConns.Length(); ++i) {
nsHttpConnection* conn = ent->mActiveConns[i];
if (conn->UsingSpdy()) {
if (!conn->CanReuse()) {
// Marking it don't-reuse will create an active
// tear down if the spdy session is idle.
conn->DontReuse();
} else {
timeToNextExpire =
std::min(timeToNextExpire, conn->TimeToLive());
}
}
}
}
// If time to next expire found is shorter than time to next
// wake-up, we need to change the time for next wake-up.
if (timeToNextExpire != UINT32_MAX) {
uint32_t now = NowInSeconds();
uint64_t timeOfNextExpire = now + timeToNextExpire;
// If pruning of dead connections is not already scheduled to
// happen or time found for next connection to expire is is
// before mTimeOfNextWakeUp, we need to schedule the pruning to
// happen after timeToNextExpire.
if (!mTimer || timeOfNextExpire < mTimeOfNextWakeUp) {
PruneDeadConnectionsAfter(timeToNextExpire);
}
} else {
ConditionallyStopPruneDeadConnectionsTimer();
}
ent->RemoveEmptyPendingQ();
// If this entry is empty, we have too many entries busy then
// we can clean it up and restart
if (mCT.Count() > 125 &&
ent->mIdleConns.Length() == 0 &&
ent->mActiveConns.Length() == 0 &&
ent->mHalfOpens.Length() == 0 &&
ent->PendingQLength() == 0 &&
ent->mUrgentStartQ.Length() == 0 &&
ent->mHalfOpenFastOpenBackups.Length() == 0 &&
!ent->mDoNotDestroy &&
(!ent->mUsingSpdy || mCT.Count() > 300)) {
LOG((" removing empty connection entry\n"));
iter.Remove();
continue;
}
// Otherwise use this opportunity to compact our arrays...
ent->mIdleConns.Compact();
ent->mActiveConns.Compact();
ent->mUrgentStartQ.Compact();
for (auto it = ent->mPendingTransactionTable.Iter();
!it.Done();
it.Next()) {
it.UserData()->Compact();
}
}
}
}
void
nsHttpConnectionMgr::OnMsgPruneNoTraffic(int32_t, ARefBase *)
{
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
LOG(("nsHttpConnectionMgr::OnMsgPruneNoTraffic\n"));
// Prune connections without traffic
for (auto iter = mCT.Iter(); !iter.Done(); iter.Next()) {
// Close the connections with no registered traffic.
RefPtr<nsConnectionEntry> ent = iter.Data();
LOG((" pruning no traffic [ci=%s]\n",
ent->mConnInfo->HashKey().get()));
uint32_t numConns = ent->mActiveConns.Length();
if (numConns) {
// Walk the list backwards to allow us to remove entries easily.
for (int index = numConns - 1; index >= 0; index--) {
if (ent->mActiveConns[index]->NoTraffic()) {
RefPtr<nsHttpConnection> conn = ent->mActiveConns[index];
ent->mActiveConns.RemoveElementAt(index);
DecrementActiveConnCount(conn);
conn->Close(NS_ERROR_ABORT);
LOG((" closed active connection due to no traffic "
"[conn=%p]\n", conn.get()));
}
}
}
}
mPruningNoTraffic = false; // not pruning anymore
}
void
nsHttpConnectionMgr::OnMsgVerifyTraffic(int32_t, ARefBase *)
{
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
LOG(("nsHttpConnectionMgr::OnMsgVerifyTraffic\n"));
if (mPruningNoTraffic) {
// Called in the time gap when the timeout to prune notraffic
// connections has triggered but the pruning hasn't happened yet.
return;
}
// Mark connections for traffic verification
for (auto iter = mCT.Iter(); !iter.Done(); iter.Next()) {
RefPtr<nsConnectionEntry> ent = iter.Data();
// Iterate over all active connections and check them.
for (uint32_t index = 0; index < ent->mActiveConns.Length(); ++index) {
ent->mActiveConns[index]->CheckForTraffic(true);
}
// Iterate the idle connections and unmark them for traffic checks.
for (uint32_t index = 0; index < ent->mIdleConns.Length(); ++index) {
ent->mIdleConns[index]->CheckForTraffic(false);
}
}
// If the timer is already there. we just re-init it
if(!mTrafficTimer) {
mTrafficTimer = NS_NewTimer();
}
// 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(OnSocketThread(), "not on socket thread");
nsHttpConnectionInfo *ci = static_cast<nsHttpConnectionInfo *>(param);
for (auto iter = mCT.Iter(); !iter.Done(); iter.Next()) {
ClosePersistentConnections(iter.Data());
}
if (ci)
ResetIPFamilyPreference(ci);
}
void
nsHttpConnectionMgr::OnMsgReclaimConnection(int32_t, ARefBase *param)
{
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
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
//
MOZ_ASSERT(conn);
nsConnectionEntry *ent = conn->ConnectionInfo() ?
mCT.GetWeak(conn->ConnectionInfo()->HashKey()) : 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);
LOG(("nsHttpConnectionMgr::OnMsgReclaimConnection [ent=%p conn=%p]\n", ent, conn));
// 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)) {
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;
}
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(OnSocketThread(), "not on socket thread");
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)) {
rv = data->mUpgradeListener->OnTransportAvailable(socketTransport,
socketIn,
socketOut);
if (NS_FAILED(rv)) {
LOG(("nsHttpConnectionMgr::OnMsgCompleteUpgrade "
"this=%p conn=%p listener=%p\n", this, data->mConn.get(),
data->mUpgradeListener.get()));
}
}
}
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_URGENT_START_Q:
mMaxUrgentExcessiveConns = 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 THROTTLING_ENABLED:
SetThrottlingEnabled(!!value);
break;
case THROTTLING_SUSPEND_FOR:
mThrottleSuspendFor = value;
break;
case THROTTLING_RESUME_FOR:
mThrottleResumeFor = value;
break;
case THROTTLING_READ_LIMIT:
mThrottleReadLimit = value;
break;
case THROTTLING_READ_INTERVAL:
mThrottleReadInterval = value;
break;
case THROTTLING_HOLD_TIME:
mThrottleHoldTime = value;
break;
case THROTTLING_MAX_TIME:
mThrottleMaxTime = TimeDuration::FromMilliseconds(value);
break;
default:
MOZ_ASSERT_UNREACHABLE("unexpected parameter name");
}
}
// nsHttpConnectionMgr::nsConnectionEntry
nsHttpConnectionMgr::nsConnectionEntry::~nsConnectionEntry()
{
LOG(("nsConnectionEntry::~nsConnectionEntry this=%p", this));
MOZ_ASSERT(!mIdleConns.Length());
MOZ_ASSERT(!mActiveConns.Length());
MOZ_ASSERT(!mHalfOpens.Length());
MOZ_ASSERT(!mUrgentStartQ.Length());
MOZ_ASSERT(!PendingQLength());
MOZ_ASSERT(!mHalfOpenFastOpenBackups.Length());
MOZ_ASSERT(!mDoNotDestroy);
MOZ_COUNT_DTOR(nsConnectionEntry);
}
// Read Timeout Tick handlers
void
nsHttpConnectionMgr::ActivateTimeoutTick()
{
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
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 = NS_NewTimer();
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);
}
class UINT64Wrapper : public ARefBase
{
public:
explicit UINT64Wrapper(uint64_t aUint64) : mUint64(aUint64) {}
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(UINT64Wrapper, override)
uint64_t GetValue()
{
return mUint64;
}
private:
uint64_t mUint64;
virtual ~UINT64Wrapper() = default;
};
nsresult
nsHttpConnectionMgr::UpdateCurrentTopLevelOuterContentWindowId(
uint64_t aWindowId)
{
RefPtr<UINT64Wrapper> windowIdWrapper = new UINT64Wrapper(aWindowId);
return PostEvent(
&nsHttpConnectionMgr::OnMsgUpdateCurrentTopLevelOuterContentWindowId,
0,
windowIdWrapper);
}
void nsHttpConnectionMgr::SetThrottlingEnabled(bool aEnable)
{
LOG(("nsHttpConnectionMgr::SetThrottlingEnabled enable=%d", aEnable));
mThrottleEnabled = aEnable;
if (mThrottleEnabled) {
EnsureThrottleTickerIfNeeded();
} else {
DestroyThrottleTicker();
ResumeReadOf(mActiveTransactions[false]);
ResumeReadOf(mActiveTransactions[true]);
}
}
bool nsHttpConnectionMgr::InThrottlingTimeWindow()
{
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
if (mThrottlingWindowEndsAt.IsNull()) {
return true;
}
return TimeStamp::NowLoRes() <= mThrottlingWindowEndsAt;
}
void nsHttpConnectionMgr::TouchThrottlingTimeWindow(bool aEnsureTicker)
{
LOG(("nsHttpConnectionMgr::TouchThrottlingTimeWindow"));
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
mThrottlingWindowEndsAt = TimeStamp::NowLoRes() + mThrottleMaxTime;
if (!mThrottleTicker &&
MOZ_LIKELY(aEnsureTicker) && MOZ_LIKELY(mThrottleEnabled)) {
EnsureThrottleTickerIfNeeded();
}
}
void nsHttpConnectionMgr::LogActiveTransactions(char operation)
{
if (!LOG_ENABLED()) {
return;
}
nsTArray<RefPtr<nsHttpTransaction>> *trs = nullptr;
uint32_t au, at, bu = 0, bt = 0;
trs = mActiveTransactions[false].Get(mCurrentTopLevelOuterContentWindowId);
au = trs ? trs->Length() : 0;
trs = mActiveTransactions[true].Get(mCurrentTopLevelOuterContentWindowId);
at = trs ? trs->Length() : 0;
for (auto iter = mActiveTransactions[false].Iter(); !iter.Done(); iter.Next()) {
bu += iter.UserData()->Length();
}
bu -= au;
for (auto iter = mActiveTransactions[true].Iter(); !iter.Done(); iter.Next()) {
bt += iter.UserData()->Length();
}
bt -= at;
// Shows counts of:
// - unthrottled transaction for the active tab
// - throttled transaction for the active tab
// - unthrottled transaction for background tabs
// - throttled transaction for background tabs
LOG(("Active transactions %c[%u,%u,%u,%u]", operation, au, at, bu, bt));
}
void
nsHttpConnectionMgr::AddActiveTransaction(nsHttpTransaction * aTrans)
{
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
uint64_t tabId = aTrans->TopLevelOuterContentWindowId();
bool throttled = aTrans->EligibleForThrottling();
nsTArray<RefPtr<nsHttpTransaction>> *transactions =
mActiveTransactions[throttled].LookupOrAdd(tabId);
MOZ_ASSERT(!transactions->Contains(aTrans));
transactions->AppendElement(aTrans);
LOG(("nsHttpConnectionMgr::AddActiveTransaction t=%p tabid=%" PRIx64 "(%d) thr=%d",
aTrans, tabId, tabId == mCurrentTopLevelOuterContentWindowId, throttled));
LogActiveTransactions('+');
if (tabId == mCurrentTopLevelOuterContentWindowId) {
mActiveTabTransactionsExist = true;
if (!throttled) {
mActiveTabUnthrottledTransactionsExist = true;
}
}
// Shift the throttling window to the future (actually, makes sure
// that throttling will engage when there is anything to throttle.)
// The |false| argument means we don't need this call to ensure
// the ticker, since we do it just below. Calling
// EnsureThrottleTickerIfNeeded directly does a bit more than call
// from inside of TouchThrottlingTimeWindow.
TouchThrottlingTimeWindow(false);
if (!mThrottleEnabled) {
return;
}
EnsureThrottleTickerIfNeeded();
}
void
nsHttpConnectionMgr::RemoveActiveTransaction(nsHttpTransaction * aTrans,
Maybe<bool> const& aOverride)
{
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
uint64_t tabId = aTrans->TopLevelOuterContentWindowId();
bool forActiveTab = tabId == mCurrentTopLevelOuterContentWindowId;
bool throttled = aOverride.valueOr(aTrans->EligibleForThrottling());
nsTArray<RefPtr<nsHttpTransaction>> *transactions =
mActiveTransactions[throttled].Get(tabId);
if (!transactions || !transactions->RemoveElement(aTrans)) {
// Was not tracked as active, probably just ignore.
return;
}
LOG(("nsHttpConnectionMgr::RemoveActiveTransaction t=%p tabid=%" PRIx64 "(%d) thr=%d",
aTrans, tabId, forActiveTab, throttled));
if (!transactions->IsEmpty()) {
// There are still transactions of the type, hence nothing in the throttling conditions
// has changed and we don't need to update "Exists" caches nor we need to wake any now
// throttled transactions.
LogActiveTransactions('-');
return;
}
// To optimize the following logic, always remove the entry when the array is empty.
mActiveTransactions[throttled].Remove(tabId);
LogActiveTransactions('-');
if (forActiveTab) {
// Update caches of the active tab transaction existence, since it's now affected
if (!throttled) {
mActiveTabUnthrottledTransactionsExist = false;
}
if (mActiveTabTransactionsExist) {
mActiveTabTransactionsExist = mActiveTransactions[!throttled].Contains(tabId);
}
}
if (!mThrottleEnabled) {
return;
}
bool unthrottledExist = !mActiveTransactions[false].IsEmpty();
bool throttledExist = !mActiveTransactions[true].IsEmpty();
if (!unthrottledExist && !throttledExist) {
// Nothing active globally, just get rid of the timer completely and we are done.
MOZ_ASSERT(!mActiveTabUnthrottledTransactionsExist);
MOZ_ASSERT(!mActiveTabTransactionsExist);
DestroyThrottleTicker();
return;
}
if (mThrottleVersion == 1) {
if (!mThrottlingInhibitsReading) {
// There is then nothing to wake up. Affected transactions will not be put
// to sleep automatically on next tick.
LOG((" reading not currently inhibited"));
return;
}
}
if (mActiveTabUnthrottledTransactionsExist) {
// There are still unthrottled transactions for the active tab, hence the state
// is unaffected and we don't need to do anything (nothing to wake).
LOG((" there are unthrottled for the active tab"));
return;
}
if (mActiveTabTransactionsExist) {
// There are only trottled transactions for the active tab.
// If the last transaction we just removed was a non-throttled for the active tab
// we can wake the throttled transactions for the active tab.
if (forActiveTab && !throttled) {
LOG((" resuming throttled for active tab"));
ResumeReadOf(mActiveTransactions[true].Get(mCurrentTopLevelOuterContentWindowId));
}
return;
}
if (!unthrottledExist) {
// There are no unthrottled transactions for any tab. Resume all throttled,
// all are only for background tabs.
LOG((" delay resuming throttled for background tabs"));
DelayedResumeBackgroundThrottledTransactions();
return;
}
if (forActiveTab) {
// Removing the last transaction for the active tab frees up the unthrottled
// background tabs transactions.
LOG((" delay resuming unthrottled for background tabs"));
DelayedResumeBackgroundThrottledTransactions();
return;
}
LOG((" not resuming anything"));
}
void
nsHttpConnectionMgr::UpdateActiveTransaction(nsHttpTransaction * aTrans)
{
LOG(("nsHttpConnectionMgr::UpdateActiveTransaction ENTER t=%p", aTrans));
// First remove then add. In case of a download that is the only active
// transaction and has just been marked as download (goes unthrottled to
// throttled), adding first would cause it to be throttled for first few
// milliseconds - becuause it would appear as if there were both throttled
// and unthrottled transactions at the time.
Maybe<bool> reversed;
reversed.emplace(!aTrans->EligibleForThrottling());
RemoveActiveTransaction(aTrans, reversed);
AddActiveTransaction(aTrans);
LOG(("nsHttpConnectionMgr::UpdateActiveTransaction EXIT t=%p", aTrans));
}
bool
nsHttpConnectionMgr::ShouldThrottle(nsHttpTransaction * aTrans)
{
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
LOG(("nsHttpConnectionMgr::ShouldThrottle trans=%p", aTrans));
if (mThrottleVersion == 1) {
if (!mThrottlingInhibitsReading || !mThrottleEnabled) {
return false;
}
} else {
if (!mThrottleEnabled) {
return false;
}
}
uint64_t tabId = aTrans->TopLevelOuterContentWindowId();
bool forActiveTab = tabId == mCurrentTopLevelOuterContentWindowId;
bool throttled = aTrans->EligibleForThrottling();
bool stop = [=]() {
if (mActiveTabTransactionsExist) {
if (!tabId) {
// Chrome initiated and unidentified transactions just respect
// their throttle flag, when something for the active tab is happening.
// This also includes downloads.
LOG((" active tab loads, trans is tab-less, throttled=%d", throttled));
return throttled;
}
if (!forActiveTab) {
// This is a background tab request, we want them to always throttle
// when there are transactions running for the ative tab.
LOG((" active tab loads, trans not of the active tab"));
return true;
}
if (mActiveTabUnthrottledTransactionsExist) {
// Unthrottled transactions for the active tab take precedence
LOG((" active tab loads unthrottled, trans throttled=%d", throttled));
return throttled;
}
LOG((" trans for active tab, don't throttle"));
return false;
}
MOZ_ASSERT(!forActiveTab);
if (!mActiveTransactions[false].IsEmpty()) {
// This means there are unthrottled active transactions for background tabs.
// If we are here, there can't be any transactions for the active tab.
// (If there is no transaction for a tab id, there is no entry for it in
// the hashtable.)
LOG((" backround tab(s) load unthrottled, trans throttled=%d", throttled));
return throttled;
}
// There are only unthrottled transactions for background tabs: don't throttle.
LOG((" backround tab(s) load throttled, don't throttle"));
return false;
}();
if (forActiveTab && !stop) {
// This is an active-tab transaction and is allowed to read. Hence,
// prolong the throttle time window to make sure all 'lower-decks'
// transactions will actually throttle.
TouchThrottlingTimeWindow();
return false;
}
// Only stop reading when in the configured throttle max-time (aka time window).
// This window is prolonged (restarted) by a call to TouchThrottlingTimeWindow
// called on new transaction activation or on receive of response bytes of an
// active tab transaction.
bool inWindow = InThrottlingTimeWindow();
LOG((" stop=%d, in-window=%d, delayed-bck-timer=%d",
stop, inWindow, !!mDelayedResumeReadTimer));
if (!forActiveTab) {
// If the delayed background resume timer exists, background transactions are
// scheduled to be woken after a delay, hence leave them throttled.
inWindow = inWindow || mDelayedResumeReadTimer;
}
return stop && inWindow;
}
bool nsHttpConnectionMgr::IsConnEntryUnderPressure(nsHttpConnectionInfo *connInfo)
{
nsConnectionEntry *ent = mCT.GetWeak(connInfo->HashKey());
if (!ent) {
// No entry, no pressure.
return false;
}
nsTArray<RefPtr<PendingTransactionInfo>> *transactions =
ent->mPendingTransactionTable.Get(mCurrentTopLevelOuterContentWindowId);
return transactions && !transactions->IsEmpty();
}
bool nsHttpConnectionMgr::IsThrottleTickerNeeded()
{
LOG(("nsHttpConnectionMgr::IsThrottleTickerNeeded"));
if (mActiveTabUnthrottledTransactionsExist &&
mActiveTransactions[false].Count() > 1) {
LOG((" there are unthrottled transactions for both active and bck"));
return true;
}
if (mActiveTabTransactionsExist &&
mActiveTransactions[true].Count() > 1) {
LOG((" there are throttled transactions for both active and bck"));
return true;
}
if (!mActiveTransactions[true].IsEmpty() &&
!mActiveTransactions[false].IsEmpty()) {
LOG((" there are both throttled and unthrottled transactions"));
return true;
}
LOG((" nothing to throttle"));
return false;
}
void
nsHttpConnectionMgr::EnsureThrottleTickerIfNeeded()
{
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
LOG(("nsHttpConnectionMgr::EnsureThrottleTickerIfNeeded"));
if (!IsThrottleTickerNeeded()) {
return;
}
// There is a new demand to throttle, hence unschedule delayed resume
// of background throttled transastions.
CancelDelayedResumeBackgroundThrottledTransactions();
if (mThrottleTicker) {
return;
}
mThrottleTicker = NS_NewTimer();
if (mThrottleTicker) {
if (mThrottleVersion == 1) {
MOZ_ASSERT(!mThrottlingInhibitsReading);
mThrottleTicker->Init(this, mThrottleSuspendFor, nsITimer::TYPE_ONE_SHOT);
mThrottlingInhibitsReading = true;
} else {
mThrottleTicker->Init(this, mThrottleReadInterval, nsITimer::TYPE_ONE_SHOT);
}
}
LogActiveTransactions('^');
}
void
nsHttpConnectionMgr::DestroyThrottleTicker()
{
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
// Nothing to throttle, hence no need for this timer anymore.
CancelDelayedResumeBackgroundThrottledTransactions();
MOZ_ASSERT(!mThrottleEnabled || !IsThrottleTickerNeeded());
if (!mThrottleTicker) {
return;
}
LOG(("nsHttpConnectionMgr::DestroyThrottleTicker"));
mThrottleTicker->Cancel();
mThrottleTicker = nullptr;
if (mThrottleVersion == 1) {
mThrottlingInhibitsReading = false;
}
LogActiveTransactions('v');
}
void
nsHttpConnectionMgr::ThrottlerTick()
{
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
if (mThrottleVersion == 1) {
mThrottlingInhibitsReading = !mThrottlingInhibitsReading;
LOG(("nsHttpConnectionMgr::ThrottlerTick inhibit=%d", mThrottlingInhibitsReading));
// If there are only background transactions to be woken after a delay, keep
// the ticker so that we woke them only for the resume-for interval and then
// throttle them again until the background-resume delay passes.
if (!mThrottlingInhibitsReading &&
!mDelayedResumeReadTimer &&
(!IsThrottleTickerNeeded() || !InThrottlingTimeWindow())) {
LOG((" last tick"));
mThrottleTicker = nullptr;
}
if (mThrottlingInhibitsReading) {
if (mThrottleTicker) {
mThrottleTicker->Init(this, mThrottleSuspendFor, nsITimer::TYPE_ONE_SHOT);
}
} else {
if (mThrottleTicker) {
mThrottleTicker->Init(this, mThrottleResumeFor, nsITimer::TYPE_ONE_SHOT);
}
ResumeReadOf(mActiveTransactions[false], true);
ResumeReadOf(mActiveTransactions[true]);
}
} else {
LOG(("nsHttpConnectionMgr::ThrottlerTick"));
// If there are only background transactions to be woken after a delay, keep
// the ticker so that we still keep the low read limit for that time.
if (!mDelayedResumeReadTimer &&
(!IsThrottleTickerNeeded() || !InThrottlingTimeWindow())) {
LOG((" last tick"));
mThrottleTicker = nullptr;
}
if (mThrottleTicker) {
mThrottleTicker->Init(this, mThrottleReadInterval, nsITimer::TYPE_ONE_SHOT);
}
ResumeReadOf(mActiveTransactions[false], true);
ResumeReadOf(mActiveTransactions[true]);
}
}
void
nsHttpConnectionMgr::DelayedResumeBackgroundThrottledTransactions()
{
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
if (mThrottleVersion == 1) {
if (mDelayedResumeReadTimer) {
return;
}
} else {
// If the mThrottleTicker doesn't exist, there is nothing currently
// being throttled. Hence, don't invoke the hold time interval.
// This is called also when a single download transaction becomes
// marked as throttleable. We would otherwise block it unnecessarily.
if (mDelayedResumeReadTimer || !mThrottleTicker) {
return;
}
}
LOG(("nsHttpConnectionMgr::DelayedResumeBackgroundThrottledTransactions"));
NS_NewTimerWithObserver(getter_AddRefs(mDelayedResumeReadTimer),
this, mThrottleHoldTime, nsITimer::TYPE_ONE_SHOT);
}
void
nsHttpConnectionMgr::CancelDelayedResumeBackgroundThrottledTransactions()
{
if (!mDelayedResumeReadTimer) {
return;
}
LOG(("nsHttpConnectionMgr::CancelDelayedResumeBackgroundThrottledTransactions"));
mDelayedResumeReadTimer->Cancel();
mDelayedResumeReadTimer = nullptr;
}
void
nsHttpConnectionMgr::ResumeBackgroundThrottledTransactions()
{
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
LOG(("nsHttpConnectionMgr::ResumeBackgroundThrottledTransactions"));
mDelayedResumeReadTimer = nullptr;
if (!IsThrottleTickerNeeded()) {
DestroyThrottleTicker();
}
if (!mActiveTransactions[false].IsEmpty()) {
ResumeReadOf(mActiveTransactions[false], true);
} else {
ResumeReadOf(mActiveTransactions[true], true);
}
}
void
nsHttpConnectionMgr::ResumeReadOf(
nsClassHashtable<nsUint64HashKey, nsTArray<RefPtr<nsHttpTransaction>>>& hashtable,
bool excludeForActiveTab)
{
for (auto iter = hashtable.Iter(); !iter.Done(); iter.Next()) {
if (excludeForActiveTab && iter.Key() == mCurrentTopLevelOuterContentWindowId) {
// These have never been throttled (never stopped reading)
continue;
}
ResumeReadOf(iter.UserData());
}
}
void
nsHttpConnectionMgr::ResumeReadOf(nsTArray<RefPtr<nsHttpTransaction>>* transactions)
{
MOZ_ASSERT(transactions);
for (const auto& trans : *transactions) {
trans->ResumeReading();
}
}
void
nsHttpConnectionMgr::NotifyConnectionOfWindowIdChange(uint64_t previousWindowId)
{
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
nsTArray<RefPtr<nsHttpTransaction>> *transactions = nullptr;
nsTArray<RefPtr<nsAHttpConnection>> connections;
auto addConnectionHelper =
[&connections](nsTArray<RefPtr<nsHttpTransaction>> *trans) {
if (!trans) {
return;
}
for (const auto& t : *trans) {
RefPtr<nsAHttpConnection> conn = t->Connection();
if (conn && !connections.Contains(conn)) {
connections.AppendElement(conn);
}
}
};
// Get unthrottled transactions with the previous and current window id.
transactions = mActiveTransactions[false].Get(previousWindowId);
addConnectionHelper(transactions);
transactions =
mActiveTransactions[false].Get(mCurrentTopLevelOuterContentWindowId);
addConnectionHelper(transactions);
// Get throttled transactions with the previous and current window id.
transactions = mActiveTransactions[true].Get(previousWindowId);
addConnectionHelper(transactions);
transactions =
mActiveTransactions[true].Get(mCurrentTopLevelOuterContentWindowId);
addConnectionHelper(transactions);
for (const auto& conn : connections) {
conn->TopLevelOuterContentWindowIdChanged(mCurrentTopLevelOuterContentWindowId);
}
}
void
nsHttpConnectionMgr::OnMsgUpdateCurrentTopLevelOuterContentWindowId(
int32_t aLoading, ARefBase *param)
{
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
uint64_t winId = static_cast<UINT64Wrapper*>(param)->GetValue();
if (mCurrentTopLevelOuterContentWindowId == winId) {
// duplicate notification
return;
}
bool activeTabWasLoading = mActiveTabTransactionsExist;
uint64_t previousWindowId = mCurrentTopLevelOuterContentWindowId;
mCurrentTopLevelOuterContentWindowId = winId;
if (gHttpHandler->ActiveTabPriority()) {
NotifyConnectionOfWindowIdChange(previousWindowId);
}
LOG(("nsHttpConnectionMgr::OnMsgUpdateCurrentTopLevelOuterContentWindowId"
" id=%" PRIx64 "\n",
mCurrentTopLevelOuterContentWindowId));
nsTArray<RefPtr<nsHttpTransaction>> *transactions = nullptr;
// Update the "Exists" caches and resume any transactions that now deserve it,
// changing the active tab changes the conditions for throttling.
transactions = mActiveTransactions[false].Get(mCurrentTopLevelOuterContentWindowId);
mActiveTabUnthrottledTransactionsExist = !!transactions;
if (!mActiveTabUnthrottledTransactionsExist) {
transactions = mActiveTransactions[true].Get(mCurrentTopLevelOuterContentWindowId);
}
mActiveTabTransactionsExist = !!transactions;
if (transactions) {
// This means there are some transactions for this newly activated tab, resume them
// but anything else.
LOG((" resuming newly activated tab transactions"));
ResumeReadOf(transactions);
return;
}
if (!activeTabWasLoading) {
// There were no transactions for the previously active tab, hence
// all remaning transactions, if there were, were all unthrottled,
// no need to wake them.
return;
}
if (!mActiveTransactions[false].IsEmpty()) {
LOG((" resuming unthrottled background transactions"));
ResumeReadOf(mActiveTransactions[false]);
return;
}
if (!mActiveTransactions[true].IsEmpty()) {
LOG((" resuming throttled background transactions"));
ResumeReadOf(mActiveTransactions[true]);
return;
}
DestroyThrottleTicker();
}
void
nsHttpConnectionMgr::TimeoutTick()
{
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
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 TimeoutTick()s can
// reduce it to their local needs.
mTimeoutTickNext = 3600; // 1hr
for (auto iter = mCT.Iter(); !iter.Done(); iter.Next()) {
RefPtr<nsConnectionEntry> ent = iter.Data();
LOG(("nsHttpConnectionMgr::TimeoutTick() this=%p host=%s "
"idle=%zu active=%zu"
" half-len=%zu pending=%zu"
" urgentStart pending=%zu\n",
this, ent->mConnInfo->Origin(), ent->mIdleConns.Length(),
ent->mActiveConns.Length(), ent->mHalfOpens.Length(),
ent->PendingQLength(), ent->mUrgentStartQ.Length()));
// First call the tick handler for each active connection.
PRIntervalTime tickTime = PR_IntervalNow();
for (uint32_t index = 0; index < ent->mActiveConns.Length(); ++index) {
uint32_t connNextTimeout =
ent->mActiveConns[index]->ReadTimeoutTick(tickTime);
mTimeoutTickNext = std::min(mTimeoutTickNext, connNextTimeout);
}
// Now check for any stalled half open sockets.
if (ent->mHalfOpens.Length()) {
TimeStamp currentTime = TimeStamp::Now();
double maxConnectTime_ms = gHttpHandler->ConnectTimeout();
for (uint32_t index = ent->mHalfOpens.Length(); index > 0; ) {
index--;
nsHalfOpenSocket *half = ent->mHalfOpens[index];
double delta = half->Duration(currentTime);
// If the socket has timed out, close it so the waiting
// transaction will get the proper signal.
if (delta > maxConnectTime_ms) {
LOG(("Force timeout of half open to %s after %.2fms.\n",
ent->mConnInfo->HashKey().get(), delta));
if (half->SocketTransport()) {
half->SocketTransport()->Close(NS_ERROR_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_ms + 5000) {
LOG(("Abandon half open to %s after %.2fms.\n",
ent->mConnInfo->HashKey().get(), delta));
half->Abandon();
}
}
}
if (ent->mHalfOpens.Length()) {
mTimeoutTickNext = 1;
}
}
if (mTimeoutTick) {
mTimeoutTickNext = std::max(mTimeoutTickNext, 1U);
mTimeoutTick->SetDelay(mTimeoutTickNext * 1000);
}
}
// GetOrCreateConnectionEntry finds a ent for a particular CI for use in
// dispatching a transaction according to these rules
// 1] use an ent that matches the ci that can be dispatched immediately
// 2] otherwise use an ent of wildcard(ci) than can be dispatched immediately
// 3] otherwise create an ent that matches ci and make new conn on it
nsHttpConnectionMgr::nsConnectionEntry *
nsHttpConnectionMgr::GetOrCreateConnectionEntry(nsHttpConnectionInfo *specificCI,
bool prohibitWildCard)
{
// step 1
nsConnectionEntry *specificEnt = mCT.GetWeak(specificCI->HashKey());
if (specificEnt && specificEnt->AvailableForDispatchNow()) {
return specificEnt;
}
// step 1 repeated for an inverted anonymous flag; we return an entry
// only when it has an h2 established connection that is not authenticated
// with a client certificate.
RefPtr<nsHttpConnectionInfo> anonInvertedCI(specificCI->Clone());
anonInvertedCI->SetAnonymous(!specificCI->GetAnonymous());
nsConnectionEntry *invertedEnt = mCT.GetWeak(anonInvertedCI->HashKey());
if (invertedEnt) {
nsHttpConnection* h2conn = GetSpdyActiveConn(invertedEnt);
if (h2conn && h2conn->IsExperienced() && h2conn->NoClientCertAuth()) {
MOZ_ASSERT(h2conn->UsingSpdy());
LOG(("GetOrCreateConnectionEntry is coalescing h2 an/onymous connections, ent=%p", invertedEnt));
return invertedEnt;
}
}
if (!specificCI->UsingHttpsProxy()) {
prohibitWildCard = true;
}
// step 2
if (!prohibitWildCard) {
RefPtr<nsHttpConnectionInfo> wildCardProxyCI;
DebugOnly<nsresult> rv = specificCI->CreateWildCard(getter_AddRefs(wildCardProxyCI));
MOZ_ASSERT(NS_SUCCEEDED(rv));
nsConnectionEntry *wildCardEnt = mCT.GetWeak(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(OnSocketThread(), "not on socket thread");
SpeculativeConnectArgs *args = static_cast<SpeculativeConnectArgs *>(param);
LOG(("nsHttpConnectionMgr::OnMsgSpeculativeConnect [ci=%s]\n",
args->mTrans->ConnectionInfo()->HashKey().get()));
nsConnectionEntry *ent =
GetOrCreateConnectionEntry(args->mTrans->ConnectionInfo(), false);
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())) {
DebugOnly<nsresult> rv = CreateTransport(ent, args->mTrans,
args->mTrans->Caps(), true,
isFromPredictor, false,
allow1918, nullptr);
MOZ_ASSERT(NS_SUCCEEDED(rv));
} else {
LOG(("OnMsgSpeculativeConnect 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_ADDREF(nsHttpConnectionMgr::nsHalfOpenSocket)
NS_IMPL_RELEASE(nsHttpConnectionMgr::nsHalfOpenSocket)
NS_INTERFACE_MAP_BEGIN(nsHttpConnectionMgr::nsHalfOpenSocket)
NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
NS_INTERFACE_MAP_ENTRY(nsIOutputStreamCallback)
NS_INTERFACE_MAP_ENTRY(nsITransportEventSink)
NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
NS_INTERFACE_MAP_ENTRY(nsITimerCallback)
NS_INTERFACE_MAP_ENTRY(nsINamed)
// we have no macro that covers this case.
if (aIID.Equals(NS_GET_IID(nsHttpConnectionMgr::nsHalfOpenSocket)) ) {
AddRef();
*aInstancePtr = this;
return NS_OK;
} else
NS_INTERFACE_MAP_END
nsHttpConnectionMgr::
nsHalfOpenSocket::nsHalfOpenSocket(nsConnectionEntry *ent,
nsAHttpTransaction *trans,
uint32_t caps,
bool speculative,
bool isFromPredictor,
bool urgentStart)
: mTransaction(trans)
, mDispatchedMTransaction(false)
, mCaps(caps)
, mSpeculative(speculative)
, mUrgentStart(urgentStart)
, mIsFromPredictor(isFromPredictor)
, mAllow1918(true)
, mHasConnected(false)
, mPrimaryConnectedOK(false)
, mBackupConnectedOK(false)
, mBackupConnStatsSet(false)
, mFreeToUse(true)
, mPrimaryStreamStatus(NS_OK)
, mFastOpenInProgress(false)
, mEnt(ent)
{
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()));
if (speculative) {
Telemetry::AutoCounter<Telemetry::HTTPCONNMGR_TOTAL_SPECULATIVE_CONN> totalSpeculativeConn;
++totalSpeculativeConn;
if (isFromPredictor) {
Telemetry::AutoCounter<Telemetry::PREDICTOR_TOTAL_PRECONNECTS_CREATED> totalPreconnectsCreated;
++totalPreconnectsCreated;
}
}
if (mEnt->mConnInfo->FirstHopSSL()) {
mFastOpenStatus = TFO_UNKNOWN;
} else {
mFastOpenStatus = TFO_HTTP;
}
MOZ_ASSERT(mEnt);
}
nsHttpConnectionMgr::nsHalfOpenSocket::~nsHalfOpenSocket()
{
MOZ_ASSERT(!mStreamOut);
MOZ_ASSERT(!mBackupStreamOut);
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(OnSocketThread(), "not on socket thread");
MOZ_ASSERT(mEnt);
nsresult rv;
const char *socketTypes[1];
uint32_t typeCount = 0;
const nsHttpConnectionInfo *ci = mEnt->mConnInfo;
if (ci->FirstHopSSL()) {
socketTypes[typeCount++] = "ssl";
} 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_DISABLE_TRR) {
tmpFlags = nsISocketTransport::DISABLE_TRR;
}
if (mCaps & NS_HTTP_LOAD_ANONYMOUS)
tmpFlags |= nsISocketTransport::ANONYMOUS_CONNECT;
if (ci->GetPrivate())
tmpFlags |= nsISocketTransport::NO_PERMANENT_STORAGE;
if ((mCaps & NS_HTTP_BE_CONSERVATIVE) || ci->GetBeConservative()) {
LOG(("Setting Socket to BE_CONSERVATIVE"));
tmpFlags |= nsISocketTransport::BE_CONSERVATIVE;
}
if (mEnt->PreferenceKnown()) {
if (mEnt->mPreferIPv6) {
tmpFlags |= nsISocketTransport::DISABLE_IPV4;
} else if (mEnt->mPreferIPv4) {
tmpFlags |= nsISocketTransport::DISABLE_IPV6;
}
// In case the host is no longer accessible via the preferred IP family,
// try the opposite one and potentially restate the preference.
tmpFlags |= nsISocketTransport::RETRY_WITH_DIFFERENT_IP_FAMILY;
// From the same reason, let the backup socket fail faster to try the other family.
uint16_t fallbackTimeout = isBackup ? gHttpHandler->GetFallbackSynTimeout() : 0;
if (fallbackTimeout) {
socketTransport->SetTimeout(nsISocketTransport::TIMEOUT_CONNECT,
fallbackTimeout);
}
} else if (isBackup && gHttpHandler->FastFallbackToIPv4()) {
// 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".
tmpFlags |= nsISocketTransport::DISABLE_IPV6;
}
if (!Allow1918()) {
tmpFlags |= nsISocketTransport::DISABLE_RFC1918;
}
if ((mFastOpenStatus != TFO_HTTP) && !isBackup) {
if (mEnt->mUseFastOpen) {
socketTransport->SetFastOpenCallback(this);
} else {
mFastOpenStatus = TFO_DISABLED;
}
}
socketTransport->SetConnectionFlags(tmpFlags);
socketTransport->SetTlsFlags(ci->GetTlsFlags());
const OriginAttributes& originAttributes = mEnt->mConnInfo->GetOriginAttributes();
if (originAttributes != OriginAttributes()) {
socketTransport->SetOriginAttributes(originAttributes);
}
socketTransport->SetQoSBits(gHttpHandler->GetQoSBits());
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(OnSocketThread(), "not on socket thread");
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=%" PRIx32 "]",
this, mEnt->mConnInfo->Origin(), static_cast<uint32_t>(rv)));
if (NS_FAILED(rv)) {
if (mStreamOut)
mStreamOut->AsyncWait(nullptr, 0, 0, nullptr);
if (mSocketTransport) {
mSocketTransport->SetFastOpenCallback(nullptr);
}
mStreamOut = nullptr;
mStreamIn = nullptr;
mSocketTransport = nullptr;
}
return rv;
}
nsresult
nsHttpConnectionMgr::nsHalfOpenSocket::SetupBackupStreams()
{
MOZ_ASSERT(mTransaction);
mBackupSynStarted = TimeStamp::Now();
nsresult rv = SetupStreams(getter_AddRefs(mBackupTransport),
getter_AddRefs(mBackupStreamIn),
getter_AddRefs(mBackupStreamOut),
true);
LOG(("nsHalfOpenSocket::SetupBackupStream [this=%p ent=%s rv=%" PRIx32 "]",
this, mEnt->mConnInfo->Origin(), static_cast<uint32_t>(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()
{
MOZ_ASSERT(mEnt);
uint16_t timeout = gHttpHandler->GetIdleSynTimeout();
MOZ_ASSERT(!mSynTimer, "timer already initd");
if (!timeout && mFastOpenInProgress) {
timeout = 250;
}
// When using Fast Open the correct transport will be setup for sure (it is
// guaranteed), but it can be that it will happened a bit later.
if (mFastOpenInProgress ||
(timeout && !mSpeculative)) {
// 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.
NS_NewTimerWithCallback(getter_AddRefs(mSynTimer),
this, timeout, nsITimer::TYPE_ONE_SHOT);
LOG(("nsHalfOpenSocket::SetupBackupTimer() [this=%p]", this));
} else if (timeout) {
LOG(("nsHalfOpenSocket::SetupBackupTimer() [this=%p], did not arm\n", 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();
// Keeping the reference to the timer to remember we have already
// performed the backup connection.
}
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(OnSocketThread(), "not on socket thread");
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->SetFastOpenCallback(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) {
if (!mFastOpenInProgress) {
// If mFastOpenInProgress is true HalfOpen are not in mHalfOpen
// list and are not counted so we do not need to decrease counter.
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).
if (mStreamIn) {
mStreamIn->AsyncWait(nullptr, 0, 0, nullptr);
mStreamIn = nullptr;
}
if (mBackupStreamIn) {
mBackupStreamIn->AsyncWait(nullptr, 0, 0, nullptr);
mBackupStreamIn = nullptr;
}
// Stop the timer - we don't want any new backups.
CancelBackupTimer();
// Remove the half open from the connection entry.
if (mEnt) {
mEnt->mDoNotDestroy = false;
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(OnSocketThread(), "not on socket thread");
MOZ_ASSERT(timer == mSynTimer, "wrong timer");
MOZ_ASSERT(!mBackupTransport);
MOZ_ASSERT(mSynTimer);
MOZ_ASSERT(mEnt);
DebugOnly<nsresult> rv = SetupBackupStreams();
MOZ_ASSERT(NS_SUCCEEDED(rv));
// Keeping the reference to the timer to remember we have already
// performed the backup connection.
return NS_OK;
}
NS_IMETHODIMP // method for nsINamed
nsHttpConnectionMgr::nsHalfOpenSocket::GetName(nsACString& aName)
{
aName.AssignLiteral("nsHttpConnectionMgr::nsHalfOpenSocket");
return NS_OK;
}
already_AddRefed<nsHttpConnectionMgr::PendingTransactionInfo>
nsHttpConnectionMgr::
nsHalfOpenSocket::FindTransactionHelper(bool removeWhenFound)
{
nsTArray<RefPtr<PendingTransactionInfo>> *pendingQ =
gHttpHandler->ConnMgr()->GetTransactionPendingQHelper(mEnt, mTransaction);
int32_t index = pendingQ
? pendingQ->IndexOf(mTransaction, 0, PendingComparator())
: -1;
RefPtr<PendingTransactionInfo> info;
if (index != -1) {
info = (*pendingQ)[index];
if (removeWhenFound) {
pendingQ->RemoveElementAt(index);
}
}
return info.forget();
}
// method for nsIAsyncOutputStreamCallback
NS_IMETHODIMP
nsHttpConnectionMgr::
nsHalfOpenSocket::OnOutputStreamReady(nsIAsyncOutputStream *out)
{
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
MOZ_ASSERT(mStreamOut || mBackupStreamOut);
MOZ_ASSERT(out == mStreamOut || out == mBackupStreamOut,
"stream mismatch");
MOZ_ASSERT(mEnt);
LOG(("nsHalfOpenSocket::OnOutputStreamReady [this=%p ent=%s %s]\n",
this, mEnt->mConnInfo->Origin(),
out == mStreamOut ? "primary" : "backup"));
mEnt->mDoNotDestroy = true;
gHttpHandler->ConnMgr()->RecvdConnect();
CancelBackupTimer();
if (mFastOpenInProgress) {
LOG(("nsHalfOpenSocket::OnOutputStreamReady backup stream is ready, "
"close the fast open socket %p [this=%p ent=%s]\n",
mSocketTransport.get(), this, mEnt->mConnInfo->Origin()));
// If fast open is used, right after a socket for the primary stream is
// created a nsHttpConnection is created for that socket. The connection
// listens for OnOutputStreamReady not HalfOpenSocket. So this stream
// cannot be mStreamOut.
MOZ_ASSERT((out == mBackupStreamOut) && mConnectionNegotiatingFastOpen);
// Here the backup, non-TFO connection has connected successfully,
// before the TFO connection.
//
// The primary, TFO connection will be cancelled and the transaction
// will be rewind. CloseConnectionFastOpenTakesTooLongOrError will
// return the rewind transaction. The transaction will be put back to
// the pending queue and as well connected to this halfOpenSocket.
// SetupConn should set up a new nsHttpConnection with the backup
// socketTransport and the rewind transaction.
mSocketTransport->SetFastOpenCallback(nullptr);
mConnectionNegotiatingFastOpen->SetFastOpen(false);
mEnt->mHalfOpenFastOpenBackups.RemoveElement(this);
RefPtr<nsAHttpTransaction> trans =
mConnectionNegotiatingFastOpen->CloseConnectionFastOpenTakesTooLongOrError(true);
mSocketTransport = nullptr;
mStreamOut = nullptr;
mStreamIn = nullptr;
if (trans && trans->QueryHttpTransaction()) {
RefPtr<PendingTransactionInfo> pendingTransInfo =
new PendingTransactionInfo(trans->QueryHttpTransaction());
pendingTransInfo->mHalfOpen =
do_GetWeakReference(static_cast<nsISupportsWeakReference*>(this));
if (trans->Caps() & NS_HTTP_URGENT_START) {
gHttpHandler->ConnMgr()->InsertTransactionSorted(mEnt->mUrgentStartQ,
pendingTransInfo,
true);
} else {
mEnt->InsertTransaction(pendingTransInfo, true);
}
}
if (mEnt->mUseFastOpen) {
gHttpHandler->IncrementFastOpenConsecutiveFailureCounter();
mEnt->mUseFastOpen = false;
}
mFastOpenInProgress = false;
mConnectionNegotiatingFastOpen = nullptr;
if (mFastOpenStatus == TFO_NOT_TRIED) {
mFastOpenStatus = TFO_FAILED_BACKUP_CONNECTION_TFO_NOT_TRIED;
} else if (mFastOpenStatus == TFO_TRIED) {
mFastOpenStatus = TFO_FAILED_BACKUP_CONNECTION_TFO_TRIED;
} else if (mFastOpenStatus == TFO_DATA_SENT) {
mFastOpenStatus = TFO_FAILED_BACKUP_CONNECTION_TFO_DATA_SENT;
} else {
// This is TFO_DATA_COOKIE_NOT_ACCEPTED (I think this cannot
// happened, because the primary connection will be already
// connected or in recovery and mFastOpenInProgress==false).
mFastOpenStatus = TFO_FAILED_BACKUP_CONNECTION_TFO_DATA_COOKIE_NOT_ACCEPTED;
}
}
if (((mFastOpenStatus == TFO_DISABLED) ||
(mFastOpenStatus == TFO_HTTP)) && !mBackupConnStatsSet) {
// Collect telemetry for backup connection being faster than primary
// connection. We want to collect this telemetry only for cases where
// TFO is not used.
mBackupConnStatsSet = true;
Telemetry::Accumulate(Telemetry::NETWORK_HTTP_BACKUP_CONN_WON_1,
(out == mBackupStreamOut));
}
if (mFastOpenStatus == TFO_UNKNOWN) {
MOZ_ASSERT(out == mStreamOut);
if (mPrimaryStreamStatus == NS_NET_STATUS_RESOLVING_HOST) {
mFastOpenStatus = TFO_UNKNOWN_RESOLVING;
} else if (mPrimaryStreamStatus == NS_NET_STATUS_RESOLVED_HOST) {
mFastOpenStatus = TFO_UNKNOWN_RESOLVED;
} else if (mPrimaryStreamStatus == NS_NET_STATUS_CONNECTING_TO) {
mFastOpenStatus = TFO_UNKNOWN_CONNECTING;
} else if (mPrimaryStreamStatus == NS_NET_STATUS_CONNECTED_TO) {
mFastOpenStatus = TFO_UNKNOWN_CONNECTED;
}
}
nsresult rv = SetupConn(out, false);
if (mEnt) {
mEnt->mDoNotDestroy = false;
}
return rv;
}
bool
nsHttpConnectionMgr::
nsHalfOpenSocket::FastOpenEnabled()
{
LOG(("nsHalfOpenSocket::FastOpenEnabled [this=%p]\n", this));
MOZ_ASSERT(mEnt);
if (!mEnt) {
return false;
}
MOZ_ASSERT(mEnt->mConnInfo->FirstHopSSL());
// If mEnt is present this HalfOpen must be in the mHalfOpens,
// but we want to be sure!!!
if (!mEnt->mHalfOpens.Contains(this)) {
return false;
}
if (!gHttpHandler->UseFastOpen()) {
// fast open was turned off.
LOG(("nsHalfOpenSocket::FastEnabled - fast open was turned off.\n"));
mEnt->mUseFastOpen = false;
mFastOpenStatus = TFO_DISABLED;
return false;
}
// We can use FastOpen if we have a transaction or if it is ssl
// connection. For ssl we will use a null transaction to drive the SSL
// handshake to completion if there is not a pending transaction. 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->UsingConnect()) {
LOG(("nsHalfOpenSocket::FastOpenEnabled - It is using Connect."));
mFastOpenStatus = TFO_DISABLED_CONNECT;
return false;
}
return true;
}
nsresult
nsHttpConnectionMgr::
nsHalfOpenSocket::StartFastOpen()
{
MOZ_ASSERT(mStreamOut);
MOZ_ASSERT(!mBackupTransport);
MOZ_ASSERT(mEnt);
MOZ_ASSERT(mFastOpenStatus == TFO_UNKNOWN);
LOG(("nsHalfOpenSocket::StartFastOpen [this=%p]\n",
this));
RefPtr<nsHalfOpenSocket> deleteProtector(this);
mFastOpenInProgress = true;
mEnt->mDoNotDestroy = true;
// Remove this HalfOpen from mEnt->mHalfOpens.
// The new connection will take care of closing this HalfOpen from now on!
if (!mEnt->mHalfOpens.RemoveElement(this)) {
MOZ_ASSERT(false, "HalfOpen is not in mHalfOpens!");
mSocketTransport->SetFastOpenCallback(nullptr);
CancelBackupTimer();
mFastOpenInProgress = false;
Abandon();
mFastOpenStatus = TFO_INIT_FAILED;
return NS_ERROR_ABORT;
}
MOZ_ASSERT(gHttpHandler->ConnMgr()->mNumHalfOpenConns);
if (gHttpHandler->ConnMgr()->mNumHalfOpenConns) { // just in case
gHttpHandler->ConnMgr()->mNumHalfOpenConns--;
}
// Count this socketTransport as connected.
gHttpHandler->ConnMgr()->RecvdConnect();
// Remove HalfOpen from callbacks, the new connection will take them.
mSocketTransport->SetEventSink(nullptr, nullptr);
mSocketTransport->SetSecurityCallbacks(nullptr);
mStreamOut->AsyncWait(nullptr, 0, 0, nullptr);
nsresult rv = SetupConn(mStreamOut, true);
if (!mConnectionNegotiatingFastOpen) {
LOG(("nsHalfOpenSocket::StartFastOpen SetupConn failed "
"[this=%p rv=%x]\n", this, static_cast<uint32_t>(rv)));
if (NS_SUCCEEDED(rv)) {
rv = NS_ERROR_ABORT;
}
// If SetupConn failed this will CloseTransaction and socketTransport
// with an error, therefore we can close this HalfOpen. socketTransport
// will remove reference to this HalfOpen as well.
mSocketTransport->SetFastOpenCallback(nullptr);
CancelBackupTimer();
mFastOpenInProgress = false;
// The connection is responsible to take care of the halfOpen so we
// need to clean it up.
Abandon();
mFastOpenStatus = TFO_INIT_FAILED;
} else {
LOG(("nsHalfOpenSocket::StartFastOpen [this=%p conn=%p]\n",
this, mConnectionNegotiatingFastOpen.get()));
mEnt->mHalfOpenFastOpenBackups.AppendElement(this);
// SetupBackupTimer should setup timer which will hold a ref to this
// halfOpen. It will failed only if it cannot create timer. Anyway just
// to be sure I will add this deleteProtector!!!
if (!mSynTimer) {
// For Fast Open we will setup backup timer also for
// NullTransaction.
// So maybe it is not set and we need to set it here.
SetupBackupTimer();
}
}
if (mEnt) {
mEnt->mDoNotDestroy = false;
}
return rv;
}
void
nsHttpConnectionMgr::
nsHalfOpenSocket::SetFastOpenConnected(nsresult aError, bool aWillRetry)
{
MOZ_ASSERT(mFastOpenInProgress);
MOZ_ASSERT(mEnt);
LOG(("nsHalfOpenSocket::SetFastOpenConnected [this=%p conn=%p error=%x]\n",
this, mConnectionNegotiatingFastOpen.get(),
static_cast<uint32_t>(aError)));
// mConnectionNegotiatingFastOpen is set after a StartFastOpen creates
// and activates a nsHttpConnection successfully (SetupConn calls
// DispatchTransaction and DispatchAbstractTransaction which calls
// conn->Activate).
// nsHttpConnection::Activate can fail which will close socketTransport
// and socketTransport will call this function. The FastOpen clean up
// in case nsHttpConnection::Activate fails will be done in StartFastOpen.
// Also OnMsgReclaimConnection can decided that we do not need this
// transaction and cancel it as well.
// In all other cases mConnectionNegotiatingFastOpen must not be nullptr.
if (!mConnectionNegotiatingFastOpen) {
return;
}
MOZ_ASSERT((mFastOpenStatus == TFO_NOT_TRIED) ||
(mFastOpenStatus == TFO_DATA_SENT) ||
(mFastOpenStatus == TFO_TRIED) ||
(mFastOpenStatus == TFO_DATA_COOKIE_NOT_ACCEPTED) ||
(mFastOpenStatus == TFO_DISABLED));
RefPtr<nsHalfOpenSocket> deleteProtector(this);
mEnt->mDoNotDestroy = true;
// Delete 2 points of entry to FastOpen function so that we do not reenter.
mEnt->mHalfOpenFastOpenBackups.RemoveElement(this);
mSocketTransport->SetFastOpenCallback(nullptr);
mConnectionNegotiatingFastOpen->SetFastOpen(false);
// Check if we want to restart connection!
if (aWillRetry &&
((aError == NS_ERROR_CONNECTION_REFUSED) ||
#if defined(_WIN64) && defined(WIN95)
// On Windows PR_ContinueConnect can return NS_ERROR_FAILURE.
// This will be fixed in bug 1386719 and this is just a temporary
// work around.
(aError == NS_ERROR_FAILURE) ||
#endif
(aError == NS_ERROR_PROXY_CONNECTION_REFUSED) ||
(aError == NS_ERROR_NET_TIMEOUT))) {
if (mEnt->mUseFastOpen) {
gHttpHandler->IncrementFastOpenConsecutiveFailureCounter();
mEnt->mUseFastOpen = false;
}
// This is called from nsSocketTransport::RecoverFromError. The
// socket will try connect and we need to rewind nsHttpTransaction.
RefPtr<nsAHttpTransaction> trans =
mConnectionNegotiatingFastOpen->CloseConnectionFastOpenTakesTooLongOrError(false);
if (trans && trans->QueryHttpTransaction()) {
RefPtr<PendingTransactionInfo> pendingTransInfo =
new PendingTransactionInfo(trans->QueryHttpTransaction());
pendingTransInfo->mHalfOpen =
do_GetWeakReference(static_cast<nsISupportsWeakReference*>(this));
if (trans->Caps() & NS_HTTP_URGENT_START) {
gHttpHandler->ConnMgr()->InsertTransactionSorted(mEnt->mUrgentStartQ,
pendingTransInfo,
true);
} else {
mEnt->InsertTransaction(pendingTransInfo, true);
}
}
// We are doing a restart without fast open, so the easiest way is to
// return mSocketTransport to the halfOpenSock and destroy connection.
// This makes http2 implemenntation easier.
// mConnectionNegotiatingFastOpen is going away and halfOpen is taking
// this mSocketTransport so add halfOpen to mEnt and update
// mNumActiveConns.
mEnt->mHalfOpens.AppendElement(this);
gHttpHandler->ConnMgr()->mNumHalfOpenConns++;
gHttpHandler->ConnMgr()->StartedConnect();
// Restore callbacks.
mStreamOut->AsyncWait(this, 0, 0, nullptr);
mSocketTransport->SetEventSink(this, nullptr);
mSocketTransport->SetSecurityCallbacks(this);
mStreamIn->AsyncWait(nullptr, 0, 0, nullptr);
if ((aError == NS_ERROR_CONNECTION_REFUSED) ||
(aError == NS_ERROR_PROXY_CONNECTION_REFUSED)) {
mFastOpenStatus = TFO_FAILED_CONNECTION_REFUSED;
} else if (aError == NS_ERROR_NET_TIMEOUT) {
mFastOpenStatus = TFO_FAILED_NET_TIMEOUT;
} else {
mFastOpenStatus = TFO_FAILED_UNKNOW_ERROR;
}
} else {
// On success or other error we proceed with connection, we just need
// to close backup timer and halfOpenSock.
CancelBackupTimer();
if (NS_SUCCEEDED(aError)) {
NetAddr peeraddr;
if (NS_SUCCEEDED(mSocketTransport->GetPeerAddr(&peeraddr))) {
mEnt->RecordIPFamilyPreference(peeraddr.raw.family);
}
gHttpHandler->ResetFastOpenConsecutiveFailureCounter();
}
mSocketTransport = nullptr;
mStreamOut = nullptr;
mStreamIn = nullptr;
// If backup transport has already started put this HalfOpen back to
// mEnt list.
if (mBackupTransport) {
mFastOpenStatus = TFO_BACKUP_CONN;
mEnt->mHalfOpens.AppendElement(this);
gHttpHandler->ConnMgr()->mNumHalfOpenConns++;
}
}
mFastOpenInProgress = false;
mConnectionNegotiatingFastOpen = nullptr;
if (mEnt) {
mEnt->mDoNotDestroy = false;
} else {
MOZ_ASSERT(!mBackupTransport);
MOZ_ASSERT(!mBackupStreamOut);
}
}
void
nsHttpConnectionMgr::
nsHalfOpenSocket::SetFastOpenStatus(uint8_t tfoStatus)
{
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
MOZ_ASSERT(mFastOpenInProgress);
mFastOpenStatus = tfoStatus;
mConnectionNegotiatingFastOpen->SetFastOpenStatus(tfoStatus);
if (mConnectionNegotiatingFastOpen->Transaction()) {
// The transaction could already be canceled in the meantime, hence nullified.
mConnectionNegotiatingFastOpen->Transaction()->SetFastOpenStatus(tfoStatus);
}
}
void
nsHttpConnectionMgr::
nsHalfOpenSocket::CancelFastOpenConnection()
{
MOZ_ASSERT(mFastOpenInProgress);
LOG(("nsHalfOpenSocket::CancelFastOpenConnection [this=%p conn=%p]\n",
this, mConnectionNegotiatingFastOpen.get()));
RefPtr<nsHalfOpenSocket> deleteProtector(this);
mEnt->mHalfOpenFastOpenBackups.RemoveElement(this);
mSocketTransport->SetFastOpenCallback(nullptr);
mConnectionNegotiatingFastOpen->SetFastOpen(false);
RefPtr<nsAHttpTransaction> trans =
mConnectionNegotiatingFastOpen->CloseConnectionFastOpenTakesTooLongOrError(true);
mSocketTransport = nullptr;
mStreamOut = nullptr;
mStreamIn = nullptr;
if (trans && trans->QueryHttpTransaction()) {
RefPtr<PendingTransactionInfo> pendingTransInfo =
new PendingTransactionInfo(trans->QueryHttpTransaction());
if (trans->Caps() & NS_HTTP_URGENT_START) {
gHttpHandler->ConnMgr()->InsertTransactionSorted(mEnt->mUrgentStartQ,
pendingTransInfo, true);
} else {
mEnt->InsertTransaction(pendingTransInfo, true);
}
}
mFastOpenInProgress = false;
mConnectionNegotiatingFastOpen = nullptr;
Abandon();
MOZ_ASSERT(!mBackupTransport);
MOZ_ASSERT(!mBackupStreamOut);
}
void
nsHttpConnectionMgr::
nsHalfOpenSocket::FastOpenNotSupported()
{
MOZ_ASSERT(mFastOpenInProgress);
gHttpHandler->SetFastOpenNotSupported();
}
nsresult
nsHttpConnectionMgr::
nsHalfOpenSocket::SetupConn(nsIAsyncOutputStream *out,
bool aFastOpen)
{
MOZ_ASSERT(!aFastOpen || (out == mStreamOut));
// assign the new socket to the http connection
RefPtr<nsHttpConnection> conn = new nsHttpConnection();
LOG(("nsHalfOpenSocket::SetupConn "
"Created new nshttpconnection %p\n", conn.get()));
NullHttpTransaction *nullTrans = mTransaction->QueryNullTransaction();
if (nullTrans) {
conn->BootstrapTimings(nullTrans->Timings());
}
// Some capabilities are needed before a transaciton actually gets
// scheduled (e.g. how to negotiate false start)
conn->SetTransactionCaps(mTransaction->Caps());
if (mUrgentStart) {
// We deliberately leave this flag unset on the connection when
// this half-open was not marked urgent to let the first transaction
// dispatched on the connection set it. Then we don't need to update
// all the speculative connect APIs to pass the urgency flag while
// we still get nearly (if not exactly) the same result.
conn->SetUrgentStartPreferred(true);
}
NetAddr peeraddr;
nsCOMPtr<nsIInterfaceRequestor> callbacks;
mTransaction->GetSecurityCallbacks(getter_AddRefs(callbacks));
nsresult rv;
if (out == mStreamOut) {
TimeDuration rtt = TimeStamp::Now() - mPrimarySynStarted;
rv = conn->Init(mEnt->mConnInfo,
gHttpHandler->ConnMgr()->mMaxRequestDelay,
mSocketTransport, mStreamIn, mStreamOut,
mPrimaryConnectedOK || aFastOpen, callbacks,
PR_MillisecondsToInterval(
static_cast<uint32_t>(rtt.ToMilliseconds())));
bool resetPreference = false;
mSocketTransport->GetResetIPFamilyPreference(&resetPreference);
if (resetPreference) {
mEnt->ResetIPFamilyPreference();
}
if (!aFastOpen &&
NS_SUCCEEDED(mSocketTransport->GetPeerAddr(&peeraddr))) {
mEnt->RecordIPFamilyPreference(peeraddr.raw.family);
}
// The nsHttpConnection object now owns these streams and sockets
if (!aFastOpen) {
mStreamOut = nullptr;
mStreamIn = nullptr;
mSocketTransport = nullptr;
} else {
conn->SetFastOpen(true);
}
} else if (out == mBackupStreamOut) {
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())));
bool resetPreference = false;
mBackupTransport->GetResetIPFamilyPreference(&resetPreference);
if (resetPreference) {
mEnt->ResetIPFamilyPreference();
}
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::SetupConn "
"conn->init (%p) failed %" PRIx32 "\n",
conn.get(), static_cast<uint32_t>(rv)));
// Set TFO status.
if ((mFastOpenStatus == TFO_HTTP) ||
(mFastOpenStatus == TFO_DISABLED) ||
(mFastOpenStatus == TFO_DISABLED_CONNECT)) {
conn->SetFastOpenStatus(mFastOpenStatus);
} else {
conn->SetFastOpenStatus(TFO_INIT_FAILED);
}
return rv;
}
// This half-open socket has created a connection. This flag excludes it
// from counter of actual connections used for checking limits.
if (!aFastOpen) {
mHasConnected = true;
}
// if this is still in the pending list, remove it and dispatch it
RefPtr<PendingTransactionInfo> pendingTransInfo = FindTransactionHelper(true);
if (pendingTransInfo) {
MOZ_ASSERT(!mSpeculative,
"Speculative Half Open found mTransaction");
gHttpHandler->ConnMgr()->AddActiveConn(conn, mEnt);
rv = gHttpHandler->ConnMgr()->DispatchTransaction(mEnt,
pendingTransInfo->mTransaction,
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->mUrgentStartQ.Length() &&
!mEnt->PendingQLength() &&
!mEnt->mConnInfo->UsingConnect()) {
LOG(("nsHalfOpenSocket::SetupConn 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);
}
gHttpHandler->ConnMgr()->AddActiveConn(conn, mEnt);
rv = gHttpHandler->ConnMgr()->
DispatchAbstractTransaction(mEnt, trans, mCaps, conn, 0);
} else {
// otherwise just put this in the persistent connection pool
LOG(("nsHalfOpenSocket::SetupConn no transaction match "
"returning conn %p to pool\n", conn.get()));
gHttpHandler->ConnMgr()->OnMsgReclaimConnection(0, conn);
// We expect that there is at least one tranasction in the pending
// queue that can take this connection, but it can happened that
// all transactions are blocked or they have took other idle
// connections. In that case the connection has been added to the
// idle queue.
// If the connection is in the idle queue but it is using ssl, make
// a nulltransaction for it to finish ssl handshake!
// !!! It can be that mEnt is null after OnMsgReclaimConnection.!!!
if (mEnt &&
mEnt->mConnInfo->FirstHopSSL() &&
!mEnt->mConnInfo->UsingConnect()) {
int32_t idx = mEnt->mIdleConns.IndexOf(conn);
if (idx != -1) {
DebugOnly<nsresult> rvDeb = gHttpHandler->ConnMgr()->RemoveIdleConnection(conn);
MOZ_ASSERT(NS_SUCCEEDED(rvDeb));
conn->EndIdleMonitoring();
RefPtr<nsAHttpTransaction> trans;
if (mTransaction->IsNullTransaction() &&
!mDispatchedMTransaction) {
mDispatchedMTransaction = true;
trans = mTransaction;
} else {
trans = new NullHttpTransaction(mEnt->mConnInfo,
callbacks, mCaps);
}
gHttpHandler->ConnMgr()->AddActiveConn(conn, mEnt);
rv = gHttpHandler->ConnMgr()->
DispatchAbstractTransaction(mEnt, trans, mCaps, conn, 0);
}
}
}
}
// If this connection has a transaction get reference to its
// ConnectionHandler.
if (aFastOpen) {
MOZ_ASSERT(mEnt);
MOZ_ASSERT(static_cast<int32_t>(mEnt->mIdleConns.IndexOf(conn)) == -1);
int32_t idx = mEnt->mActiveConns.IndexOf(conn);
if (NS_SUCCEEDED(rv) && (idx != -1)) {
mConnectionNegotiatingFastOpen = conn;
} else {
conn->SetFastOpen(false);
conn->SetFastOpenStatus(TFO_INIT_FAILED);
}
} else {
conn->SetFastOpenStatus(mFastOpenStatus);
if ((mFastOpenStatus != TFO_HTTP) && (mFastOpenStatus != TFO_DISABLED) &&
(mFastOpenStatus != TFO_DISABLED_CONNECT)) {
mFastOpenStatus = TFO_BACKUP_CONN; // Set this to TFO_BACKUP_CONN
// so that if a backup
// connection is established we
// do not report values twice.
}
}
// If this halfOpenConn was speculative, but at the end the conn got a
// non-null transaction than this halfOpen is not speculative anymore!
if (conn->Transaction() && !conn->Transaction()->IsNullTransaction()) {
Claim();
}
return rv;
}
// register a connection to receive CanJoinConnection() for particular
// origin keys
void
nsHttpConnectionMgr::RegisterOriginCoalescingKey(nsHttpConnection *conn,
const nsACString &host,
int32_t port)
{
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
nsHttpConnectionInfo *ci = conn ? conn->ConnectionInfo() : nullptr;
if (!ci || !conn->CanDirectlyActivate()) {
return;
}
nsCString newKey;
BuildOriginFrameHashKey(newKey, ci, host, port);
nsTArray<nsWeakPtr> *listOfWeakConns = mCoalescingHash.Get(newKey);
if (!listOfWeakConns) {
listOfWeakConns = new nsTArray<nsWeakPtr>(1);
mCoalescingHash.Put(newKey, listOfWeakConns);
}
listOfWeakConns->AppendElement(do_GetWeakReference(static_cast<nsISupportsWeakReference*>(conn)));
LOG(("nsHttpConnectionMgr::RegisterOriginCoalescingKey "
"Established New Coalescing Key %s to %p %s\n",
newKey.get(), conn, ci->HashKey().get()));
}
// method for nsITransportEventSink
NS_IMETHODIMP
nsHttpConnectionMgr::nsHalfOpenSocket::OnTransportStatus(nsITransport *trans,
nsresult status,
int64_t progress,
int64_t progressMax)
{
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
MOZ_ASSERT((trans == mSocketTransport) || (trans == mBackupTransport));
MOZ_ASSERT(mEnt);
if (mTransaction) {
if ((trans == mSocketTransport) ||
((trans == mBackupTransport) && (status == NS_NET_STATUS_CONNECTED_TO) &&
mSocketTransport)) {
// Send this status event only if the transaction is still pending,
// i.e. it has not found a free already connected socket.
// Sockets in halfOpen state can only get following events:
// NS_NET_STATUS_RESOLVING_HOST, NS_NET_STATUS_RESOLVED_HOST,
// NS_NET_STATUS_CONNECTING_TO and NS_NET_STATUS_CONNECTED_TO.
// mBackupTransport is only started after
// NS_NET_STATUS_CONNECTING_TO of mSocketTransport, so ignore all
// mBackupTransport events until NS_NET_STATUS_CONNECTED_TO.
// mBackupTransport must be connected before mSocketTransport.
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;
}
mPrimaryStreamStatus = status;
// 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->SetLength(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());
newKey->AppendLiteral("/[");
nsAutoCString suffix;
mEnt->mConnInfo->GetOriginAttributes().CreateSuffix(suffix);
newKey->Append(suffix);
newKey->AppendLiteral("]viaDNS");
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;
}
bool
nsHttpConnectionMgr::nsHalfOpenSocket::AcceptsTransaction(nsHttpTransaction * trans)
{
// When marked as urgent start, only accept urgent start marked transactions.
// Otherwise, accept any kind of transaction.
return !mUrgentStart || (trans->Caps() & nsIClassOfService::UrgentStart);
}
bool
nsHttpConnectionMgr::nsHalfOpenSocket::Claim()
{
if (mSpeculative) {
mSpeculative = false;
uint32_t flags;
if (mSocketTransport && NS_SUCCEEDED(mSocketTransport->GetConnectionFlags(&flags))) {
flags &= ~nsISocketTransport::DISABLE_RFC1918;
mSocketTransport->SetConnectionFlags(flags);
}
Telemetry::AutoCounter<Telemetry::HTTPCONNMGR_USED_SPECULATIVE_CONN> usedSpeculativeConn;
++usedSpeculativeConn;
if (mIsFromPredictor) {
Telemetry::AutoCounter<Telemetry::PREDICTOR_TOTAL_PRECONNECTS_USED> totalPreconnectsUsed;
++totalPreconnectsUsed;
}
if ((mPrimaryStreamStatus == NS_NET_STATUS_CONNECTING_TO) &&
mEnt && !mBackupTransport && !mSynTimer) {
SetupBackupTimer();
}
}
if (mFreeToUse) {
mFreeToUse = false;
return true;
}
return false;
}
void
nsHttpConnectionMgr::nsHalfOpenSocket::Unclaim()
{
MOZ_ASSERT(!mSpeculative && !mFreeToUse);
// We will keep the backup-timer running. Most probably this halfOpen will
// be used by a transaction from which this transaction took the halfOpen.
// (this is happening because of the transaction priority.)
mFreeToUse = true;
}
already_AddRefed<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);
return mConn.forget();
}
already_AddRefed<nsHttpConnection>
ConnectionHandle::HttpConnection()
{
RefPtr<nsHttpConnection> rv(mConn);
return rv.forget();
}
void
ConnectionHandle::TopLevelOuterContentWindowIdChanged(uint64_t windowId)
{
// Do nothing.
}
// nsConnectionEntry
nsHttpConnectionMgr::
nsConnectionEntry::nsConnectionEntry(nsHttpConnectionInfo *ci)
: mConnInfo(ci)
, mUsingSpdy(false)
, mPreferIPv4(false)
, mPreferIPv6(false)
, mUsedForConnection(false)
, mDoNotDestroy(false)
{
MOZ_COUNT_CTOR(nsConnectionEntry);
if (mConnInfo->FirstHopSSL()) {
mUseFastOpen = gHttpHandler->UseFastOpen();
} else {
// Only allow the TCP fast open on a secure connection.
mUseFastOpen = false;
}
LOG(("nsConnectionEntry::nsConnectionEntry this=%p key=%s",
this, ci->HashKey().get()));
}
bool
nsHttpConnectionMgr::nsConnectionEntry::AvailableForDispatchNow()
{
if (mIdleConns.Length() && mIdleConns[0]->CanReuse()) {
return true;
}
return gHttpHandler->ConnMgr()->
GetSpdyActiveConn(this) ? true : false;
}
bool
nsHttpConnectionMgr::GetConnectionData(nsTArray<HttpRetParams> *aArg)
{
for (auto iter = mCT.Iter(); !iter.Done(); iter.Next()) {
RefPtr<nsConnectionEntry> ent = iter.Data();
if (ent->mConnInfo->GetPrivate()) {
continue;
}
HttpRetParams data;
data.host = ent->mConnInfo->Origin();
data.port = ent->mConnInfo->OriginPort();
for (uint32_t i = 0; i < ent->mActiveConns.Length(); i++) {
HttpConnInfo info;
info.ttl = ent->mActiveConns[i]->TimeToLive();
info.rtt = ent->mActiveConns[i]->Rtt();
if (ent->mActiveConns[i]->UsingSpdy()) {
info.SetHTTP2ProtocolVersion(
ent->mActiveConns[i]->GetSpdyVersion());
} else {
info.SetHTTP1ProtocolVersion(
ent->mActiveConns[i]->GetLastHttpResponseVersion());
}
data.active.AppendElement(info);
}
for (uint32_t i = 0; i < ent->mIdleConns.Length(); i++) {
HttpConnInfo info;
info.ttl = ent->mIdleConns[i]->TimeToLive();
info.rtt = ent->mIdleConns[i]->Rtt();
info.SetHTTP1ProtocolVersion(
ent->mIdleConns[i]->GetLastHttpResponseVersion());
data.idle.AppendElement(info);
}
for (uint32_t i = 0; i < ent->mHalfOpens.Length(); i++) {
HalfOpenSockets hSocket;
hSocket.speculative = ent->mHalfOpens[i]->IsSpeculative();
data.halfOpens.AppendElement(hSocket);
}
data.spdy = ent->mUsingSpdy;
data.ssl = ent->mConnInfo->EndToEndSSL();
aArg->AppendElement(data);
}
return true;
}
void
nsHttpConnectionMgr::ResetIPFamilyPreference(nsHttpConnectionInfo *ci)
{
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
nsConnectionEntry *ent = mCT.GetWeak(ci->HashKey());
if (ent) {
ent->ResetIPFamilyPreference();
}
}
uint32_t
nsHttpConnectionMgr::
nsConnectionEntry::UnconnectedHalfOpens()
{
uint32_t unconnectedHalfOpens = 0;
for (uint32_t i = 0; i < mHalfOpens.Length(); ++i) {
if (!mHalfOpens[i]->HasConnected())
++unconnectedHalfOpens;
}
return unconnectedHalfOpens;
}
void
nsHttpConnectionMgr::
nsConnectionEntry::RemoveHalfOpen(nsHalfOpenSocket *halfOpen)
{
// A failure to create the transport object at all
// will result in it not being present in the halfopen table. That's expected.
if (mHalfOpens.RemoveElement(halfOpen)) {
if (halfOpen->IsSpeculative()) {
Telemetry::AutoCounter<Telemetry::HTTPCONNMGR_UNUSED_SPECULATIVE_CONN> unusedSpeculativeConn;
++unusedSpeculativeConn;
if (halfOpen->IsFromPredictor()) {
Telemetry::AutoCounter<Telemetry::PREDICTOR_TOTAL_PRECONNECTS_UNUSED> totalPreconnectsUnused;
++totalPreconnectsUnused;
}
}
MOZ_ASSERT(gHttpHandler->ConnMgr()->mNumHalfOpenConns);
if (gHttpHandler->ConnMgr()->mNumHalfOpenConns) { // just in case
gHttpHandler->ConnMgr()->mNumHalfOpenConns--;
}
} else {
mHalfOpenFastOpenBackups.RemoveElement(halfOpen);
}
if (!UnconnectedHalfOpens()) {
// perhaps this reverted RestrictConnections()
// use the PostEvent version of processpendingq to avoid
// altering the pending q vector from an arbitrary stack
nsresult rv = gHttpHandler->ConnMgr()->ProcessPendingQ(mConnInfo);
if (NS_FAILED(rv)) {
LOG(("nsHttpConnectionMgr::nsConnectionEntry::RemoveHalfOpen\n"
" failed to process pending queue\n"));
}
}
}
void
nsHttpConnectionMgr::
nsConnectionEntry::RecordIPFamilyPreference(uint16_t family)
{
LOG(("nsConnectionEntry::RecordIPFamilyPreference %p, af=%u", this, family));
if (family == PR_AF_INET && !mPreferIPv6) {
mPreferIPv4 = true;
}
if (family == PR_AF_INET6 && !mPreferIPv4) {
mPreferIPv6 = true;
}
LOG((" %p prefer ipv4=%d, ipv6=%d", this, (bool)mPreferIPv4, (bool)mPreferIPv6));
}
void
nsHttpConnectionMgr::
nsConnectionEntry::ResetIPFamilyPreference()
{
LOG(("nsConnectionEntry::ResetIPFamilyPreference %p", this));
mPreferIPv4 = false;
mPreferIPv6 = false;
}
bool net::nsHttpConnectionMgr::nsConnectionEntry::PreferenceKnown() const
{
return (bool)mPreferIPv4 || (bool)mPreferIPv6;
}
size_t
nsHttpConnectionMgr::nsConnectionEntry::PendingQLength() const
{
size_t length = 0;
for (auto it = mPendingTransactionTable.ConstIter(); !it.Done(); it.Next()) {
length += it.UserData()->Length();
}
return length;
}
void
nsHttpConnectionMgr::
nsConnectionEntry::InsertTransaction(PendingTransactionInfo *info,
bool aInsertAsFirstForTheSamePriority /*= false*/)
{
LOG(("nsHttpConnectionMgr::nsConnectionEntry::InsertTransaction"
" trans=%p, windowId=%" PRIu64 "\n",
info->mTransaction.get(),
info->mTransaction->TopLevelOuterContentWindowId()));
uint64_t windowId = TabIdForQueuing(info->mTransaction);
nsTArray<RefPtr<PendingTransactionInfo>> *infoArray;
if (!mPendingTransactionTable.Get(windowId, &infoArray)) {
infoArray = new nsTArray<RefPtr<PendingTransactionInfo>>();
mPendingTransactionTable.Put(windowId, infoArray);
}
gHttpHandler->ConnMgr()->InsertTransactionSorted(*infoArray, info,
aInsertAsFirstForTheSamePriority);
}
void
nsHttpConnectionMgr::
nsConnectionEntry::AppendPendingQForFocusedWindow(
uint64_t windowId,
nsTArray<RefPtr<PendingTransactionInfo>> &result,
uint32_t maxCount)
{
nsTArray<RefPtr<PendingTransactionInfo>> *infoArray = nullptr;
if (!mPendingTransactionTable.Get(windowId, &infoArray)) {
result.Clear();
return;
}
uint32_t countToAppend = maxCount;
countToAppend =
countToAppend > infoArray->Length() || countToAppend == 0 ?
infoArray->Length() :
countToAppend;
result.InsertElementsAt(result.Length(),
infoArray->Elements(),
countToAppend);
infoArray->RemoveElementsAt(0, countToAppend);
LOG(("nsConnectionEntry::AppendPendingQForFocusedWindow [ci=%s], "
"pendingQ count=%zu window.count=%zu for focused window (id=%" PRIu64 ")\n",
mConnInfo->HashKey().get(), result.Length(), infoArray->Length(),
windowId));
}
void
nsHttpConnectionMgr::
nsConnectionEntry::AppendPendingQForNonFocusedWindows(
uint64_t windowId,
nsTArray<RefPtr<PendingTransactionInfo>> &result,
uint32_t maxCount)
{
// XXX Adjust the order of transactions in a smarter manner.
uint32_t totalCount = 0;
for (auto it = mPendingTransactionTable.Iter(); !it.Done(); it.Next()) {
if (windowId && it.Key() == windowId) {
continue;
}
uint32_t count = 0;
for (; count < it.UserData()->Length(); ++count) {
if (maxCount && totalCount == maxCount) {
break;
}
// Because elements in |result| could come from multiple penndingQ,
// call |InsertTransactionSorted| to make sure the order is correct.
gHttpHandler->ConnMgr()->InsertTransactionSorted(
result,
it.UserData()->ElementAt(count));
++totalCount;
}
it.UserData()->RemoveElementsAt(0, count);
if (maxCount && totalCount == maxCount) {
if (it.UserData()->Length()) {
// There are still some pending transactions for background
// tabs but we limit their dispatch. This is considered as
// an active tab optimization.
nsHttp::NotifyActiveTabLoadOptimization();
}
break;
}
}
LOG(("nsConnectionEntry::AppendPendingQForNonFocusedWindows [ci=%s], "
"pendingQ count=%zu for non focused window\n",
mConnInfo->HashKey().get(), result.Length()));
}
void
nsHttpConnectionMgr::nsConnectionEntry::RemoveEmptyPendingQ()
{
for (auto it = mPendingTransactionTable.Iter(); !it.Done(); it.Next()) {
if (it.UserData()->IsEmpty()) {
it.Remove();
}
}
}
void
nsHttpConnectionMgr::MoveToWildCardConnEntry(nsHttpConnectionInfo *specificCI,
nsHttpConnectionInfo *wildCardCI,
nsHttpConnection *proxyConn)
{
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
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 = mCT.GetWeak(specificCI->HashKey());
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=%zu active=%zu half=%zu pending=%zu\n",
ent, ent->mIdleConns.Length(), ent->mActiveConns.Length(),
ent->mHalfOpens.Length(), ent->PendingQLength()));
LOG(("nsHttpConnectionMgr::MakeConnEntryWildCard wc-ent %p "
"idle=%zu active=%zu half=%zu pending=%zu\n",
wcEnt, wcEnt->mIdleConns.Length(), wcEnt->mActiveConns.Length(),
wcEnt->mHalfOpens.Length(), wcEnt->PendingQLength()));
int32_t count = ent->mActiveConns.Length();
RefPtr<nsHttpConnection> deleteProtector(proxyConn);
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