Source code

Revision control

Other Tools

/* vim:set ts=4 sw=2 sts=2 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 <algorithm>
#include <utility>
#include "NullHttpTransaction.h"
#include "mozilla/ChaosMode.h"
#include "mozilla/Services.h"
#include "mozilla/Telemetry.h"
#include "mozilla/Unused.h"
#include "mozilla/net/DNS.h"
#include "mozilla/net/DashboardTypes.h"
#include "nsCOMPtr.h"
#include "nsHttpConnectionMgr.h"
#include "nsHttpHandler.h"
#include "nsIClassOfService.h"
#include "nsIDNSByTypeRecord.h"
#include "nsIDNSRecord.h"
#include "nsIDNSListener.h"
#include "nsIDNSService.h"
#include "nsIHttpChannelInternal.h"
#include "nsIRequestContext.h"
#include "nsISocketTransport.h"
#include "nsISocketTransportService.h"
#include "nsITransport.h"
#include "nsIXPConnect.h"
#include "nsInterfaceRequestorAgg.h"
#include "nsNetCID.h"
#include "nsNetUtil.h"
#include "nsQueryObject.h"
#include "HttpConnectionUDP.h"
#include "TCPFastOpenLayer.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),
mBeConservativeForProxy(true),
mIsShuttingDown(false),
mNumActiveConns(0),
mNumIdleConns(0),
mNumSpdyHttp3ActiveConns(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,
bool beConservativeForProxy) {
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);
mBeConservativeForProxy = beConservativeForProxy;
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(HttpTransactionShell* trans,
int32_t priority) {
LOG(("nsHttpConnectionMgr::AddTransaction [trans=%p %d]\n", trans, priority));
return PostEvent(&nsHttpConnectionMgr::OnMsgNewTransaction, priority,
trans->AsHttpTransaction());
}
class NewTransactionData : public ARefBase {
public:
NewTransactionData(nsHttpTransaction* trans, int32_t priority,
nsHttpTransaction* transWithStickyConn)
: mTrans(trans),
mPriority(priority),
mTransWithStickyConn(transWithStickyConn) {}
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(NewTransactionData, override)
RefPtr<nsHttpTransaction> mTrans;
int32_t mPriority;
RefPtr<nsHttpTransaction> mTransWithStickyConn;
private:
virtual ~NewTransactionData() = default;
};
nsresult nsHttpConnectionMgr::AddTransactionWithStickyConn(
HttpTransactionShell* trans, int32_t priority,
HttpTransactionShell* transWithStickyConn) {
LOG(
("nsHttpConnectionMgr::AddTransactionWithStickyConn "
"[trans=%p %d transWithStickyConn=%p]\n",
trans, priority, transWithStickyConn));
RefPtr<NewTransactionData> data =
new NewTransactionData(trans->AsHttpTransaction(), priority,
transWithStickyConn->AsHttpTransaction());
return PostEvent(&nsHttpConnectionMgr::OnMsgNewTransactionWithStickyConn, 0,
data);
}
nsresult nsHttpConnectionMgr::RescheduleTransaction(HttpTransactionShell* trans,
int32_t priority) {
LOG(("nsHttpConnectionMgr::RescheduleTransaction [trans=%p %d]\n", trans,
priority));
return PostEvent(&nsHttpConnectionMgr::OnMsgReschedTransaction, priority,
trans->AsHttpTransaction());
}
void nsHttpConnectionMgr::UpdateClassOfServiceOnTransaction(
HttpTransactionShell* 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->AsHttpTransaction());
}
nsresult nsHttpConnectionMgr::CancelTransaction(HttpTransactionShell* 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->AsHttpTransaction());
}
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() {
return PostEvent(&nsHttpConnectionMgr::OnMsgDoShiftReloadConnectionCleanup, 0,
nullptr);
}
nsresult nsHttpConnectionMgr::DoShiftReloadConnectionCleanupWithConnInfo(
nsHttpConnectionInfo* aCI) {
if (!aCI) {
return NS_ERROR_INVALID_ARG;
}
RefPtr<nsHttpConnectionInfo> ci = aCI->Clone();
return PostEvent(&nsHttpConnectionMgr::OnMsgDoShiftReloadConnectionCleanup, 0,
ci);
}
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) {
if (!IsNeckoChild() && NS_IsMainThread()) {
// 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(HttpConnectionBase* conn) {
LOG(("nsHttpConnectionMgr::ReclaimConnection [conn=%p]\n", conn));
Unused << EnsureSocketThreadTarget();
ReentrantMonitorAutoEnter mon(mReentrantMonitor);
if (!mSocketThreadTarget) {
NS_WARNING("cannot post event if not initialized");
return NS_ERROR_NOT_INITIALIZED;
}
RefPtr<HttpConnectionBase> connRef(conn);
RefPtr<nsHttpConnectionMgr> self(this);
return mSocketThreadTarget->Dispatch(NS_NewRunnableFunction(
"nsHttpConnectionMgr::CallReclaimConnection",
[conn{std::move(connRef)}, self{std::move(self)}]() {
self->OnMsgReclaimConnection(conn);
}));
}
// A structure used to marshall 6 pointers across the various necessary
// threads to complete an HTTP upgrade.
class nsCompleteUpgradeData : public ARefBase {
public:
nsCompleteUpgradeData(nsHttpTransaction* aTrans,
nsIHttpUpgradeListener* aListener, bool aJsWrapped)
: mTrans(aTrans), mUpgradeListener(aListener), mJsWrapped(aJsWrapped) {}
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(nsCompleteUpgradeData, override)
RefPtr<nsHttpTransaction> mTrans;
nsCOMPtr<nsIHttpUpgradeListener> mUpgradeListener;
nsCOMPtr<nsISocketTransport> mSocketTransport;
nsCOMPtr<nsIAsyncInputStream> mSocketIn;
nsCOMPtr<nsIAsyncOutputStream> mSocketOut;
bool mJsWrapped;
private:
virtual ~nsCompleteUpgradeData() {
NS_ReleaseOnMainThread("nsCompleteUpgradeData.mUpgradeListener",
mUpgradeListener.forget());
}
};
nsresult nsHttpConnectionMgr::CompleteUpgrade(
HttpTransactionShell* aTrans, nsIHttpUpgradeListener* aUpgradeListener) {
// test if aUpgradeListener is a wrapped JsObject
nsCOMPtr<nsIXPConnectWrappedJS> wrapper = do_QueryInterface(aUpgradeListener);
bool wrapped = !!wrapper;
RefPtr<nsCompleteUpgradeData> data = new nsCompleteUpgradeData(
aTrans->AsHttpTransaction(), aUpgradeListener, wrapped);
return PostEvent(&nsHttpConnectionMgr::OnMsgCompleteUpgrade, 0, data);
}
nsresult nsHttpConnectionMgr::UpdateParam(nsParamName name, uint16_t value) {
uint32_t param = (uint32_t(name) << 16) | uint32_t(value);
return PostEvent(&nsHttpConnectionMgr::OnMsgUpdateParam,
static_cast<int32_t>(param), nullptr);
}
nsresult nsHttpConnectionMgr::ProcessPendingQ(nsHttpConnectionInfo* aCI) {
LOG(("nsHttpConnectionMgr::ProcessPendingQ [ci=%s]\n", aCI->HashKey().get()));
RefPtr<nsHttpConnectionInfo> ci;
if (aCI) {
ci = aCI->Clone();
}
return PostEvent(&nsHttpConnectionMgr::OnMsgProcessPendingQ, 0, ci);
}
nsresult nsHttpConnectionMgr::ProcessPendingQ() {
LOG(("nsHttpConnectionMgr::ProcessPendingQ [All CI]\n"));
return PostEvent(&nsHttpConnectionMgr::OnMsgProcessPendingQ, 0, nullptr);
}
void nsHttpConnectionMgr::OnMsgUpdateRequestTokenBucket(int32_t,
ARefBase* param) {
EventTokenBucket* tokenBucket = static_cast<EventTokenBucket*>(param);
gHttpHandler->SetRequestTokenBucket(tokenBucket);
}
nsresult nsHttpConnectionMgr::UpdateRequestTokenBucket(
EventTokenBucket* aBucket) {
// Call From main thread when a new EventTokenBucket has been made in order
// to post the new value to the socket thread.
return PostEvent(&nsHttpConnectionMgr::OnMsgUpdateRequestTokenBucket, 0,
aBucket);
}
nsresult nsHttpConnectionMgr::ClearConnectionHistory() {
return PostEvent(&nsHttpConnectionMgr::OnMsgClearConnectionHistory, 0,
nullptr);
}
void nsHttpConnectionMgr::OnMsgClearConnectionHistory(int32_t,
ARefBase* param) {
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
LOG(("nsHttpConnectionMgr::OnMsgClearConnectionHistory"));
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();
}
}
}
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;
}
HttpConnectionBase* nsHttpConnectionMgr::FindCoalescableConnectionByHashKey(
nsConnectionEntry* ent, const nsCString& key, bool justKidding,
bool aNoHttp3) {
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<HttpConnectionBase> 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->RemoveLastElement();
MOZ_ASSERT(listOfWeakConns->Length() == listLen - 1);
listLen--;
continue; // without adjusting iterator
}
if (aNoHttp3 && potentialMatch->UsingHttp3()) {
j++;
continue;
}
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");
}
HttpConnectionBase* nsHttpConnectionMgr::FindCoalescableConnection(
nsConnectionEntry* ent, bool justKidding, bool aNoHttp3) {
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());
HttpConnectionBase* conn =
FindCoalescableConnectionByHashKey(ent, newKey, justKidding, aNoHttp3);
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, aNoHttp3);
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(
HttpConnectionBase* 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);
HttpConnectionBase* existingConn =
FindCoalescableConnection(ent, true, false);
if (existingConn) {
// Prefer http3 connection.
if (newConn->UsingHttp3() && existingConn->UsingSpdy()) {
LOG(
("UpdateCoalescingForNewConn() found existing active H2 conn that "
"could have served newConn, but new connection is H3, therefore "
"close the H2 conncetion"));
existingConn->DontReuse();
} else {
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) {
HttpConnectionBase* 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;
mNumSpdyHttp3ActiveConns++;
// 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)));
}
}
void nsHttpConnectionMgr::ReportHttp3Connection(HttpConnectionBase* conn) {
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
if (!conn->ConnectionInfo()) {
return;
}
nsConnectionEntry* ent = mCT.GetWeak(conn->ConnectionInfo()->HashKey());
if (!ent) {
return;
}
mNumSpdyHttp3ActiveConns++;
UpdateCoalescingForNewConn(conn, ent);
nsresult rv = ProcessPendingQ(ent->mConnInfo);
if (NS_FAILED(rv)) {
LOG(
("ReportHttp3Connection 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(
("ReportHttp3Connection 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<HttpConnectionBase> 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()));
}
}
if (!ent->mConnInfo->IsHttp3()) {
LOG(("] active urgent conns ["));
for (HttpConnectionBase* conn : ent->mActiveConns) {
RefPtr<nsHttpConnection> connTCP = do_QueryObject(conn);
MOZ_ASSERT(connTCP);
if (connTCP->IsUrgentStartPreferred()) {
LOG((" %p", conn));
}
}
LOG(("] active regular conns ["));
for (HttpConnectionBase* conn : ent->mActiveConns) {
RefPtr<nsHttpConnection> connTCP = do_QueryObject(conn);
MOZ_ASSERT(connTCP);
if (!connTCP->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));
}
}
} else {
for (HttpConnectionBase* conn : ent->mActiveConns) {
LOG((" %p", conn));
}
MOZ_ASSERT(ent->mIdleConns.Length() == 0);
}
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);
if (ci->IsHttp3()) {
return totalCount > 0;
}
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) {
HttpConnectionBase* conn = ent->mActiveConns[index];
RefPtr<nsHttpConnection> connTCP = do_QueryObject(conn);
if ((connTCP && !connTCP->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) {
HttpConnectionBase* 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<HttpConnectionBase> 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<HttpConnectionBase> conn = GetH2orH3ActiveConn(
ent,
(!gHttpHandler->IsHttp3Enabled() || (caps & NS_HTTP_DISALLOW_HTTP3)));
if (conn) {
if (trans->IsWebsocketUpgrade() && !conn->CanAcceptWebsocket()) {
// This is a websocket transaction and we already have a h2 connection
// that do not support websockets, we should disable h2 for this
// transaction.
trans->DisableSpdy();
caps &= NS_HTTP_DISALLOW_SPDY;
} else {
if ((caps & NS_HTTP_ALLOW_KEEPALIVE) ||
(caps & NS_HTTP_ALLOW_SPDY_WITHOUT_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 - mNumSpdyHttp3ActiveConns) <=
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
// Don't dispatch if this transaction is waiting for HTTPS RR.
// Note that this is only used in test currently.
if (caps & NS_HTTP_WAIT_HTTPSSVC_RESULT) {
return NS_ERROR_NOT_AVAILABLE;
}
// 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()));