--- a/modules/libpref/src/init/all.js
+++ b/modules/libpref/src/init/all.js
@@ -905,16 +905,20 @@ pref("network.http.qos", 0);
// to wait before trying a different connection. 0 means do not use a second
// connection.
pref("network.http.connection-retry-timeout", 250);
// The number of seconds after sending initial SYN for an HTTP connection
// to give up if the OS does not give up first
pref("network.http.connection-timeout", 90);
+// The maximum number of current global half open sockets allowable
+// when starting a new speculative connection.
+pref("network.http.speculative-parallel-limit", 6);
+
// Disable IPv6 for backup connections to workaround problems about broken
// IPv6 connectivity.
pref("network.http.fast-fallback-to-IPv4", true);
// Try and use SPDY when using SSL
pref("network.http.spdy.enabled", true);
pref("network.http.spdy.enabled.v2", true);
pref("network.http.spdy.enabled.v3", true);
--- a/netwerk/base/public/nsILoadGroup.idl
+++ b/netwerk/base/public/nsILoadGroup.idl
@@ -3,21 +3,22 @@
* 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/. */
#include "nsIRequest.idl"
interface nsISimpleEnumerator;
interface nsIRequestObserver;
interface nsIInterfaceRequestor;
+interface nsILoadGroupConnectionInfo;
/**
* A load group maintains a collection of nsIRequest objects.
*/
-[scriptable, uuid(3de0a31c-feaf-400f-9f1e-4ef71f8b20cc)]
+[scriptable, uuid(19501006-46e3-4634-b97d-26eff894b4d3)]
interface nsILoadGroup : nsIRequest
{
/**
* The group observer is notified when requests are added to and removed
* from this load group. The groupObserver is weak referenced.
*/
attribute nsIRequestObserver groupObserver;
@@ -66,9 +67,41 @@ interface nsILoadGroup : nsIRequest
* LOAD_BACKGROUND bit set).
*/
readonly attribute unsigned long activeCount;
/**
* Notification callbacks for the load group.
*/
attribute nsIInterfaceRequestor notificationCallbacks;
+
+ /**
+ * Connection information for managing things like js/css
+ * connection blocking, and per-tab connection grouping
+ */
+ readonly attribute nsILoadGroupConnectionInfo connectionInfo;
};
+
+/**
+ * Used to maintain state about the connections of a load group and
+ * how they interact with blocking items like HEAD css/js loads.
+ */
+[uuid(d1f9f18e-3d85-473a-ad58-a2367d7cdb2a)]
+interface nsILoadGroupConnectionInfo : nsISupports
+{
+ /**
+ * Number of active blocking transactions associated with this load group
+ */
+ readonly attribute unsigned long blockingTransactionCount;
+
+ /**
+ * Increase the number of active blocking transactions associated
+ * with this load group by one.
+ */
+ void addBlockingTransaction();
+
+ /**
+ * Decrease the number of active blocking transactions associated
+ * with this load group by one. The return value is the number of remaining
+ * blockers.
+ */
+ unsigned long removeBlockingTransaction();
+};
--- a/netwerk/base/src/nsLoadGroup.cpp
+++ b/netwerk/base/src/nsLoadGroup.cpp
@@ -141,40 +141,16 @@ nsLoadGroup::~nsLoadGroup()
}
mDefaultLoadRequest = 0;
LOG(("LOADGROUP [%x]: Destroyed.\n", this));
}
-nsresult nsLoadGroup::Init()
-{
- static PLDHashTableOps hash_table_ops =
- {
- PL_DHashAllocTable,
- PL_DHashFreeTable,
- PL_DHashVoidPtrKeyStub,
- RequestHashMatchEntry,
- PL_DHashMoveEntryStub,
- RequestHashClearEntry,
- PL_DHashFinalizeStub,
- RequestHashInitEntry
- };
-
- if (!PL_DHashTableInit(&mRequests, &hash_table_ops, nullptr,
- sizeof(RequestMapEntry), 16)) {
- mRequests.ops = nullptr;
-
- return NS_ERROR_OUT_OF_MEMORY;
- }
-
- return NS_OK;
-}
-
////////////////////////////////////////////////////////////////////////////////
// nsISupports methods:
NS_IMPL_AGGREGATED(nsLoadGroup)
NS_INTERFACE_MAP_BEGIN_AGGREGATED(nsLoadGroup)
NS_INTERFACE_MAP_ENTRY(nsILoadGroup)
NS_INTERFACE_MAP_ENTRY(nsIRequest)
NS_INTERFACE_MAP_ENTRY(nsISupportsPriority)
@@ -786,16 +762,25 @@ nsLoadGroup::GetNotificationCallbacks(ns
NS_IMETHODIMP
nsLoadGroup::SetNotificationCallbacks(nsIInterfaceRequestor *aCallbacks)
{
mCallbacks = aCallbacks;
return NS_OK;
}
+NS_IMETHODIMP
+nsLoadGroup::GetConnectionInfo(nsILoadGroupConnectionInfo **aCI)
+{
+ NS_ENSURE_ARG_POINTER(aCI);
+ *aCI = mConnectionInfo;
+ NS_IF_ADDREF(*aCI);
+ return NS_OK;
+}
+
////////////////////////////////////////////////////////////////////////////////
// nsISupportsPriority methods:
NS_IMETHODIMP
nsLoadGroup::GetPriority(int32_t *aValue)
{
*aValue = mPriority;
return NS_OK;
@@ -999,8 +984,78 @@ nsresult nsLoadGroup::MergeLoadFlags(nsI
VALIDATE_NEVER));
if (flags != oldFlags)
rv = aRequest->SetLoadFlags(flags);
outFlags = flags;
return rv;
}
+
+// nsLoadGroupConnectionInfo
+
+class nsLoadGroupConnectionInfo MOZ_FINAL : public nsILoadGroupConnectionInfo
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSILOADGROUPCONNECTIONINFO
+
+ nsLoadGroupConnectionInfo();
+private:
+ int32_t mBlockingTransactionCount; // signed for PR_ATOMIC_*
+};
+
+NS_IMPL_THREADSAFE_ISUPPORTS1(nsLoadGroupConnectionInfo, nsILoadGroupConnectionInfo)
+
+nsLoadGroupConnectionInfo::nsLoadGroupConnectionInfo()
+ : mBlockingTransactionCount(0)
+{
+}
+
+NS_IMETHODIMP
+nsLoadGroupConnectionInfo::GetBlockingTransactionCount(uint32_t *aBlockingTransactionCount)
+{
+ NS_ENSURE_ARG_POINTER(aBlockingTransactionCount);
+ *aBlockingTransactionCount = static_cast<uint32_t>(mBlockingTransactionCount);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLoadGroupConnectionInfo::AddBlockingTransaction()
+{
+ PR_ATOMIC_INCREMENT(&mBlockingTransactionCount);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLoadGroupConnectionInfo::RemoveBlockingTransaction(uint32_t *_retval)
+{
+ NS_ENSURE_ARG_POINTER(_retval);
+ *_retval =
+ static_cast<uint32_t>(PR_ATOMIC_DECREMENT(&mBlockingTransactionCount));
+ return NS_OK;
+}
+
+nsresult nsLoadGroup::Init()
+{
+ static PLDHashTableOps hash_table_ops =
+ {
+ PL_DHashAllocTable,
+ PL_DHashFreeTable,
+ PL_DHashVoidPtrKeyStub,
+ RequestHashMatchEntry,
+ PL_DHashMoveEntryStub,
+ RequestHashClearEntry,
+ PL_DHashFinalizeStub,
+ RequestHashInitEntry
+ };
+
+ if (!PL_DHashTableInit(&mRequests, &hash_table_ops, nullptr,
+ sizeof(RequestMapEntry), 16)) {
+ mRequests.ops = nullptr;
+
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ mConnectionInfo = new nsLoadGroupConnectionInfo();
+
+ return NS_OK;
+}
--- a/netwerk/base/src/nsLoadGroup.h
+++ b/netwerk/base/src/nsLoadGroup.h
@@ -16,16 +16,17 @@
#include "nsIInterfaceRequestor.h"
#include "nsIInterfaceRequestorUtils.h"
#include "nsISupportsPriority.h"
#include "nsITimedChannel.h"
#include "pldhash.h"
#include "mozilla/TimeStamp.h"
class nsISupportsArray;
+class nsILoadGroupConnectionInfo;
class nsLoadGroup : public nsILoadGroup,
public nsISupportsPriority,
public nsSupportsWeakReference
{
public:
NS_DECL_AGGREGATED
@@ -59,16 +60,17 @@ private:
bool defaultRequest);
protected:
uint32_t mForegroundCount;
uint32_t mLoadFlags;
nsCOMPtr<nsILoadGroup> mLoadGroup; // load groups can contain load groups
nsCOMPtr<nsIInterfaceRequestor> mCallbacks;
+ nsCOMPtr<nsILoadGroupConnectionInfo> mConnectionInfo;
nsCOMPtr<nsIRequest> mDefaultLoadRequest;
PLDHashTable mRequests;
nsWeakPtr mObserver;
nsresult mStatus;
int32_t mPriority;
--- a/netwerk/protocol/http/nsHttp.h
+++ b/netwerk/protocol/http/nsHttp.h
@@ -88,23 +88,29 @@ typedef uint8_t nsHttpVersion;
// a transaction with this caps flag will not pass SSL client-certificates
// to the server (see bug #466080), but is may also be used for other things
#define NS_HTTP_LOAD_ANONYMOUS (1<<4)
// a transaction with this caps flag keeps timing information
#define NS_HTTP_TIMING_ENABLED (1<<5)
-// (1<<6) is unused
+// a transaction with this flag blocks the initiation of other transactons
+// in the same load group until it is complete
+#define NS_HTTP_LOAD_AS_BLOCKING (1<<6)
// Disallow the use of the SPDY protocol. This is meant for the contexts
// such as HTTP upgrade which are nonsensical for SPDY, it is not the
// SPDY configuration variable.
#define NS_HTTP_DISALLOW_SPDY (1<<7)
+// a transaction with this flag loads without respect to whether the load
+// group is currently blocking on some resources
+#define NS_HTTP_LOAD_UNBLOCKED (1<<8)
+
//-----------------------------------------------------------------------------
// some default values
//-----------------------------------------------------------------------------
#define NS_HTTP_DEFAULT_PORT 80
#define NS_HTTPS_DEFAULT_PORT 443
#define NS_HTTP_HEADER_SEPS ", \t"
--- a/netwerk/protocol/http/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -676,16 +676,41 @@ nsHttpChannel::ContinueHandleAsyncFallba
mIsPending = false;
if (mLoadGroup)
mLoadGroup->RemoveRequest(this, nullptr, mStatus);
return rv;
}
+void
+nsHttpChannel::SetupTransactionLoadGroupInfo()
+{
+ // Find the loadgroup at the end of the chain in order
+ // to make sure all channels derived from the load group
+ // use the same connection scope.
+ nsCOMPtr<nsILoadGroup> rootLoadGroup = mLoadGroup;
+ while (rootLoadGroup) {
+ nsCOMPtr<nsILoadGroup> tmp;
+ rootLoadGroup->GetLoadGroup(getter_AddRefs(tmp));
+ if (tmp)
+ rootLoadGroup.swap(tmp);
+ else
+ break;
+ }
+
+ // Set the load group connection scope on the transaction
+ if (rootLoadGroup) {
+ nsCOMPtr<nsILoadGroupConnectionInfo> ci;
+ rootLoadGroup->GetConnectionInfo(getter_AddRefs(ci));
+ if (ci)
+ mTransaction->SetLoadGroupConnectionInfo(ci);
+ }
+}
+
nsresult
nsHttpChannel::SetupTransaction()
{
LOG(("nsHttpChannel::SetupTransaction [this=%p]\n", this));
NS_ENSURE_TRUE(!mTransaction, NS_ERROR_ALREADY_INITIALIZED);
nsresult rv;
@@ -849,16 +874,18 @@ nsHttpChannel::SetupTransaction()
mUploadStream, mUploadStreamHasHeaders,
NS_GetCurrentThread(), callbacks, this,
getter_AddRefs(responseStream));
if (NS_FAILED(rv)) {
mTransaction = nullptr;
return rv;
}
+ SetupTransactionLoadGroupInfo();
+
rv = nsInputStreamPump::Create(getter_AddRefs(mTransactionPump),
responseStream);
return rv;
}
// NOTE: This function duplicates code from nsBaseChannel. This will go away
// once HTTP uses nsBaseChannel (part of bug 312760)
static void
@@ -4413,16 +4440,21 @@ nsHttpChannel::BeginConnect()
// trying to establish a keep-alive connection.
if (mRequestHead.HasHeaderValue(nsHttp::Connection, "close"))
mCaps &= ~(NS_HTTP_ALLOW_KEEPALIVE | NS_HTTP_ALLOW_PIPELINING);
if ((mLoadFlags & VALIDATE_ALWAYS) ||
(BYPASS_LOCAL_CACHE(mLoadFlags)))
mCaps |= NS_HTTP_REFRESH_DNS;
+ if (mLoadAsBlocking)
+ mCaps |= NS_HTTP_LOAD_AS_BLOCKING;
+ if (mLoadUnblocked)
+ mCaps |= NS_HTTP_LOAD_UNBLOCKED;
+
// Force-Reload should reset the persistent connection pool for this host
if (mLoadFlags & LOAD_FRESH_CONNECTION) {
// just the initial document resets the whole pool
if (mLoadFlags & LOAD_INITIAL_DOCUMENT_URI)
gHttpHandler->ConnMgr()->ClosePersistentConnections();
// each sub resource gets a fresh connection
mCaps &= ~(NS_HTTP_ALLOW_KEEPALIVE | NS_HTTP_ALLOW_PIPELINING);
}
--- a/netwerk/protocol/http/nsHttpChannel.h
+++ b/netwerk/protocol/http/nsHttpChannel.h
@@ -155,16 +155,17 @@ private:
typedef nsresult (nsHttpChannel::*nsContinueRedirectionFunc)(nsresult result);
bool RequestIsConditional();
nsresult BeginConnect();
nsresult Connect();
nsresult ContinueConnect();
void SpeculativeConnect();
nsresult SetupTransaction();
+ void SetupTransactionLoadGroupInfo();
nsresult CallOnStartRequest();
nsresult ProcessResponse();
nsresult ContinueProcessResponse(nsresult);
nsresult ProcessNormal();
nsresult ContinueProcessNormal(nsresult);
nsresult ProcessNotModified();
nsresult AsyncProcessRedirection(uint32_t httpStatus);
nsresult ContinueProcessRedirection(nsresult);
--- a/netwerk/protocol/http/nsHttpConnectionMgr.cpp
+++ b/netwerk/protocol/http/nsHttpConnectionMgr.cpp
@@ -55,16 +55,17 @@ InsertTransactionSorted(nsTArray<nsHttpT
nsHttpConnectionMgr::nsHttpConnectionMgr()
: mReentrantMonitor("nsHttpConnectionMgr.mReentrantMonitor")
, mMaxConns(0)
, mMaxPersistConnsPerHost(0)
, mMaxPersistConnsPerProxy(0)
, mIsShuttingDown(false)
, mNumActiveConns(0)
, mNumIdleConns(0)
+ , mNumHalfOpenConns(0)
, mTimeOfNextWakeUp(UINT64_MAX)
, mTimeoutTickArmed(false)
{
LOG(("Creating nsHttpConnectionMgr @%x\n", this));
mCT.Init();
mAlternateProtocolHash.Init(16);
mSpdyPreferredHash.Init();
}
@@ -397,16 +398,23 @@ nsHttpConnectionMgr::ProcessPendingQ(nsH
NS_ADDREF(ci);
nsresult rv = PostEvent(&nsHttpConnectionMgr::OnMsgProcessPendingQ, 0, ci);
if (NS_FAILED(rv))
NS_RELEASE(ci);
return rv;
}
+nsresult
+nsHttpConnectionMgr::ProcessPendingQ()
+{
+ LOG(("nsHttpConnectionMgr::ProcessPendingQ [All CI]\n"));
+ return PostEvent(&nsHttpConnectionMgr::OnMsgProcessPendingQ, 0, nullptr);
+}
+
// 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,
@@ -719,22 +727,32 @@ nsHttpConnectionMgr::RemoveSpdyPreferred
PLDHashOperator
nsHttpConnectionMgr::ProcessOneTransactionCB(const nsACString &key,
nsAutoPtr<nsConnectionEntry> &ent,
void *closure)
{
nsHttpConnectionMgr *self = (nsHttpConnectionMgr *) closure;
- if (self->ProcessPendingQForEntry(ent))
+ if (self->ProcessPendingQForEntry(ent, false))
return PL_DHASH_STOP;
return PL_DHASH_NEXT;
}
+PLDHashOperator
+nsHttpConnectionMgr::ProcessAllTransactionsCB(const nsACString &key,
+ nsAutoPtr<nsConnectionEntry> &ent,
+ void *closure)
+{
+ nsHttpConnectionMgr *self = (nsHttpConnectionMgr *) closure;
+ self->ProcessPendingQForEntry(ent, true);
+ return PL_DHASH_NEXT;
+}
+
// If the global number of idle connections is preventing the opening of
// new connections to a host without idle connections, then
// close them regardless of their TTL
PLDHashOperator
nsHttpConnectionMgr::PurgeExcessIdleConnectionsCB(const nsACString &key,
nsAutoPtr<nsConnectionEntry> &ent,
void *closure)
{
@@ -890,32 +908,33 @@ nsHttpConnectionMgr::ShutdownPassCB(cons
ent->mHalfOpens[i]->Abandon();
return PL_DHASH_REMOVE;
}
//-----------------------------------------------------------------------------
bool
-nsHttpConnectionMgr::ProcessPendingQForEntry(nsConnectionEntry *ent)
+nsHttpConnectionMgr::ProcessPendingQForEntry(nsConnectionEntry *ent, bool considerAll)
{
NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
LOG(("nsHttpConnectionMgr::ProcessPendingQForEntry [ci=%s]\n",
ent->mConnInfo->HashKey().get()));
ProcessSpdyPendingQ(ent);
uint32_t count = ent->mPendingQ.Length();
nsHttpTransaction *trans;
nsresult rv;
bool dispatchedSuccessfully = false;
- // iterate the pending list until one is dispatched successfully. Keep
- // iterating afterwards only until a transaction fails to dispatch.
+ // 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 < count; ++i) {
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.
@@ -940,34 +959,34 @@ nsHttpConnectionMgr::ProcessPendingQForE
// reset index and array length after RemoveElementAt()
dispatchedSuccessfully = true;
count = ent->mPendingQ.Length();
--i;
continue;
}
- if (dispatchedSuccessfully)
- return true;
-
+ if (dispatchedSuccessfully && !considerAll)
+ break;
+
NS_ABORT_IF_FALSE(count == ent->mPendingQ.Length(),
"something mutated pending queue from "
"GetConnection()");
}
- return false;
+ return dispatchedSuccessfully;
}
bool
nsHttpConnectionMgr::ProcessPendingQForEntry(nsHttpConnectionInfo *ci)
{
NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
nsConnectionEntry *ent = mCT.Get(ci->HashKey());
if (ent)
- return ProcessPendingQForEntry(ent);
+ return ProcessPendingQForEntry(ent, false);
return false;
}
bool
nsHttpConnectionMgr::SupportsPipelining(nsHttpConnectionInfo *ci)
{
NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
@@ -1439,21 +1458,46 @@ nsHttpConnectionMgr::TryDispatchTransact
// 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()) {
nsRefPtr<nsHttpConnection> conn = GetSpdyPreferredConn(ent);
if (conn) {
LOG((" dispatch to spdy: [conn=%x]\n", conn.get()));
+ trans->RemoveDispatchedAsBlocking(); /* just in case */
DispatchTransaction(ent, trans, conn);
return NS_OK;
}
}
+ // If this is not a blocking transaction and the loadgroup 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)) {
+ nsILoadGroupConnectionInfo *loadGroupCI = trans->LoadGroupConnectionInfo();
+ if (loadGroupCI) {
+ uint32_t blockers = 0;
+ if (NS_SUCCEEDED(loadGroupCI->GetBlockingTransactionCount(&blockers)) &&
+ blockers) {
+ // need to wait for blockers to clear
+ LOG((" blocked by load group: [blockers=%d]\n", 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)) {
return NS_OK;
@@ -1569,16 +1613,17 @@ nsHttpConnectionMgr::DispatchTransaction
"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);
@@ -1788,22 +1833,23 @@ nsHttpConnectionMgr::RecvdConnect()
nsresult
nsHttpConnectionMgr::CreateTransport(nsConnectionEntry *ent,
nsAHttpTransaction *trans,
uint32_t caps,
bool speculative)
{
NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
-
+
nsRefPtr<nsHalfOpenSocket> sock = new nsHalfOpenSocket(ent, trans, caps);
nsresult rv = sock->SetupPrimaryStreams();
NS_ENSURE_SUCCESS(rv, rv);
ent->mHalfOpens.AppendElement(sock);
+ mNumHalfOpenConns++;
if (speculative)
sock->SetSpeculative(true);
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
@@ -1991,21 +2037,29 @@ nsHttpConnectionMgr::OnMsgCancelTransact
}
void
nsHttpConnectionMgr::OnMsgProcessPendingQ(int32_t, void *param)
{
NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
nsHttpConnectionInfo *ci = (nsHttpConnectionInfo *) param;
- LOG(("nsHttpConnectionMgr::OnMsgProcessPendingQ [ci=%s]\n", ci->HashKey().get()));
+ if (!ci) {
+ LOG(("nsHttpConnectionMgr::OnMsgProcessPendingQ [ci=nullptr]\n"));
+ // Try and dispatch everything
+ mCT.Enumerate(ProcessAllTransactionsCB, this);
+ return;
+ }
+
+ LOG(("nsHttpConnectionMgr::OnMsgProcessPendingQ [ci=%s]\n",
+ ci->HashKey().get()));
// start by processing the queue identified by the given connection info.
nsConnectionEntry *ent = mCT.Get(ci->HashKey());
- if (!(ent && ProcessPendingQForEntry(ent))) {
+ 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...
mCT.Enumerate(ProcessOneTransactionCB, this);
}
NS_RELEASE(ci);
}
@@ -2352,20 +2406,24 @@ nsHttpConnectionMgr::OnMsgSpeculativeCon
// 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;
- if (!ent->mIdleConns.Length() && !RestrictConnections(ent) &&
+ if (mNumHalfOpenConns <= gHttpHandler->ParallelSpeculativeConnectLimit() &&
+ !ent->mIdleConns.Length() && !RestrictConnections(ent) &&
!AtActiveConnectionLimit(ent, trans->Caps())) {
CreateTransport(ent, trans, trans->Caps(), true);
}
+ else {
+ LOG((" Transport not created due to existing connection count\n"));
+ }
}
bool
nsHttpConnectionMgr::nsConnectionHandle::IsPersistent()
{
return mConn->IsPersistent();
}
@@ -3147,15 +3205,16 @@ nsHttpConnectionMgr::nsConnectionEntry::
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
// so ignore failures of RemoveElement()
mHalfOpens.RemoveElement(halfOpen);
+ 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);
}
--- a/netwerk/protocol/http/nsHttpConnectionMgr.h
+++ b/netwerk/protocol/http/nsHttpConnectionMgr.h
@@ -202,16 +202,19 @@ public:
// NOTE: functions below may be called only on the socket thread.
//-------------------------------------------------------------------------
// called to force the transaction queue to be processed once more, giving
// preference to the specified connection.
nsresult ProcessPendingQ(nsHttpConnectionInfo *);
bool ProcessPendingQForEntry(nsHttpConnectionInfo *);
+ // Try and process all pending transactions
+ nsresult ProcessPendingQ();
+
// This is used to force an idle connection to be closed and removed from
// the idle connection list. It is called when the idle connection detects
// that the network peer has closed the transport.
nsresult CloseIdleConnection(nsHttpConnection *);
// The connection manager needs to know when a normal HTTP connection has been
// upgraded to SPDY because the dispatch and idle semantics are a little
// bit different.
@@ -448,22 +451,23 @@ private:
uint16_t mMaxOptimisticPipelinedRequests;
bool mIsShuttingDown;
//-------------------------------------------------------------------------
// NOTE: these members are only accessed on the socket transport thread
//-------------------------------------------------------------------------
static PLDHashOperator ProcessOneTransactionCB(const nsACString &, nsAutoPtr<nsConnectionEntry> &, void *);
+ static PLDHashOperator ProcessAllTransactionsCB(const nsACString &, nsAutoPtr<nsConnectionEntry> &, void *);
static PLDHashOperator PruneDeadConnectionsCB(const nsACString &, nsAutoPtr<nsConnectionEntry> &, void *);
static PLDHashOperator ShutdownPassCB(const nsACString &, nsAutoPtr<nsConnectionEntry> &, void *);
static PLDHashOperator PurgeExcessIdleConnectionsCB(const nsACString &, nsAutoPtr<nsConnectionEntry> &, void *);
static PLDHashOperator ClosePersistentConnectionsCB(const nsACString &, nsAutoPtr<nsConnectionEntry> &, void *);
- bool ProcessPendingQForEntry(nsConnectionEntry *);
+ bool ProcessPendingQForEntry(nsConnectionEntry *, bool considerAll);
bool IsUnderPressure(nsConnectionEntry *ent,
nsHttpTransaction::Classifier classification);
bool AtActiveConnectionLimit(nsConnectionEntry *, uint32_t caps);
nsresult TryDispatchTransaction(nsConnectionEntry *ent,
bool onlyReusedConnection,
nsHttpTransaction *trans);
nsresult DispatchTransaction(nsConnectionEntry *,
nsHttpTransaction *,
@@ -574,16 +578,19 @@ private:
void OnMsgProcessFeedback (int32_t, void *);
// Total number of active connections in all of the ConnectionEntry objects
// that are accessed from mCT connection table.
uint16_t mNumActiveConns;
// Total number of idle connections in all of the ConnectionEntry objects
// that are accessed from mCT connection table.
uint16_t mNumIdleConns;
+ // Total number of connections in mHalfOpens ConnectionEntry objects
+ // that are accessed from mCT connection table
+ uint32_t mNumHalfOpenConns;
// Holds time in seconds for next wake-up to prune dead connections.
uint64_t mTimeOfNextWakeUp;
// Timer for next pruning of dead connections.
nsCOMPtr<nsITimer> mTimer;
// A 1s tick to call nsHttpConnection::ReadTimeoutTick on
// active http/1 connections and check for orphaned half opens.
--- a/netwerk/protocol/http/nsHttpHandler.cpp
+++ b/netwerk/protocol/http/nsHttpHandler.cpp
@@ -173,16 +173,17 @@ nsHttpHandler::nsHttpHandler()
, mSpdyV3(true)
, mCoalesceSpdy(true)
, mUseAlternateProtocol(false)
, mSpdySendingChunkSize(ASpdySession::kSendingChunkSize)
, mSpdySendBufferSize(ASpdySession::kTCPSendBufferSize)
, mSpdyPingThreshold(PR_SecondsToInterval(58))
, mSpdyPingTimeout(PR_SecondsToInterval(8))
, mConnectTimeout(90000)
+ , mParallelSpeculativeConnectLimit(6)
{
#if defined(PR_LOGGING)
gHttpLog = PR_NewLogModule("nsHttp");
#endif
LOG(("Creating nsHttpHandler [this=%x].\n", this));
NS_ASSERTION(!gHttpHandler, "HTTP handler already created!");
@@ -1133,16 +1134,24 @@ nsHttpHandler::PrefsChanged(nsIPrefBranc
// established
if (PREF_CHANGED(HTTP_PREF("connection-timeout"))) {
rv = prefs->GetIntPref(HTTP_PREF("connection-timeout"), &val);
if (NS_SUCCEEDED(rv))
// the pref is in seconds, but the variable is in milliseconds
mConnectTimeout = clamped(val, 1, 0xffff) * PR_MSEC_PER_SEC;
}
+ // The maximum number of current global half open sockets allowable
+ // for starting a new speculative connection.
+ if (PREF_CHANGED(HTTP_PREF("speculative-parallel-limit"))) {
+ rv = prefs->GetIntPref(HTTP_PREF("speculative-parallel-limit"), &val);
+ if (NS_SUCCEEDED(rv))
+ mParallelSpeculativeConnectLimit = (uint32_t) clamped(val, 0, 1024);
+ }
+
// on transition of network.http.diagnostics to true print
// a bunch of information to the console
if (pref && PREF_CHANGED(HTTP_PREF("diagnostics"))) {
rv = prefs->GetBoolPref(HTTP_PREF("diagnostics"), &cVar);
if (NS_SUCCEEDED(rv) && cVar) {
if (mConnMgr)
mConnMgr->PrintDiagnostics();
}
--- a/netwerk/protocol/http/nsHttpHandler.h
+++ b/netwerk/protocol/http/nsHttpHandler.h
@@ -92,16 +92,17 @@ public:
bool IsSpdyV3Enabled() { return mSpdyV3; }
bool CoalesceSpdy() { return mCoalesceSpdy; }
bool UseAlternateProtocol() { return mUseAlternateProtocol; }
uint32_t SpdySendingChunkSize() { return mSpdySendingChunkSize; }
uint32_t SpdySendBufferSize() { return mSpdySendBufferSize; }
PRIntervalTime SpdyPingThreshold() { return mSpdyPingThreshold; }
PRIntervalTime SpdyPingTimeout() { return mSpdyPingTimeout; }
uint32_t ConnectTimeout() { return mConnectTimeout; }
+ uint32_t ParallelSpeculativeConnectLimit() { return mParallelSpeculativeConnectLimit; }
bool PromptTempRedirect() { return mPromptTempRedirect; }
nsHttpAuthCache *AuthCache(bool aPrivate) {
return aPrivate ? &mPrivateAuthCache : &mAuthCache;
}
nsHttpConnectionMgr *ConnMgr() { return mConnMgr; }
@@ -149,16 +150,21 @@ public:
return mConnMgr->ReclaimConnection(conn);
}
nsresult ProcessPendingQ(nsHttpConnectionInfo *cinfo)
{
return mConnMgr->ProcessPendingQ(cinfo);
}
+ nsresult ProcessPendingQ()
+ {
+ return mConnMgr->ProcessPendingQ();
+ }
+
nsresult GetSocketThreadTarget(nsIEventTarget **target)
{
return mConnMgr->GetSocketThreadTarget(target);
}
nsresult SpeculativeConnect(nsHttpConnectionInfo *ci,
nsIInterfaceRequestor *callbacks)
{
@@ -385,16 +391,20 @@ private:
uint32_t mSpdySendingChunkSize;
uint32_t mSpdySendBufferSize;
PRIntervalTime mSpdyPingThreshold;
PRIntervalTime mSpdyPingTimeout;
// The maximum amount of time to wait for socket transport to be
// established. In milliseconds.
uint32_t mConnectTimeout;
+
+ // The maximum number of current global half open sockets allowable
+ // when starting a new speculative connection.
+ uint32_t mParallelSpeculativeConnectLimit;
};
//-----------------------------------------------------------------------------
extern nsHttpHandler *gHttpHandler;
//-----------------------------------------------------------------------------
// nsHttpsHandler - thin wrapper to distinguish the HTTP handler from the
--- a/netwerk/protocol/http/nsHttpTransaction.cpp
+++ b/netwerk/protocol/http/nsHttpTransaction.cpp
@@ -102,16 +102,17 @@ nsHttpTransaction::nsHttpTransaction()
, mNoContent(false)
, mSentData(false)
, mReceivedData(false)
, mStatusEventPending(false)
, mHasRequestBody(false)
, mProxyConnectFailed(false)
, mHttpResponseMatched(false)
, mPreserveStream(false)
+ , mDispatchedAsBlocking(false)
, mReportedStart(false)
, mReportedResponseHeader(false)
, mForTakeResponseHead(nullptr)
, mResponseHeadTaken(false)
{
LOG(("Creating nsHttpTransaction @%x\n", this));
gHttpHandler->GetMaxPipelineObjectSize(&mMaxPipelineObjectSize);
}
@@ -124,16 +125,17 @@ nsHttpTransaction::~nsHttpTransaction()
mCallbacks = nullptr;
NS_IF_RELEASE(mConnection);
NS_IF_RELEASE(mConnInfo);
delete mResponseHead;
delete mForTakeResponseHead;
delete mChunkedDecoder;
+ ReleaseBlockingTransaction();
}
nsHttpTransaction::Classifier
nsHttpTransaction::Classify()
{
if (!(mCaps & NS_HTTP_ALLOW_PIPELINING))
return (mClassification = CLASS_SOLO);
@@ -781,16 +783,17 @@ nsHttpTransaction::Close(nsresult reason
mTimings.responseEnd = TimeStamp::Now();
if (relConn && mConnection)
NS_RELEASE(mConnection);
mStatus = reason;
mTransactionDone = true; // forcibly flag the transaction as complete
mClosed = true;
+ ReleaseBlockingTransaction();
// release some resources that we no longer need
mRequestStream = nullptr;
mReqHeaderBuf.Truncate();
mLineBuf.Truncate();
if (mChunkedDecoder) {
delete mChunkedDecoder;
mChunkedDecoder = nullptr;
@@ -1378,16 +1381,17 @@ nsHttpTransaction::HandleContent(char *b
}
// check for end-of-file
if ((mContentRead == mContentLength) ||
(mChunkedDecoder && mChunkedDecoder->ReachedEOF())) {
// the transaction is done with a complete response.
mTransactionDone = true;
mResponseIsComplete = true;
+ ReleaseBlockingTransaction();
if (TimingEnabled())
mTimings.responseEnd = TimeStamp::Now();
// report the entire response has arrived
if (mActivityDistributor)
mActivityDistributor->ObserveActivity(
mChannel,
@@ -1490,16 +1494,67 @@ nsHttpTransaction::CancelPipeline(uint32
mConnection->CancelPipeline(NS_ERROR_ABORT);
// Avoid pipelining this transaction on restart by classifying it as solo.
// This also prevents BadUnexpectedLarge from being reported more
// than one time per transaction.
mClassification = CLASS_SOLO;
}
+// Called when the transaction marked for blocking is associated with a connection
+// (i.e. added to a spdy session, an idle http connection, or placed into
+// a http pipeline). It is safe to call this multiple times with it only
+// having an effect once.
+void
+nsHttpTransaction::DispatchedAsBlocking()
+{
+ if (mDispatchedAsBlocking)
+ return;
+
+ LOG(("nsHttpTransaction %p dispatched as blocking\n", this));
+
+ if (!mLoadGroupCI)
+ return;
+
+ LOG(("nsHttpTransaction adding blocking channel %p from "
+ "loadgroup %p\n", this, mLoadGroupCI.get()));
+
+ mLoadGroupCI->AddBlockingTransaction();
+ mDispatchedAsBlocking = true;
+}
+
+void
+nsHttpTransaction::RemoveDispatchedAsBlocking()
+{
+ if (!mLoadGroupCI || !mDispatchedAsBlocking)
+ return;
+
+ uint32_t blockers = 0;
+ nsresult rv = mLoadGroupCI->RemoveBlockingTransaction(&blockers);
+
+ LOG(("nsHttpTransaction removing blocking channel %p from "
+ "loadgroup %p. %d blockers remain.\n", this,
+ mLoadGroupCI.get(), blockers));
+
+ if (NS_SUCCEEDED(rv) && !blockers) {
+ LOG(("nsHttpTransaction %p triggering release of blocked channels.\n",
+ this));
+ gHttpHandler->ConnMgr()->ProcessPendingQ();
+ }
+
+ mDispatchedAsBlocking = false;
+}
+
+void
+nsHttpTransaction::ReleaseBlockingTransaction()
+{
+ RemoveDispatchedAsBlocking();
+ mLoadGroupCI = nullptr;
+}
+
//-----------------------------------------------------------------------------
// nsHttpTransaction deletion event
//-----------------------------------------------------------------------------
class nsDeleteHttpTransaction : public nsRunnable {
public:
nsDeleteHttpTransaction(nsHttpTransaction *trans)
: mTrans(trans)
--- a/netwerk/protocol/http/nsHttpTransaction.h
+++ b/netwerk/protocol/http/nsHttpTransaction.h
@@ -9,16 +9,17 @@
#include "nsHttp.h"
#include "nsHttpHeaderArray.h"
#include "nsAHttpTransaction.h"
#include "nsAHttpConnection.h"
#include "nsCOMPtr.h"
#include "nsIPipe.h"
#include "nsIInputStream.h"
+#include "nsILoadGroup.h"
#include "nsIOutputStream.h"
#include "nsIInterfaceRequestor.h"
#include "nsISocketTransportService.h"
#include "nsITransport.h"
#include "nsIEventTarget.h"
#include "TimingStruct.h"
//-----------------------------------------------------------------------------
@@ -106,28 +107,34 @@ public:
void PrintDiagnostics(nsCString &log);
// Sets mPendingTime to the current time stamp or to a null time stamp (if now is false)
void SetPendingTime(bool now = true) { mPendingTime = now ? mozilla::TimeStamp::Now() : mozilla::TimeStamp(); }
const mozilla::TimeStamp GetPendingTime() { return mPendingTime; }
bool UsesPipelining() const { return mCaps & NS_HTTP_ALLOW_PIPELINING; }
+ void SetLoadGroupConnectionInfo(nsILoadGroupConnectionInfo *aLoadGroupCI) { mLoadGroupCI = aLoadGroupCI; }
+ nsILoadGroupConnectionInfo *LoadGroupConnectionInfo() { return mLoadGroupCI.get(); }
+ void DispatchedAsBlocking();
+ void RemoveDispatchedAsBlocking();
+
private:
nsresult Restart();
nsresult RestartInProgress();
char *LocateHttpStart(char *buf, uint32_t len,
bool aAllowPartialMatch);
nsresult ParseLine(char *line);
nsresult ParseLineSegment(char *seg, uint32_t len);
nsresult ParseHead(char *, uint32_t count, uint32_t *countRead);
nsresult HandleContentStart();
nsresult HandleContent(char *, uint32_t count, uint32_t *contentRead, uint32_t *contentRemaining);
nsresult ProcessData(char *, uint32_t, uint32_t *);
void DeleteSelfOnConsumerThread();
+ void ReleaseBlockingTransaction();
Classifier Classify();
void CancelPipeline(uint32_t reason);
static NS_METHOD ReadRequestSegment(nsIInputStream *, void *, const char *,
uint32_t, uint32_t, uint32_t *);
static NS_METHOD WritePipeSegment(nsIOutputStream *, void *, char *,
uint32_t, uint32_t, uint32_t *);
@@ -138,16 +145,17 @@ private:
mozilla::Mutex mCallbacksLock;
nsCOMPtr<nsIInterfaceRequestor> mCallbacks;
nsCOMPtr<nsITransportEventSink> mTransportSink;
nsCOMPtr<nsIEventTarget> mConsumerTarget;
nsCOMPtr<nsISupports> mSecurityInfo;
nsCOMPtr<nsIAsyncInputStream> mPipeIn;
nsCOMPtr<nsIAsyncOutputStream> mPipeOut;
+ nsCOMPtr<nsILoadGroupConnectionInfo> mLoadGroupCI;
nsCOMPtr<nsISupports> mChannel;
nsCOMPtr<nsIHttpActivityObserver> mActivityDistributor;
nsCString mReqHeaderBuf; // flattened request headers
nsCOMPtr<nsIInputStream> mRequestStream;
uint64_t mRequestSize;
@@ -199,16 +207,17 @@ private:
bool mNoContent; // expecting an empty entity body
bool mSentData;
bool mReceivedData;
bool mStatusEventPending;
bool mHasRequestBody;
bool mProxyConnectFailed;
bool mHttpResponseMatched;
bool mPreserveStream;
+ bool mDispatchedAsBlocking;
// mClosed := transaction has been explicitly closed
// mTransactionDone := transaction ran to completion or was interrupted
// mResponseComplete := transaction ran to completion
// For Restart-In-Progress Functionality
bool mReportedStart;
bool mReportedResponseHeader;