Bug 1386746 - Throttle HTTP response by allowing only small amount of data to read periodically, r=mcmanus

This commit is contained in:
Honza Bambas 2017-11-28 05:37:00 +02:00
Родитель 04a25ecb14
Коммит 1ee6257bc3
7 изменённых файлов: 283 добавлений и 105 удалений

Просмотреть файл

@ -2205,16 +2205,25 @@ pref("network.auth.private-browsing-sso", false);
// Control how throttling of http responses works - number of ms that each
// suspend and resume period lasts (prefs named appropriately)
pref("network.http.throttle.enable", true);
pref("network.http.throttle.version", 1);
// V1 prefs
pref("network.http.throttle.suspend-for", 900);
pref("network.http.throttle.resume-for", 100);
// V2 prefs
pref("network.http.throttle.read-limit-bytes", 8000);
pref("network.http.throttle.read-interval-ms", 500);
// Common prefs
// 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", 1000);
pref("network.http.throttle.hold-time-ms", 800);
// After the last transaction activation or last data chunk response we only
// throttle for this period of time. This prevents comet and unresponsive
// http requests to engage long-standing throttling.
pref("network.http.throttle.time-window", 3000);
pref("network.http.throttle.max-time-ms", 500);
// Give higher priority to requests resulting from a user interaction event
// like click-to-play, image fancy-box zoom, navigation.

Просмотреть файл

@ -116,10 +116,13 @@ nsHttpConnectionMgr::nsHttpConnectionMgr()
, mMaxPersistConnsPerProxy(0)
, mMaxRequestDelay(0)
, mThrottleEnabled(false)
, mThrottleVersion(2)
, mThrottleSuspendFor(0)
, mThrottleResumeFor(0)
, mThrottleResumeIn(0)
, mThrottleTimeWindow(0)
, mThrottleReadLimit(0)
, mThrottleReadInterval(0)
, mThrottleHoldTime(0)
, mThrottleMaxTime(0)
, mIsShuttingDown(false)
, mNumActiveConns(0)
, mNumIdleConns(0)
@ -174,10 +177,13 @@ nsHttpConnectionMgr::Init(uint16_t maxUrgentExcessiveConns,
uint16_t maxPersistConnsPerProxy,
uint16_t maxRequestDelay,
bool throttleEnabled,
uint32_t throttleVersion,
uint32_t throttleSuspendFor,
uint32_t throttleResumeFor,
uint32_t throttleResumeIn,
uint32_t throttleTimeWindow)
uint32_t throttleReadLimit,
uint32_t throttleReadInterval,
uint32_t throttleHoldTime,
uint32_t throttleMaxTime)
{
LOG(("nsHttpConnectionMgr::Init\n"));
@ -191,10 +197,13 @@ nsHttpConnectionMgr::Init(uint16_t maxUrgentExcessiveConns,
mMaxRequestDelay = maxRequestDelay;
mThrottleEnabled = throttleEnabled;
mThrottleVersion = throttleVersion;
mThrottleSuspendFor = throttleSuspendFor;
mThrottleResumeFor = throttleResumeFor;
mThrottleResumeIn = throttleResumeIn;
mThrottleTimeWindow = TimeDuration::FromMilliseconds(throttleTimeWindow);
mThrottleReadLimit = throttleReadLimit;
mThrottleReadInterval = throttleReadInterval;
mThrottleHoldTime = throttleHoldTime;
mThrottleMaxTime = TimeDuration::FromMilliseconds(throttleMaxTime);
mIsShuttingDown = false;
}
@ -2832,11 +2841,17 @@ nsHttpConnectionMgr::OnMsgUpdateParam(int32_t inParam, ARefBase *)
case THROTTLING_RESUME_FOR:
mThrottleResumeFor = value;
break;
case THROTTLING_RESUME_IN:
mThrottleResumeIn = value;
case THROTTLING_READ_LIMIT:
mThrottleReadLimit = value;
break;
case THROTTLING_TIME_WINDOW:
mThrottleTimeWindow = TimeDuration::FromMilliseconds(value);
case THROTTLING_READ_INTERVAL:
mThrottleReadInterval = value;
break;
case THROTTLING_HOLD_TIME:
mThrottleHoldTime = value;
break;
case THROTTLING_MAX_TIME:
mThrottleMaxTime = TimeDuration::FromMilliseconds(value);
break;
default:
NS_NOTREACHED("unexpected parameter name");
@ -2952,7 +2967,7 @@ void nsHttpConnectionMgr::TouchThrottlingTimeWindow(bool aEnsureTicker)
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
mThrottlingWindowEndsAt = TimeStamp::NowLoRes() + mThrottleTimeWindow;
mThrottlingWindowEndsAt = TimeStamp::NowLoRes() + mThrottleMaxTime;
if (!mThrottleTicker &&
MOZ_LIKELY(aEnsureTicker) && MOZ_LIKELY(mThrottleEnabled)) {
@ -3091,11 +3106,13 @@ nsHttpConnectionMgr::RemoveActiveTransaction(nsHttpTransaction * aTrans,
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 (mThrottleVersion == 1) {
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) {
@ -3140,22 +3157,36 @@ nsHttpConnectionMgr::UpdateActiveTransaction(nsHttpTransaction * aTrans)
{
LOG(("nsHttpConnectionMgr::UpdateActiveTransaction ENTER t=%p", aTrans));
AddActiveTransaction(aTrans);
// First remove then add. In case of a download that is the only active
// transaction and has just been marked as download (goes unthrottled to
// throttled), adding first would cause it to be throttled for first few
// milliseconds - becuause it would appear as if there were both throttled
// and unthrottled transactions at the time.
Maybe<bool> reversed;
reversed.emplace(!aTrans->EligibleForThrottling());
RemoveActiveTransaction(aTrans, reversed);
AddActiveTransaction(aTrans);
LOG(("nsHttpConnectionMgr::UpdateActiveTransaction EXIT t=%p", aTrans));
}
bool
nsHttpConnectionMgr::ShouldStopReading(nsHttpTransaction * aTrans)
nsHttpConnectionMgr::ShouldThrottle(nsHttpTransaction * aTrans)
{
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
if (!mThrottlingInhibitsReading || !mThrottleEnabled) {
return false;
LOG(("nsHttpConnectionMgr::ShouldThrottle trans=%p", aTrans));
if (mThrottleVersion == 1) {
if (!mThrottlingInhibitsReading || !mThrottleEnabled) {
return false;
}
} else {
if (!mThrottleEnabled) {
return false;
}
}
uint64_t tabId = aTrans->TopLevelOuterContentWindowId();
@ -3168,54 +3199,66 @@ nsHttpConnectionMgr::ShouldStopReading(nsHttpTransaction * aTrans)
// Chrome initiated and unidentified transactions just respect
// their throttle flag, when something for the active tab is happening.
// This also includes downloads.
LOG((" active tab loads, trans is tab-less, throttled=%d", throttled));
return throttled;
}
if (!forActiveTab) {
// This is a background tab request, we want them to always throttle
// when there are transactions running for the ative tab.
LOG((" active tab loads, trans not of the active tab"));
return true;
}
if (mActiveTabUnthrottledTransactionsExist) {
// Unthrottled transactions for the active tab take precedence
LOG((" active tab loads unthrottled, trans throttled=%d", throttled));
return throttled;
}
LOG((" trans for active tab, don't throttle"));
return false;
}
MOZ_ASSERT(!forActiveTab);
if (mDelayedResumeReadTimer) {
// If this timer exists, background transactions are scheduled to be woken
// after a delay, hence leave them asleep.
return true;
}
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.)
LOG((" backround tab(s) load unthrottled, trans throttled=%d", throttled));
return throttled;
}
// There are only unthrottled transactions for background tabs: don't throttle.
LOG((" backround tab(s) load throttled, don't throttle"));
return false;
}();
if (forActiveTab && !stop) {
// This active-tab transaction is allowed to read even though
// we are in the middle of "stop reading" interval. Hence,
// This is an active-tab transaction and is allowed to read. Hence,
// prolong the throttle time window to make sure all 'lower-decks'
// transactions will actually throttle.
TouchThrottlingTimeWindow();
return false;
}
// Only stop reading when in the 3 seconds throttle time window.
// This window is prolonged (restarted) by a call to TouchThrottlingTimeWindow.
return stop && InThrottlingTimeWindow();
// Only stop reading when in the configured throttle max-time (aka time window).
// This window is prolonged (restarted) by a call to TouchThrottlingTimeWindow
// called on new transaction activation or on receive of response bytes of an
// active tab transaction.
bool inWindow = InThrottlingTimeWindow();
LOG((" stop=%d, in-window=%d, delayed-bck-timer=%d",
stop, inWindow, !!mDelayedResumeReadTimer));
if (!forActiveTab) {
// If the delayed background resume timer exists, background transactions are
// scheduled to be woken after a delay, hence leave them throttled.
inWindow = inWindow || mDelayedResumeReadTimer;
}
return stop && inWindow;
}
bool nsHttpConnectionMgr::IsConnEntryUnderPressure(nsHttpConnectionInfo *connInfo)
@ -3276,12 +3319,16 @@ nsHttpConnectionMgr::EnsureThrottleTickerIfNeeded()
return;
}
MOZ_ASSERT(!mThrottlingInhibitsReading);
mThrottleTicker = NS_NewTimer();
if (mThrottleTicker) {
mThrottleTicker->Init(this, mThrottleSuspendFor, nsITimer::TYPE_ONE_SHOT);
mThrottlingInhibitsReading = true;
if (mThrottleVersion == 1) {
MOZ_ASSERT(!mThrottlingInhibitsReading);
mThrottleTicker->Init(this, mThrottleSuspendFor, nsITimer::TYPE_ONE_SHOT);
mThrottlingInhibitsReading = true;
} else {
mThrottleTicker->Init(this, mThrottleReadInterval, nsITimer::TYPE_ONE_SHOT);
}
}
LogActiveTransactions('^');
@ -3304,7 +3351,10 @@ nsHttpConnectionMgr::DestroyThrottleTicker()
LOG(("nsHttpConnectionMgr::DestroyThrottleTicker"));
mThrottleTicker->Cancel();
mThrottleTicker = nullptr;
mThrottlingInhibitsReading = false;
if (mThrottleVersion == 1) {
mThrottlingInhibitsReading = false;
}
LogActiveTransactions('v');
}
@ -3314,27 +3364,46 @@ nsHttpConnectionMgr::ThrottlerTick()
{
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
mThrottlingInhibitsReading = !mThrottlingInhibitsReading;
if (mThrottleVersion == 1) {
mThrottlingInhibitsReading = !mThrottlingInhibitsReading;
LOG(("nsHttpConnectionMgr::ThrottlerTick inhibit=%d", mThrottlingInhibitsReading));
LOG(("nsHttpConnectionMgr::ThrottlerTick inhibit=%d", mThrottlingInhibitsReading));
// If there are only background transactions to be woken after a delay, keep
// the ticker so that we woke them only for the resume-for interval and then
// throttle them again until the background-resume delay passes.
if (!mThrottlingInhibitsReading &&
!mDelayedResumeReadTimer &&
(!IsThrottleTickerNeeded() || !InThrottlingTimeWindow())) {
LOG((" last tick"));
mThrottleTicker = nullptr;
}
// If there are only background transactions to be woken after a delay, keep
// the ticker so that we woke them only for the resume-for interval and then
// throttle them again until the background-resume delay passes.
if (!mThrottlingInhibitsReading &&
!mDelayedResumeReadTimer &&
(!IsThrottleTickerNeeded() || !InThrottlingTimeWindow())) {
LOG((" last tick"));
mThrottleTicker = nullptr;
}
if (mThrottlingInhibitsReading) {
if (mThrottleTicker) {
mThrottleTicker->Init(this, mThrottleSuspendFor, nsITimer::TYPE_ONE_SHOT);
if (mThrottlingInhibitsReading) {
if (mThrottleTicker) {
mThrottleTicker->Init(this, mThrottleSuspendFor, nsITimer::TYPE_ONE_SHOT);
}
} else {
if (mThrottleTicker) {
mThrottleTicker->Init(this, mThrottleResumeFor, nsITimer::TYPE_ONE_SHOT);
}
ResumeReadOf(mActiveTransactions[false], true);
ResumeReadOf(mActiveTransactions[true]);
}
} else {
LOG(("nsHttpConnectionMgr::ThrottlerTick"));
// If there are only background transactions to be woken after a delay, keep
// the ticker so that we still keep the low read limit for that time.
if (!mDelayedResumeReadTimer &&
(!IsThrottleTickerNeeded() || !InThrottlingTimeWindow())) {
LOG((" last tick"));
mThrottleTicker = nullptr;
}
if (mThrottleTicker) {
mThrottleTicker->Init(this, mThrottleResumeFor, nsITimer::TYPE_ONE_SHOT);
mThrottleTicker->Init(this, mThrottleReadInterval, nsITimer::TYPE_ONE_SHOT);
}
ResumeReadOf(mActiveTransactions[false], true);
@ -3345,13 +3414,25 @@ nsHttpConnectionMgr::ThrottlerTick()
void
nsHttpConnectionMgr::DelayedResumeBackgroundThrottledTransactions()
{
if (mDelayedResumeReadTimer) {
return;
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
if (mThrottleVersion == 1) {
if (mDelayedResumeReadTimer) {
return;
}
} else {
// If the mThrottleTicker doesn't exist, there is nothing currently
// being throttled. Hence, don't invoke the hold time interval.
// This is called also when a single download transaction becomes
// marked as throttleable. We would otherwise block it unnecessarily.
if (mDelayedResumeReadTimer || !mThrottleTicker) {
return;
}
}
LOG(("nsHttpConnectionMgr::DelayedResumeBackgroundThrottledTransactions"));
NS_NewTimerWithObserver(getter_AddRefs(mDelayedResumeReadTimer),
this, mThrottleResumeIn, nsITimer::TYPE_ONE_SHOT);
this, mThrottleHoldTime, nsITimer::TYPE_ONE_SHOT);
}
void
@ -3464,7 +3545,6 @@ nsHttpConnectionMgr::OnMsgUpdateCurrentTopLevelOuterContentWindowId(
}
bool activeTabWasLoading = mActiveTabTransactionsExist;
bool activeTabIdChanged = mCurrentTopLevelOuterContentWindowId != winId;
uint64_t previousWindowId = mCurrentTopLevelOuterContentWindowId;
mCurrentTopLevelOuterContentWindowId = winId;
@ -3479,17 +3559,15 @@ nsHttpConnectionMgr::OnMsgUpdateCurrentTopLevelOuterContentWindowId(
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;
// 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 (!mActiveTabUnthrottledTransactionsExist) {
transactions = mActiveTransactions[true].Get(mCurrentTopLevelOuterContentWindowId);
}
mActiveTabTransactionsExist = !!transactions;
if (transactions) {
// This means there are some transactions for this newly activated tab, resume them
@ -3513,8 +3591,8 @@ nsHttpConnectionMgr::OnMsgUpdateCurrentTopLevelOuterContentWindowId(
}
if (!mActiveTransactions[true].IsEmpty()) {
LOG((" delayed resuming throttled background transactions"));
DelayedResumeBackgroundThrottledTransactions();
LOG((" resuming throttled background transactions"));
ResumeReadOf(mActiveTransactions[true]);
return;
}

Просмотреть файл

@ -60,8 +60,10 @@ public:
THROTTLING_ENABLED,
THROTTLING_SUSPEND_FOR,
THROTTLING_RESUME_FOR,
THROTTLING_RESUME_IN,
THROTTLING_TIME_WINDOW
THROTTLING_READ_LIMIT,
THROTTLING_READ_INTERVAL,
THROTTLING_HOLD_TIME,
THROTTLING_MAX_TIME
};
//-------------------------------------------------------------------------
@ -76,10 +78,13 @@ public:
uint16_t maxPersistentConnectionsPerProxy,
uint16_t maxRequestDelay,
bool throttleEnabled,
uint32_t throttleVersion,
uint32_t throttleSuspendFor,
uint32_t throttleResumeFor,
uint32_t throttleResumeIn,
uint32_t throttleTimeWindow);
uint32_t throttleReadLimit,
uint32_t throttleReadInterval,
uint32_t throttleHoldTime,
uint32_t throttleMaxTime);
MOZ_MUST_USE nsresult Shutdown();
//-------------------------------------------------------------------------
@ -225,19 +230,19 @@ public:
void UpdateActiveTransaction(nsHttpTransaction* aTrans);
// 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);
// should limit reading its reponse data. There are various conditions this
// methods evaluates. If called by an active-tab non-throttled transaction,
// the throttling window time will be prolonged.
bool ShouldThrottle(nsHttpTransaction* aTrans);
// prolongs the throttling time window to now + the window preferred size
// prolongs the throttling time window to now + the window preferred delay.
// called when:
// - any transaction is activated
// - or when a currently unthrottled transaction for the active window receives data
void TouchThrottlingTimeWindow(bool aEnsureTicker = true);
// return true iff the connection has pending transactions for the active tab.
// it's mainly used to disallow throttling (stop reading) of a response
// it's mainly used to disallow throttling (limit reading) of a response
// belonging to the same conn info to free up a connection ASAP.
// NOTE: relatively expensive to call, there are two hashtable lookups.
bool IsConnEntryUnderPressure(nsHttpConnectionInfo*);
@ -531,10 +536,13 @@ private:
uint16_t mMaxPersistConnsPerProxy;
uint16_t mMaxRequestDelay; // in seconds
bool mThrottleEnabled;
uint32_t mThrottleVersion;
uint32_t mThrottleSuspendFor;
uint32_t mThrottleResumeFor;
uint32_t mThrottleResumeIn;
TimeDuration mThrottleTimeWindow;
uint32_t mThrottleReadLimit;
uint32_t mThrottleReadInterval;
uint32_t mThrottleHoldTime;
TimeDuration mThrottleMaxTime;
Atomic<bool, mozilla::Relaxed> mIsShuttingDown;
//-------------------------------------------------------------------------
@ -717,6 +725,7 @@ private:
// mActiveTransactions[0] are all unthrottled transactions, mActiveTransactions[1] throttled.
nsClassHashtable<nsUint64HashKey, nsTArray<RefPtr<nsHttpTransaction>>> mActiveTransactions[2];
// V1 specific
// Whether we are inside the "stop reading" interval, altered by the throttle ticker
bool mThrottlingInhibitsReading;
@ -729,10 +738,17 @@ private:
// The method also unschedules the delayed resume of background tabs timer
// if the ticker was about to be scheduled.
void EnsureThrottleTickerIfNeeded();
// V1:
// Drops also the mThrottlingInhibitsReading flag. Immediate or delayed resume
// of currently throttled transactions is not affected by this method.
// V2:
// Immediate or delayed resume of currently throttled transactions is not
// affected by this method.
void DestroyThrottleTicker();
// V1:
// Handler for the ticker: alters the mThrottlingInhibitsReading flag.
// V2:
// Handler for the ticker: calls ResumeReading() for all throttled transactions.
void ThrottlerTick();
// mechanism to delay immediate resume of background tabs and chrome initiated

Просмотреть файл

@ -211,10 +211,13 @@ nsHttpHandler::nsHttpHandler()
, mMaxPersistentConnectionsPerServer(2)
, mMaxPersistentConnectionsPerProxy(4)
, mThrottleEnabled(true)
, mThrottleVersion(2)
, mThrottleSuspendFor(3000)
, mThrottleResumeFor(200)
, mThrottleResumeIn(400)
, mThrottleTimeWindow(3000)
, mThrottleReadLimit(8000)
, mThrottleReadInterval(500)
, mThrottleHoldTime(600)
, mThrottleMaxTime(3000)
, mUrgentStartEnabled(true)
, mTailBlockingEnabled(true)
, mTailDelayQuantum(600)
@ -613,10 +616,13 @@ nsHttpHandler::InitConnectionMgr()
mMaxPersistentConnectionsPerProxy,
mMaxRequestDelay,
mThrottleEnabled,
mThrottleVersion,
mThrottleSuspendFor,
mThrottleResumeFor,
mThrottleResumeIn,
mThrottleTimeWindow);
mThrottleReadLimit,
mThrottleReadInterval,
mThrottleHoldTime,
mThrottleMaxTime);
return rv;
}
@ -1639,6 +1645,11 @@ nsHttpHandler::PrefsChanged(nsIPrefBranch *prefs, const char *pref)
}
}
if (PREF_CHANGED(HTTP_PREF("throttle.version"))) {
rv = prefs->GetIntPref(HTTP_PREF("throttle.version"), &val);
mThrottleVersion = (uint32_t)clamped(val, 1, 2);
}
if (PREF_CHANGED(HTTP_PREF("throttle.suspend-for"))) {
rv = prefs->GetIntPref(HTTP_PREF("throttle.suspend-for"), &val);
mThrottleSuspendFor = (uint32_t)clamped(val, 0, 120000);
@ -1657,21 +1668,39 @@ nsHttpHandler::PrefsChanged(nsIPrefBranch *prefs, const char *pref)
}
}
if (PREF_CHANGED(HTTP_PREF("throttle.resume-background-in"))) {
rv = prefs->GetIntPref(HTTP_PREF("throttle.resume-background-in"), &val);
mThrottleResumeIn = (uint32_t)clamped(val, 0, 120000);
if (PREF_CHANGED(HTTP_PREF("throttle.read-limit-bytes"))) {
rv = prefs->GetIntPref(HTTP_PREF("throttle.read-limit-bytes"), &val);
mThrottleReadLimit = (uint32_t)clamped(val, 0, 500000);
if (NS_SUCCEEDED(rv) && mConnMgr) {
Unused << mConnMgr->UpdateParam(nsHttpConnectionMgr::THROTTLING_RESUME_IN,
mThrottleResumeIn);
Unused << mConnMgr->UpdateParam(nsHttpConnectionMgr::THROTTLING_READ_LIMIT,
mThrottleReadLimit);
}
}
if (PREF_CHANGED(HTTP_PREF("throttle.time-window"))) {
rv = prefs->GetIntPref(HTTP_PREF("throttle.time-window"), &val);
mThrottleTimeWindow = (uint32_t)clamped(val, 0, 120000);
if (PREF_CHANGED(HTTP_PREF("throttle.read-interval-ms"))) {
rv = prefs->GetIntPref(HTTP_PREF("throttle.read-interval-ms"), &val);
mThrottleReadInterval = (uint32_t)clamped(val, 0, 120000);
if (NS_SUCCEEDED(rv) && mConnMgr) {
Unused << mConnMgr->UpdateParam(nsHttpConnectionMgr::THROTTLING_READ_INTERVAL,
mThrottleReadInterval);
}
}
if (PREF_CHANGED(HTTP_PREF("throttle.hold-time-ms"))) {
rv = prefs->GetIntPref(HTTP_PREF("throttle.hold-time-ms"), &val);
mThrottleHoldTime = (uint32_t)clamped(val, 0, 120000);
if (NS_SUCCEEDED(rv) && mConnMgr) {
Unused << mConnMgr->UpdateParam(nsHttpConnectionMgr::THROTTLING_HOLD_TIME,
mThrottleHoldTime);
}
}
if (PREF_CHANGED(HTTP_PREF("throttle.max-time-ms"))) {
rv = prefs->GetIntPref(HTTP_PREF("throttle.max-time-ms"), &val);
mThrottleMaxTime = (uint32_t)clamped(val, 0, 120000);
if (NS_SUCCEEDED(rv) && mConnMgr) {
Unused << mConnMgr->UpdateParam(nsHttpConnectionMgr::THROTTLING_TIME_WINDOW,
mThrottleTimeWindow);
Unused << mConnMgr->UpdateParam(nsHttpConnectionMgr::THROTTLING_MAX_TIME,
mThrottleMaxTime);
}
}

Просмотреть файл

@ -145,6 +145,8 @@ public:
}
uint32_t TailBlockingDelayMax() { return mTailDelayMax; }
uint32_t ThrottlingReadLimit() { return mThrottleVersion == 1 ? 0 : mThrottleReadLimit; }
// TCP Keepalive configuration values.
// Returns true if TCP keepalive should be enabled for short-lived conns.
@ -484,10 +486,13 @@ private:
uint8_t mMaxPersistentConnectionsPerProxy;
bool mThrottleEnabled;
uint32_t mThrottleVersion;
uint32_t mThrottleSuspendFor;
uint32_t mThrottleResumeFor;
uint32_t mThrottleResumeIn;
uint32_t mThrottleTimeWindow;
uint32_t mThrottleReadLimit;
uint32_t mThrottleReadInterval;
uint32_t mThrottleHoldTime;
uint32_t mThrottleMaxTime;
bool mUrgentStartEnabled;
bool mTailBlockingEnabled;

Просмотреть файл

@ -104,6 +104,7 @@ nsHttpTransaction::nsHttpTransaction()
, mHttpVersion(NS_HTTP_VERSION_UNKNOWN)
, mHttpResponseCode(0)
, mCurrentHttpResponseHeaderSize(0)
, mThrottlingReadAllowance(THROTTLE_NO_LIMIT)
, mCapsToClear(0)
, mResponseIsComplete(false)
, mReadingStopped(false)
@ -166,6 +167,11 @@ void nsHttpTransaction::ResumeReading()
LOG(("nsHttpTransaction::ResumeReading %p", this));
mReadingStopped = false;
// This with either reengage the limit when still throttled in WriteSegments or
// simply reset to allow unlimeted reading again.
mThrottlingReadAllowance = THROTTLE_NO_LIMIT;
if (mConnection) {
nsresult rv = mConnection->ResumeRecv();
if (NS_FAILED(rv)) {
@ -853,16 +859,16 @@ nsHttpTransaction::WritePipeSegment(nsIOutputStream *stream,
return rv; // failure code only stops WriteSegments; it is not propagated.
}
bool nsHttpTransaction::ShouldStopReading()
bool nsHttpTransaction::ShouldThrottle()
{
if (mActivatedAsH2) {
// Throttling feature is now disabled for http/2 transactions
// because of bug 1367861. The logic around mActivatedAsH2
// will be removed when that is fixed.
//
// Calling ShouldStopReading on the manager just to make sure
// Calling ShouldThrottle on the manager just to make sure
// the throttling time window is correctly updated by this transaction.
Unused << gHttpHandler->ConnMgr()->ShouldStopReading(this);
Unused << gHttpHandler->ConnMgr()->ShouldThrottle(this);
return false;
}
@ -876,21 +882,25 @@ bool nsHttpTransaction::ShouldStopReading()
return false;
}
if (!gHttpHandler->ConnMgr()->ShouldStopReading(this)) {
if (!gHttpHandler->ConnMgr()->ShouldThrottle(this)) {
// We are not obligated to throttle
return false;
}
if (mContentRead < 16000) {
// Let the first bytes go, it may also well be all the content we get
LOG(("nsHttpTransaction::ShouldStopReading too few content (%" PRIi64 ") this=%p", mContentRead, this));
LOG(("nsHttpTransaction::ShouldThrottle too few content (%" PRIi64 ") this=%p", mContentRead, this));
return false;
}
if (gHttpHandler->ConnMgr()->IsConnEntryUnderPressure(mConnInfo)) {
LOG(("nsHttpTransaction::ShouldStopReading entry pressure this=%p", this));
if (!(mClassOfService & nsIClassOfService::Throttleable) &&
gHttpHandler->ConnMgr()->IsConnEntryUnderPressure(mConnInfo)) {
LOG(("nsHttpTransaction::ShouldThrottle entry pressure this=%p", this));
// This is expensive to check (two hashtable lookups) but may help
// freeing connections for active tab transactions.
// Checking this only for transactions that are not explicitly marked
// as throttleable because trackers and (specially) downloads should
// keep throttling even under pressure.
return false;
}
@ -909,7 +919,16 @@ nsHttpTransaction::WriteSegments(nsAHttpSegmentWriter *writer,
return NS_SUCCEEDED(mStatus) ? NS_BASE_STREAM_CLOSED : mStatus;
}
if (ShouldStopReading()) {
if (ShouldThrottle()) {
if (mThrottlingReadAllowance == THROTTLE_NO_LIMIT) { // no limit set
// V1: ThrottlingReadLimit() returns 0
mThrottlingReadAllowance = gHttpHandler->ThrottlingReadLimit();
}
} else {
mThrottlingReadAllowance = THROTTLE_NO_LIMIT; // don't limit
}
if (mThrottlingReadAllowance == 0) { // depleted
if (gHttpHandler->ConnMgr()->CurrentTopLevelOuterContentWindowId() !=
mTopLevelOuterContentWindowId) {
nsHttp::NotifyActiveTabLoadOptimization();
@ -937,6 +956,12 @@ nsHttpTransaction::WriteSegments(nsAHttpSegmentWriter *writer,
return NS_ERROR_UNEXPECTED;
}
if (mThrottlingReadAllowance > 0) {
LOG(("nsHttpTransaction::WriteSegments %p limiting read from %u to %d",
this, count, mThrottlingReadAllowance));
count = std::min(count, static_cast<uint32_t>(mThrottlingReadAllowance));
}
nsresult rv = mPipeOut->WriteSegments(WritePipeSegment, this, count, countWritten);
mWriter = nullptr;
@ -963,6 +988,9 @@ nsHttpTransaction::WriteSegments(nsAHttpSegmentWriter *writer,
NS_ERROR("no socket thread event target");
rv = NS_ERROR_UNEXPECTED;
}
} else if (mThrottlingReadAllowance > 0 && NS_SUCCEEDED(rv)) {
MOZ_ASSERT(count >= *countWritten);
mThrottlingReadAllowance -= *countWritten;
}
return rv;

Просмотреть файл

@ -229,7 +229,7 @@ private:
// Called from WriteSegments. Checks for conditions whether to throttle reading
// the content. When this returns true, WriteSegments returns WOULD_BLOCK.
bool ShouldStopReading();
bool ShouldThrottle();
private:
class UpdateSecurityCallbacks : public Runnable
@ -311,6 +311,19 @@ private:
uint32_t mCurrentHttpResponseHeaderSize;
int32_t const THROTTLE_NO_LIMIT = -1;
// This can have 3 possible values:
// * THROTTLE_NO_LIMIT - this means the transaction is not in any way limited
// to read the response, this is the default
// * a positive number - a limit is set because the transaction is obligated
// to throttle the response read, this is decresed with
// every piece of data the transaction receives
// * zero - when the transaction depletes the limit for reading, this makes it
// stop reading and return WOULD_BLOCK from WriteSegments; transaction
// then waits for a call of ResumeReading that resets this member back
// to THROTTLE_NO_LIMIT
int32_t mThrottlingReadAllowance;
// mCapsToClear holds flags that should be cleared in mCaps, e.g. unset
// NS_HTTP_REFRESH_DNS when DNS refresh request has completed to avoid
// redundant requests on the network. The member itself is atomic, but