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