/* 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 "nsIRequestContext.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<RefPtr<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 = nullptr;

        if (NS_FAILED(rv)) {
            NS_WARNING("unable to post SHUTDOWN message");
            return rv;
        }
    }

    // wait for shutdown event to complete
    while (!shutdownWrapper->mBool) {
        NS_ProcessNextEvent(NS_GetCurrentThread());
    }

    return NS_OK;
}

class ConnEvent : public Runnable
{
public:
    ConnEvent(nsHttpConnectionMgr *mgr,
              nsConnEventHandler handler, int32_t iparam, ARefBase *vparam)
        : mMgr(mgr)
        , mHandler(handler)
        , mIParam(iparam)
        , mVParam(vparam) {}

    NS_IMETHOD Run() override
    {
        (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 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!");

    if (!IsNeckoChild()) {
        // HACK: make sure PSM gets initialized on the main thread.
        net_EnsurePSMInit();
    }

    LOG(("nsHttpConnectionMgr::SpeculativeConnect [ci=%s]\n",
         ci->HashKey().get()));

    nsCOMPtr<nsISpeculativeConnectionOverrider> overrider =
        do_GetInterface(callbacks);

    bool allow1918 = overrider ? overrider->GetAllow1918() : false;

    // Hosts that are Local IP Literals should not be speculatively
    // connected - Bug 853423.
    if ((!allow1918) && ci && ci->HostIsLocalIPLiteral()) {
        LOG(("nsHttpConnectionMgr::SpeculativeConnect skipping RFC1918 "
             "address [%s]", ci->Origin()));
        return NS_OK;
    }

    RefPtr<SpeculativeConnectArgs> args = new SpeculativeConnectArgs();

    // Wrap up the callbacks and the target to ensure they're released on the target
    // thread properly.
    nsCOMPtr<nsIInterfaceRequestor> wrappedCallbacks;
    NS_NewInterfaceRequestorAggregation(callbacks, nullptr, getter_AddRefs(wrappedCallbacks));

    caps |= ci->GetAnonymous() ? NS_HTTP_LOAD_ANONYMOUS : 0;
    caps |= NS_HTTP_ERROR_SOFTLY;
    args->mTrans =
        nullTransaction ? nullTransaction : new NullHttpTransaction(ci, wrappedCallbacks, caps);

    if (overrider) {
        args->mOverridesOK = true;
        args->mParallelSpeculativeConnectLimit =
            overrider->GetParallelSpeculativeConnectLimit();
        args->mIgnoreIdle = overrider->GetIgnoreIdle();
        args->mIsFromPredictor = overrider->GetIsFromPredictor();
        args->mAllow1918 = overrider->GetAllow1918();
    }

    return PostEvent(&nsHttpConnectionMgr::OnMsgSpeculativeConnect, 0, args);
}

nsresult
nsHttpConnectionMgr::GetSocketThreadTarget(nsIEventTarget **target)
{
    EnsureSocketThreadTarget();

    ReentrantMonitorAutoEnter mon(mReentrantMonitor);
    nsCOMPtr<nsIEventTarget> temp(mSocketThreadTarget);
    temp.forget(target);
    return NS_OK;
}

nsresult
nsHttpConnectionMgr::ReclaimConnection(nsHttpConnection *conn)
{
    LOG(("nsHttpConnectionMgr::ReclaimConnection [conn=%p]\n", conn));
    return PostEvent(&nsHttpConnectionMgr::OnMsgReclaimConnection, 0, conn);
}

// A structure used to marshall 2 pointers across the various necessary
// threads to complete an HTTP upgrade.
class nsCompleteUpgradeData : public ARefBase
{
public:
    nsCompleteUpgradeData(nsAHttpConnection *aConn,
                          nsIHttpUpgradeListener *aListener)
        : mConn(aConn)
        , mUpgradeListener(aListener) { }

    NS_INLINE_DECL_THREADSAFE_REFCOUNTING(nsCompleteUpgradeData)

    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);

    RefPtr<nsHttpConnection> deleteProtector(conn);
    if (!ent || !ent->mIdleConns.RemoveElement(conn))
        return NS_ERROR_UNEXPECTED;

    conn->Close(NS_ERROR_ABORT);
    mNumIdleConns--;
    ConditionallyStopPruneDeadConnectionsTimer();
    return NS_OK;
}

// This function lets a connection, after completing the NPN phase,
// report whether or not it is using spdy through the usingSpdy
// argument. It would not be necessary if NPN were driven out of
// the connection manager. The connection entry associated with the
// connection is then updated to indicate whether or not we want to use
// spdy with that host and update the preliminary preferred host
// entries used for de-sharding hostsnames.
void
nsHttpConnectionMgr::ReportSpdyConnection(nsHttpConnection *conn,
                                          bool usingSpdy)
{
    MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);

    nsConnectionEntry *ent = LookupConnectionEntry(conn->ConnectionInfo(),
                                                   conn, nullptr);

    if (!ent)
        return;

    if (!usingSpdy)
        return;

    ent->mUsingSpdy = true;
    mNumSpdyActiveConns++;

    uint32_t ttl = conn->TimeToLive();
    uint64_t timeOfExpire = NowInSeconds() + ttl;
    if (!mTimer || timeOfExpire < mTimeOfNextWakeUp)
        PruneDeadConnectionsAfter(ttl);

    // Lookup preferred directly from the hash instead of using
    // GetSpdyPreferredEnt() because we want to avoid the cert compatibility
    // check at this point because the cert is never part of the hash
    // lookup. Filtering on that has to be done at the time of use
    // rather than the time of registration (i.e. now).
    nsConnectionEntry *joinedConnection;
    nsConnectionEntry *preferred = LookupPreferredHash(ent);

    LOG(("ReportSpdyConnection %p,%s conn %p prefers %p,%s\n",
         ent, ent->mConnInfo->Origin(), conn, preferred,
         preferred ? preferred->mConnInfo->Origin() : ""));

    if (!preferred) {
        // this becomes the preferred entry
        StorePreferredHash(ent);
        preferred = ent;
    } else if ((preferred != ent) &&
               (joinedConnection = GetSpdyPreferredEnt(ent)) &&
               (joinedConnection != ent)) {
        //
        // A connection entry (e.g. made with a different hostname) with
        // the same IP address is preferred for future transactions over this
        // connection entry. Gracefully close down the connection to help
        // new transactions migrate over.

        LOG(("ReportSpdyConnection graceful close of conn=%p ent=%p to "
                 "migrate to preferred (desharding)\n", conn, ent));
        conn->DontReuse();
    } else if (preferred != ent) {
        LOG (("ReportSpdyConnection preferred host may be in false start or "
              "may have insufficient cert. Leave mapping in place but do not "
              "abandon this connection yet."));
    }

    if ((preferred == ent) && conn->CanDirectlyActivate()) {
        // this is a new spdy connection to the preferred entry

        // Cancel any other pending connections - their associated transactions
        // are in the pending queue and will be dispatched onto this connection
        for (int32_t index = ent->mHalfOpens.Length() - 1;
             index >= 0; --index) {
            LOG(("ReportSpdyConnection forcing halfopen abandon %p\n",
                 ent->mHalfOpens[index]));
            ent->mHalfOpens[index]->Abandon();
        }

        if (ent->mActiveConns.Length() > 1) {
            // this is a new connection to an established preferred spdy host.
            // if there is more than 1 live and established spdy connection (e.g.
            // some could still be handshaking, shutting down, etc..) then close
            // this one down after any transactions that are on it are complete.
            // This probably happened due to the parallel connection algorithm
            // that is used only before the host is known to speak spdy.
            for (uint32_t index = 0; index < ent->mActiveConns.Length(); ++index) {
                nsHttpConnection *otherConn = ent->mActiveConns[index];
                if (otherConn != conn) {
                    LOG(("ReportSpdyConnection shutting down connection (%p) because new "
                         "spdy connection (%p) takes precedence\n", otherConn, conn));
                    otherConn->DontReuse();
                }
            }
        }
    }

    ProcessPendingQ(ent->mConnInfo);
    PostEvent(&nsHttpConnectionMgr::OnMsgProcessAllSpdyPendingQ);
}

nsHttpConnectionMgr::nsConnectionEntry *
nsHttpConnectionMgr::GetSpdyPreferredEnt(nsConnectionEntry *aOriginalEntry)
{
    if (!gHttpHandler->IsSpdyEnabled() ||
        !gHttpHandler->CoalesceSpdy() ||
        aOriginalEntry->mConnInfo->GetNoSpdy() ||
        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::GetSpdyPreferredEnt "
             "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::GetSpdyPreferredEnt "
             "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::GetSpdyPreferredEnt "
         "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)) {
                // trans is now potentially destroyed
                dispatchedSuccessfully = true;
                continue; // dont ++i as we just made the array shorter
            }

            LOG(("  transaction not found in pending queue\n"));
        }

        if (dispatchedSuccessfully && !considerAll)
            break;

        ++i;
    }
    return dispatchedSuccessfully;
}

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,
                                 NeckoOriginAttributes(), 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()) {
        RefPtr<nsHttpConnection> conn(ent->mIdleConns[0]);
        ent->mIdleConns.RemoveElementAt(0);
        mNumIdleConns--;
        conn->Close(NS_ERROR_ABORT);
    }

    int32_t activeCount = ent->mActiveConns.Length();
    for (int32_t i=0; i < activeCount; i++)
        ent->mActiveConns[i]->DontReuse();
}

bool
nsHttpConnectionMgr::RestrictConnections(nsConnectionEntry *ent)
{
    MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);

    // If this host is trying to negotiate a SPDY session right now,
    // don't create any new ssl connections until the result of the
    // negotiation is known.

    bool doRestrict =
        ent->mConnInfo->FirstHopSSL() && gHttpHandler->IsSpdyEnabled() &&
        ent->mUsingSpdy && (ent->mHalfOpens.Length() || ent->mActiveConns.Length());

    // If there are no restrictions, we are done
    if (!doRestrict)
        return false;

    // If the restriction is based on a tcp handshake in progress
    // let that connect and then see if it was SPDY or not
    if (ent->UnconnectedHalfOpens()) {
        return true;
    }

    // There is a concern that a host is using a mix of HTTP/1 and SPDY.
    // In that case we don't want to restrict connections just because
    // there is a single active HTTP/1 session in use.
    if (ent->mUsingSpdy && ent->mActiveConns.Length()) {
        bool confirmedRestrict = false;
        for (uint32_t index = 0; index < ent->mActiveConns.Length(); ++index) {
            nsHttpConnection *conn = ent->mActiveConns[index];
            if (!conn->ReportedNPN() || conn->CanDirectlyActivate()) {
                confirmedRestrict = true;
                break;
            }
        }
        doRestrict = confirmedRestrict;
        if (!confirmedRestrict) {
            LOG(("nsHttpConnectionMgr spdy connection restriction to "
                 "%s bypassed.\n", ent->mConnInfo->Origin()));
        }
    }
    return doRestrict;
}

// returns NS_OK if a connection was started
// return NS_ERROR_NOT_AVAILABLE if a new connection cannot be made due to
//        ephemeral limits
// returns other NS_ERROR on hard failure conditions
nsresult
nsHttpConnectionMgr::MakeNewConnection(nsConnectionEntry *ent,
                                       nsHttpTransaction *trans)
{
    LOG(("nsHttpConnectionMgr::MakeNewConnection %p ent=%p trans=%p",
         this, ent, trans));
    MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);

    uint32_t halfOpenLength = ent->mHalfOpens.Length();
    for (uint32_t i = 0; i < halfOpenLength; i++) {
        if (ent->mHalfOpens[i]->IsSpeculative()) {
            // We've found a speculative connection in the half
            // open list. Remove the speculative bit from it and that
            // connection can later be used for this transaction
            // (or another one in the pending queue) - we don't
            // need to open a new connection here.
            LOG(("nsHttpConnectionMgr::MakeNewConnection [ci = %s]\n"
                 "Found a speculative half open connection\n",
                 ent->mConnInfo->HashKey().get()));

            uint32_t flags;
            ent->mHalfOpens[i]->SetSpeculative(false);
            nsISocketTransport *transport = ent->mHalfOpens[i]->SocketTransport();
            if (transport && NS_SUCCEEDED(transport->GetConnectionFlags(&flags))) {
                flags &= ~nsISocketTransport::DISABLE_RFC1918;
                transport->SetConnectionFlags(flags);
            }

            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"
    // because we have already determined there are no idle connections
    // to our destination

    if ((mNumIdleConns + mNumActiveConns + 1 >= mMaxConns) && mNumIdleConns) {
        // If the global number of connections is preventing the opening of new
        // connections to a host without idle connections, then close them
        // regardless of their TTL.
        auto iter = mCT.Iter();
        while (mNumIdleConns + mNumActiveConns + 1 >= mMaxConns &&
               !iter.Done()) {
            nsAutoPtr<nsConnectionEntry> &entry = iter.Data();
            if (!entry->mIdleConns.Length()) {
              iter.Next();
              continue;
            }
            RefPtr<nsHttpConnection> conn(entry->mIdleConns[0]);
            entry->mIdleConns.RemoveElementAt(0);
            conn->Close(NS_ERROR_ABORT);
            mNumIdleConns--;
            ConditionallyStopPruneDeadConnectionsTimer();
        }
    }

    if ((mNumIdleConns + mNumActiveConns + 1 >= mMaxConns) &&
        mNumActiveConns && gHttpHandler->IsSpdyEnabled())
    {
        // If the global number of connections is preventing the opening of new
        // connections to a host without idle connections, then close any spdy
        // ASAP.
        for (auto iter = mCT.Iter(); !iter.Done(); iter.Next()) {
            nsAutoPtr<nsConnectionEntry> &entry = iter.Data();
            if (!entry->mUsingSpdy) {
                continue;
            }

            for (uint32_t index = 0;
                 index < entry->mActiveConns.Length();
                 ++index) {
                nsHttpConnection *conn = entry->mActiveConns[index];
                if (conn->UsingSpdy() && conn->CanReuse()) {
                    conn->DontReuse();
                    // Stop on <= (particularly =) because this dontreuse
                    // causes async close.
                    if (mNumIdleConns + mNumActiveConns + 1 <= mMaxConns) {
                        goto outerLoopEnd;
                    }
                }
            }
        }
      outerLoopEnd:
        ;
    }

    if (AtActiveConnectionLimit(ent, trans->Caps()))
        return NS_ERROR_NOT_AVAILABLE;

    nsresult rv = CreateTransport(ent, trans, trans->Caps(), false, false, 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 request context for it is
    // currently processing one or more blocking transactions then we
    // need to just leave it in the queue until those are complete unless it is
    // explicitly marked as unblocked.
    if (!(caps & NS_HTTP_LOAD_AS_BLOCKING)) {
        if (!(caps & NS_HTTP_LOAD_UNBLOCKED)) {
            nsIRequestContext *requestContext = trans->RequestContext();
            if (requestContext) {
                uint32_t blockers = 0;
                if (NS_SUCCEEDED(requestContext->GetBlockingTransactionCount(&blockers)) &&
                    blockers) {
                    // need to wait for blockers to clear
                    LOG(("   blocked by request context: [rc=%p trans=%p blockers=%d]\n",
                         requestContext, trans, blockers));
                    return NS_ERROR_NOT_AVAILABLE;
                }
            }
        }
    } else {
        // Mark the transaction and its load group as blocking right now to prevent
        // other transactions from being reordered in the queue due to slow syns.
        trans->DispatchedAsBlocking();
    }

    // step 1
    // If connection pressure, then we want to favor pipelining of any kind
    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--;

            // 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) : mConn(conn) { }
    void Reset() { mConn = nullptr; }
private:
    virtual ~ConnectionHandle();
    RefPtr<nsHttpConnection> mConn;
};

nsAHttpConnection *
nsHttpConnectionMgr::MakeConnectionHandle(nsHttpConnection *aWrapped)
{
    return new ConnectionHandle(aWrapped);
}

ConnectionHandle::~ConnectionHandle()
{
    if (mConn) {
        gHttpHandler->ReclaimConnection(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);
        handle->Reset(); // destroy the connection
    }

    // 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) {
        LOG(("  ProcessNewTransaction %p tied to h2 session push %p\n",
             trans, pushedStream->Session()));
        return pushedStream->Session()->
            AddStream(trans, trans->Priority(), false, nullptr) ?
            NS_OK : NS_ERROR_UNEXPECTED;
    }

    nsresult rv = NS_OK;
    nsHttpConnectionInfo *ci = trans->ConnectionInfo();
    MOZ_ASSERT(ci);

    nsConnectionEntry *ent =
        GetOrCreateConnectionEntry(ci, !!trans->TunnelProvider());

    // 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 = 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);
        return NS_OK;
    }

    LOG(("  ProcessNewTransaction Hard Error trans=%p rv=%x\n", trans, rv));
    return rv;
}


void
nsHttpConnectionMgr::AddActiveConn(nsHttpConnection *conn,
                                   nsConnectionEntry *ent)
{
    ent->mActiveConns.AppendElement(conn);
    mNumActiveConns++;
    ActivateTimeoutTick();
}

void
nsHttpConnectionMgr::DecrementActiveConnCount(nsHttpConnection *conn)
{
    mNumActiveConns--;
    if (conn->EverUsedSpdy())
        mNumSpdyActiveConns--;
}

void
nsHttpConnectionMgr::StartedConnect()
{
    mNumActiveConns++;
    ActivateTimeoutTick(); // likely disabled by RecvdConnect()
}

void
nsHttpConnectionMgr::RecvdConnect()
{
    mNumActiveConns--;
    ConditionallyStopTimeoutTick();
}

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<RefPtr<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);
        }
    }

    // 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()) {
            RefPtr<nsHttpConnection> conn(ent->mActiveConns[0]);
            ent->mActiveConns.RemoveElementAt(0);
            DecrementActiveConnCount(conn);
            // Since nsHttpConnection::Close doesn't break the bond with
            // the connection's transaction, we must explicitely tell it
            // to close its transaction and not just self.
            conn->CloseTransaction(conn->Transaction(), NS_ERROR_ABORT, true);
        }

        // Close all idle connections.
        while (ent->mIdleConns.Length()) {
            RefPtr<nsHttpConnection> conn(ent->mIdleConns[0]);

            ent->mIdleConns.RemoveElementAt(0);
            mNumIdleConns--;

            conn->Close(NS_ERROR_ABORT);
        }

        // If all idle connections are removed we can stop pruning dead
        // connections.
        ConditionallyStopPruneDeadConnectionsTimer();

        // Close all pending transactions.
        while (ent->mPendingQ.Length()) {
            nsHttpTransaction *trans = ent->mPendingQ[0];
            trans->Close(NS_ERROR_ABORT);
            ent->mPendingQ.RemoveElementAt(0);
        }

        // 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));

    RefPtr<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);

    // caller holds a ref to param/trans on stack
    nsHttpTransaction *trans = static_cast<nsHttpTransaction *>(param);

    //
    // if the transaction owns a connection and the transaction is not done,
    // then ask the connection to close the transaction.  otherwise, close the
    // transaction directly (removing it from the pending queue first).
    //
    RefPtr<nsAHttpConnection> conn(trans->Connection());
    if (conn && !trans->IsDone()) {
        conn->CloseTransaction(trans, closeCode);
    } else {
        nsConnectionEntry *ent =
            LookupConnectionEntry(trans->ConnectionInfo(), nullptr, trans);

        if (ent) {
            int32_t transIndex = ent->mPendingQ.IndexOf(trans);
            if (transIndex >= 0) {
                LOG(("nsHttpConnectionMgr::OnMsgCancelTransaction [trans=%p]"
                     " found in pending queue\n", trans));
                ent->mPendingQ.RemoveElementAt(transIndex);
            }

            // 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;
    }

    for (int32_t i = ent->mPendingQ.Length() - 1; i >= 0; --i) {
        nsHttpTransaction *trans = ent->mPendingQ[i];
        LOG(("nsHttpConnectionMgr::OnMsgCancelTransactions %s %p %p\n",
             ci->HashKey().get(), ent, trans));
        trans->Close(reason);
        ent->mPendingQ.RemoveElementAt(i);
    }
}

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) {
                    RefPtr<nsHttpConnection> conn(ent->mIdleConns[i]);
                    if (!conn->CanReuse()) {
                        ent->mIdleConns.RemoveElementAt(i);
                        conn->Close(NS_ERROR_ABORT);
                        mNumIdleConns--;
                    } else {
                        timeToNextExpire =
                            std::min(timeToNextExpire, conn->TimeToLive());
                    }
                }
            }

            if (ent->mUsingSpdy) {
                for (uint32_t i = 0; i < ent->mActiveConns.Length(); ++i) {
                    nsHttpConnection* conn = ent->mActiveConns[i];
                    if (conn->UsingSpdy()) {
                        if (!conn->CanReuse()) {
                            // Marking it don't-reuse will create an active
                            // tear down if the spdy session is idle.
                            conn->DontReuse();
                        } else {
                            timeToNextExpire =
                                std::min(timeToNextExpire, conn->TimeToLive());
                        }
                    }
                }
            }

            // If time to next expire found is shorter than time to next
            // wake-up, we need to change the time for next wake-up.
            if (timeToNextExpire != UINT32_MAX) {
                uint32_t now = NowInSeconds();
                uint64_t timeOfNextExpire = now + timeToNextExpire;
                // If pruning of dead connections is not already scheduled to
                // happen or time found for next connection to expire is is
                // before mTimeOfNextWakeUp, we need to schedule the pruning to
                // happen after timeToNextExpire.
                if (!mTimer || timeOfNextExpire < mTimeOfNextWakeUp) {
                    PruneDeadConnectionsAfter(timeToNextExpire);
                }
            } else {
                ConditionallyStopPruneDeadConnectionsTimer();
            }

            // 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->mUsingSpdy || mCT.Count() > 300)) {
                LOG(("    removing empty connection entry\n"));
                iter.Remove();
                continue;
            }

            // Otherwise use this opportunity to compact our arrays...
            ent->mIdleConns.Compact();
            ent->mActiveConns.Compact();
            ent->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 = 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();
        }
        DecrementActiveConnCount(conn);
        ConditionallyStopTimeoutTick();
    }

    if (conn->CanReuse()) {
        LOG(("  adding connection to idle list\n"));
        // Keep The idle connection list sorted with the connections that
        // have moved the largest data pipelines at the front because these
        // connections have the largest cwnds on the server.

        // The linear search is ok here because the number of idleconns
        // in a single entry is generally limited to a small number (i.e. 6)

        uint32_t idx;
        for (idx = 0; idx < ent->mIdleConns.Length(); idx++) {
            nsHttpConnection *idleConn = ent->mIdleConns[idx];
            if (idleConn->MaxBytesRead() < conn->MaxBytesRead())
                break;
        }

        ent->mIdleConns.InsertElementAt(idx, conn);
        mNumIdleConns++;
        conn->BeginIdleMonitoring();

        // If the added connection was first idle connection or has shortest
        // time to live among the watched connections, pruning dead
        // connections needs to be done when it can't be reused anymore.
        uint32_t timeToLive = conn->TimeToLive();
        if(!mTimer || NowInSeconds() + timeToLive < mTimeOfNextWakeUp)
            PruneDeadConnectionsAfter(timeToLive);
    } else {
        LOG(("  connection cannot be reused; closing connection\n"));
        conn->Close(NS_ERROR_ABORT);
    }

    OnMsgProcessPendingQ(0, ci);
}

void
nsHttpConnectionMgr::OnMsgCompleteUpgrade(int32_t, ARefBase *param)
{
    MOZ_ASSERT(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 TimeoutTick()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::TimeoutTick() 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 tickTime = PR_IntervalNow();
        for (uint32_t index = 0; index < ent->mActiveConns.Length(); ++index) {
            uint32_t connNextTimeout =
                ent->mActiveConns[index]->ReadTimeoutTick(tickTime);
            mTimeoutTickNext = std::min(mTimeoutTickNext, connNextTimeout);
        }

        // Now check for any stalled half open sockets.
        if (ent->mHalfOpens.Length()) {
            TimeStamp currentTime = TimeStamp::Now();
            double maxConnectTime_ms = gHttpHandler->ConnectTimeout();

            for (uint32_t index = ent->mHalfOpens.Length(); index > 0; ) {
                index--;

                nsHalfOpenSocket *half = ent->mHalfOpens[index];
                double delta = half->Duration(currentTime);
                // If the socket has timed out, close it so the waiting
                // transaction will get the proper signal.
                if (delta > maxConnectTime_ms) {
                    LOG(("Force timeout of half open to %s after %.2fms.\n",
                         ent->mConnInfo->HashKey().get(), delta));
                    if (half->SocketTransport()) {
                        half->SocketTransport()->Close(NS_ERROR_NET_TIMEOUT);
                    }
                    if (half->BackupTransport()) {
                        half->BackupTransport()->Close(NS_ERROR_NET_TIMEOUT);
                    }
                }

                // If this half open hangs around for 5 seconds after we've
                // closed() it then just abandon the socket.
                if (delta > maxConnectTime_ms + 5000) {
                    LOG(("Abandon half open to %s after %.2fms.\n",
                         ent->mConnInfo->HashKey().get(), delta));
                    half->Abandon();
                }
            }
        }
        if (ent->mHalfOpens.Length()) {
            mTimeoutTickNext = 1;
        }
    }

    if (mTimeoutTick) {
        mTimeoutTickNext = std::max(mTimeoutTickNext, 1U);
        mTimeoutTick->SetDelay(mTimeoutTickNext * 1000);
    }
}

// GetOrCreateConnectionEntry finds a ent for a particular CI for use in
// dispatching a transaction according to these rules
// 1] use an ent that matches the ci that can be dispatched immediately
// 2] otherwise use an ent of wildcard(ci) than can be dispatched immediately
// 3] otherwise create an ent that matches ci and make new conn on it

nsHttpConnectionMgr::nsConnectionEntry *
nsHttpConnectionMgr::GetOrCreateConnectionEntry(nsHttpConnectionInfo *specificCI,
                                                bool prohibitWildCard)
{
    // step 1
    nsConnectionEntry *specificEnt = mCT.Get(specificCI->HashKey());
    if (specificEnt && specificEnt->AvailableForDispatchNow()) {
        return specificEnt;
    }

    if (!specificCI->UsingHttpsProxy()) {
        prohibitWildCard = true;
    }

    // step 2
    if (!prohibitWildCard) {
        RefPtr<nsHttpConnectionInfo> wildCardProxyCI;
        specificCI->CreateWildCard(getter_AddRefs(wildCardProxyCI));
        nsConnectionEntry *wildCardEnt = mCT.Get(wildCardProxyCI->HashKey());
        if (wildCardEnt && wildCardEnt->AvailableForDispatchNow()) {
            return wildCardEnt;
        }
    }

    // step 3
    if (!specificEnt) {
        RefPtr<nsHttpConnectionInfo> clone(specificCI->Clone());
        specificEnt = new nsConnectionEntry(clone);
        mCT.Put(clone->HashKey(), specificEnt);
    }
    return specificEnt;
}

nsresult
ConnectionHandle::OnHeadersAvailable(nsAHttpTransaction *trans,
                                     nsHttpRequestHead *req,
                                     nsHttpResponseHead *resp,
                                     bool *reset)
{
    return mConn->OnHeadersAvailable(trans, req, resp, reset);
}

void
ConnectionHandle::CloseTransaction(nsAHttpTransaction *trans, nsresult reason)
{
    mConn->CloseTransaction(trans, reason);
}

nsresult
ConnectionHandle::TakeTransport(nsISocketTransport  **aTransport,
                                nsIAsyncInputStream **aInputStream,
                                nsIAsyncOutputStream **aOutputStream)
{
    return mConn->TakeTransport(aTransport, aInputStream, aOutputStream);
}

void
nsHttpConnectionMgr::OnMsgSpeculativeConnect(int32_t, ARefBase *param)
{
    MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);

    SpeculativeConnectArgs *args = static_cast<SpeculativeConnectArgs *>(param);

    LOG(("nsHttpConnectionMgr::OnMsgSpeculativeConnect [ci=%s]\n",
         args->mTrans->ConnectionInfo()->HashKey().get()));

    nsConnectionEntry *ent =
        GetOrCreateConnectionEntry(args->mTrans->ConnectionInfo(), false);

    // If spdy has previously made a preferred entry for this host via
    // the ip pooling rules. If so, connect to the preferred host instead of
    // the one directly passed in here.
    nsConnectionEntry *preferredEntry = GetSpdyPreferredEnt(ent);
    if (preferredEntry)
        ent = preferredEntry;

    uint32_t parallelSpeculativeConnectLimit =
        gHttpHandler->ParallelSpeculativeConnectLimit();
    bool ignoreIdle = false;
    bool isFromPredictor = false;
    bool allow1918 = false;

    if (args->mOverridesOK) {
        parallelSpeculativeConnectLimit = args->mParallelSpeculativeConnectLimit;
        ignoreIdle = args->mIgnoreIdle;
        isFromPredictor = args->mIsFromPredictor;
        allow1918 = args->mAllow1918;
    }

    bool keepAlive = args->mTrans->Caps() & NS_HTTP_ALLOW_KEEPALIVE;
    if (mNumHalfOpenConns < parallelSpeculativeConnectLimit &&
        ((ignoreIdle && (ent->mIdleConns.Length() < parallelSpeculativeConnectLimit)) ||
         !ent->mIdleConns.Length()) &&
        !(keepAlive && RestrictConnections(ent)) &&
        !AtActiveConnectionLimit(ent, args->mTrans->Caps())) {
        CreateTransport(ent, args->mTrans, args->mTrans->Caps(), true, isFromPredictor, allow1918);
    } else {
        LOG(("OnMsgSpeculativeConnect Transport "
             "not created due to existing connection count\n"));
    }
}

bool
ConnectionHandle::IsPersistent()
{
    return mConn->IsPersistent();
}

bool
ConnectionHandle::IsReused()
{
    return mConn->IsReused();
}

void
ConnectionHandle::DontReuse()
{
    mConn->DontReuse();
}

nsresult
ConnectionHandle::PushBack(const char *buf, uint32_t bufLen)
{
    return mConn->PushBack(buf, bufLen);
}


//////////////////////// nsHalfOpenSocket

NS_IMPL_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;
    const nsHttpConnectionInfo *ci = mEnt->mConnInfo;
    if (ci->FirstHopSSL()) {
        socketTypes[typeCount++] = "ssl";
    } else {
        socketTypes[typeCount] = gHttpHandler->DefaultSocketType();
        if (socketTypes[typeCount]) {
            typeCount++;
        }
    }

    nsCOMPtr<nsISocketTransport> socketTransport;
    nsCOMPtr<nsISocketTransportService> sts;

    sts = 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 ((mCaps & NS_HTTP_BE_CONSERVATIVE) || ci->GetBeConservative()) {
        LOG(("Setting Socket to BE_CONSERVATIVE"));
        tmpFlags |= nsISocketTransport::BE_CONSERVATIVE;
    }

    // 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);

    NeckoOriginAttributes originAttributes =
        mEnt->mConnInfo->GetOriginAttributes();
    if (originAttributes != NeckoOriginAttributes()) {
        socketTransport->SetOriginAttributes(originAttributes);
    }

    socketTransport->SetQoSBits(gHttpHandler->GetQoSBits());

    if (!ci->GetNetworkInterfaceId().IsEmpty()) {
        socketTransport->SetNetworkInterfaceId(ci->GetNetworkInterfaceId());
    }

    rv = socketTransport->SetEventSink(this, nullptr);
    NS_ENSURE_SUCCESS(rv, rv);

    rv = socketTransport->SetSecurityCallbacks(this);
    NS_ENSURE_SUCCESS(rv, rv);

    Telemetry::Accumulate(Telemetry::HTTP_CONNECTION_ENTRY_CACHE_HIT_1,
                          mEnt->mUsedForConnection);
    mEnt->mUsedForConnection = true;

    nsCOMPtr<nsIOutputStream> sout;
    rv = socketTransport->OpenOutputStream(nsITransport::OPEN_UNBUFFERED,
                                            0, 0,
                                            getter_AddRefs(sout));
    NS_ENSURE_SUCCESS(rv, rv);

    nsCOMPtr<nsIInputStream> sin;
    rv = socketTransport->OpenInputStream(nsITransport::OPEN_UNBUFFERED,
                                           0, 0,
                                           getter_AddRefs(sin));
    NS_ENSURE_SUCCESS(rv, rv);

    socketTransport.forget(transport);
    CallQueryInterface(sin, instream);
    CallQueryInterface(sout, outstream);

    rv = (*outstream)->AsyncWait(this, 0, 0, nullptr);
    if (NS_SUCCEEDED(rv))
        gHttpHandler->ConnMgr()->StartedConnect();

    return rv;
}

nsresult
nsHttpConnectionMgr::nsHalfOpenSocket::SetupPrimaryStreams()
{
    MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);

    nsresult rv;

    mPrimarySynStarted = TimeStamp::Now();
    rv = SetupStreams(getter_AddRefs(mSocketTransport),
                      getter_AddRefs(mStreamIn),
                      getter_AddRefs(mStreamOut),
                      false);
    LOG(("nsHalfOpenSocket::SetupPrimaryStream [this=%p ent=%s rv=%x]",
         this, mEnt->mConnInfo->Origin(), rv));
    if (NS_FAILED(rv)) {
        if (mStreamOut)
            mStreamOut->AsyncWait(nullptr, 0, 0, nullptr);
        mStreamOut = nullptr;
        mStreamIn = nullptr;
        mSocketTransport = nullptr;
    }
    return rv;
}

nsresult
nsHttpConnectionMgr::nsHalfOpenSocket::SetupBackupStreams()
{
    MOZ_ASSERT(mTransaction);
    MOZ_ASSERT(!mTransaction->IsNullTransaction(),
               "null transactions dont have backup streams");

    mBackupSynStarted = TimeStamp::Now();
    nsresult rv = SetupStreams(getter_AddRefs(mBackupTransport),
                               getter_AddRefs(mBackupStreamIn),
                               getter_AddRefs(mBackupStreamOut),
                               true);
    LOG(("nsHalfOpenSocket::SetupBackupStream [this=%p ent=%s rv=%x]",
         this, mEnt->mConnInfo->Origin(), rv));
    if (NS_FAILED(rv)) {
        if (mBackupStreamOut)
            mBackupStreamOut->AsyncWait(nullptr, 0, 0, nullptr);
        mBackupStreamOut = nullptr;
        mBackupStreamIn = nullptr;
        mBackupTransport = nullptr;
    }
    return rv;
}

void
nsHttpConnectionMgr::nsHalfOpenSocket::SetupBackupTimer()
{
    uint16_t timeout = gHttpHandler->GetIdleSynTimeout();
    MOZ_ASSERT(!mSynTimer, "timer already initd");
    if (timeout && !mTransaction->IsDone() && !mTransaction->IsNullTransaction()) {
        // Setup the timer that will establish a backup socket
        // if we do not get a writable event on the main one.
        // We do this because a lost SYN takes a very long time
        // to repair at the TCP level.
        //
        // Failure to setup the timer is something we can live with,
        // so don't return an error in that case.
        nsresult rv;
        mSynTimer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv);
        if (NS_SUCCEEDED(rv)) {
            mSynTimer->InitWithCallback(this, timeout, nsITimer::TYPE_ONE_SHOT);
            LOG(("nsHalfOpenSocket::SetupBackupTimer() [this=%p]", this));
        }
    } else if (timeout) {
        LOG(("nsHalfOpenSocket::SetupBackupTimer() [this=%p], did not arm\n", this));
    }
}

void
nsHttpConnectionMgr::nsHalfOpenSocket::CancelBackupTimer()
{
    // If the syntimer is still armed, we can cancel it because no backup
    // socket should be formed at this point
    if (!mSynTimer)
        return;

    LOG(("nsHalfOpenSocket::CancelBackupTimer()"));
    mSynTimer->Cancel();
    mSynTimer = nullptr;
}

void
nsHttpConnectionMgr::nsHalfOpenSocket::Abandon()
{
    LOG(("nsHalfOpenSocket::Abandon [this=%p ent=%s] %p %p %p %p",
         this, mEnt->mConnInfo->Origin(),
         mSocketTransport.get(), mBackupTransport.get(),
         mStreamOut.get(), mBackupStreamOut.get()));

    MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);

    RefPtr<nsHalfOpenSocket> deleteProtector(this);

    // Tell socket (and backup socket) to forget the half open socket.
    if (mSocketTransport) {
        mSocketTransport->SetEventSink(nullptr, nullptr);
        mSocketTransport->SetSecurityCallbacks(nullptr);
        mSocketTransport = nullptr;
    }
    if (mBackupTransport) {
        mBackupTransport->SetEventSink(nullptr, nullptr);
        mBackupTransport->SetSecurityCallbacks(nullptr);
        mBackupTransport = nullptr;
    }

    // Tell output stream (and backup) to forget the half open socket.
    if (mStreamOut) {
        gHttpHandler->ConnMgr()->RecvdConnect();
        mStreamOut->AsyncWait(nullptr, 0, 0, nullptr);
        mStreamOut = nullptr;
    }
    if (mBackupStreamOut) {
        gHttpHandler->ConnMgr()->RecvdConnect();
        mBackupStreamOut->AsyncWait(nullptr, 0, 0, nullptr);
        mBackupStreamOut = nullptr;
    }

    // Lose references to input stream (and backup).
    mStreamIn = mBackupStreamIn = nullptr;

    // Stop the timer - we don't want any new backups.
    CancelBackupTimer();

    // Remove the half open from the connection entry.
    if (mEnt)
        mEnt->RemoveHalfOpen(this);
    mEnt = nullptr;
}

double
nsHttpConnectionMgr::nsHalfOpenSocket::Duration(TimeStamp epoch)
{
    if (mPrimarySynStarted.IsNull())
        return 0;

    return (epoch - mPrimarySynStarted).ToMilliseconds();
}


NS_IMETHODIMP // method for nsITimerCallback
nsHttpConnectionMgr::nsHalfOpenSocket::Notify(nsITimer *timer)
{
    MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
    MOZ_ASSERT(timer == mSynTimer, "wrong timer");
    MOZ_ASSERT(mTransaction && !mTransaction->IsNullTransaction(),
               "null transactions dont have backup streams");

    SetupBackupStreams();

    mSynTimer = nullptr;
    return NS_OK;
}

// method for nsIAsyncOutputStreamCallback
NS_IMETHODIMP
nsHttpConnectionMgr::
nsHalfOpenSocket::OnOutputStreamReady(nsIAsyncOutputStream *out)
{
    MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
    MOZ_ASSERT(out == mStreamOut || out == mBackupStreamOut,
               "stream mismatch");
    LOG(("nsHalfOpenSocket::OnOutputStreamReady [this=%p ent=%s %s]\n",
         this, mEnt->mConnInfo->Origin(),
         out == mStreamOut ? "primary" : "backup"));
    int32_t index;
    nsresult rv;

    gHttpHandler->ConnMgr()->RecvdConnect();

    CancelBackupTimer();

    // assign the new socket to the http connection
    RefPtr<nsHttpConnection> conn = new nsHttpConnection();
    LOG(("nsHalfOpenSocket::OnOutputStreamReady "
         "Created new nshttpconnection %p\n", conn.get()));

    // Some capabilities are needed before a transaciton actually gets
    // scheduled (e.g. how to negotiate false start)
    conn->SetTransactionCaps(mTransaction->Caps());

    NetAddr peeraddr;
    nsCOMPtr<nsIInterfaceRequestor> callbacks;
    mTransaction->GetSecurityCallbacks(getter_AddRefs(callbacks));
    if (out == mStreamOut) {
        TimeDuration rtt = TimeStamp::Now() - mPrimarySynStarted;
        rv = conn->Init(mEnt->mConnInfo,
                        gHttpHandler->ConnMgr()->mMaxRequestDelay,
                        mSocketTransport, mStreamIn, mStreamOut,
                        mPrimaryConnectedOK, callbacks,
                        PR_MillisecondsToInterval(
                          static_cast<uint32_t>(rtt.ToMilliseconds())));

        if (NS_SUCCEEDED(mSocketTransport->GetPeerAddr(&peeraddr)))
            mEnt->RecordIPFamilyPreference(peeraddr.raw.family);

        // The nsHttpConnection object now owns these streams and sockets
        mStreamOut = nullptr;
        mStreamIn = nullptr;
        mSocketTransport = nullptr;
    } else if (out == mBackupStreamOut) {
        MOZ_ASSERT(!mTransaction->IsNullTransaction(),
                   "null transactions dont have backup streams");
        TimeDuration rtt = TimeStamp::Now() - mBackupSynStarted;
        rv = conn->Init(mEnt->mConnInfo,
                        gHttpHandler->ConnMgr()->mMaxRequestDelay,
                        mBackupTransport, mBackupStreamIn, mBackupStreamOut,
                        mBackupConnectedOK, callbacks,
                        PR_MillisecondsToInterval(
                          static_cast<uint32_t>(rtt.ToMilliseconds())));

        if (NS_SUCCEEDED(mBackupTransport->GetPeerAddr(&peeraddr)))
            mEnt->RecordIPFamilyPreference(peeraddr.raw.family);

        // The nsHttpConnection object now owns these streams and sockets
        mBackupStreamOut = nullptr;
        mBackupStreamIn = nullptr;
        mBackupTransport = nullptr;
    } else {
        MOZ_ASSERT(false, "unexpected stream");
        rv = NS_ERROR_UNEXPECTED;
    }

    if (NS_FAILED(rv)) {
        LOG(("nsHalfOpenSocket::OnOutputStreamReady "
             "conn->init (%p) failed %x\n", conn.get(), rv));
        return rv;
    }

    // This half-open socket has created a connection.  This flag excludes it
    // from counter of actual connections used for checking limits.
    mHasConnected = true;

    // if this is still in the pending list, remove it and dispatch it
    index = mEnt->mPendingQ.IndexOf(mTransaction);
    if (index != -1) {
        MOZ_ASSERT(!mSpeculative,
                   "Speculative Half Open found mTransaction");
        RefPtr<nsHttpTransaction> temp = mEnt->mPendingQ[index];
        mEnt->mPendingQ.RemoveElementAt(index);
        gHttpHandler->ConnMgr()->AddActiveConn(conn, mEnt);
        rv = gHttpHandler->ConnMgr()->DispatchTransaction(mEnt, temp, conn);
    } else {
        // this transaction was dispatched off the pending q before all the
        // sockets established themselves.

        // After about 1 second allow for the possibility of restarting a
        // transaction due to server close. Keep at sub 1 second as that is the
        // minimum granularity we can expect a server to be timing out with.
        conn->SetIsReusedAfter(950);

        // if we are using ssl and no other transactions are waiting right now,
        // then form a null transaction to drive the SSL handshake to
        // completion. Afterwards the connection will be 100% ready for the next
        // transaction to use it. Make an exception for SSL tunneled HTTP proxy as the
        // NullHttpTransaction does not know how to drive Connect
        if (mEnt->mConnInfo->FirstHopSSL() && !mEnt->mPendingQ.Length() &&
            !mEnt->mConnInfo->UsingConnect()) {
            LOG(("nsHalfOpenSocket::OnOutputStreamReady null transaction will "
                 "be used to finish SSL handshake on conn %p\n", conn.get()));
            RefPtr<nsAHttpTransaction> trans;
            if (mTransaction->IsNullTransaction() && !mDispatchedMTransaction) {
                // null transactions cannot be put in the entry queue, so that
                // explains why it is not present.
                mDispatchedMTransaction = true;
                trans = mTransaction;
            } else {
                trans = new NullHttpTransaction(mEnt->mConnInfo,
                                                callbacks,
                                                mCaps & ~NS_HTTP_ALLOW_PIPELINING);
            }

            gHttpHandler->ConnMgr()->AddActiveConn(conn, mEnt);
            conn->Classify(nsAHttpTransaction::CLASS_SOLO);
            rv = gHttpHandler->ConnMgr()->
                DispatchAbstractTransaction(mEnt, trans, mCaps, conn, 0);
        } else {
            // otherwise just put this in the persistent connection pool
            LOG(("nsHalfOpenSocket::OnOutputStreamReady no transaction match "
                 "returning conn %p to pool\n", conn.get()));
            gHttpHandler->ConnMgr()->OnMsgReclaimConnection(0, conn);
        }
    }

    return rv;
}

// method for nsITransportEventSink
NS_IMETHODIMP
nsHttpConnectionMgr::nsHalfOpenSocket::OnTransportStatus(nsITransport *trans,
                                                         nsresult status,
                                                         int64_t progress,
                                                         int64_t progressMax)
{
    MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);

    if (mTransaction)
        mTransaction->OnTransportStatus(trans, status, progress);

    MOZ_ASSERT(trans == mSocketTransport || trans == mBackupTransport);
    if (status == NS_NET_STATUS_CONNECTED_TO) {
        if (trans == mSocketTransport) {
            mPrimaryConnectedOK = true;
        } else {
            mBackupConnectedOK = true;
        }
    }

    // The rest of this method only applies to the primary transport
    if (trans != mSocketTransport) {
        return NS_OK;
    }

    // if we are doing spdy coalescing and haven't recorded the ip address
    // for this entry before then make the hash key if our dns lookup
    // just completed. We can't do coalescing if using a proxy because the
    // ip addresses are not available to the client.

    if (status == NS_NET_STATUS_CONNECTING_TO &&
        gHttpHandler->IsSpdyEnabled() &&
        gHttpHandler->CoalesceSpdy() &&
        mEnt && mEnt->mConnInfo && mEnt->mConnInfo->EndToEndSSL() &&
        !mEnt->mConnInfo->UsingProxy() &&
        mEnt->mCoalescingKeys.IsEmpty()) {

        nsCOMPtr<nsIDNSRecord> dnsRecord(do_GetInterface(mSocketTransport));
        nsTArray<NetAddr> addressSet;
        nsresult rv = NS_ERROR_NOT_AVAILABLE;
        if (dnsRecord) {
            rv = dnsRecord->GetAddresses(addressSet);
        }

        if (NS_SUCCEEDED(rv) && !addressSet.IsEmpty()) {
            for (uint32_t i = 0; i < addressSet.Length(); ++i) {
                nsCString *newKey = mEnt->mCoalescingKeys.AppendElement(nsCString());
                newKey->SetCapacity(kIPv6CStrBufSize + 26);
                NetAddrToString(&addressSet[i], newKey->BeginWriting(), kIPv6CStrBufSize);
                newKey->SetLength(strlen(newKey->BeginReading()));
                if (mEnt->mConnInfo->GetAnonymous()) {
                    newKey->AppendLiteral("~A:");
                } else {
                    newKey->AppendLiteral("~.:");
                }
                newKey->AppendInt(mEnt->mConnInfo->OriginPort());
                LOG(("nsHttpConnectionMgr::nsHalfOpenSocket::OnTransportStatus "
                     "STATUS_CONNECTING_TO Established New Coalescing Key # %d for host "
                     "%s [%s]", i, mEnt->mConnInfo->Origin(), newKey->get()));
            }
            gHttpHandler->ConnMgr()->ProcessSpdyPendingQ(mEnt);
        }
    }

    switch (status) {
    case NS_NET_STATUS_CONNECTING_TO:
        // Passed DNS resolution, now trying to connect, start the backup timer
        // only prevent creating another backup transport.
        // We also check for mEnt presence to not instantiate the timer after
        // this half open socket has already been abandoned.  It may happen
        // when we get this notification right between main-thread calls to
        // nsHttpConnectionMgr::Shutdown and nsSocketTransportService::Shutdown
        // where the first abandons all half open socket instances and only
        // after that the second stops the socket thread.
        if (mEnt && !mBackupTransport && !mSynTimer)
            SetupBackupTimer();
        break;

    case NS_NET_STATUS_CONNECTED_TO:
        // TCP connection's up, now transfer or SSL negotiantion starts,
        // no need for backup socket
        CancelBackupTimer();
        break;

    default:
        break;
    }

    return NS_OK;
}

// method for nsIInterfaceRequestor
NS_IMETHODIMP
nsHttpConnectionMgr::nsHalfOpenSocket::GetInterface(const nsIID &iid,
                                                    void **result)
{
    if (mTransaction) {
        nsCOMPtr<nsIInterfaceRequestor> callbacks;
        mTransaction->GetSecurityCallbacks(getter_AddRefs(callbacks));
        if (callbacks)
            return callbacks->GetInterface(iid, result);
    }
    return NS_ERROR_NO_INTERFACE;
}


already_AddRefed<nsHttpConnection>
ConnectionHandle::TakeHttpConnection()
{
    // return our connection object to the caller and clear it internally
    // do not drop our reference - the caller now owns it.
    MOZ_ASSERT(mConn);
    return mConn.forget();
}

uint32_t
ConnectionHandle::CancelPipeline(nsresult reason)
{
    // no pipeline to cancel
    return 0;
}

nsAHttpTransaction::Classifier
ConnectionHandle::Classification()
{
    if (mConn)
        return mConn->Classification();

    LOG(("ConnectionHandle::Classification this=%p "
         "has null mConn using CLASS_SOLO default", this));
    return nsAHttpTransaction::CLASS_SOLO;
}

// nsConnectionEntry

nsHttpConnectionMgr::
nsConnectionEntry::nsConnectionEntry(nsHttpConnectionInfo *ci)
    : mConnInfo(ci)
    , mPipelineState(PS_YELLOW)
    , mYellowGoodEvents(0)
    , mYellowBadEvents(0)
    , mYellowConnection(nullptr)
    , mGreenDepth(kPipelineOpen)
    , mPipeliningPenalty(0)
    , mUsingSpdy(false)
    , mInPreferredHash(false)
    , mPreferIPv4(false)
    , mPreferIPv6(false)
    , mUsedForConnection(false)
{
    MOZ_COUNT_CTOR(nsConnectionEntry);
    if (gHttpHandler->GetPipelineAggressive()) {
        mGreenDepth = kPipelineUnlimited;
        mPipelineState = PS_GREEN;
    }
    mInitialGreenDepth = mGreenDepth;
    memset(mPipeliningClassPenalty, 0, sizeof(int16_t) * nsAHttpTransaction::CLASS_MAX);
}

bool
nsHttpConnectionMgr::nsConnectionEntry::AvailableForDispatchNow()
{
    if (mIdleConns.Length() && mIdleConns[0]->CanReuse()) {
        return true;
    }

    return gHttpHandler->ConnMgr()->
        GetSpdyPreferredConn(this) ? true : false;
}

bool
nsHttpConnectionMgr::nsConnectionEntry::SupportsPipelining()
{
    return mPipelineState != nsHttpConnectionMgr::PS_RED;
}

nsHttpConnectionMgr::PipeliningState
nsHttpConnectionMgr::nsConnectionEntry::PipelineState()
{
    return mPipelineState;
}

void
nsHttpConnectionMgr::
nsConnectionEntry::OnPipelineFeedbackInfo(
    nsHttpConnectionMgr::PipelineFeedbackInfoType info,
    nsHttpConnection *conn,
    uint32_t data)
{
    MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);

    if (mPipelineState == PS_YELLOW) {
        if (info & kPipelineInfoTypeBad)
            mYellowBadEvents++;
        else if (info & (kPipelineInfoTypeNeutral | kPipelineInfoTypeGood))
            mYellowGoodEvents++;
    }

    if (mPipelineState == PS_GREEN && info == GoodCompletedOK) {
        int32_t depth = data;
        LOG(("Transaction completed at pipeline depth of %d. Host = %s\n",
             depth, mConnInfo->Origin()));

        if (depth >= 3)
            mGreenDepth = kPipelineUnlimited;
    }

    nsAHttpTransaction::Classifier classification;
    if (conn)
        classification = conn->Classification();
    else if (info == BadInsufficientFraming ||
             info == BadUnexpectedLarge)
        classification = (nsAHttpTransaction::Classifier) data;
    else
        classification = nsAHttpTransaction::CLASS_SOLO;

    if (gHttpHandler->GetPipelineAggressive() &&
        info & kPipelineInfoTypeBad &&
        info != BadExplicitClose &&
        info != RedVersionTooLow &&
        info != RedBannedServer &&
        info != RedCorruptedContent &&
        info != BadInsufficientFraming) {
        LOG(("minor negative feedback ignored "
             "because of pipeline aggressive mode"));
    }
    else if (info & kPipelineInfoTypeBad) {
        if ((info & kPipelineInfoTypeRed) && (mPipelineState != PS_RED)) {
            LOG(("transition to red from %d. Host = %s.\n",
                 mPipelineState, mConnInfo->Origin()));
            mPipelineState = PS_RED;
            mPipeliningPenalty = 0;
        }

        if (mLastCreditTime.IsNull())
            mLastCreditTime = TimeStamp::Now();

        // Red* events impact the host globally via mPipeliningPenalty, while
        // Bad* events impact the per class penalty.

        // The individual penalties should be < 16bit-signed-maxint - 25000
        // (approx 7500). Penalties are paid-off either when something promising
        // happens (a successful transaction, or promising headers) or when
        // time goes by at a rate of 1 penalty point every 16 seconds.

        switch (info) {
        case RedVersionTooLow:
            mPipeliningPenalty += 1000;
            break;
        case RedBannedServer:
            mPipeliningPenalty += 7000;
            break;
        case RedCorruptedContent:
            mPipeliningPenalty += 7000;
            break;
        case RedCanceledPipeline:
            mPipeliningPenalty += 60;
            break;
        case BadExplicitClose:
            mPipeliningClassPenalty[classification] += 250;
            break;
        case BadSlowReadMinor:
            mPipeliningClassPenalty[classification] += 5;
            break;
        case BadSlowReadMajor:
            mPipeliningClassPenalty[classification] += 25;
            break;
        case BadInsufficientFraming:
            mPipeliningClassPenalty[classification] += 7000;
            break;
        case BadUnexpectedLarge:
            mPipeliningClassPenalty[classification] += 120;
            break;

        default:
            MOZ_ASSERT(false, "Unknown Bad/Red Pipeline Feedback Event");
        }

        const int16_t kPenalty = 25000;
        mPipeliningPenalty = std::min(mPipeliningPenalty, kPenalty);
        mPipeliningClassPenalty[classification] =
          std::min(mPipeliningClassPenalty[classification], kPenalty);

        LOG(("Assessing red penalty to %s class %d for event %d. "
             "Penalty now %d, throttle[%d] = %d\n", mConnInfo->Origin(),
             classification, info, mPipeliningPenalty, classification,
             mPipeliningClassPenalty[classification]));
    }
    else {
        // hand out credits for neutral and good events such as
        // "headers look ok" events

        mPipeliningPenalty = std::max(mPipeliningPenalty - 1, 0);
        mPipeliningClassPenalty[classification] = std::max(mPipeliningClassPenalty[classification] - 1, 0);
    }

    if (mPipelineState == PS_RED && !mPipeliningPenalty)
    {
        LOG(("transition %s to yellow\n", mConnInfo->Origin()));
        mPipelineState = PS_YELLOW;
        mYellowConnection = nullptr;
    }
}

void
nsHttpConnectionMgr::
nsConnectionEntry::SetYellowConnection(nsHttpConnection *conn)
{
    MOZ_ASSERT(!mYellowConnection && mPipelineState == PS_YELLOW,
               "yellow connection already set or state is not yellow");
    mYellowConnection = conn;
    mYellowGoodEvents = mYellowBadEvents = 0;
}

void
nsHttpConnectionMgr::
nsConnectionEntry::OnYellowComplete()
{
    if (mPipelineState == PS_YELLOW) {
        if (mYellowGoodEvents && !mYellowBadEvents) {
            LOG(("transition %s to green\n", mConnInfo->Origin()));
            mPipelineState = PS_GREEN;
            mGreenDepth = mInitialGreenDepth;
        }
        else {
            // The purpose of the yellow state is to witness at least
            // one successful pipelined transaction without seeing any
            // kind of negative feedback before opening the flood gates.
            // If we haven't confirmed that, then transfer back to red.
            LOG(("transition %s to red from yellow return\n",
                 mConnInfo->Origin()));
            mPipelineState = PS_RED;
        }
    }

    mYellowConnection = nullptr;
}

void
nsHttpConnectionMgr::
nsConnectionEntry::CreditPenalty()
{
    if (mLastCreditTime.IsNull())
        return;

    // Decrease penalty values by 1 for every 16 seconds
    // (i.e 3.7 per minute, or 1000 every 4h20m)

    TimeStamp now = TimeStamp::Now();
    TimeDuration elapsedTime = now - mLastCreditTime;
    uint32_t creditsEarned =
        static_cast<uint32_t>(elapsedTime.ToSeconds()) >> 4;

    bool failed = false;
    if (creditsEarned > 0) {
        mPipeliningPenalty =
            std::max(int32_t(mPipeliningPenalty - creditsEarned), 0);
        if (mPipeliningPenalty > 0)
            failed = true;

        for (int32_t i = 0; i < nsAHttpTransaction::CLASS_MAX; ++i) {
            mPipeliningClassPenalty[i]  =
                std::max(int32_t(mPipeliningClassPenalty[i] - creditsEarned), 0);
            failed = failed || (mPipeliningClassPenalty[i] > 0);
        }

        // update last credit mark to reflect elapsed time
        mLastCreditTime += TimeDuration::FromSeconds(creditsEarned << 4);
    }
    else {
        failed = true;                         /* just assume this */
    }

    // If we are no longer red then clear the credit counter - you only
    // get credits for time spent in the red state
    if (!failed)
        mLastCreditTime = TimeStamp();    /* reset to null timestamp */

    if (mPipelineState == PS_RED && !mPipeliningPenalty)
    {
        LOG(("transition %s to yellow based on time credit\n",
             mConnInfo->Origin()));
        mPipelineState = PS_YELLOW;
        mYellowConnection = nullptr;
    }
}

uint32_t
nsHttpConnectionMgr::
nsConnectionEntry::MaxPipelineDepth(nsAHttpTransaction::Classifier aClass)
{
    // Still subject to configuration limit no matter return value

    if ((mPipelineState == PS_RED) || (mPipeliningClassPenalty[aClass] > 0))
        return 0;

    if (mPipelineState == PS_YELLOW)
        return kPipelineRestricted;

    return mGreenDepth;
}

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;

    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();
    RefPtr<nsHttpConnection> deleteProtector(proxyConn);
    for (int32_t i = 0; i < count; ++i) {
        if (ent->mActiveConns[i] == proxyConn) {
            ent->mActiveConns.RemoveElementAt(i);
            wcEnt->mActiveConns.InsertElementAt(0, proxyConn);
            return;
        }
    }

    count = ent->mIdleConns.Length();
    for (int32_t i = 0; i < count; ++i) {
        if (ent->mIdleConns[i] == proxyConn) {
            ent->mIdleConns.RemoveElementAt(i);
            wcEnt->mIdleConns.InsertElementAt(0, proxyConn);
            return;
        }
    }
}

} // namespace net
} // namespace mozilla
