Bug 1365307 - Throttling of HTTP transactions. r=mcmanus

This commit is contained in:
Honza Bambas 2017-06-01 12:16:00 -04:00
parent 10539cc2c6
commit e98f5af878
11 changed files with 756 additions and 74 deletions

View File

@ -2086,11 +2086,15 @@ pref("network.auth.subresource-img-cross-origin-http-auth-allow", true);
// in that case default credentials will always be used.
pref("network.auth.private-browsing-sso", false);
// Control how the throttling service works - number of ms that each
// Control how throttling of http responses works - number of ms that each
// suspend and resume period lasts (prefs named appropriately)
pref("network.throttle.suspend-for", 3000);
pref("network.throttle.resume-for", 200);
pref("network.throttle.enable", true);
pref("network.http.throttle.enable", true);
pref("network.http.throttle.suspend-for", 3000);
pref("network.http.throttle.resume-for", 200);
// Delay we resume throttled background responses after the last unthrottled
// response has finished. Prevents resuming too soon during an active page load
// at which sub-resource reqeusts quickly come and go.
pref("network.http.throttle.resume-background-in", 400);
pref("permissions.default.image", 1); // 1-Accept, 2-Deny, 3-dontAcceptForeign

View File

@ -404,6 +404,7 @@ Http2Session::AddStream(nsAHttpTransaction *aHttpTransaction,
}
aHttpTransaction->SetConnection(this);
aHttpTransaction->OnActivated(true);
if (aUseTunnel) {
LOG3(("Http2Session::AddStream session=%p trans=%p OnTunnel",

View File

@ -45,6 +45,10 @@ public:
// called by the connection when it takes ownership of the transaction.
virtual void SetConnection(nsAHttpConnection *) = 0;
// called by the connection after a successfull activation of this transaction
// in other words, tells the transaction it transitioned to the "active" state.
virtual void OnActivated(bool h2) {}
// used to obtain the connection associated with this transaction
virtual nsAHttpConnection *Connection() = 0;

View File

@ -711,10 +711,6 @@ nsHttpChannel::ContinueConnect()
while (suspendCount--)
mTransactionPump->Suspend();
if (mClassOfService & nsIClassOfService::Throttleable) {
gHttpHandler->ThrottleTransaction(mTransaction, true);
}
return NS_OK;
}
@ -6622,10 +6618,8 @@ nsHttpChannel::ContinueBeginConnect()
void
nsHttpChannel::OnClassOfServiceUpdated()
{
bool throttleable = !!(mClassOfService & nsIClassOfService::Throttleable);
if (mTransaction) {
gHttpHandler->ThrottleTransaction(mTransaction, throttleable);
gHttpHandler->UpdateClassOfServiceOnTransaction(mTransaction, mClassOfService);
}
}
@ -8009,10 +8003,6 @@ nsHttpChannel::DoAuthRetry(nsAHttpConnection *conn)
while (suspendCount--)
mTransactionPump->Suspend();
if (mSuspendCount && mClassOfService & nsIClassOfService::Throttleable) {
gHttpHandler->ThrottleTransaction(mTransaction, true);
}
return NS_OK;
}
@ -8719,10 +8709,6 @@ nsHttpChannel::SuspendInternal()
if (mSuspendCount == 1) {
mSuspendTimestamp = TimeStamp::NowLoRes();
if (mClassOfService & nsIClassOfService::Throttleable && mTransaction) {
gHttpHandler->ThrottleTransaction(mTransaction, true);
}
}
nsresult rvTransaction = NS_OK;
@ -8748,10 +8734,6 @@ nsHttpChannel::ResumeInternal()
mSuspendTotalTime += (TimeStamp::NowLoRes() - mSuspendTimestamp).
ToMilliseconds();
if (mClassOfService & nsIClassOfService::Throttleable && mTransaction) {
gHttpHandler->ThrottleTransaction(mTransaction, false);
}
if (mCallOnResume) {
nsresult rv = AsyncCall(mCallOnResume);
mCallOnResume = nullptr;

View File

@ -664,6 +664,8 @@ nsHttpConnection::Activate(nsAHttpTransaction *trans, uint32_t caps, int32_t pri
mTransaction = mTLSFilter;
}
trans->OnActivated(false);
rv = OnOutputStreamReady(mSocketOut);
failed_activation:

View File

@ -115,6 +115,11 @@ nsHttpConnectionMgr::nsHttpConnectionMgr()
, mMaxConns(0)
, mMaxPersistConnsPerHost(0)
, mMaxPersistConnsPerProxy(0)
, mMaxRequestDelay(0)
, mThrottleEnabled(false)
, mThrottleSuspendFor(0)
, mThrottleResumeFor(0)
, mThrottleResumeIn(0)
, mIsShuttingDown(false)
, mNumActiveConns(0)
, mNumIdleConns(0)
@ -125,6 +130,9 @@ nsHttpConnectionMgr::nsHttpConnectionMgr()
, mTimeoutTickArmed(false)
, mTimeoutTickNext(1)
, mCurrentTopLevelOuterContentWindowId(0)
, mThrottlingInhibitsReading(false)
, mActiveTabTransactionsExist(false)
, mActiveTabUnthrottledTransactionsExist(false)
{
LOG(("Creating nsHttpConnectionMgr @%p\n", this));
}
@ -164,7 +172,11 @@ nsHttpConnectionMgr::Init(uint16_t maxUrgentExcessiveConns,
uint16_t maxConns,
uint16_t maxPersistConnsPerHost,
uint16_t maxPersistConnsPerProxy,
uint16_t maxRequestDelay)
uint16_t maxRequestDelay,
bool throttleEnabled,
uint32_t throttleSuspendFor,
uint32_t throttleResumeFor,
uint32_t throttleResumeIn)
{
LOG(("nsHttpConnectionMgr::Init\n"));
@ -177,6 +189,11 @@ nsHttpConnectionMgr::Init(uint16_t maxUrgentExcessiveConns,
mMaxPersistConnsPerProxy = maxPersistConnsPerProxy;
mMaxRequestDelay = maxRequestDelay;
mThrottleEnabled = throttleEnabled;
mThrottleSuspendFor = throttleSuspendFor;
mThrottleResumeFor = throttleResumeFor;
mThrottleResumeIn = throttleResumeIn;
mIsShuttingDown = false;
}
@ -344,13 +361,15 @@ nsHttpConnectionMgr::Observe(nsISupports *subject,
nsCOMPtr<nsITimer> timer = do_QueryInterface(subject);
if (timer == mTimer) {
Unused << PruneDeadConnections();
}
else if (timer == mTimeoutTick) {
} else if (timer == mTimeoutTick) {
TimeoutTick();
} else if (timer == mTrafficTimer) {
Unused << PruneNoTraffic();
}
else {
} else if (timer == mThrottleTicker) {
ThrottlerTick();
} else if (timer == mDelayedResumeReadTimer) {
ResumeBackgroundThrottledTransactions();
} else {
MOZ_ASSERT(false, "unexpected timer-callback");
LOG(("Unexpected timer object\n"));
return NS_ERROR_UNEXPECTED;
@ -378,12 +397,12 @@ nsHttpConnectionMgr::RescheduleTransaction(nsHttpTransaction *trans, int32_t pri
}
void
nsHttpConnectionMgr::ThrottleTransaction(nsHttpTransaction *trans, bool throttle)
nsHttpConnectionMgr::UpdateClassOfServiceOnTransaction(nsHttpTransaction *trans, uint32_t classOfService)
{
LOG(("nsHttpConnectionMgr::ThrottleTransaction [trans=%p throttle=%" PRIx32 "]\n",
trans, static_cast<uint32_t>(throttle)));
Unused << PostEvent(&nsHttpConnectionMgr::OnMsgThrottleTransaction,
static_cast<int32_t>(throttle), trans);
LOG(("nsHttpConnectionMgr::UpdateClassOfServiceOnTransaction [trans=%p classOfService=%" PRIu32 "]\n",
trans, static_cast<uint32_t>(classOfService)));
Unused << PostEvent(&nsHttpConnectionMgr::OnMsgUpdateClassOfServiceOnTransaction,
static_cast<int32_t>(classOfService), trans);
}
nsresult
@ -2157,6 +2176,10 @@ nsHttpConnectionMgr::OnMsgShutdown(int32_t, ARefBase *param)
mTrafficTimer->Cancel();
mTrafficTimer = nullptr;
}
DestroyThrottleTicker();
mActiveTransactions[false].Clear();
mActiveTransactions[true].Clear();
mCoalescingHash.Clear();
// signal shutdown complete
@ -2224,15 +2247,15 @@ nsHttpConnectionMgr::OnMsgReschedTransaction(int32_t priority, ARefBase *param)
}
}
void nsHttpConnectionMgr::OnMsgThrottleTransaction(int32_t arg, ARefBase *param)
void nsHttpConnectionMgr::OnMsgUpdateClassOfServiceOnTransaction(int32_t arg, ARefBase *param)
{
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
LOG(("nsHttpConnectionMgr::OnMsgThrottleTransaction [trans=%p]\n", param));
LOG(("nsHttpConnectionMgr::OnMsgUpdateClassOfServiceOnTransaction [trans=%p]\n", param));
bool throttle = static_cast<bool>(arg);
uint32_t cos = static_cast<uint32_t>(arg);
nsHttpTransaction *trans = static_cast<nsHttpTransaction *>(param);
trans->ThrottleResponse(throttle);
trans->SetClassOfService(cos);
}
void
@ -2730,6 +2753,18 @@ nsHttpConnectionMgr::OnMsgUpdateParam(int32_t inParam, ARefBase *)
case MAX_REQUEST_DELAY:
mMaxRequestDelay = value;
break;
case THROTTLING_ENABLED:
SetThrottlingEnabled(!!value);
break;
case THROTTLING_SUSPEND_FOR:
mThrottleSuspendFor = value;
break;
case THROTTLING_RESUME_FOR:
mThrottleResumeFor = value;
break;
case THROTTLING_RESUME_IN:
mThrottleResumeIn = value;
break;
default:
NS_NOTREACHED("unexpected parameter name");
}
@ -2803,16 +2838,480 @@ nsHttpConnectionMgr::UpdateCurrentTopLevelOuterContentWindowId(
windowIdWrapper);
}
void nsHttpConnectionMgr::SetThrottlingEnabled(bool aEnable)
{
LOG(("nsHttpConnectionMgr::SetThrottlingEnabled enable=%d", aEnable));
mThrottleEnabled = aEnable;
if (mThrottleEnabled) {
EnsureThrottleTickerIfNeeded();
} else {
DestroyThrottleTicker();
ResumeReadOf(mActiveTransactions[false]);
ResumeReadOf(mActiveTransactions[true]);
}
}
void nsHttpConnectionMgr::LogActiveTransactions(char operation)
{
if (!LOG_ENABLED()) {
return;
}
nsTArray<RefPtr<nsHttpTransaction>> *trs = nullptr;
uint32_t au, at, bu = 0, bt = 0;
trs = mActiveTransactions[false].Get(mCurrentTopLevelOuterContentWindowId);
au = trs ? trs->Length() : 0;
trs = mActiveTransactions[true].Get(mCurrentTopLevelOuterContentWindowId);
at = trs ? trs->Length() : 0;
for (auto iter = mActiveTransactions[false].Iter(); !iter.Done(); iter.Next()) {
bu += iter.UserData()->Length();
}
bu -= au;
for (auto iter = mActiveTransactions[true].Iter(); !iter.Done(); iter.Next()) {
bt += iter.UserData()->Length();
}
bt -= at;
// Shows counts of:
// - unthrottled transaction for the active tab
// - throttled transaction for the active tab
// - unthrottled transaction for background tabs
// - throttled transaction for background tabs
LOG(("Active transactions %c[%u,%u,%u,%u]", operation, au, at, bu, bt));
}
void
nsHttpConnectionMgr::OnMsgUpdateCurrentTopLevelOuterContentWindowId(
int32_t, ARefBase *param)
nsHttpConnectionMgr::AddActiveTransaction(nsHttpTransaction * aTrans, bool aThrottled)
{
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
mCurrentTopLevelOuterContentWindowId =
static_cast<UINT64Wrapper*>(param)->GetValue();
uint64_t tabId = aTrans->TopLevelOuterContentWindowId();
nsTArray<RefPtr<nsHttpTransaction>> *transactions =
mActiveTransactions[aThrottled].LookupOrAdd(tabId);
MOZ_ASSERT(!transactions->Contains(aTrans));
transactions->AppendElement(aTrans);
LOG(("nsHttpConnectionMgr::AddActiveTransaction t=%p tabid=%" PRIx64 "(%d) thr=%d",
aTrans, tabId, tabId == mCurrentTopLevelOuterContentWindowId, aThrottled));
LogActiveTransactions('+');
if (tabId == mCurrentTopLevelOuterContentWindowId) {
mActiveTabTransactionsExist = true;
if (!aThrottled) {
mActiveTabUnthrottledTransactionsExist = true;
}
}
if (!mThrottleEnabled) {
return;
}
EnsureThrottleTickerIfNeeded();
}
void
nsHttpConnectionMgr::RemoveActiveTransaction(nsHttpTransaction * aTrans, bool aThrottled)
{
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
uint64_t tabId = aTrans->TopLevelOuterContentWindowId();
bool forActiveTab = tabId == mCurrentTopLevelOuterContentWindowId;
nsTArray<RefPtr<nsHttpTransaction>> *transactions =
mActiveTransactions[aThrottled].Get(tabId);
if (!transactions || !transactions->RemoveElement(aTrans)) {
// Was not tracked as active, probably just ignore.
return;
}
LOG(("nsHttpConnectionMgr::RemoveActiveTransaction t=%p tabid=%" PRIx64 "(%d) thr=%d",
aTrans, tabId, forActiveTab, aThrottled));
if (!transactions->IsEmpty()) {
// There are still transactions of the type, hence nothing in the throttling conditions
// has changed and we don't need to update "Exists" caches nor we need to wake any now
// throttled transactions.
LogActiveTransactions('-');
return;
}
// To optimize the following logic, always remove the entry when the array is empty.
mActiveTransactions[aThrottled].Remove(tabId);
LogActiveTransactions('-');
if (forActiveTab) {
// Update caches of the active tab transaction existence, since it's now affected
if (!aThrottled) {
mActiveTabUnthrottledTransactionsExist = false;
}
if (mActiveTabTransactionsExist) {
mActiveTabTransactionsExist = mActiveTransactions[!aThrottled].Contains(tabId);
}
}
if (!mThrottleEnabled) {
return;
}
bool unthrottledExist = !mActiveTransactions[false].IsEmpty();
bool throttledExist = !mActiveTransactions[true].IsEmpty();
if (!unthrottledExist && !throttledExist) {
// Nothing active globally, just get rid of the timer completely and we are done.
MOZ_ASSERT(!mActiveTabUnthrottledTransactionsExist);
MOZ_ASSERT(!mActiveTabTransactionsExist);
DestroyThrottleTicker();
return;
}
if (!mThrottlingInhibitsReading) {
// There is then nothing to wake up. Affected transactions will not be put
// to sleep automatically on next tick.
LOG((" reading not currently inhibited"));
return;
}
if (mActiveTabUnthrottledTransactionsExist) {
// There are still unthrottled transactions for the active tab, hence the state
// is unaffected and we don't need to do anything (nothing to wake).
LOG((" there are unthrottled for the active tab"));
return;
}
if (mActiveTabTransactionsExist) {
// There are only trottled transactions for the active tab.
// If the last transaction we just removed was a non-throttled for the active tab
// we can wake the throttled transactions for the active tab.
if (forActiveTab && !aThrottled) {
LOG((" resuming throttled for active tab"));
ResumeReadOf(mActiveTransactions[true].Get(mCurrentTopLevelOuterContentWindowId));
}
return;
}
if (!unthrottledExist) {
// There are no unthrottled transactions for any tab. Resume all throttled,
// all are only for background tabs.
LOG((" delay resuming throttled for background tabs"));
DelayedResumeBackgroundThrottledTransactions();
return;
}
if (forActiveTab) {
// Removing the last transaction for the active tab frees up the unthrottled
// background tabs transactions.
LOG((" resuming unthrottled for background tabs"));
ResumeReadOf(mActiveTransactions[false]);
return;
}
LOG((" not resuming anything"));
}
void
nsHttpConnectionMgr::MoveActiveTransaction(nsHttpTransaction * aTrans, bool aThrottled)
{
LOG(("nsHttpConnectionMgr::MoveActiveTransaction ENTER t=%p", aTrans));
AddActiveTransaction(aTrans, aThrottled);
RemoveActiveTransaction(aTrans, !aThrottled);
LOG(("nsHttpConnectionMgr::MoveActiveTransaction EXIT t=%p", aTrans));
}
bool
nsHttpConnectionMgr::ShouldStopReading(nsHttpTransaction * aTrans, bool aThrottled)
{
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
if (!mThrottlingInhibitsReading || !mThrottleEnabled) {
return false;
}
uint64_t tabId = aTrans->TopLevelOuterContentWindowId();
if (mActiveTabTransactionsExist) {
if (!tabId) {
// Chrome initiated and unidentified transactions just respect
// their throttle flag, when something for the active tab is happening.
return aThrottled;
}
if (tabId != mCurrentTopLevelOuterContentWindowId) {
// This is a background tab request, we want them to always throttle.
return true;
}
if (mActiveTabUnthrottledTransactionsExist) {
// Unthrottled transactions for the active tab take precedence
return aThrottled;
}
// This is a throttled transaction for the active tab and there are no
// unthrottled for the active tab, just let go on full fuel.
return false;
}
if (!mActiveTransactions[false].IsEmpty()) {
// This means there are unthrottled active transactions for background tabs.
// If we are here, there can't be any transactions for the active tab.
// (If there is no transaction for a tab id, there is no entry for it in the hashtable.)
return aThrottled;
}
// There are only unthrottled transactions for background tabs: don't throttle.
return false;
}
bool nsHttpConnectionMgr::IsThrottleTickerNeeded()
{
LOG(("nsHttpConnectionMgr::IsThrottleTickerNeeded"));
if (mActiveTabUnthrottledTransactionsExist &&
mActiveTransactions[false].Count() > 1) {
LOG((" there are unthrottled transactions for both active and bck"));
return true;
}
if (mActiveTabTransactionsExist &&
mActiveTransactions[true].Count() > 1) {
LOG((" there are throttled transactions for both active and bck"));
return true;
}
if (!mActiveTransactions[true].IsEmpty() &&
!mActiveTransactions[false].IsEmpty()) {
LOG((" there are both throttled and unthrottled transactions"));
return true;
}
LOG((" nothing to throttle"));
return false;
}
void
nsHttpConnectionMgr::EnsureThrottleTickerIfNeeded()
{
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
LOG(("nsHttpConnectionMgr::EnsureThrottleTickerIfNeeded"));
if (!IsThrottleTickerNeeded()) {
return;
}
// There is a new demand to throttle, hence unschedule delayed resume
// of background throttled transastions.
CancelDelayedResumeBackgroundThrottledTransactions();
if (mThrottleTicker) {
return;
}
MOZ_ASSERT(!mThrottlingInhibitsReading);
mThrottleTicker = do_CreateInstance("@mozilla.org/timer;1");
if (mThrottleTicker) {
mThrottleTicker->Init(this, mThrottleSuspendFor, nsITimer::TYPE_ONE_SHOT);
mThrottlingInhibitsReading = true;
}
LogActiveTransactions('^');
}
void
nsHttpConnectionMgr::DestroyThrottleTicker()
{
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
// Nothing to throttle, hence no need for this timer anymore.
CancelDelayedResumeBackgroundThrottledTransactions();
MOZ_ASSERT(!mThrottleEnabled || !IsThrottleTickerNeeded());
if (!mThrottleTicker) {
return;
}
LOG(("nsHttpConnectionMgr::DestroyThrottleTicker"));
mThrottleTicker->Cancel();
mThrottleTicker = nullptr;
mThrottlingInhibitsReading = false;
LogActiveTransactions('v');
}
void
nsHttpConnectionMgr::ThrottlerTick()
{
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
mThrottlingInhibitsReading = !mThrottlingInhibitsReading;
LOG(("nsHttpConnectionMgr::ThrottlerTick inhibit=%d", mThrottlingInhibitsReading));
if (!mThrottlingInhibitsReading && !IsThrottleTickerNeeded()) {
mThrottleTicker = nullptr;
} else {
mThrottleTicker = do_CreateInstance("@mozilla.org/timer;1");
}
if (mThrottlingInhibitsReading) {
if (mThrottleTicker) {
mThrottleTicker->Init(this, mThrottleSuspendFor, nsITimer::TYPE_ONE_SHOT);
}
} else {
// Resume by the ticker happens sooner than delayed resume, no need
// for the delayed resume timer
CancelDelayedResumeBackgroundThrottledTransactions();
if (mThrottleTicker) {
mThrottleTicker->Init(this, mThrottleResumeFor, nsITimer::TYPE_ONE_SHOT);
}
ResumeReadOf(mActiveTransactions[false], true);
ResumeReadOf(mActiveTransactions[true]);
}
}
void
nsHttpConnectionMgr::DelayedResumeBackgroundThrottledTransactions()
{
if (mDelayedResumeReadTimer) {
return;
}
mDelayedResumeReadTimer = do_CreateInstance("@mozilla.org/timer;1");
if (!mDelayedResumeReadTimer) {
return;
}
LOG(("nsHttpConnectionMgr::DelayedResumeBackgroundThrottledTransactions"));
mDelayedResumeReadTimer->Init(this, mThrottleResumeIn, nsITimer::TYPE_ONE_SHOT);
}
void
nsHttpConnectionMgr::CancelDelayedResumeBackgroundThrottledTransactions()
{
if (!mDelayedResumeReadTimer) {
return;
}
LOG(("nsHttpConnectionMgr::CancelDelayedResumeBackgroundThrottledTransactions"));
mDelayedResumeReadTimer->Cancel();
mDelayedResumeReadTimer = nullptr;
}
void
nsHttpConnectionMgr::ResumeBackgroundThrottledTransactions()
{
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
LOG(("nsHttpConnectionMgr::ResumeBackgroundThrottledTransactions"));
mDelayedResumeReadTimer = nullptr;
// We can destroy the ticker, since only transactions to resume now
// are background throttled. If 'higher class' transactions have
// been added, we don't get here - the ticker has been scheduled
// and hence the delayed resume timer canceled.
MOZ_ASSERT(mActiveTransactions[false].IsEmpty() &&
!mActiveTabTransactionsExist);
DestroyThrottleTicker();
ResumeReadOf(mActiveTransactions[true], true);
}
void
nsHttpConnectionMgr::ResumeReadOf(
nsClassHashtable<nsUint64HashKey, nsTArray<RefPtr<nsHttpTransaction>>>& hashtable,
bool excludeForActiveTab)
{
for (auto iter = hashtable.Iter(); !iter.Done(); iter.Next()) {
if (excludeForActiveTab && iter.Key() == mCurrentTopLevelOuterContentWindowId) {
// These have never been throttled (never stopped reading)
continue;
}
ResumeReadOf(iter.UserData());
}
}
void
nsHttpConnectionMgr::ResumeReadOf(nsTArray<RefPtr<nsHttpTransaction>>* transactions)
{
MOZ_ASSERT(transactions);
for (auto trans : *transactions) {
trans->ResumeReading();
}
}
void
nsHttpConnectionMgr::OnMsgUpdateCurrentTopLevelOuterContentWindowId(
int32_t aLoading, ARefBase *param)
{
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
uint64_t winId = static_cast<UINT64Wrapper*>(param)->GetValue();
if (mCurrentTopLevelOuterContentWindowId == winId) {
// duplicate notification
return;
}
bool activeTabWasLoading = mActiveTabTransactionsExist;
bool activeTabIdChanged = mCurrentTopLevelOuterContentWindowId != winId;
mCurrentTopLevelOuterContentWindowId = winId;
LOG(("nsHttpConnectionMgr::OnMsgUpdateCurrentTopLevelOuterContentWindowId"
" id=%" PRIu64 "\n",
" id=%" PRIx64 "\n",
mCurrentTopLevelOuterContentWindowId));
nsTArray<RefPtr<nsHttpTransaction>> *transactions = nullptr;
if (activeTabIdChanged) {
// Update the "Exists" caches and resume any transactions that now deserve it,
// changing the active tab changes the conditions for throttling.
transactions = mActiveTransactions[false].Get(mCurrentTopLevelOuterContentWindowId);
mActiveTabUnthrottledTransactionsExist = !!transactions;
if (!mActiveTabUnthrottledTransactionsExist) {
transactions = mActiveTransactions[true].Get(mCurrentTopLevelOuterContentWindowId);
}
mActiveTabTransactionsExist = !!transactions;
}
if (transactions) {
// This means there are some transactions for this newly activated tab, resume them
// but anything else.
LOG((" resuming newly activated tab transactions"));
ResumeReadOf(transactions);
return;
}
if (!activeTabWasLoading) {
// There were no transactions for the previously active tab, hence
// all remaning transactions, if there were, were all unthrottled,
// no need to wake them.
return;
}
if (!mActiveTransactions[false].IsEmpty()) {
LOG((" resuming unthrottled background transactions"));
ResumeReadOf(mActiveTransactions[false]);
return;
}
if (!mActiveTransactions[true].IsEmpty()) {
LOG((" delayed resuming throttled background transactions"));
DelayedResumeBackgroundThrottledTransactions();
return;
}
DestroyThrottleTicker();
}
void

View File

@ -55,7 +55,11 @@ public:
MAX_CONNECTIONS,
MAX_PERSISTENT_CONNECTIONS_PER_HOST,
MAX_PERSISTENT_CONNECTIONS_PER_PROXY,
MAX_REQUEST_DELAY
MAX_REQUEST_DELAY,
THROTTLING_ENABLED,
THROTTLING_SUSPEND_FOR,
THROTTLING_RESUME_FOR,
THROTTLING_RESUME_IN
};
//-------------------------------------------------------------------------
@ -68,7 +72,11 @@ public:
uint16_t maxConnections,
uint16_t maxPersistentConnectionsPerHost,
uint16_t maxPersistentConnectionsPerProxy,
uint16_t maxRequestDelay);
uint16_t maxRequestDelay,
bool throttleEnabled,
uint32_t throttleSuspendFor,
uint32_t throttleResumeFor,
uint32_t throttleResumeIn);
MOZ_MUST_USE nsresult Shutdown();
//-------------------------------------------------------------------------
@ -95,13 +103,9 @@ public:
MOZ_MUST_USE nsresult RescheduleTransaction(nsHttpTransaction *,
int32_t priority);
// tells the transaction to stop receiving the response when |throttle|
// is true. to start receiving again, this must be called with |throttle|
// set to false. calling multiple times with the same value of |throttle|
// has no effect. called by nsHttpChannels with the Throttleable class set
// and controlled by net::ThrottlingService.
// there is nothing to do when this fails, hence the void result.
void ThrottleTransaction(nsHttpTransaction *, bool throttle);
// TOOD
void UpdateClassOfServiceOnTransaction(nsHttpTransaction *,
uint32_t classOfService);
// cancels a transaction w/ the given reason.
MOZ_MUST_USE nsresult CancelTransaction(nsHttpTransaction *,
@ -211,6 +215,17 @@ public:
nsresult UpdateCurrentTopLevelOuterContentWindowId(uint64_t aWindowId);
// tracks and untracks active transactions according their throttle status
void AddActiveTransaction(nsHttpTransaction* aTrans, bool aThrottled);
void RemoveActiveTransaction(nsHttpTransaction* aTrans, bool aThrottled);
void MoveActiveTransaction(nsHttpTransaction* aTrans, bool aThrottled);
// called by nsHttpTransaction::WriteSegments. decides whether the transaction
// should stop reading data based on: the throttling ticker status, overall
// status of all active transactions regarding active tab and respective
// throttling state.
bool ShouldStopReading(nsHttpTransaction* aTrans, bool aThrottled);
private:
virtual ~nsHttpConnectionMgr();
@ -486,6 +501,10 @@ private:
uint16_t mMaxPersistConnsPerHost;
uint16_t mMaxPersistConnsPerProxy;
uint16_t mMaxRequestDelay; // in seconds
bool mThrottleEnabled;
uint32_t mThrottleSuspendFor;
uint32_t mThrottleResumeFor;
uint32_t mThrottleResumeIn;
Atomic<bool, mozilla::Relaxed> mIsShuttingDown;
//-------------------------------------------------------------------------
@ -579,7 +598,7 @@ private:
void OnMsgShutdownConfirm (int32_t, ARefBase *);
void OnMsgNewTransaction (int32_t, ARefBase *);
void OnMsgReschedTransaction (int32_t, ARefBase *);
void OnMsgThrottleTransaction (int32_t, ARefBase *);
void OnMsgUpdateClassOfServiceOnTransaction (int32_t, ARefBase *);
void OnMsgCancelTransaction (int32_t, ARefBase *);
void OnMsgCancelTransactions (int32_t, ARefBase *);
void OnMsgProcessPendingQ (int32_t, ARefBase *);
@ -640,6 +659,58 @@ private:
nsCString mLogData;
uint64_t mCurrentTopLevelOuterContentWindowId;
// Called on a pref change
void SetThrottlingEnabled(bool aEnable);
// Two hashtalbes keeping track of active transactions regarding window id and throttling.
// Used by the throttling algorithm to obtain number of transactions for the active tab
// and for inactive tabs according their throttle status.
// mActiveTransactions[0] are all unthrottled transactions, mActiveTransactions[1] throttled.
nsClassHashtable<nsUint64HashKey, nsTArray<RefPtr<nsHttpTransaction>>> mActiveTransactions[2];
// Whether we are inside the "stop reading" interval, altered by the throttle ticker
bool mThrottlingInhibitsReading;
// ticker for the 'stop reading'/'resume reading' signal
nsCOMPtr<nsITimer> mThrottleTicker;
// Checks if the combination of active transactions requires the ticker.
bool IsThrottleTickerNeeded();
// The method also unschedules the delayed resume of background tabs timer
// if the ticker was about to be scheduled.
void EnsureThrottleTickerIfNeeded();
// Drops also the mThrottlingInhibitsReading flag. Immediate or delayed resume
// of currently throttled transactions is not affected by this method.
void DestroyThrottleTicker();
// Handler for the ticker: alters the mThrottlingInhibitsReading flag.
void ThrottlerTick();
// mechanism to delay immediate resume of background tabs and chrome initiated
// throttled transactions after the last transaction blocking their unthrottle
// has been removed. Needs to be delayed because during a page load there is
// a number of intervals when there is no transaction that would cause throttling.
// Hence, throttling of long standing responses, like downloads, would be mostly
// ineffective if resumed during every such interval.
nsCOMPtr<nsITimer> mDelayedResumeReadTimer;
// Schedule the resume
void DelayedResumeBackgroundThrottledTransactions();
// Simply destroys the timer
void CancelDelayedResumeBackgroundThrottledTransactions();
// Handler for the timer: resumes all background throttled transactions
void ResumeBackgroundThrottledTransactions();
// Simple helpers, iterates the given hash/array and resume.
// @param excludeActive: skip active tabid transactions.
void ResumeReadOf(nsClassHashtable<nsUint64HashKey, nsTArray<RefPtr<nsHttpTransaction>>>&,
bool excludeActive = false);
void ResumeReadOf(nsTArray<RefPtr<nsHttpTransaction>>*);
// Cached status of the active tab active transactions existence,
// saves a lot of hashtable lookups
bool mActiveTabTransactionsExist;
bool mActiveTabUnthrottledTransactionsExist;
void LogActiveTransactions(char);
};
NS_DEFINE_STATIC_IID_ACCESSOR(nsHttpConnectionMgr::nsHalfOpenSocket, NS_HALFOPENSOCKET_IID)

View File

@ -191,6 +191,10 @@ nsHttpHandler::nsHttpHandler()
, mMaxConnections(24)
, mMaxPersistentConnectionsPerServer(2)
, mMaxPersistentConnectionsPerProxy(4)
, mThrottleEnabled(true)
, mThrottleSuspendFor(3000)
, mThrottleResumeFor(200)
, mThrottleResumeIn(400)
, mRedirectionLimit(10)
, mPhishyUserPassLength(1)
, mQoSBits(0x00)
@ -549,7 +553,11 @@ nsHttpHandler::InitConnectionMgr()
mMaxConnections,
mMaxPersistentConnectionsPerServer,
mMaxPersistentConnectionsPerProxy,
mMaxRequestDelay);
mMaxRequestDelay,
mThrottleEnabled,
mThrottleSuspendFor,
mThrottleResumeFor,
mThrottleResumeIn);
return rv;
}
@ -1546,6 +1554,41 @@ nsHttpHandler::PrefsChanged(nsIPrefBranch *prefs, const char *pref)
}
}
if (PREF_CHANGED("network.http.throttle.enable")) {
rv = prefs->GetBoolPref("network.http.throttle.enable", &mThrottleEnabled);
if (NS_SUCCEEDED(rv) && mConnMgr) {
Unused << mConnMgr->UpdateParam(nsHttpConnectionMgr::THROTTLING_ENABLED,
static_cast<int32_t>(mThrottleEnabled));
}
}
if (PREF_CHANGED("network.http.throttle.suspend-for")) {
rv = prefs->GetIntPref("network.http.throttle.suspend-for", &val);
mThrottleSuspendFor = (uint32_t)clamped(val, 0, 120000);
if (NS_SUCCEEDED(rv) && mConnMgr) {
Unused << mConnMgr->UpdateParam(nsHttpConnectionMgr::THROTTLING_SUSPEND_FOR,
mThrottleSuspendFor);
}
}
if (PREF_CHANGED("network.http.throttle.resume-for")) {
rv = prefs->GetIntPref("network.http.throttle.resume-for", &val);
mThrottleResumeFor = (uint32_t)clamped(val, 0, 120000);
if (NS_SUCCEEDED(rv) && mConnMgr) {
Unused << mConnMgr->UpdateParam(nsHttpConnectionMgr::THROTTLING_RESUME_FOR,
mThrottleResumeFor);
}
}
if (PREF_CHANGED("network.http.throttle.resume-background-in")) {
rv = prefs->GetIntPref("network.http.throttle.resume-background-in", &val);
mThrottleResumeIn = (uint32_t)clamped(val, 0, 120000);
if (NS_SUCCEEDED(rv) && mConnMgr) {
Unused << mConnMgr->UpdateParam(nsHttpConnectionMgr::THROTTLING_RESUME_IN,
mThrottleResumeIn);
}
}
if (PREF_CHANGED(HTTP_PREF("focused_window_transaction_ratio"))) {
float ratio = 0;
rv = prefs->GetFloatPref(HTTP_PREF("focused_window_transaction_ratio"), &ratio);

View File

@ -224,10 +224,10 @@ public:
return mConnMgr->RescheduleTransaction(trans, priority);
}
void ThrottleTransaction(nsHttpTransaction *trans,
bool throttle)
void UpdateClassOfServiceOnTransaction(nsHttpTransaction *trans,
uint32_t classOfService)
{
mConnMgr->ThrottleTransaction(trans, throttle);
mConnMgr->UpdateClassOfServiceOnTransaction(trans, classOfService);
}
// Called to cancel a transaction, which may or may not be assigned to
@ -448,6 +448,11 @@ private:
uint8_t mMaxPersistentConnectionsPerServer;
uint8_t mMaxPersistentConnectionsPerProxy;
bool mThrottleEnabled;
uint32_t mThrottleSuspendFor;
uint32_t mThrottleResumeFor;
uint32_t mThrottleResumeIn;
uint8_t mRedirectionLimit;
// we'll warn the user if we load an URL containing a userpass field

View File

@ -31,6 +31,7 @@
#include "nsIHttpActivityObserver.h"
#include "nsSocketTransportService2.h"
#include "nsICancelable.h"
#include "nsIClassOfService.h"
#include "nsIEventTarget.h"
#include "nsIHttpChannelInternal.h"
#include "nsIInputStream.h"
@ -105,9 +106,11 @@ nsHttpTransaction::nsHttpTransaction()
, mCurrentHttpResponseHeaderSize(0)
, mCapsToClear(0)
, mResponseIsComplete(false)
, mThrottleResponse(false)
, mReadingStopped(false)
, mClosed(false)
, mConnected(false)
, mActivated(false)
, mActivatedAsH2(false)
, mHaveStatusLine(false)
, mHaveAllHeaders(false)
, mTransactionDone(false)
@ -151,11 +154,43 @@ nsHttpTransaction::nsHttpTransaction()
mPeerAddr.raw.family = PR_AF_UNSPEC;
}
void nsHttpTransaction::ThrottleResponse(bool aThrottle)
void nsHttpTransaction::ResumeReading()
{
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
mThrottleResponse = aThrottle;
if (!mReadingStopped) {
return;
}
LOG(("nsHttpTransaction::ResumeReading %p", this));
mReadingStopped = false;
if (mConnection) {
nsresult rv = mConnection->ResumeRecv();
if (NS_FAILED(rv)) {
LOG((" resume failed with rv=%" PRIx32, static_cast<uint32_t>(rv)));
}
}
}
void nsHttpTransaction::SetClassOfService(uint32_t cos)
{
bool wasThrottling = mClassOfService & nsIClassOfService::Throttleable;
mClassOfService = cos;
bool isThrottling = mClassOfService & nsIClassOfService::Throttleable;
if (mConnection && wasThrottling != isThrottling) {
// Do nothing until we are actually activated. For now
// only remember the throttle flag. Call to MoveActiveTransaction
// would add this transaction to the list too early.
gHttpHandler->ConnMgr()->MoveActiveTransaction(this, isThrottling);
if (mReadingStopped && !isThrottling) {
ResumeReading();
}
}
}
nsHttpTransaction::~nsHttpTransaction()
@ -472,6 +507,21 @@ nsHttpTransaction::SetConnection(nsAHttpConnection *conn)
}
}
void
nsHttpTransaction::OnActivated(bool h2)
{
MOZ_ASSERT(OnSocketThread());
mActivatedAsH2 = h2;
if (mActivated) {
return;
}
mActivated = true;
gHttpHandler->ConnMgr()->AddActiveTransaction(
this, mClassOfService & nsIClassOfService::Throttleable);
}
void
nsHttpTransaction::GetSecurityCallbacks(nsIInterfaceRequestor **cb)
{
@ -774,18 +824,30 @@ nsresult
nsHttpTransaction::WriteSegments(nsAHttpSegmentWriter *writer,
uint32_t count, uint32_t *countWritten)
{
static bool reentrantFlag = false;
LOG(("nsHttpTransaction::WriteSegments %p reentrantFlag=%d",
this, reentrantFlag));
MOZ_DIAGNOSTIC_ASSERT(!reentrantFlag);
reentrantFlag = true;
LOG(("nsHttpTransaction::WriteSegments %p", this));
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
if (mTransactionDone) {
reentrantFlag = false;
return NS_SUCCEEDED(mStatus) ? NS_BASE_STREAM_CLOSED : mStatus;
}
// Throttling feature is now disabled for http/2 transactions
// because of bug 1367861. The logic around mActivatedAsH2
// will be removed when that is fixed
if (!mActivatedAsH2 &&
gHttpHandler->ConnMgr()->ShouldStopReading(this,
mClassOfService & nsIClassOfService::Throttleable)) {
LOG(("nsHttpTransaction::WriteSegments %p response throttled", this));
// Must remember that we have to call ResumeRecv() on our connection when
// called back by the conn manager to resume reading.
mReadingStopped = true;
// This makes the underlaying connection or stream wait for explicit resume.
// For h1 this means we stop reading from the socket.
// For h2 this means we stop updating recv window for the stream.
return NS_BASE_STREAM_WOULD_BLOCK;
}
mWriter = writer;
#ifdef WIN32 // bug 1153929
@ -795,7 +857,6 @@ nsHttpTransaction::WriteSegments(nsAHttpSegmentWriter *writer,
#endif // WIN32
if (!mPipeOut) {
reentrantFlag = false;
return NS_ERROR_UNEXPECTED;
}
@ -827,7 +888,6 @@ nsHttpTransaction::WriteSegments(nsAHttpSegmentWriter *writer,
}
}
reentrantFlag = false;
return rv;
}
@ -837,6 +897,12 @@ nsHttpTransaction::Close(nsresult reason)
LOG(("nsHttpTransaction::Close [this=%p reason=%" PRIx32 "]\n",
this, static_cast<uint32_t>(reason)));
if (!mClosed) {
gHttpHandler->ConnMgr()->RemoveActiveTransaction(
this, mClassOfService & nsIClassOfService::Throttleable);
mActivated = false;
}
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
if (reason == NS_BINDING_RETARGETED) {
LOG((" close %p skipped due to ERETARGETED\n", this));

View File

@ -89,6 +89,8 @@ public:
uint64_t topLevelOuterContentWindowId,
nsIAsyncInputStream **responseBody);
void OnActivated(bool h2) override;
// attributes
nsHttpResponseHead *ResponseHead() { return mHaveAllHeaders ? mResponseHead : nullptr; }
nsISupports *SecurityInfo() { return mSecurityInfo; }
@ -307,14 +309,17 @@ private:
Atomic<uint32_t> mCapsToClear;
Atomic<bool, ReleaseAcquire> mResponseIsComplete;
// If true, this transaction was asked to stop receiving the response.
// NOTE: this flag is currently unused. A useful remnant of an old throttling algorithm.
bool mThrottleResponse;
// True iff WriteSegments was called while this transaction should be throttled (stop reading)
// Used to resume read on unblock of reading. Conn manager is responsible for calling back
// to resume reading.
bool mReadingStopped;
// state flags, all logically boolean, but not packed together into a
// bitfield so as to avoid bitfield-induced races. See bug 560579.
bool mClosed;
bool mConnected;
bool mActivated;
bool mActivatedAsH2;
bool mHaveStatusLine;
bool mHaveAllHeaders;
bool mTransactionDone;
@ -373,9 +378,9 @@ public:
// but later can be dispatched via spdy (not subject to rate pacing).
void CancelPacing(nsresult reason);
// Called only on the socket thread. Updates the flag whether the transaction
// should make the underlying connection or session stop reading from the socket.
void ThrottleResponse(bool aThrottle);
// Called by the connetion manager on the socket thread when reading for this
// previously throttled transaction has to be resumed.
void ResumeReading();
private:
bool mSubmittedRatePacing;
@ -383,7 +388,7 @@ private:
bool mSynchronousRatePaceRequest;
nsCOMPtr<nsICancelable> mTokenBucketCancel;
public:
void SetClassOfService(uint32_t cos) { mClassOfService = cos; }
void SetClassOfService(uint32_t cos);
uint32_t ClassOfService() { return mClassOfService; }
private:
uint32_t mClassOfService;