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/threading/thread_task_runner_handle.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 // Set timers slightly further into the future than they need to be set, so | |
23 // that the algorithm isn't vulnerable to timer round off errors triggering | |
24 // the callback before the throttle would be considered aged out of the set. | |
25 // Set to 17 to hanlde systems with |!base::TimeTicks::IsHighResolution()|. | |
26 // Note that even if the timer goes off before it should, all that should cost | |
27 // is a second task; this class does not rely on timer accuracy for its | |
28 // correctness. | |
29 const int kTimerFudgeInMs = 17; | |
30 | |
31 class NetworkThrottleManagerImpl::ThrottleImpl | |
32 : public NetworkThrottleManager::Throttle { | |
33 public: | |
34 // Allowed state transitions are BLOCKED -> OUTSTANDING -> AGED. | |
35 // Throttles may be created in the BLOCKED or OUTSTANDING states. | |
36 enum class State { | |
37 // Not allowed to proceed by manager. | |
38 BLOCKED, | |
39 | |
40 // Allowed to proceed, counts as an "outstanding" request for | |
41 // manager accounting purposes. | |
42 OUTSTANDING, | |
43 | |
44 // Old enough to not count as "outstanding" anymore for | |
45 // manager accounting purposes. | |
46 AGED | |
47 }; | |
48 | |
49 using ThrottleListQueuePointer = | |
50 NetworkThrottleManagerImpl::ThrottleList::iterator; | |
51 | |
52 // Caller must arrange that |*delegate| and |*manager| outlive | |
53 // the ThrottleImpl class. | |
54 ThrottleImpl(bool blocked, | |
55 RequestPriority priority, | |
56 ThrottleDelegate* delegate, | |
57 NetworkThrottleManagerImpl* manager, | |
58 ThrottleListQueuePointer queue_pointer); | |
59 | |
60 ~ThrottleImpl() override; | |
61 | |
62 // Throttle: | |
63 bool IsBlocked() const override; | |
64 RequestPriority Priority() const override; | |
65 void SetPriority(RequestPriority priority) override; | |
66 | |
67 State state() const { return state_; } | |
68 | |
69 ThrottleListQueuePointer queue_pointer() const { return queue_pointer_; } | |
70 void set_queue_pointer(const ThrottleListQueuePointer& pointer) { | |
71 if (state_ != State::AGED) | |
72 DCHECK_EQ(this, *pointer); | |
73 queue_pointer_ = pointer; | |
74 } | |
75 | |
76 void set_start_time(base::TimeTicks start_time) { start_time_ = start_time; } | |
77 base::TimeTicks start_time() const { return start_time_; } | |
78 | |
79 // Change the throttle's state to AGED. The previous | |
80 // state must be OUTSTANDING. | |
81 void SetAged(); | |
82 | |
83 // Note that this call calls the delegate, and hence may result in | |
84 // re-entrant calls into the manager or ThrottleImpl. The manager should | |
85 // not rely on any state other than its own existence being persistent | |
86 // across this call. | |
87 void NotifyUnblocked(); | |
88 | |
89 private: | |
90 State state_; | |
91 RequestPriority priority_; | |
92 ThrottleDelegate* const delegate_; | |
93 NetworkThrottleManagerImpl* const manager_; | |
94 | |
95 base::TimeTicks start_time_; | |
96 | |
97 // To allow deletion from the blocked queue (when the throttle is in the | |
98 // blocked queue). | |
99 ThrottleListQueuePointer queue_pointer_; | |
100 | |
101 DISALLOW_COPY_AND_ASSIGN(ThrottleImpl); | |
102 }; | |
103 | |
104 NetworkThrottleManagerImpl::ThrottleImpl::ThrottleImpl( | |
105 bool blocked, | |
106 RequestPriority priority, | |
107 NetworkThrottleManager::ThrottleDelegate* delegate, | |
108 NetworkThrottleManagerImpl* manager, | |
109 ThrottleListQueuePointer queue_pointer) | |
110 : state_(blocked ? State::BLOCKED : State::OUTSTANDING), | |
111 priority_(priority), | |
112 delegate_(delegate), | |
113 manager_(manager), | |
114 queue_pointer_(queue_pointer) { | |
115 DCHECK(delegate); | |
116 if (!blocked) | |
117 start_time_ = manager->tick_clock_->NowTicks(); | |
118 } | |
119 | |
120 NetworkThrottleManagerImpl::ThrottleImpl::~ThrottleImpl() { | |
121 manager_->OnThrottleDestroyed(this); | |
122 } | |
123 | |
124 bool NetworkThrottleManagerImpl::ThrottleImpl::IsBlocked() const { | |
125 return state_ == State::BLOCKED; | |
126 } | |
127 | |
128 RequestPriority NetworkThrottleManagerImpl::ThrottleImpl::Priority() const { | |
129 return priority_; | |
130 } | |
131 | |
132 void NetworkThrottleManagerImpl::ThrottleImpl::SetPriority( | |
133 RequestPriority new_priority) { | |
134 RequestPriority old_priority(priority_); | |
135 if (old_priority == new_priority) | |
136 return; | |
137 priority_ = new_priority; | |
138 manager_->OnThrottlePriorityChanged(this, old_priority, new_priority); | |
139 } | |
140 | |
141 void NetworkThrottleManagerImpl::ThrottleImpl::SetAged() { | |
142 DCHECK_EQ(State::OUTSTANDING, state_); | |
143 state_ = State::AGED; | |
144 } | |
145 | |
146 void NetworkThrottleManagerImpl::ThrottleImpl::NotifyUnblocked() { | |
147 // This method should only be called once, and only if the | |
148 // current state is blocked. | |
149 DCHECK_EQ(State::BLOCKED, state_); | |
150 state_ = State::OUTSTANDING; | |
151 delegate_->OnThrottleUnblocked(this); | |
152 } | |
153 | |
154 NetworkThrottleManagerImpl::NetworkThrottleManagerImpl() | |
155 : lifetime_median_estimate_(PercentileEstimator::kMedianPercentile, | |
156 kInitialMedianInMs), | |
157 outstanding_recomputation_timer_(false /* retain_user_task */, | |
158 false /* is_repeating */), | |
159 tick_clock_(new base::DefaultTickClock()), | |
160 weak_ptr_factory_(this) { | |
161 outstanding_recomputation_timer_.SetTaskRunner( | |
162 base::ThreadTaskRunnerHandle::Get()); | |
163 } | |
164 | |
165 NetworkThrottleManagerImpl::~NetworkThrottleManagerImpl() {} | |
166 | |
167 std::unique_ptr<NetworkThrottleManager::Throttle> | |
168 NetworkThrottleManagerImpl::CreateThrottle( | |
169 NetworkThrottleManager::ThrottleDelegate* delegate, | |
170 RequestPriority priority, | |
171 bool ignore_limits) { | |
172 bool blocked = | |
173 (!ignore_limits && priority == THROTTLED && | |
174 outstanding_throttles_.size() >= kActiveRequestThrottlingLimit); | |
175 | |
176 std::unique_ptr<NetworkThrottleManagerImpl::ThrottleImpl> throttle( | |
177 new ThrottleImpl(blocked, priority, delegate, this, | |
178 blocked_throttles_.end())); | |
179 | |
180 ThrottleList& insert_list(blocked ? blocked_throttles_ | |
181 : outstanding_throttles_); | |
182 | |
183 throttle->set_queue_pointer( | |
184 insert_list.insert(insert_list.end(), throttle.get())); | |
185 | |
186 // In case oustanding_throttles_ was empty, set up timer. | |
187 if (!blocked) | |
188 RecomputeOutstanding(); | |
189 | |
190 return std::move(throttle); | |
191 } | |
192 | |
193 void NetworkThrottleManagerImpl::SetTickClockForTesting( | |
194 std::unique_ptr<base::TickClock> tick_clock) { | |
195 tick_clock_ = std::move(tick_clock); | |
196 } | |
197 | |
198 bool NetworkThrottleManagerImpl::ConditionallyTriggerTimerForTesting() { | |
199 if (!outstanding_recomputation_timer_.IsRunning() || | |
200 (tick_clock_->NowTicks() < | |
201 outstanding_recomputation_timer_.desired_run_time())) { | |
202 return false; | |
203 } | |
204 | |
205 base::Closure timer_callback(outstanding_recomputation_timer_.user_task()); | |
206 outstanding_recomputation_timer_.Stop(); | |
207 timer_callback.Run(); | |
208 return true; | |
209 } | |
210 | |
211 void NetworkThrottleManagerImpl::OnThrottlePriorityChanged( | |
212 NetworkThrottleManagerImpl::ThrottleImpl* throttle, | |
213 RequestPriority old_priority, | |
214 RequestPriority new_priority) { | |
215 // The only case requiring a state change is if the priority change | |
216 // implies unblocking, which can only happen on a transition from blocked | |
217 // (implies THROTTLED) to non-THROTTLED. | |
218 if (throttle->IsBlocked() && new_priority != THROTTLED) { | |
219 // May result in re-entrant calls into this class. | |
220 UnblockThrottle(throttle); | |
221 } | |
222 } | |
223 | |
224 void NetworkThrottleManagerImpl::OnThrottleDestroyed(ThrottleImpl* throttle) { | |
225 switch (throttle->state()) { | |
226 case ThrottleImpl::State::BLOCKED: | |
227 DCHECK(throttle->queue_pointer() != blocked_throttles_.end()); | |
228 DCHECK_EQ(throttle, *(throttle->queue_pointer())); | |
229 blocked_throttles_.erase(throttle->queue_pointer()); | |
230 break; | |
231 case ThrottleImpl::State::OUTSTANDING: | |
232 DCHECK(throttle->queue_pointer() != outstanding_throttles_.end()); | |
233 DCHECK_EQ(throttle, *(throttle->queue_pointer())); | |
234 outstanding_throttles_.erase(throttle->queue_pointer()); | |
235 // Fall through | |
236 case ThrottleImpl::State::AGED: | |
237 DCHECK(!throttle->start_time().is_null()); | |
238 lifetime_median_estimate_.AddSample( | |
239 (tick_clock_->NowTicks() - throttle->start_time()) | |
240 .InMillisecondsRoundedUp()); | |
241 break; | |
242 } | |
243 | |
244 DCHECK(std::find(blocked_throttles_.begin(), blocked_throttles_.end(), | |
245 throttle) == blocked_throttles_.end()); | |
246 DCHECK(std::find(outstanding_throttles_.begin(), outstanding_throttles_.end(), | |
247 throttle) == outstanding_throttles_.end()); | |
248 | |
249 // Unblock the throttles if there's some chance there's a throttle to | |
250 // unblock. | |
251 if (outstanding_throttles_.size() < kActiveRequestThrottlingLimit && | |
252 !blocked_throttles_.empty()) { | |
253 // Via PostTask so there aren't upcalls from within destructors. | |
254 base::ThreadTaskRunnerHandle::Get()->PostTask( | |
255 FROM_HERE, | |
256 base::Bind(&NetworkThrottleManagerImpl::MaybeUnblockThrottles, | |
257 weak_ptr_factory_.GetWeakPtr())); | |
258 } | |
259 } | |
260 | |
261 void NetworkThrottleManagerImpl::RecomputeOutstanding() { | |
262 // Remove all throttles that have aged out of the outstanding set. | |
263 base::TimeTicks now(tick_clock_->NowTicks()); | |
264 base::TimeDelta age_horizon(base::TimeDelta::FromMilliseconds(( | |
265 kMedianLifetimeMultiple * lifetime_median_estimate_.current_estimate()))); | |
266 while (!outstanding_throttles_.empty()) { | |
267 ThrottleImpl* throttle = *outstanding_throttles_.begin(); | |
268 if (throttle->start_time() + age_horizon >= now) | |
269 break; | |
270 | |
271 outstanding_throttles_.erase(outstanding_throttles_.begin()); | |
272 throttle->SetAged(); | |
273 throttle->set_queue_pointer(outstanding_throttles_.end()); | |
274 } | |
275 | |
276 if (outstanding_throttles_.empty()) | |
277 return; | |
278 | |
279 // If the timer is already running, be conservative and leave it alone; | |
280 // the time for which it would be set will only be later than when it's | |
281 // currently set. | |
282 // This addresses, e.g., situations where a RecomputeOutstanding() races | |
283 // with a running timer which would unblock blocked throttles. | |
284 if (outstanding_recomputation_timer_.IsRunning()) | |
285 return; | |
286 | |
287 ThrottleImpl* first_throttle(*outstanding_throttles_.begin()); | |
288 DCHECK_GE(first_throttle->start_time() + age_horizon, now); | |
289 outstanding_recomputation_timer_.Start( | |
290 FROM_HERE, ((first_throttle->start_time() + age_horizon) - now + | |
291 base::TimeDelta::FromMilliseconds(kTimerFudgeInMs)), | |
292 // Unretained use of |this| is safe because the timer is | |
293 // owned by this object, and will be torn down if this object | |
294 // is destroyed. | |
295 base::Bind(&NetworkThrottleManagerImpl::MaybeUnblockThrottles, | |
296 base::Unretained(this))); | |
297 } | |
298 | |
299 void NetworkThrottleManagerImpl::UnblockThrottle(ThrottleImpl* throttle) { | |
300 DCHECK(throttle->IsBlocked()); | |
301 | |
302 blocked_throttles_.erase(throttle->queue_pointer()); | |
303 throttle->set_start_time(tick_clock_->NowTicks()); | |
304 throttle->set_queue_pointer( | |
305 outstanding_throttles_.insert(outstanding_throttles_.end(), throttle)); | |
306 | |
307 // Called in case |*throttle| was added to a null set. | |
308 RecomputeOutstanding(); | |
309 | |
310 // May result in re-entrant calls into this class. | |
311 throttle->NotifyUnblocked(); | |
312 } | |
313 | |
314 void NetworkThrottleManagerImpl::MaybeUnblockThrottles() { | |
315 RecomputeOutstanding(); | |
316 | |
317 while (outstanding_throttles_.size() < kActiveRequestThrottlingLimit && | |
318 !blocked_throttles_.empty()) { | |
319 // NOTE: This call may result in reentrant calls into | |
320 // NetworkThrottleManagerImpl; no state should be assumed to be | |
321 // persistent across this call. | |
322 UnblockThrottle(blocked_throttles_.front()); | |
323 } | |
324 } | |
325 | |
326 } // namespace net | |
OLD | NEW |