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

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 p404484. Created 4 years, 5 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 "base/logging.h"
8 #include "base/message_loop/message_loop.h"
9 #include "base/time/default_tick_clock.h"
10
11 namespace net {
12
13 // Maximum number of active requests before new THROTTLED throttles
14 // are created blocked. Throttles are unblocked as the active requests
15 // fall below this limit.
mmenke 2016/07/11 17:17:20 Docs should be with declaration, not definition.
Randy Smith (Not in Mondays) 2016/08/29 19:44:52 So, I'll call out that I think these constants (ot
16 const size_t NetworkThrottleManagerImpl::kActiveRequestThrottlingLimit = 2;
17
18 // Constants used for the running estimate of the median lifetime
19 // for throttles created by this class. That estimate is used to detect
20 // throttles that are "unusually old" and hence may represent hanging GETs
21 // or long-running streams. Such throttles should not be considered
22 // "active" for the purposes of determining whether THROTTLED throttles
23 // should be created in a blocked state.
24 // Note that the precise details of this algorithm aren't very important;
25 // specifically, if it takes a while for the median estimate to reach the
26 // "actual" median of a request stream, the consequence is either a bit more
27 // of a delay in unblocking THROTTLED requests or more THROTTLED requests
28 // being unblocked than would be ideal (i.e. performance tweaks at
29 // the margins).
30
31 // Multiple of the current median lifetime beyond which a throttle is
32 // considered "unusually old" and not considered in counting active
33 // requests.
34 const int NetworkThrottleManagerImpl::kMedianLifetimeMultiple = 5;
35
36 // The median lifetime estimate starts at class creation at
37 // |kInitialMedianInMS|. When a throttle completes, the median is adjusted
38 // upwards or downwards by |kMedianEstimatorDeltaMS| milliseconds.
39 // |kMedianEstimatorDeltaMS| was chosen for simplicity, as per
40 // "precise details aren't very important", above.
41 const int NetworkThrottleManagerImpl::kInitialMedianInMS = 100;
42 const double NetworkThrottleManagerImpl::kMedianEstimatorDeltaMS = 1;
mmenke 2016/07/11 17:17:20 nit: Ms (x2)
mmenke 2016/07/11 17:17:20 There doesn't seem to be any reason for kMedianEst
Randy Smith (Not in Mondays) 2016/08/29 19:44:52 Yes, but moot; it's gone.
Randy Smith (Not in Mondays) 2016/08/29 19:44:52 One done, the other is moot and has been removed.
43
44 NetworkThrottleManagerImpl::ThrottleImpl::ThrottleImpl(
45 bool blocked,
46 RequestPriority priority,
47 NetworkThrottleManager::ThrottleDelegate* delegate,
48 NetworkThrottleManagerImpl* manager,
49 QueuePointer queue_pointer)
50 : blocked_(blocked),
51 priority_(priority),
52 delegate_(delegate),
53 manager_(manager),
54 creation_time_(manager->tick_clock_->NowTicks()),
55 queue_pointer_(queue_pointer) {
56 DCHECK(delegate);
57 }
58
59 NetworkThrottleManagerImpl::ThrottleImpl::~ThrottleImpl() {
60 manager_->OnThrottleDestroyed(this);
61 }
62
63 bool NetworkThrottleManagerImpl::ThrottleImpl::IsBlocked() const {
64 return blocked_;
65 }
66
67 RequestPriority NetworkThrottleManagerImpl::ThrottleImpl::Priority() const {
68 return priority_;
69 }
70
71 void NetworkThrottleManagerImpl::ThrottleImpl::SetPriority(
72 RequestPriority new_priority) {
73 RequestPriority old_priority(priority_);
74 priority_ = new_priority;
75 manager_->OnThrottlePriorityChanged(this, old_priority, new_priority);
76 }
77
78 void NetworkThrottleManagerImpl::ThrottleImpl::NotifyUnblocked() {
79 // This methods should only be called once, and only if the
80 // current state is blocked.
81 DCHECK(blocked_);
82 blocked_ = false;
83 delegate_->OnThrottleStateChanged(this);
84 }
85
86 NetworkThrottleManagerImpl::NetworkThrottleManagerImpl()
87 : lifetime_median_estimate_(
88 base::TimeDelta::FromMilliseconds(kInitialMedianInMS)),
89 creation_ordering_set_(
90 &NetworkThrottleManagerImpl::CompareThrottlesForCreationTime),
91 num_throttles_blocked_(0u),
92 num_effective_outstanding_(0u),
93 outstanding_recomputation_timer_(false /* retain_user_task */,
94 false /* is_repeating */),
95 tick_clock_(new base::DefaultTickClock()) {
96 outstanding_recomputation_timer_.SetTaskRunner(
97 base::MessageLoop::current()->task_runner());
98 }
99
100 NetworkThrottleManagerImpl::~NetworkThrottleManagerImpl() {}
101
102 std::unique_ptr<NetworkThrottleManager::Throttle>
103 NetworkThrottleManagerImpl::CreateThrottle(
104 NetworkThrottleManager::ThrottleDelegate* delegate,
105 RequestPriority priority,
106 bool ignore_limits) {
107 bool blocked = (!ignore_limits && priority == THROTTLED &&
108 num_effective_outstanding_ >= kActiveRequestThrottlingLimit);
109
110 std::unique_ptr<NetworkThrottleManagerImpl::ThrottleImpl> throttle(
111 new ThrottleImpl(blocked, priority, delegate, this,
112 // Just to have a meaningful default value
113 unblocked_requests_[priority].end()));
114
115 std::list<ThrottleImpl*>* throttle_list = ListForThrottle(blocked, priority);
116
117 throttle->set_queue_pointer(
118 throttle_list->insert(throttle_list->end(), throttle.get()));
119 creation_ordering_set_.insert(throttle.get());
120 if (blocked)
121 ++num_throttles_blocked_;
122
123 // Can only have increased the outstanding count (decreases due to aging out
124 // would be caught by timer) so a MaybeUnblockThrottles() isn't needed.
125 RecomputeOutstanding();
126
127 return std::move(throttle);
128 }
129
130 void NetworkThrottleManagerImpl::SetTickClockForTesting(
131 std::unique_ptr<base::TickClock> tick_clock) {
132 tick_clock_ = std::move(tick_clock);
133 }
134
135 void NetworkThrottleManagerImpl::ConditionallyTriggerTimerForTesting() {
136 // Relies on |!retain_user_task| in timer constructor.
137 if (!outstanding_recomputation_timer_.user_task().is_null() &&
138 (tick_clock_->NowTicks() >
139 outstanding_recomputation_timer_.desired_run_time())) {
140 base::Closure timer_callback(outstanding_recomputation_timer_.user_task());
141 outstanding_recomputation_timer_.Stop();
142 timer_callback.Run();
143 }
144 }
145
146 bool NetworkThrottleManagerImpl::CompareThrottlesForCreationTime(
147 ThrottleImpl* throttle1,
148 ThrottleImpl* throttle2) {
149 return (throttle1->creation_time() < throttle2->creation_time() ||
150 // So different throttles don't look equal to the comparison
151 // function.
152 (throttle1->creation_time() == throttle2->creation_time() &&
153 throttle1 < throttle2));
154 }
155
156 void NetworkThrottleManagerImpl::OnThrottlePriorityChanged(
157 NetworkThrottleManagerImpl::ThrottleImpl* throttle,
158 RequestPriority old_priority,
159 RequestPriority new_priority) {
160 ListForThrottle(throttle->IsBlocked(), old_priority)
161 ->erase(throttle->queue_pointer());
162
163 std::list<ThrottleImpl*>* new_throttle_list =
164 ListForThrottle(throttle->IsBlocked(), new_priority);
165
166 throttle->set_queue_pointer(
167 new_throttle_list->insert(new_throttle_list->end(), throttle));
168
169 // There is no need to call |MaybeUnblockThrottles()| because
170 // changing a throttle priority will not have decreased the number of
171 // outstanding throttles. But if the throttle whose priority was changed
172 // was transitioned away from THROTTLED then it may need to be unblocked.
173 if (old_priority == THROTTLED && new_priority != THROTTLED &&
174 throttle->IsBlocked()) {
175 // NOTE: This call may result in reentrant calls into
176 // NetworkThrottleManagerImpl; no state should be assumed to be
177 // persistent across this call.
178 --num_throttles_blocked_;
179 throttle->NotifyUnblocked();
180 }
181 }
182
183 void NetworkThrottleManagerImpl::OnThrottleDestroyed(ThrottleImpl* throttle) {
184 // Adjust median estimate. Algorithm from
185 // http://arxiv.org/pdf/1407.1121v1.pdf (Algorithm 1).
186 bool older_than_median =
187 (tick_clock_->NowTicks() - throttle->creation_time() >
188 lifetime_median_estimate_);
189
190 lifetime_median_estimate_ += base::TimeDelta::FromMilliseconds(
191 kMedianEstimatorDeltaMS * (older_than_median ? 1 : -1));
192
193 if (lifetime_median_estimate_ < base::TimeDelta::FromMilliseconds(1))
194 lifetime_median_estimate_ = base::TimeDelta::FromMilliseconds(1);
195
196 ListForThrottle(throttle)->erase(throttle->queue_pointer());
197 size_t items_erased = creation_ordering_set_.erase(throttle);
198 DCHECK_EQ(1u, items_erased);
199 if (throttle->IsBlocked())
200 --num_throttles_blocked_;
201
202 // TODO(rdsmith): Via PostTask so there aren't upcalls from within
203 // destructors?
204 MaybeUnblockThrottles();
205 }
206
207 void NetworkThrottleManagerImpl::RecomputeOutstanding() {
208 num_effective_outstanding_ =
mmenke 2016/07/11 17:17:20 This doesn't seem like it should be a class variab
Randy Smith (Not in Mondays) 2016/08/29 19:44:52 Hmmm. I started to do this refactor, and I think
209 creation_ordering_set_.size() - num_throttles_blocked_;
210 if (num_effective_outstanding_ == 0) {
211 outstanding_recomputation_timer_.Stop();
212 return;
213 }
214
215 // The number of throttles that "count" as outstanding is the
216 // total number of throttles minus all throttles that are
217 // older than |kMedianLifetimeMultiple * lifetime_median_estimate_|.
218 base::TimeTicks age_horizon(tick_clock_->NowTicks() -
219 kMedianLifetimeMultiple *
220 lifetime_median_estimate_);
221 for (const auto* throttle : creation_ordering_set_) {
222 if (throttle->creation_time() > age_horizon) {
223 // The earliest we might need to re-evaluate the outstanding count
224 // and blocked status is the amount of time it will take the current
225 // element to age out of the effective set.
226 outstanding_recomputation_timer_.Start(
227 FROM_HERE, throttle->creation_time() - age_horizon,
228 // Unretained use of |this| is safe because the timer is
229 // owned by this object, and will be torn down if this object
230 // is destroyed.
231 base::Bind(&NetworkThrottleManagerImpl::MaybeUnblockThrottles,
232 base::Unretained(this)));
233 break;
234 }
235 // Only unblocked throttles are considered outstanding, so only those
236 // throttles need to be subtracted out due to aging.
237 if (!throttle->IsBlocked())
238 --num_effective_outstanding_;
239 }
240 }
241
242 void NetworkThrottleManagerImpl::MaybeUnblockThrottles() {
243 RecomputeOutstanding();
244
245 while (num_effective_outstanding_ < kActiveRequestThrottlingLimit &&
246 !blocked_requests_[THROTTLED].empty()) {
mmenke 2016/07/11 17:17:20 I'd suggest making a method: UnblockThrottle(Thro
Randy Smith (Not in Mondays) 2016/08/29 19:44:52 Done, though I'm not convinced it's a win because
247 ThrottleImpl* to_unblock = blocked_requests_[THROTTLED].front();
248 blocked_requests_[THROTTLED].pop_front();
249
250 to_unblock->set_queue_pointer(unblocked_requests_[THROTTLED].insert(
251 unblocked_requests_[THROTTLED].end(), to_unblock));
252
253 ++num_effective_outstanding_;
254 --num_throttles_blocked_;
255
256 // NOTE: This call may result in reentrant calls into
257 // NetworkThrottleManagerImpl; no state should be assumed to be
258 // persistent across this call.
259 to_unblock->NotifyUnblocked();
260 }
261 }
262
263 std::list<NetworkThrottleManagerImpl::ThrottleImpl*>*
264 NetworkThrottleManagerImpl::ListForThrottle(bool blocked,
265 RequestPriority priority) {
266 return &((blocked ? blocked_requests_ : unblocked_requests_)[priority]);
267 }
268
269 std::list<NetworkThrottleManagerImpl::ThrottleImpl*>*
270 NetworkThrottleManagerImpl::ListForThrottle(ThrottleImpl* throttle) {
271 return ListForThrottle(throttle->IsBlocked(), throttle->priority());
272 }
273
274 } // namespace net
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698