Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(352)

Side by Side Diff: net/base/network_throttle_manager_impl.cc

Issue 2130493002: Implement THROTTLED priority semantics. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@NetworkStreamThrottler
Patch Set: Fix use of message_loop_ in Android tests. Created 4 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(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
OLDNEW
« no previous file with comments | « net/base/network_throttle_manager_impl.h ('k') | net/base/network_throttle_manager_impl_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698