Chromium Code Reviews| Index: net/base/network_throttle_manager_impl.cc |
| diff --git a/net/base/network_throttle_manager_impl.cc b/net/base/network_throttle_manager_impl.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..0985084be8ebd73cb9a3aa6e11f0813f7d468126 |
| --- /dev/null |
| +++ b/net/base/network_throttle_manager_impl.cc |
| @@ -0,0 +1,234 @@ |
| +// Copyright 2016 The Chromium Authors. All rights reserved. |
| +// Use of this source code is governed by a BSD-style license that can be |
| +// found in the LICENSE file. |
| + |
| +#include "net/base/network_throttle_manager_impl.h" |
| + |
| +#include "base/logging.h" |
| +#include "base/message_loop/message_loop.h" |
| +#include "base/time/default_clock.h" |
| + |
| +namespace net { |
| + |
| +const size_t NetworkThrottleManagerImpl::kActiveRequestThrottlingLimit = 2; |
|
Charlie Harrison
2016/07/07 20:17:26
Please document these constants. They frighten me.
Randy Smith (Not in Mondays)
2016/07/08 20:54:23
What could possibly be frightening about a couple
|
| +const double NetworkThrottleManagerImpl::kMedianEstimatorLearningParameter = 1; |
| +const int NetworkThrottleManagerImpl::kInitialMedianInMS = 100; |
| +const int NetworkThrottleManagerImpl::kMedianLifetimeMultiple = 5; |
| + |
| +NetworkThrottleManagerImpl::ThrottleImpl::ThrottleImpl( |
| + bool blocked, |
| + RequestPriority priority, |
| + NetworkThrottleManager::ThrottleDelegate* delegate, |
| + NetworkThrottleManagerImpl* manager, |
| + QueuePointer queue_pointer) |
| + : blocked_(blocked), |
| + priority_(priority), |
| + delegate_(delegate), |
| + manager_(manager), |
| + creation_time_(manager->clock_->Now()), |
| + queue_pointer_(queue_pointer) { |
| + DCHECK(delegate); |
| +} |
| + |
| +NetworkThrottleManagerImpl::ThrottleImpl::~ThrottleImpl() { |
| + manager_->OnThrottleDestroyed(this); |
| +} |
| + |
| +bool NetworkThrottleManagerImpl::ThrottleImpl::IsBlocked() const { |
| + return blocked_; |
| +} |
| + |
| +RequestPriority NetworkThrottleManagerImpl::ThrottleImpl::Priority() const { |
| + return priority_; |
| +} |
| + |
| +void NetworkThrottleManagerImpl::ThrottleImpl::SetPriority( |
| + RequestPriority new_priority) { |
| + RequestPriority old_priority(priority_); |
| + priority_ = new_priority; |
| + manager_->OnThrottlePriorityChanged(this, old_priority, new_priority); |
| +} |
| + |
| +void NetworkThrottleManagerImpl::ThrottleImpl::NotifyUnblocked() { |
| + // This methods should only be called once, and only if the |
| + // current state is blocked. |
| + DCHECK(blocked_); |
| + blocked_ = false; |
| + delegate_->OnThrottleStateChanged(this); |
| +} |
| + |
| +NetworkThrottleManagerImpl::NetworkThrottleManagerImpl() |
| + : lifetime_median_estimate_( |
| + base::TimeDelta::FromMilliseconds(kInitialMedianInMS)), |
| + creation_ordering_set_( |
| + &NetworkThrottleManagerImpl::CompareThrottlesForCreationTime), |
| + num_throttles_blocked_(0u), |
| + num_effective_outstanding_(0u), |
| + outstanding_recomputation_timer_(true, false), |
|
Charlie Harrison
2016/07/07 20:17:26
Do you mind saying:
outstanding_recomputation_time
Randy Smith (Not in Mondays)
2016/07/08 20:54:23
Done.
|
| + clock_(new base::DefaultClock()) { |
|
Charlie Harrison
2016/07/07 20:17:25
This clock uses wall time. Shouldn't you use monot
Randy Smith (Not in Mondays)
2016/07/08 20:54:24
Good catch. Done.
|
| + outstanding_recomputation_timer_.SetTaskRunner( |
| + base::MessageLoop::current()->task_runner()); |
| +} |
| + |
| +NetworkThrottleManagerImpl::~NetworkThrottleManagerImpl() {} |
| + |
| +std::unique_ptr<NetworkThrottleManager::Throttle> |
| +NetworkThrottleManagerImpl::CreateThrottle( |
| + NetworkThrottleManager::ThrottleDelegate* delegate, |
| + RequestPriority priority, |
| + bool ignore_limits) { |
| + bool blocked = (!ignore_limits && priority == THROTTLED && |
| + num_effective_outstanding_ >= kActiveRequestThrottlingLimit); |
| + |
| + std::unique_ptr<NetworkThrottleManagerImpl::ThrottleImpl> throttle( |
| + new ThrottleImpl(blocked, priority, delegate, this, |
| + // Just to have a meaningful default value |
| + unblocked_requests_[priority].end())); |
| + |
| + std::list<ThrottleImpl*>* throttle_list = ListForThrottle(blocked, priority); |
| + |
| + throttle->set_queue_pointer( |
| + throttle_list->insert(throttle_list->end(), throttle.get())); |
| + creation_ordering_set_.insert(throttle.get()); |
| + if (blocked) |
| + ++num_throttles_blocked_; |
| + |
| + RecomputeOutstanding(); |
| + // Can only have increased the outstanding count (decreases due to |
|
Charlie Harrison
2016/07/07 20:17:26
nit: You can fit more words on this comment line.
Randy Smith (Not in Mondays)
2016/07/08 20:54:23
Done but if git format changes it back I'm not goi
Charlie Harrison
2016/07/08 22:36:05
The problem with git cl format is that it happily
|
| + // aging out would be caught by timer) so a MaybeUnthrottle() isn't needed. |
|
Charlie Harrison
2016/07/07 20:17:26
s/MaybeUnthrottle/MaybeUnblock/
Randy Smith (Not in Mondays)
2016/07/08 20:54:23
Ooops. Query replace failure. Done.
|
| + |
| + return std::move(throttle); |
| +} |
| + |
| +void NetworkThrottleManagerImpl::SetClockForTesting( |
| + std::unique_ptr<base::Clock> clock) { |
| + clock_ = std::move(clock); |
| +} |
| + |
| +bool NetworkThrottleManagerImpl::CompareThrottlesForCreationTime( |
| + ThrottleImpl* throttle1, |
| + ThrottleImpl* throttle2) { |
| + return (throttle1->creation_time() < throttle2->creation_time() || |
| + // So different throttles don't look equal to the comparison |
| + // function. |
| + (throttle1->creation_time() == throttle2->creation_time() && |
| + throttle1 < throttle2)); |
| +} |
| + |
| +void NetworkThrottleManagerImpl::OnThrottlePriorityChanged( |
| + NetworkThrottleManagerImpl::ThrottleImpl* throttle, |
| + RequestPriority old_priority, |
| + RequestPriority new_priority) { |
| + ListForThrottle(throttle->IsBlocked(), old_priority) |
|
Charlie Harrison
2016/07/07 20:17:26
Can you either:
1. DCHECK in the throttle impl tha
Randy Smith (Not in Mondays)
2016/07/08 20:54:24
So possibly the fact that you and Matt have called
Charlie Harrison
2016/07/08 22:36:05
You raise a good point, but I think my mind has no
Randy Smith (Not in Mondays)
2016/08/29 19:44:51
Ok, I yield. Done (no-op if priority is the same,
|
| + ->erase(throttle->queue_pointer()); |
| + |
| + std::list<ThrottleImpl*>* new_throttle_list = |
| + ListForThrottle(throttle->IsBlocked(), new_priority); |
| + |
| + throttle->set_queue_pointer( |
| + new_throttle_list->insert(new_throttle_list->end(), throttle)); |
| + |
| + // |MaybeUnblock()|, below, only checks the THROTTLED priority; if |throttle| |
|
Charlie Harrison
2016/07/07 20:17:26
nit: Remove the commas and replace the semicolon w
Randy Smith (Not in Mondays)
2016/07/08 20:54:23
Done.
|
| + // was transitioned away from that priority it may need to be unblocked. |
| + if (old_priority == THROTTLED && new_priority != THROTTLED && |
| + throttle->IsBlocked()) { |
| + // NOTE: This call may result in reentrant calls into |
| + // NetworkThrottleManagerImpl; no state should be assumed to be |
| + // persistent across this call. |
| + --num_throttles_blocked_; |
| + throttle->NotifyUnblocked(); |
| + } |
| + |
| + MaybeUnblock(); |
|
Charlie Harrison
2016/07/07 20:17:26
I don't really like this method name but I can't c
Randy Smith (Not in Mondays)
2016/07/08 20:54:23
Horrible enough to be funny, at least :-}.
I thin
|
| +} |
| + |
| +void NetworkThrottleManagerImpl::OnThrottleDestroyed(ThrottleImpl* throttle) { |
| + // Adjust median estimate. Algorithm from |
| + // http://stackoverflow.com/questions/1309263/rolling-median-algorithm-in-c |
|
Charlie Harrison
2016/07/07 20:17:26
Better link: this paper has the algorithm and expl
Randy Smith (Not in Mondays)
2016/07/08 20:54:23
Wow. Nice reference. I think estimating the roll
|
| + lifetime_median_estimate_ += base::TimeDelta::FromMilliseconds( |
| + kMedianEstimatorLearningParameter * |
| + (clock_->Now() - throttle->creation_time() > lifetime_median_estimate_ |
| + ? 1 |
| + : -1)); |
| + |
| + if (lifetime_median_estimate_ < base::TimeDelta::FromMilliseconds(1)) |
| + lifetime_median_estimate_ = base::TimeDelta::FromMilliseconds(1); |
| + |
| + ListForThrottle(throttle)->erase(throttle->queue_pointer()); |
| + creation_ordering_set_.erase(throttle); |
|
Charlie Harrison
2016/07/07 20:17:26
Can you DCHECK(creation_ordering_set_.erase(thrott
Randy Smith (Not in Mondays)
2016/07/08 20:54:23
Yes, conceptually, but I don't want the erase insi
|
| + if (throttle->IsBlocked()) |
| + --num_throttles_blocked_; |
| + RecomputeOutstanding(); |
| + |
| + // TODO(rdsmith): Via PostTask so there aren't upcalls from within |
| + // destructors? |
| + MaybeUnblock(); |
| +} |
| + |
| +void NetworkThrottleManagerImpl::RecomputeOutstanding() { |
| + num_effective_outstanding_ = |
| + creation_ordering_set_.size() - num_throttles_blocked_; |
| + if (num_effective_outstanding_ == 0) { |
| + outstanding_recomputation_timer_.Stop(); |
| + return; |
| + } |
| + |
| + // The number of throttles that "count" as outstanding is the |
| + // total number of throttles minus all throttles that are |
| + // older than |kMedianLifetimeMultiple * lifetime_median_estimate_|. |
| + base::Time age_horizon(clock_->Now() - |
| + kMedianLifetimeMultiple * lifetime_median_estimate_); |
| + for (auto it = creation_ordering_set_.begin(); |
|
Charlie Harrison
2016/07/07 20:17:26
Can you just use a range for loop here?
Randy Smith (Not in Mondays)
2016/07/08 20:54:24
Fair point; done.
|
| + it != creation_ordering_set_.end(); ++it) { |
| + if ((*it)->creation_time() > age_horizon) { |
| + // The earliest we might need to recompute the outstanding count |
| + // is the amount of time it will take the current element to age |
| + // out of the effective set. |
| + outstanding_recomputation_timer_.Start( |
| + FROM_HERE, (*it)->creation_time() - age_horizon, |
| + // Unretained use of |this| is safe because the timer is |
| + // owned by this object, and will be torn down if this object |
| + // is destroyed. |
| + base::Bind(&NetworkThrottleManagerImpl::RecomputeOutstanding, |
|
Charlie Harrison
2016/07/07 20:17:25
Don't we need to recompute outstanding AND maybe u
Randy Smith (Not in Mondays)
2016/07/08 20:54:24
Good catch. This requires a test case, which I'm
|
| + base::Unretained(this))); |
| + break; |
| + } |
| + |
| + // Only unblocked throttles are considered outstanding, so only those |
| + // throttles need to be subtracted out due to aging. |
| + if (!(*it)->IsBlocked()) |
| + --num_effective_outstanding_; |
| + } |
| +} |
| + |
| +void NetworkThrottleManagerImpl::MaybeUnblock() { |
| + while (num_effective_outstanding_ < kActiveRequestThrottlingLimit && |
| + !blocked_requests_[THROTTLED].empty()) { |
| + ThrottleImpl* to_unblock = blocked_requests_[THROTTLED].front(); |
| + blocked_requests_[THROTTLED].pop_front(); |
| + |
| + to_unblock->set_queue_pointer(unblocked_requests_[THROTTLED].insert( |
| + unblocked_requests_[THROTTLED].end(), to_unblock)); |
| + |
| + ++num_effective_outstanding_; |
| + --num_throttles_blocked_; |
| + |
| + // NOTE: This call may result in reentrant calls into |
| + // NetworkThrottleManagerImpl; no state should be assumed to be |
| + // persistent across this call. |
| + to_unblock->NotifyUnblocked(); |
| + } |
| +} |
| + |
| +std::list<NetworkThrottleManagerImpl::ThrottleImpl*>* |
| +NetworkThrottleManagerImpl::ListForThrottle(bool blocked, |
| + RequestPriority priority) { |
| + return &((blocked ? blocked_requests_ : unblocked_requests_)[priority]); |
| +} |
| + |
| +std::list<NetworkThrottleManagerImpl::ThrottleImpl*>* |
| +NetworkThrottleManagerImpl::ListForThrottle(ThrottleImpl* throttle) { |
| + return ListForThrottle(throttle->IsBlocked(), throttle->priority()); |
| +} |
| + |
| +} // namespace net |