Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright 2016 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #include "net/base/network_throttle_manager_impl.h" | |
| 6 | |
| 7 #include <algorithm> | |
| 8 | |
| 9 #include "base/logging.h" | |
| 10 #include "base/message_loop/message_loop.h" | |
| 11 #include "base/time/default_tick_clock.h" | |
| 12 | |
| 13 namespace net { | |
| 14 | |
| 15 const size_t NetworkThrottleManagerImpl::kActiveRequestThrottlingLimit = 2; | |
| 16 const int NetworkThrottleManagerImpl::kMedianLifetimeMultiple = 5; | |
| 17 | |
| 18 // Initial estimate based on the median in the | |
| 19 // Net.RequestTime2.Success histogram, excluding cached results by eye. | |
| 20 const int NetworkThrottleManagerImpl::kInitialMedianInMs = 400; | |
| 21 | |
| 22 class NetworkThrottleManagerImpl::ThrottleImpl | |
| 23 : public NetworkThrottleManager::Throttle { | |
| 24 public: | |
| 25 // Allowed state transitions are BLOCKED -> OUTSTANDING -> AGED. | |
| 26 // Throttles may be created in the BLOCKED or OUTSTANDING states. | |
| 27 enum State { | |
| 28 // Not allowed to proceed by manager. | |
| 29 BLOCKED, | |
| 30 | |
| 31 // Allowed to proceed, counts as an "outstanding" request for | |
| 32 // manager accounting purposes. | |
| 33 OUTSTANDING, | |
| 34 | |
| 35 // Old enough to not count as "outstanding" anymore for | |
| 36 // manager accounting purposes. | |
| 37 AGED | |
| 38 }; | |
| 39 | |
| 40 using QueuePointer = NetworkThrottleManagerImpl::ThrottleList::iterator; | |
| 41 | |
| 42 // Caller must arrange that |*delegate| and |*manager| outlive | |
| 43 // the ThrottleImpl class. | |
| 44 ThrottleImpl(bool blocked, | |
| 45 RequestPriority priority, | |
| 46 ThrottleDelegate* delegate, | |
| 47 NetworkThrottleManagerImpl* manager, | |
| 48 QueuePointer queue_pointer); | |
| 49 | |
| 50 ~ThrottleImpl() override; | |
| 51 | |
| 52 // Throttle: | |
| 53 bool IsBlocked() const override; | |
| 54 RequestPriority Priority() const override; | |
| 55 void SetPriority(RequestPriority priority) override; | |
| 56 | |
| 57 // Change the throttle's state to AGED. The previous | |
| 58 // state must be OUTSTANDING. | |
| 59 void SetAged(); | |
|
mmenke
2016/09/26 17:31:44
SetAged / NotifyUnblocked should probably be next
Randy Smith (Not in Mondays)
2016/10/03 01:06:48
Moved but didn't change naming, for basically the
| |
| 60 State state() const { return state_; } | |
| 61 RequestPriority priority() const { return priority_; } | |
| 62 QueuePointer queue_pointer() const { return queue_pointer_; } | |
| 63 void set_queue_pointer(const QueuePointer& pointer) { | |
| 64 queue_pointer_ = pointer; | |
| 65 } | |
| 66 void set_start_time(base::TimeTicks start_time) { start_time_ = start_time; } | |
| 67 base::TimeTicks start_time() const { return start_time_; } | |
|
mmenke
2016/09/26 17:31:43
Suggest a couple blank lines in this block, non-ov
Randy Smith (Not in Mondays)
2016/10/03 01:06:48
Done.
| |
| 68 | |
| 69 // Note that this call calls the delegate, and hence | |
| 70 // may result in re-entrant calls into the manager or | |
| 71 // ThrottleImpl. The manager should not rely on | |
| 72 // any state other than its own existence being persistent | |
| 73 // across this call. | |
| 74 void NotifyUnblocked(); | |
| 75 | |
| 76 private: | |
| 77 State state_; | |
| 78 RequestPriority priority_; | |
| 79 ThrottleDelegate* const delegate_; | |
| 80 NetworkThrottleManagerImpl* const manager_; | |
| 81 | |
| 82 base::TimeTicks start_time_; | |
| 83 | |
| 84 // For deletion from owning queue. | |
| 85 QueuePointer queue_pointer_; | |
| 86 | |
| 87 DISALLOW_COPY_AND_ASSIGN(ThrottleImpl); | |
| 88 }; | |
| 89 | |
| 90 NetworkThrottleManagerImpl::ThrottleImpl::ThrottleImpl( | |
| 91 bool blocked, | |
| 92 RequestPriority priority, | |
| 93 NetworkThrottleManager::ThrottleDelegate* delegate, | |
| 94 NetworkThrottleManagerImpl* manager, | |
| 95 QueuePointer queue_pointer) | |
| 96 : state_(blocked ? BLOCKED : OUTSTANDING), | |
| 97 priority_(priority), | |
| 98 delegate_(delegate), | |
| 99 manager_(manager), | |
| 100 queue_pointer_(queue_pointer) { | |
| 101 DCHECK(delegate); | |
| 102 if (!blocked) | |
| 103 start_time_ = manager->tick_clock_->NowTicks(); | |
| 104 } | |
| 105 | |
| 106 NetworkThrottleManagerImpl::ThrottleImpl::~ThrottleImpl() { | |
| 107 manager_->OnThrottleDestroyed(this); | |
| 108 } | |
| 109 | |
| 110 bool NetworkThrottleManagerImpl::ThrottleImpl::IsBlocked() const { | |
| 111 return state_ == BLOCKED; | |
| 112 } | |
| 113 | |
| 114 RequestPriority NetworkThrottleManagerImpl::ThrottleImpl::Priority() const { | |
| 115 return priority_; | |
| 116 } | |
| 117 | |
| 118 void NetworkThrottleManagerImpl::ThrottleImpl::SetPriority( | |
| 119 RequestPriority new_priority) { | |
| 120 RequestPriority old_priority(priority_); | |
| 121 if (old_priority == new_priority) | |
| 122 return; | |
| 123 priority_ = new_priority; | |
| 124 manager_->OnThrottlePriorityChanged(this, old_priority, new_priority); | |
| 125 } | |
| 126 | |
| 127 void NetworkThrottleManagerImpl::ThrottleImpl::SetAged() { | |
| 128 DCHECK_EQ(OUTSTANDING, state_); | |
| 129 state_ = AGED; | |
| 130 } | |
| 131 | |
| 132 void NetworkThrottleManagerImpl::ThrottleImpl::NotifyUnblocked() { | |
| 133 // This methods should only be called once, and only if the | |
| 134 // current state is blocked. | |
| 135 DCHECK_EQ(BLOCKED, state_); | |
| 136 state_ = OUTSTANDING; | |
| 137 delegate_->OnThrottleStateChanged(this); | |
| 138 } | |
| 139 | |
| 140 NetworkThrottleManagerImpl::NetworkThrottleManagerImpl() | |
| 141 : lifetime_median_estimate_(PercentileEstimator::kMedianPercentile, | |
| 142 kInitialMedianInMs), | |
| 143 outstanding_recomputation_timer_(false /* retain_user_task */, | |
| 144 false /* is_repeating */), | |
| 145 outstanding_throttles_( | |
| 146 &NetworkThrottleManagerImpl::CreationTimeSetCompare), | |
| 147 first_outstanding_throttle_(nullptr), | |
| 148 tick_clock_(new base::DefaultTickClock()), | |
| 149 weak_ptr_factory_(this) { | |
| 150 outstanding_recomputation_timer_.SetTaskRunner( | |
| 151 base::MessageLoop::current()->task_runner()); | |
| 152 } | |
| 153 | |
| 154 NetworkThrottleManagerImpl::~NetworkThrottleManagerImpl() {} | |
| 155 | |
| 156 std::unique_ptr<NetworkThrottleManager::Throttle> | |
| 157 NetworkThrottleManagerImpl::CreateThrottle( | |
| 158 NetworkThrottleManager::ThrottleDelegate* delegate, | |
| 159 RequestPriority priority, | |
| 160 bool ignore_limits) { | |
| 161 bool blocked = | |
| 162 (!ignore_limits && priority == THROTTLED && | |
| 163 outstanding_throttles_.size() >= kActiveRequestThrottlingLimit); | |
| 164 | |
| 165 std::unique_ptr<NetworkThrottleManagerImpl::ThrottleImpl> throttle( | |
| 166 new ThrottleImpl(blocked, priority, delegate, this, | |
| 167 blocked_throttles_.end())); | |
| 168 | |
| 169 if (blocked) { | |
| 170 throttle->set_queue_pointer( | |
| 171 blocked_throttles_.insert(blocked_throttles_.end(), throttle.get())); | |
| 172 } else { | |
| 173 outstanding_throttles_.insert(throttle.get()); | |
| 174 } | |
| 175 | |
| 176 // Throttles may need to be unblocked if the outstanding throttles | |
| 177 // that blocked them have aged out of the oustanding set since | |
|
Charlie Harrison
2016/09/26 22:27:28
outstanding
Randy Smith (Not in Mondays)
2016/10/03 01:06:48
Why thank you!!
(:-} Done.)
| |
| 178 // the last entry to MaybeUnblockThrottles(). | |
| 179 MaybeUnblockThrottles(); | |
|
mmenke
2016/09/26 17:31:43
Should we be calling RecomputeThrottles instead?
Randy Smith (Not in Mondays)
2016/10/03 01:06:48
Agreed. Done.
| |
| 180 | |
| 181 return std::move(throttle); | |
| 182 } | |
| 183 | |
| 184 void NetworkThrottleManagerImpl::SetTickClockForTesting( | |
| 185 std::unique_ptr<base::TickClock> tick_clock) { | |
| 186 tick_clock_ = std::move(tick_clock); | |
| 187 } | |
| 188 | |
| 189 void NetworkThrottleManagerImpl::ConditionallyTriggerTimerForTesting() { | |
| 190 // Relies on |!retain_user_task| in timer constructor. | |
| 191 if (!outstanding_recomputation_timer_.user_task().is_null() && | |
| 192 (tick_clock_->NowTicks() > | |
| 193 outstanding_recomputation_timer_.desired_run_time())) { | |
| 194 base::Closure timer_callback(outstanding_recomputation_timer_.user_task()); | |
| 195 outstanding_recomputation_timer_.Stop(); | |
| 196 timer_callback.Run(); | |
| 197 } | |
| 198 } | |
| 199 | |
| 200 // static | |
| 201 bool NetworkThrottleManagerImpl::CreationTimeSetCompare( | |
| 202 ThrottleImpl* throttle1, | |
| 203 ThrottleImpl* throttle2) { | |
| 204 // No throttle should be in the CreationTimeOrderedSet with a null start | |
| 205 // time. | |
| 206 DCHECK_NE(throttle1->start_time(), base::TimeTicks()); | |
| 207 DCHECK_NE(throttle2->start_time(), base::TimeTicks()); | |
| 208 return (throttle1->start_time() < throttle2->start_time() || | |
| 209 // So different throttles don't look equal to the comparison | |
| 210 // function. | |
| 211 (throttle1->start_time() == throttle2->start_time() && | |
| 212 throttle1 < throttle2)); | |
| 213 } | |
| 214 | |
| 215 void NetworkThrottleManagerImpl::OnThrottlePriorityChanged( | |
| 216 NetworkThrottleManagerImpl::ThrottleImpl* throttle, | |
| 217 RequestPriority old_priority, | |
| 218 RequestPriority new_priority) { | |
| 219 // The only case requiring a state change is if the priority change | |
| 220 // implies unblocking. | |
| 221 if (throttle->IsBlocked() && // Implies |old_priority == THROTTLED| | |
| 222 new_priority != THROTTLED) { | |
| 223 // May result in re-entrant calls into this class. | |
| 224 UnblockThrottle(throttle); | |
| 225 } | |
| 226 | |
| 227 // In case timing has caused throttles to age out. | |
|
mmenke
2016/09/26 17:31:43
If a throttle has aged out, I don't think this get
Randy Smith (Not in Mondays)
2016/10/03 01:06:48
A throttle doesn't start aging until it's unblocke
| |
| 228 MaybeUnblockThrottles(); | |
|
mmenke
2016/09/26 17:31:43
Also, is there a test that fails if we don't call
Randy Smith (Not in Mondays)
2016/10/03 01:06:48
That's only necessary if you believe this call act
| |
| 229 } | |
| 230 | |
| 231 void NetworkThrottleManagerImpl::OnThrottleDestroyed(ThrottleImpl* throttle) { | |
| 232 switch (throttle->state()) { | |
| 233 case ThrottleImpl::BLOCKED: | |
| 234 DCHECK(throttle->queue_pointer() != blocked_throttles_.end()); | |
| 235 blocked_throttles_.erase(throttle->queue_pointer()); | |
| 236 break; | |
| 237 case ThrottleImpl::OUTSTANDING: { | |
| 238 size_t items_erased = outstanding_throttles_.erase(throttle); | |
| 239 DCHECK_EQ(1u, items_erased); | |
|
mmenke
2016/09/26 17:31:43
Do a similar DCHECK for the blocked case, too?
Randy Smith (Not in Mondays)
2016/10/03 01:06:48
Done (std::find(), linear time, but in a DCHECK, s
| |
| 240 } | |
| 241 // Fall through | |
| 242 case ThrottleImpl::AGED: | |
| 243 DCHECK_NE(throttle->start_time(), base::TimeTicks()); | |
| 244 lifetime_median_estimate_.AddSample( | |
| 245 (tick_clock_->NowTicks() - throttle->start_time()) | |
| 246 .InMillisecondsRoundedUp()); | |
| 247 break; | |
| 248 } | |
| 249 | |
|
mmenke
2016/09/26 17:31:43
Maybe add:
DCHECK_EQ(0u, blocked_throttles_.count
Randy Smith (Not in Mondays)
2016/10/03 01:06:48
Done, though note that std::list doesn't have a co
| |
| 250 // Via PostTask so there aren't upcalls from within destructors. | |
| 251 base::MessageLoop::current()->task_runner()->PostTask( | |
| 252 FROM_HERE, base::Bind(&NetworkThrottleManagerImpl::MaybeUnblockThrottles, | |
| 253 weak_ptr_factory_.GetWeakPtr())); | |
| 254 } | |
| 255 | |
| 256 void NetworkThrottleManagerImpl::RecomputeOutstanding() { | |
| 257 // Remove all throttles that have aged out of the outstanding set. | |
| 258 base::TimeTicks now(tick_clock_->NowTicks()); | |
| 259 base::TimeDelta age_horizon(base::TimeDelta::FromMilliseconds(( | |
| 260 kMedianLifetimeMultiple * lifetime_median_estimate_.current_estimate()))); | |
| 261 while (!outstanding_throttles_.empty()) { | |
| 262 ThrottleImpl* throttle = *outstanding_throttles_.begin(); | |
| 263 if (throttle->start_time() > now - age_horizon) | |
| 264 break; | |
| 265 | |
| 266 outstanding_throttles_.erase(throttle); | |
| 267 throttle->SetAged(); | |
| 268 } | |
| 269 | |
| 270 // If the set is now empty, no timer is needed. | |
| 271 if (outstanding_throttles_.empty()) { | |
| 272 outstanding_recomputation_timer_.Stop(); | |
| 273 return; | |
| 274 } | |
| 275 | |
| 276 // If the initial throttle hasn't changed, neither has the | |
| 277 // right timing for the timer (modulo changes in lifetime_median_estimate_, | |
| 278 // which should change slowly enough that we don't need to track it | |
| 279 // exactly for a heuristic). | |
| 280 ThrottleImpl* first_throttle = *outstanding_throttles_.begin(); | |
| 281 if (first_outstanding_throttle_ == reinterpret_cast<void*>(first_throttle)) | |
| 282 return; | |
| 283 first_outstanding_throttle_ = reinterpret_cast<void*>(first_throttle); | |
|
mmenke
2016/09/26 17:31:43
For correctness, should clear this in OnThrottleDe
Randy Smith (Not in Mondays)
2016/10/03 01:06:48
Whoops, thank you.
| |
| 284 | |
| 285 outstanding_recomputation_timer_.Start( | |
| 286 FROM_HERE, (first_throttle->start_time() + age_horizon) - now, | |
| 287 // Unretained use of |this| is safe because the timer is | |
| 288 // owned by this object, and will be torn down if this object | |
| 289 // is destroyed. | |
| 290 base::Bind(&NetworkThrottleManagerImpl::MaybeUnblockThrottles, | |
| 291 base::Unretained(this))); | |
| 292 } | |
| 293 | |
| 294 void NetworkThrottleManagerImpl::UnblockThrottle(ThrottleImpl* throttle) { | |
| 295 DCHECK(throttle->IsBlocked()); | |
| 296 | |
| 297 blocked_throttles_.erase(throttle->queue_pointer()); | |
| 298 throttle->set_queue_pointer(blocked_throttles_.end()); | |
| 299 throttle->set_start_time(tick_clock_->NowTicks()); | |
| 300 outstanding_throttles_.insert(throttle); | |
| 301 | |
| 302 // May result in re-entrant calls into this class. | |
| 303 throttle->NotifyUnblocked(); | |
| 304 } | |
| 305 | |
| 306 void NetworkThrottleManagerImpl::MaybeUnblockThrottles() { | |
|
mmenke
2016/09/26 17:31:43
We're strongly relying on the fact that if we do:
Charlie Harrison
2016/09/26 22:27:28
No, we can only (barely) rely on monotonicity. We
mmenke
2016/09/26 22:54:43
Sory, unblock == mark as aged.
Randy Smith (Not in Mondays)
2016/10/03 01:06:48
I agree that that's a problem, and I should solve
mmenke
2016/10/04 15:31:54
No, it does not. The timer in RecomputeOutstandin
Randy Smith (Not in Mondays)
2016/10/04 15:46:39
Ah, good point; sorry I missed that. I'd like to
Randy Smith (Not in Mondays)
2016/10/04 15:56:43
Result of offline conversation: Matt pointed out t
| |
| 307 RecomputeOutstanding(); | |
| 308 | |
| 309 while (outstanding_throttles_.size() < kActiveRequestThrottlingLimit && | |
| 310 !blocked_throttles_.empty()) { | |
| 311 // NOTE: This call may result in reentrant calls into | |
| 312 // NetworkThrottleManagerImpl; no state should be assumed to be | |
| 313 // persistent across this call. | |
| 314 UnblockThrottle(blocked_throttles_.front()); | |
| 315 } | |
|
mmenke
2016/09/26 17:31:44
Think this needs a RecomputeOutstanding() at the e
Charlie Harrison
2016/09/26 22:27:28
Wont we likely unblock a lot of throttles when Bli
mmenke
2016/09/26 22:57:14
Not as-is. This code has no clue what a body tag
Randy Smith (Not in Mondays)
2016/10/03 01:06:48
[The only way I could make the original comment ma
| |
| 316 } | |
| 317 | |
| 318 } // namespace net | |
| OLD | NEW |