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

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, short circuit timer reset if same throttle, age_horizon->TimeDelta. 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
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
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698