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

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: Incorporated comments and substantially simplified state storage. Created 4 years, 3 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 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
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698