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 |