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 const int NetworkThrottleManagerImpl::kInitialMedianInMs = 100; | |
| 18 | |
| 19 class NetworkThrottleManagerImpl::ThrottleImpl | |
| 20 : public NetworkThrottleManager::Throttle { | |
| 21 public: | |
| 22 // Allowed state transitions are BLOCKED -> OUTSTANDING -> AGED. | |
| 23 // Throttles may be created in the BLOCKED or OUTSTANDING states. | |
| 24 enum State { | |
| 25 // Not allowed to proceed by manager. | |
| 26 BLOCKED, | |
| 27 | |
| 28 // Allowed to proceed, counts as an "outstanding" request for | |
| 29 // manager accounting purposes. | |
| 30 OUTSTANDING, | |
| 31 | |
| 32 // Old enough to not count as "outstanding" anymore for | |
| 33 // manager accounting purposes. | |
| 34 AGED | |
| 35 }; | |
| 36 | |
| 37 using QueuePointer = NetworkThrottleManagerImpl::ThrottleList::iterator; | |
| 38 | |
| 39 // Caller must arrange that |*delegate| and |*manager| outlive | |
| 40 // the ThrottleImpl class. | |
| 41 ThrottleImpl(bool blocked, | |
| 42 RequestPriority priority, | |
| 43 ThrottleDelegate* delegate, | |
| 44 NetworkThrottleManagerImpl* manager, | |
| 45 QueuePointer queue_pointer); | |
| 46 | |
| 47 ~ThrottleImpl() override; | |
| 48 | |
| 49 // Throttle: | |
| 50 bool IsBlocked() const override; | |
| 51 RequestPriority Priority() const override; | |
| 52 void SetPriority(RequestPriority priority) override; | |
| 53 | |
| 54 // Change the throttle's state to AGED. The previous | |
| 55 // state must be OUTSTANDING. | |
| 56 void SetAged(); | |
| 57 State state() const { return state_; } | |
| 58 RequestPriority priority() const { return priority_; } | |
| 59 QueuePointer queue_pointer() const { return queue_pointer_; } | |
| 60 void set_queue_pointer(const QueuePointer& pointer) { | |
| 61 queue_pointer_ = pointer; | |
| 62 } | |
| 63 base::TimeTicks start_time() const { return start_time_; } | |
| 64 void set_start_time(base::TimeTicks start_time) { start_time_ = start_time; } | |
|
Charlie Harrison
2016/09/19 16:35:35
Could this logic be moved to NotifyUnblocked so we
Randy Smith (Not in Mondays)
2016/09/22 21:52:26
So I like this idea in terms of layering (the Thro
| |
| 65 | |
| 66 // Note that this call calls the delegate, and hence | |
| 67 // may result in re-entrant calls into the manager or | |
| 68 // ThrottleImpl. The manager should not rely on | |
| 69 // any state other than its own existence being persistent | |
| 70 // across this call. | |
| 71 void NotifyUnblocked(); | |
| 72 | |
| 73 private: | |
| 74 State state_; | |
| 75 RequestPriority priority_; | |
| 76 ThrottleDelegate* const delegate_; | |
| 77 NetworkThrottleManagerImpl* const manager_; | |
| 78 | |
| 79 base::TimeTicks start_time_; | |
| 80 | |
| 81 // For deletion from owning queue. | |
| 82 QueuePointer queue_pointer_; | |
| 83 | |
| 84 DISALLOW_COPY_AND_ASSIGN(ThrottleImpl); | |
| 85 }; | |
| 86 | |
| 87 NetworkThrottleManagerImpl::ThrottleImpl::ThrottleImpl( | |
| 88 bool blocked, | |
| 89 RequestPriority priority, | |
| 90 NetworkThrottleManager::ThrottleDelegate* delegate, | |
| 91 NetworkThrottleManagerImpl* manager, | |
| 92 QueuePointer queue_pointer) | |
| 93 : state_(blocked ? BLOCKED : OUTSTANDING), | |
| 94 priority_(priority), | |
| 95 delegate_(delegate), | |
| 96 manager_(manager), | |
| 97 queue_pointer_(queue_pointer) { | |
| 98 DCHECK(delegate); | |
| 99 if (!blocked) | |
| 100 start_time_ = manager->tick_clock_->NowTicks(); | |
| 101 } | |
| 102 | |
| 103 NetworkThrottleManagerImpl::ThrottleImpl::~ThrottleImpl() { | |
| 104 manager_->OnThrottleDestroyed(this); | |
| 105 } | |
| 106 | |
| 107 bool NetworkThrottleManagerImpl::ThrottleImpl::IsBlocked() const { | |
| 108 return state_ == BLOCKED; | |
| 109 } | |
| 110 | |
| 111 RequestPriority NetworkThrottleManagerImpl::ThrottleImpl::Priority() const { | |
| 112 return priority_; | |
| 113 } | |
| 114 | |
| 115 void NetworkThrottleManagerImpl::ThrottleImpl::SetPriority( | |
| 116 RequestPriority new_priority) { | |
| 117 RequestPriority old_priority(priority_); | |
| 118 if (old_priority == new_priority) | |
| 119 return; | |
| 120 priority_ = new_priority; | |
| 121 manager_->OnThrottlePriorityChanged(this, old_priority, new_priority); | |
| 122 } | |
| 123 | |
| 124 void NetworkThrottleManagerImpl::ThrottleImpl::SetAged() { | |
| 125 DCHECK_EQ(OUTSTANDING, state_); | |
| 126 state_ = AGED; | |
| 127 } | |
| 128 | |
| 129 void NetworkThrottleManagerImpl::ThrottleImpl::NotifyUnblocked() { | |
| 130 // This methods should only be called once, and only if the | |
| 131 // current state is blocked. | |
| 132 DCHECK_EQ(BLOCKED, state_); | |
| 133 state_ = OUTSTANDING; | |
| 134 delegate_->OnThrottleStateChanged(this); | |
| 135 } | |
| 136 | |
| 137 NetworkThrottleManagerImpl::NetworkThrottleManagerImpl() | |
| 138 : lifetime_median_estimate_(QuantileEstimator::kMedianQuantile, | |
| 139 kInitialMedianInMs), | |
| 140 outstanding_recomputation_timer_(false /* retain_user_task */, | |
| 141 false /* is_repeating */), | |
| 142 outstanding_throttles_( | |
| 143 &NetworkThrottleManagerImpl::CreationTimeSetCompare), | |
| 144 tick_clock_(new base::DefaultTickClock()), | |
| 145 weak_ptr_factory_(this) { | |
| 146 outstanding_recomputation_timer_.SetTaskRunner( | |
| 147 base::MessageLoop::current()->task_runner()); | |
| 148 } | |
| 149 | |
| 150 NetworkThrottleManagerImpl::~NetworkThrottleManagerImpl() {} | |
| 151 | |
| 152 std::unique_ptr<NetworkThrottleManager::Throttle> | |
| 153 NetworkThrottleManagerImpl::CreateThrottle( | |
| 154 NetworkThrottleManager::ThrottleDelegate* delegate, | |
| 155 RequestPriority priority, | |
| 156 bool ignore_limits) { | |
| 157 bool blocked = | |
| 158 (!ignore_limits && priority == THROTTLED && | |
| 159 outstanding_throttles_.size() >= kActiveRequestThrottlingLimit); | |
| 160 | |
| 161 std::unique_ptr<NetworkThrottleManagerImpl::ThrottleImpl> throttle( | |
| 162 new ThrottleImpl(blocked, priority, delegate, this, | |
| 163 blocked_throttles_.end())); | |
| 164 | |
| 165 if (blocked) { | |
| 166 throttle->set_queue_pointer( | |
| 167 blocked_throttles_.insert(blocked_throttles_.end(), throttle.get())); | |
| 168 } else { | |
| 169 outstanding_throttles_.insert(throttle.get()); | |
| 170 } | |
| 171 | |
| 172 RecomputeOutstanding(); | |
| 173 | |
| 174 return std::move(throttle); | |
| 175 } | |
| 176 | |
| 177 void NetworkThrottleManagerImpl::SetTickClockForTesting( | |
| 178 std::unique_ptr<base::TickClock> tick_clock) { | |
| 179 tick_clock_ = std::move(tick_clock); | |
| 180 } | |
| 181 | |
| 182 void NetworkThrottleManagerImpl::ConditionallyTriggerTimerForTesting() { | |
| 183 // Relies on |!retain_user_task| in timer constructor. | |
| 184 if (!outstanding_recomputation_timer_.user_task().is_null() && | |
| 185 (tick_clock_->NowTicks() > | |
| 186 outstanding_recomputation_timer_.desired_run_time())) { | |
| 187 base::Closure timer_callback(outstanding_recomputation_timer_.user_task()); | |
| 188 outstanding_recomputation_timer_.Stop(); | |
| 189 timer_callback.Run(); | |
| 190 } | |
| 191 } | |
| 192 | |
|
Charlie Harrison
2016/09/19 16:35:36
nit: // static
Randy Smith (Not in Mondays)
2016/09/22 21:52:27
Done.
| |
| 193 bool NetworkThrottleManagerImpl::CreationTimeSetCompare( | |
| 194 ThrottleImpl* throttle1, | |
| 195 ThrottleImpl* throttle2) { | |
| 196 // No throttle should be in the CreationTimeOrderedSet with a null start | |
| 197 // time. | |
| 198 DCHECK_NE(throttle1->start_time(), base::TimeTicks()); | |
| 199 DCHECK_NE(throttle2->start_time(), base::TimeTicks()); | |
| 200 return (throttle1->start_time() < throttle2->start_time() || | |
| 201 // So different throttles don't look equal to the comparison | |
| 202 // function. | |
| 203 (throttle1->start_time() == throttle2->start_time() && | |
| 204 throttle1 < throttle2)); | |
| 205 } | |
| 206 | |
| 207 void NetworkThrottleManagerImpl::OnThrottlePriorityChanged( | |
| 208 NetworkThrottleManagerImpl::ThrottleImpl* throttle, | |
| 209 RequestPriority old_priority, | |
| 210 RequestPriority new_priority) { | |
| 211 // The only case requiring a state change is if the priority change | |
| 212 // implies unblocking. | |
| 213 if (throttle->IsBlocked() && old_priority == THROTTLED && | |
|
Charlie Harrison
2016/09/19 16:35:36
Doesn't throttle->IsBlocked() imply old_priority =
Randy Smith (Not in Mondays)
2016/09/22 21:52:26
Yes, though it probably won't be true in the next
| |
| 214 new_priority != THROTTLED) { | |
| 215 // May result in re-entrant calls into this class. | |
| 216 UnblockThrottle(throttle); | |
| 217 } | |
| 218 RecomputeOutstanding(); | |
| 219 } | |
| 220 | |
| 221 void NetworkThrottleManagerImpl::OnThrottleDestroyed(ThrottleImpl* throttle) { | |
| 222 switch (throttle->state()) { | |
| 223 case ThrottleImpl::BLOCKED: | |
| 224 DCHECK(throttle->queue_pointer() != blocked_throttles_.end()); | |
| 225 blocked_throttles_.erase(throttle->queue_pointer()); | |
| 226 break; | |
| 227 case ThrottleImpl::OUTSTANDING: { | |
|
Charlie Harrison
2016/09/19 16:35:36
Why do you wrap this case in {} but not the others
Randy Smith (Not in Mondays)
2016/09/22 21:52:26
I needed the scope of items_erased to be restricte
| |
| 228 size_t items_erased = outstanding_throttles_.erase(throttle); | |
| 229 DCHECK_EQ(1u, items_erased); | |
| 230 } | |
| 231 // Fall through | |
| 232 case ThrottleImpl::AGED: | |
| 233 DCHECK_NE(throttle->start_time(), base::TimeTicks()); | |
| 234 lifetime_median_estimate_.AddSample( | |
| 235 (tick_clock_->NowTicks() - throttle->start_time()) | |
| 236 .InMillisecondsRoundedUp()); | |
|
mmenke
2016/09/19 15:26:16
This includes cancellations and failures, as long
Randy Smith (Not in Mondays)
2016/09/22 21:52:26
I'm inclined not to worry about it. My thought is
| |
| 237 break; | |
| 238 } | |
| 239 | |
| 240 // Via PostTask so there aren't upcalls from within destructors. | |
| 241 base::MessageLoop::current()->task_runner()->PostTask( | |
| 242 FROM_HERE, base::Bind(&NetworkThrottleManagerImpl::MaybeUnblockThrottles, | |
| 243 weak_ptr_factory_.GetWeakPtr())); | |
| 244 } | |
| 245 | |
| 246 void NetworkThrottleManagerImpl::RecomputeOutstanding() { | |
| 247 if (outstanding_throttles_.empty()) | |
| 248 // No changes needed. | |
|
mmenke
2016/09/19 15:26:16
nit: Use braces or move comment before the if.
Randy Smith (Not in Mondays)
2016/09/22 21:52:27
Looking at this in light of your comment, I think
| |
| 249 return; | |
| 250 | |
| 251 // Remove all throttles that have aged out of the outstanding set. | |
| 252 base::TimeTicks age_horizon( | |
| 253 tick_clock_->NowTicks() - | |
| 254 base::TimeDelta::FromMilliseconds( | |
| 255 (kMedianLifetimeMultiple * | |
| 256 lifetime_median_estimate_.current_estimate()))); | |
| 257 while (!outstanding_throttles_.empty()) { | |
|
mmenke
2016/09/19 15:26:16
Where's this declared? Am I missing something obv
Randy Smith (Not in Mondays)
2016/09/22 21:52:26
I think so? It's a data member in the class decla
mmenke
2016/09/22 21:57:39
Weird, I searched the header for it multiple times
| |
| 258 ThrottleImpl* throttle = *outstanding_throttles_.begin(); | |
| 259 if (throttle->start_time() > age_horizon) | |
| 260 break; | |
| 261 | |
| 262 outstanding_throttles_.erase(throttle); | |
| 263 throttle->SetAged(); | |
| 264 } | |
| 265 | |
| 266 // If the set's now empty, we don't need a timer. | |
|
mmenke
2016/09/19 15:26:16
nit: --we
Randy Smith (Not in Mondays)
2016/09/22 21:52:26
Ooops. Done.
| |
| 267 if (outstanding_throttles_.empty()) { | |
|
mmenke
2016/09/19 15:26:17
This seems too fragile to me - if we marked all ou
Randy Smith (Not in Mondays)
2016/09/22 21:52:26
So I think of this in terms of routine responsibil
| |
| 268 outstanding_recomputation_timer_.Stop(); | |
| 269 return; | |
| 270 } | |
| 271 | |
| 272 // The earliest we'll need to recompute is when the earliest timer | |
|
mmenke
2016/09/19 15:26:16
--we
Randy Smith (Not in Mondays)
2016/09/22 21:52:26
Done.
| |
| 273 // ages out of the set. This isn't precise because the median estimate | |
| 274 // changes over time, but this is a heuristic and doesn't need to be | |
| 275 // precise. | |
| 276 // TODO(rdsmith): Under that same "not precise" rubric, worthwhile avoiding | |
| 277 // resetting the timer if it's "close"? | |
| 278 ThrottleImpl* first_throttle = *outstanding_throttles_.begin(); | |
| 279 base::TimeTicks new_timer_start = | |
| 280 base::TimeTicks::Now() + (first_throttle->start_time() - age_horizon); | |
|
Charlie Harrison
2016/09/19 16:35:35
Why not use tick_clock_->NowTicks() here?
Randy Smith (Not in Mondays)
2016/09/22 21:52:26
So the basic answer is "We could, but it won't hel
| |
| 281 if (!outstanding_recomputation_timer_.user_task().is_null() && | |
| 282 outstanding_recomputation_timer_.desired_run_time() == new_timer_start) { | |
|
Charlie Harrison
2016/09/19 16:35:35
When will these be equal? Seeing as you just calle
Randy Smith (Not in Mondays)
2016/09/22 21:52:26
Consider the case where we call RecomputeOustandin
Charlie Harrison
2016/09/23 15:11:12
Calling TimeTicks::Now() gets you halfway, but des
Randy Smith (Not in Mondays)
2016/09/23 22:16:49
Ok, after reading through the code carefully, I ag
| |
| 283 return; | |
| 284 } | |
| 285 | |
| 286 outstanding_recomputation_timer_.Start( | |
| 287 FROM_HERE, first_throttle->start_time() - age_horizon, | |
| 288 // Unretained use of |this| is safe because the timer is | |
| 289 // owned by this object, and will be torn down if this object | |
| 290 // is destroyed. | |
| 291 base::Bind(&NetworkThrottleManagerImpl::MaybeUnblockThrottles, | |
| 292 base::Unretained(this))); | |
| 293 } | |
| 294 | |
| 295 void NetworkThrottleManagerImpl::UnblockThrottle(ThrottleImpl* throttle) { | |
| 296 DCHECK(throttle->IsBlocked()); | |
| 297 | |
| 298 blocked_throttles_.erase(throttle->queue_pointer()); | |
| 299 throttle->set_queue_pointer(blocked_throttles_.end()); | |
| 300 throttle->set_start_time(tick_clock_->NowTicks()); | |
| 301 outstanding_throttles_.insert(throttle); | |
| 302 | |
| 303 // May result in re-entrant calls into this class. | |
| 304 throttle->NotifyUnblocked(); | |
| 305 } | |
| 306 | |
| 307 void NetworkThrottleManagerImpl::MaybeUnblockThrottles() { | |
| 308 RecomputeOutstanding(); | |
| 309 | |
| 310 while (outstanding_throttles_.size() < kActiveRequestThrottlingLimit && | |
| 311 !blocked_throttles_.empty()) { | |
| 312 // NOTE: This call may result in reentrant calls into | |
| 313 // NetworkThrottleManagerImpl; no state should be assumed to be | |
| 314 // persistent across this call. | |
| 315 UnblockThrottle(blocked_throttles_.front()); | |
| 316 } | |
| 317 } | |
| 318 | |
| 319 } // namespace net | |
| OLD | NEW |