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

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: Sync'd to p420478 Created 4 years, 2 months 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/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();
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_; }
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 tick_clock_(new base::DefaultTickClock()),
148 weak_ptr_factory_(this) {
149 outstanding_recomputation_timer_.SetTaskRunner(
150 base::MessageLoop::current()->task_runner());
151 }
152
153 NetworkThrottleManagerImpl::~NetworkThrottleManagerImpl() {}
154
155 std::unique_ptr<NetworkThrottleManager::Throttle>
156 NetworkThrottleManagerImpl::CreateThrottle(
157 NetworkThrottleManager::ThrottleDelegate* delegate,
158 RequestPriority priority,
159 bool ignore_limits) {
160 bool blocked =
161 (!ignore_limits && priority == THROTTLED &&
162 outstanding_throttles_.size() >= kActiveRequestThrottlingLimit);
163
164 std::unique_ptr<NetworkThrottleManagerImpl::ThrottleImpl> throttle(
165 new ThrottleImpl(blocked, priority, delegate, this,
166 blocked_throttles_.end()));
167
168 if (blocked) {
169 throttle->set_queue_pointer(
170 blocked_throttles_.insert(blocked_throttles_.end(), throttle.get()));
171 } else {
172 outstanding_throttles_.insert(throttle.get());
173 }
174
175 // Throttles may need to be unblocked if the outstanding throttles
176 // that blocked them have aged out of the oustanding set since
177 // the last entry to MaybeUnblockThrottles().
178 MaybeUnblockThrottles();
179
180 return std::move(throttle);
181 }
182
183 void NetworkThrottleManagerImpl::SetTickClockForTesting(
184 std::unique_ptr<base::TickClock> tick_clock) {
185 tick_clock_ = std::move(tick_clock);
186 }
187
188 void NetworkThrottleManagerImpl::ConditionallyTriggerTimerForTesting() {
189 // Relies on |!retain_user_task| in timer constructor.
190 if (!outstanding_recomputation_timer_.user_task().is_null() &&
191 (tick_clock_->NowTicks() >
192 outstanding_recomputation_timer_.desired_run_time())) {
193 base::Closure timer_callback(outstanding_recomputation_timer_.user_task());
194 outstanding_recomputation_timer_.Stop();
195 timer_callback.Run();
196 }
197 }
198
199 // static
200 bool NetworkThrottleManagerImpl::CreationTimeSetCompare(
201 ThrottleImpl* throttle1,
202 ThrottleImpl* throttle2) {
203 // No throttle should be in the CreationTimeOrderedSet with a null start
204 // time.
205 DCHECK_NE(throttle1->start_time(), base::TimeTicks());
206 DCHECK_NE(throttle2->start_time(), base::TimeTicks());
207 return (throttle1->start_time() < throttle2->start_time() ||
208 // So different throttles don't look equal to the comparison
209 // function.
210 (throttle1->start_time() == throttle2->start_time() &&
211 throttle1 < throttle2));
212 }
213
214 void NetworkThrottleManagerImpl::OnThrottlePriorityChanged(
215 NetworkThrottleManagerImpl::ThrottleImpl* throttle,
216 RequestPriority old_priority,
217 RequestPriority new_priority) {
218 // The only case requiring a state change is if the priority change
219 // implies unblocking.
220 if (throttle->IsBlocked() && // Implies |old_priority == THROTTLED|
221 new_priority != THROTTLED) {
222 // May result in re-entrant calls into this class.
223 UnblockThrottle(throttle);
224 }
225
226 // In case timing has caused throttles to age out.
227 MaybeUnblockThrottles();
228 }
229
230 void NetworkThrottleManagerImpl::OnThrottleDestroyed(ThrottleImpl* throttle) {
231 switch (throttle->state()) {
232 case ThrottleImpl::BLOCKED:
233 DCHECK(throttle->queue_pointer() != blocked_throttles_.end());
234 blocked_throttles_.erase(throttle->queue_pointer());
235 break;
236 case ThrottleImpl::OUTSTANDING: {
237 size_t items_erased = outstanding_throttles_.erase(throttle);
238 DCHECK_EQ(1u, items_erased);
239 }
240 // Fall through
241 case ThrottleImpl::AGED:
242 DCHECK_NE(throttle->start_time(), base::TimeTicks());
243 lifetime_median_estimate_.AddSample(
244 (tick_clock_->NowTicks() - throttle->start_time())
245 .InMillisecondsRoundedUp());
246 break;
247 }
248
249 // Via PostTask so there aren't upcalls from within destructors.
250 base::MessageLoop::current()->task_runner()->PostTask(
251 FROM_HERE, base::Bind(&NetworkThrottleManagerImpl::MaybeUnblockThrottles,
252 weak_ptr_factory_.GetWeakPtr()));
253 }
254
255 void NetworkThrottleManagerImpl::RecomputeOutstanding() {
256 // Remove all throttles that have aged out of the outstanding set.
257 base::TimeTicks now(tick_clock_->NowTicks());
258 base::TimeTicks age_horizon(
259 now - base::TimeDelta::FromMilliseconds(
260 (kMedianLifetimeMultiple *
261 lifetime_median_estimate_.current_estimate())));
262 while (!outstanding_throttles_.empty()) {
263 ThrottleImpl* throttle = *outstanding_throttles_.begin();
264 if (throttle->start_time() > age_horizon)
265 break;
266
267 outstanding_throttles_.erase(throttle);
268 throttle->SetAged();
269 }
270
271 // If the set's now empty, no timer is needed.
272 if (outstanding_throttles_.empty()) {
273 outstanding_recomputation_timer_.Stop();
274 return;
275 }
276
277 // The earliest the timer will need to be reset is when the earliest throttle
278 // ages out of the set. This isn't precise because the median estimate
279 // changes over time, but this is a heuristic and doesn't need to be
280 // precise.
281 // TODO(rdsmith): Under that same "not precise" rubric, worthwhile avoiding
282 // resetting the timer if it's "close"?
283 ThrottleImpl* first_throttle = *outstanding_throttles_.begin();
284 base::TimeTicks new_timer_start =
285 now + (first_throttle->start_time() - age_horizon);
286 if (!outstanding_recomputation_timer_.user_task().is_null() &&
287 outstanding_recomputation_timer_.desired_run_time() == new_timer_start) {
288 return;
289 }
290
291 outstanding_recomputation_timer_.Start(
292 FROM_HERE, first_throttle->start_time() - age_horizon,
293 // Unretained use of |this| is safe because the timer is
294 // owned by this object, and will be torn down if this object
295 // is destroyed.
296 base::Bind(&NetworkThrottleManagerImpl::MaybeUnblockThrottles,
297 base::Unretained(this)));
298 }
299
300 void NetworkThrottleManagerImpl::UnblockThrottle(ThrottleImpl* throttle) {
301 DCHECK(throttle->IsBlocked());
302
303 blocked_throttles_.erase(throttle->queue_pointer());
304 throttle->set_queue_pointer(blocked_throttles_.end());
305 throttle->set_start_time(tick_clock_->NowTicks());
306 outstanding_throttles_.insert(throttle);
307
308 // May result in re-entrant calls into this class.
309 throttle->NotifyUnblocked();
310 }
311
312 void NetworkThrottleManagerImpl::MaybeUnblockThrottles() {
313 RecomputeOutstanding();
314
315 while (outstanding_throttles_.size() < kActiveRequestThrottlingLimit &&
316 !blocked_throttles_.empty()) {
317 // NOTE: This call may result in reentrant calls into
318 // NetworkThrottleManagerImpl; no state should be assumed to be
319 // persistent across this call.
320 UnblockThrottle(blocked_throttles_.front());
321 }
322 }
323
324 } // namespace net
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698