OLD | NEW |
| (Empty) |
1 // Copyright 2014 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 "components/scheduler/base/task_queue_selector.h" | |
6 | |
7 #include "base/logging.h" | |
8 #include "base/trace_event/trace_event_argument.h" | |
9 #include "components/scheduler/base/task_queue_impl.h" | |
10 #include "components/scheduler/base/work_queue.h" | |
11 | |
12 namespace scheduler { | |
13 namespace internal { | |
14 | |
15 TaskQueueSelector::TaskQueueSelector() | |
16 : enabled_selector_(this, "enabled"), | |
17 blocked_selector_(this, "blocked"), | |
18 immediate_starvation_count_(0), | |
19 high_priority_starvation_count_(0), | |
20 num_blocked_queues_to_report_(0), | |
21 task_queue_selector_observer_(nullptr) {} | |
22 | |
23 TaskQueueSelector::~TaskQueueSelector() {} | |
24 | |
25 void TaskQueueSelector::AddQueue(internal::TaskQueueImpl* queue) { | |
26 DCHECK(main_thread_checker_.CalledOnValidThread()); | |
27 DCHECK(queue->IsQueueEnabled()); | |
28 enabled_selector_.AddQueue(queue, TaskQueue::NORMAL_PRIORITY); | |
29 } | |
30 | |
31 void TaskQueueSelector::RemoveQueue(internal::TaskQueueImpl* queue) { | |
32 DCHECK(main_thread_checker_.CalledOnValidThread()); | |
33 if (queue->IsQueueEnabled()) { | |
34 enabled_selector_.RemoveQueue(queue); | |
35 // The #if DCHECK_IS_ON() shouldn't be necessary but this doesn't compile on | |
36 // chromeos bots without it :( | |
37 #if DCHECK_IS_ON() | |
38 DCHECK(!blocked_selector_.CheckContainsQueueForTest(queue)); | |
39 #endif | |
40 } else if (queue->should_report_when_execution_blocked()) { | |
41 DCHECK_GT(num_blocked_queues_to_report_, 0u); | |
42 num_blocked_queues_to_report_--; | |
43 blocked_selector_.RemoveQueue(queue); | |
44 #if DCHECK_IS_ON() | |
45 DCHECK(!enabled_selector_.CheckContainsQueueForTest(queue)); | |
46 #endif | |
47 } | |
48 } | |
49 | |
50 void TaskQueueSelector::EnableQueue(internal::TaskQueueImpl* queue) { | |
51 DCHECK(main_thread_checker_.CalledOnValidThread()); | |
52 DCHECK(queue->IsQueueEnabled()); | |
53 if (queue->should_report_when_execution_blocked()) { | |
54 DCHECK_GT(num_blocked_queues_to_report_, 0u); | |
55 num_blocked_queues_to_report_--; | |
56 blocked_selector_.RemoveQueue(queue); | |
57 } | |
58 enabled_selector_.AddQueue(queue, queue->GetQueuePriority()); | |
59 if (task_queue_selector_observer_) | |
60 task_queue_selector_observer_->OnTaskQueueEnabled(queue); | |
61 } | |
62 | |
63 void TaskQueueSelector::DisableQueue(internal::TaskQueueImpl* queue) { | |
64 DCHECK(main_thread_checker_.CalledOnValidThread()); | |
65 DCHECK(!queue->IsQueueEnabled()); | |
66 enabled_selector_.RemoveQueue(queue); | |
67 if (queue->should_report_when_execution_blocked()) { | |
68 blocked_selector_.AddQueue(queue, queue->GetQueuePriority()); | |
69 num_blocked_queues_to_report_++; | |
70 } | |
71 } | |
72 | |
73 void TaskQueueSelector::SetQueuePriority(internal::TaskQueueImpl* queue, | |
74 TaskQueue::QueuePriority priority) { | |
75 DCHECK_LT(priority, TaskQueue::QUEUE_PRIORITY_COUNT); | |
76 DCHECK(main_thread_checker_.CalledOnValidThread()); | |
77 if (queue->IsQueueEnabled()) { | |
78 enabled_selector_.ChangeSetIndex(queue, priority); | |
79 } else if (queue->should_report_when_execution_blocked()) { | |
80 blocked_selector_.ChangeSetIndex(queue, priority); | |
81 } else { | |
82 // Normally blocked_selector_.ChangeSetIndex would assign the queue's | |
83 // priority, however if |queue->should_report_when_execution_blocked()| is | |
84 // false then the disabled queue is not in any set so we need to do it here. | |
85 queue->delayed_work_queue()->AssignSetIndex(priority); | |
86 queue->immediate_work_queue()->AssignSetIndex(priority); | |
87 } | |
88 DCHECK_EQ(priority, queue->GetQueuePriority()); | |
89 } | |
90 | |
91 TaskQueue::QueuePriority TaskQueueSelector::NextPriority( | |
92 TaskQueue::QueuePriority priority) { | |
93 DCHECK(priority < TaskQueue::QUEUE_PRIORITY_COUNT); | |
94 return static_cast<TaskQueue::QueuePriority>(static_cast<int>(priority) + 1); | |
95 } | |
96 | |
97 TaskQueueSelector::PrioritizingSelector::PrioritizingSelector( | |
98 TaskQueueSelector* task_queue_selector, | |
99 const char* name) | |
100 : task_queue_selector_(task_queue_selector), | |
101 delayed_work_queue_sets_(TaskQueue::QUEUE_PRIORITY_COUNT, name), | |
102 immediate_work_queue_sets_(TaskQueue::QUEUE_PRIORITY_COUNT, name) {} | |
103 | |
104 void TaskQueueSelector::PrioritizingSelector::AddQueue( | |
105 internal::TaskQueueImpl* queue, | |
106 TaskQueue::QueuePriority priority) { | |
107 #if DCHECK_IS_ON() | |
108 DCHECK(!CheckContainsQueueForTest(queue)); | |
109 #endif | |
110 delayed_work_queue_sets_.AddQueue(queue->delayed_work_queue(), priority); | |
111 immediate_work_queue_sets_.AddQueue(queue->immediate_work_queue(), priority); | |
112 #if DCHECK_IS_ON() | |
113 DCHECK(CheckContainsQueueForTest(queue)); | |
114 #endif | |
115 } | |
116 | |
117 void TaskQueueSelector::PrioritizingSelector::ChangeSetIndex( | |
118 internal::TaskQueueImpl* queue, | |
119 TaskQueue::QueuePriority priority) { | |
120 #if DCHECK_IS_ON() | |
121 DCHECK(CheckContainsQueueForTest(queue)); | |
122 #endif | |
123 delayed_work_queue_sets_.ChangeSetIndex(queue->delayed_work_queue(), | |
124 priority); | |
125 immediate_work_queue_sets_.ChangeSetIndex(queue->immediate_work_queue(), | |
126 priority); | |
127 #if DCHECK_IS_ON() | |
128 DCHECK(CheckContainsQueueForTest(queue)); | |
129 #endif | |
130 } | |
131 | |
132 void TaskQueueSelector::PrioritizingSelector::RemoveQueue( | |
133 internal::TaskQueueImpl* queue) { | |
134 #if DCHECK_IS_ON() | |
135 DCHECK(CheckContainsQueueForTest(queue)); | |
136 #endif | |
137 delayed_work_queue_sets_.RemoveQueue(queue->delayed_work_queue()); | |
138 immediate_work_queue_sets_.RemoveQueue(queue->immediate_work_queue()); | |
139 | |
140 #if DCHECK_IS_ON() | |
141 DCHECK(!CheckContainsQueueForTest(queue)); | |
142 #endif | |
143 } | |
144 | |
145 bool TaskQueueSelector::PrioritizingSelector:: | |
146 ChooseOldestImmediateTaskWithPriority(TaskQueue::QueuePriority priority, | |
147 WorkQueue** out_work_queue) const { | |
148 return immediate_work_queue_sets_.GetOldestQueueInSet(priority, | |
149 out_work_queue); | |
150 } | |
151 | |
152 bool TaskQueueSelector::PrioritizingSelector:: | |
153 ChooseOldestDelayedTaskWithPriority(TaskQueue::QueuePriority priority, | |
154 WorkQueue** out_work_queue) const { | |
155 return delayed_work_queue_sets_.GetOldestQueueInSet(priority, out_work_queue); | |
156 } | |
157 | |
158 bool TaskQueueSelector::PrioritizingSelector:: | |
159 ChooseOldestImmediateOrDelayedTaskWithPriority( | |
160 TaskQueue::QueuePriority priority, | |
161 bool* out_chose_delayed_over_immediate, | |
162 WorkQueue** out_work_queue) const { | |
163 WorkQueue* immediate_queue; | |
164 DCHECK_EQ(*out_chose_delayed_over_immediate, false); | |
165 if (immediate_work_queue_sets_.GetOldestQueueInSet(priority, | |
166 &immediate_queue)) { | |
167 WorkQueue* delayed_queue; | |
168 if (delayed_work_queue_sets_.GetOldestQueueInSet(priority, | |
169 &delayed_queue)) { | |
170 if (immediate_queue->ShouldRunBefore(delayed_queue)) { | |
171 *out_work_queue = immediate_queue; | |
172 } else { | |
173 *out_chose_delayed_over_immediate = true; | |
174 *out_work_queue = delayed_queue; | |
175 } | |
176 } else { | |
177 *out_work_queue = immediate_queue; | |
178 } | |
179 return true; | |
180 } | |
181 return delayed_work_queue_sets_.GetOldestQueueInSet(priority, out_work_queue); | |
182 } | |
183 | |
184 bool TaskQueueSelector::PrioritizingSelector::ChooseOldestWithPriority( | |
185 TaskQueue::QueuePriority priority, | |
186 bool* out_chose_delayed_over_immediate, | |
187 WorkQueue** out_work_queue) const { | |
188 // Select an immediate work queue if we are starving immediate tasks. | |
189 if (task_queue_selector_->immediate_starvation_count_ >= | |
190 kMaxDelayedStarvationTasks) { | |
191 if (ChooseOldestImmediateTaskWithPriority(priority, out_work_queue)) { | |
192 return true; | |
193 } | |
194 if (ChooseOldestDelayedTaskWithPriority(priority, out_work_queue)) { | |
195 return true; | |
196 } | |
197 return false; | |
198 } | |
199 return ChooseOldestImmediateOrDelayedTaskWithPriority( | |
200 priority, out_chose_delayed_over_immediate, out_work_queue); | |
201 } | |
202 | |
203 bool TaskQueueSelector::PrioritizingSelector::SelectWorkQueueToService( | |
204 TaskQueue::QueuePriority max_priority, | |
205 WorkQueue** out_work_queue, | |
206 bool* out_chose_delayed_over_immediate) { | |
207 DCHECK(task_queue_selector_->main_thread_checker_.CalledOnValidThread()); | |
208 DCHECK_EQ(*out_chose_delayed_over_immediate, false); | |
209 | |
210 // Always service the control queue if it has any work. | |
211 if (max_priority > TaskQueue::CONTROL_PRIORITY && | |
212 ChooseOldestWithPriority(TaskQueue::CONTROL_PRIORITY, | |
213 out_chose_delayed_over_immediate, | |
214 out_work_queue)) { | |
215 return true; | |
216 } | |
217 | |
218 // Select from the normal priority queue if we are starving it. | |
219 if (max_priority > TaskQueue::NORMAL_PRIORITY && | |
220 task_queue_selector_->high_priority_starvation_count_ >= | |
221 kMaxHighPriorityStarvationTasks && | |
222 ChooseOldestWithPriority(TaskQueue::NORMAL_PRIORITY, | |
223 out_chose_delayed_over_immediate, | |
224 out_work_queue)) { | |
225 return true; | |
226 } | |
227 // Otherwise choose in priority order. | |
228 for (TaskQueue::QueuePriority priority = TaskQueue::HIGH_PRIORITY; | |
229 priority < max_priority; priority = NextPriority(priority)) { | |
230 if (ChooseOldestWithPriority(priority, out_chose_delayed_over_immediate, | |
231 out_work_queue)) { | |
232 return true; | |
233 } | |
234 } | |
235 return false; | |
236 } | |
237 | |
238 #if DCHECK_IS_ON() || !defined(NDEBUG) | |
239 bool | |
240 TaskQueueSelector::PrioritizingSelector::CheckContainsQueueForTest( | |
241 const internal::TaskQueueImpl* queue) const { | |
242 bool contains_delayed_work_queue = | |
243 delayed_work_queue_sets_.ContainsWorkQueueForTest( | |
244 queue->delayed_work_queue()); | |
245 | |
246 bool contains_immediate_work_queue = | |
247 immediate_work_queue_sets_.ContainsWorkQueueForTest( | |
248 queue->immediate_work_queue()); | |
249 | |
250 DCHECK_EQ(contains_delayed_work_queue, contains_immediate_work_queue); | |
251 return contains_delayed_work_queue; | |
252 } | |
253 #endif | |
254 | |
255 bool TaskQueueSelector::SelectWorkQueueToService(WorkQueue** out_work_queue) { | |
256 DCHECK(main_thread_checker_.CalledOnValidThread()); | |
257 bool chose_delayed_over_immediate = false; | |
258 bool found_queue = enabled_selector_.SelectWorkQueueToService( | |
259 TaskQueue::QUEUE_PRIORITY_COUNT, out_work_queue, | |
260 &chose_delayed_over_immediate); | |
261 if (!found_queue) { | |
262 TrySelectingBlockedQueue(); | |
263 return false; | |
264 } | |
265 | |
266 TrySelectingBlockedQueueOverEnabledQueue(**out_work_queue); | |
267 DidSelectQueueWithPriority( | |
268 (*out_work_queue)->task_queue()->GetQueuePriority(), | |
269 chose_delayed_over_immediate); | |
270 return true; | |
271 } | |
272 | |
273 void TaskQueueSelector::TrySelectingBlockedQueue() { | |
274 DCHECK(main_thread_checker_.CalledOnValidThread()); | |
275 if (!num_blocked_queues_to_report_ || !task_queue_selector_observer_) | |
276 return; | |
277 WorkQueue* chosen_blocked_queue; | |
278 bool chose_delayed_over_immediate = false; | |
279 // There was nothing unblocked to run, see if we could have run a blocked | |
280 // task. | |
281 if (blocked_selector_.SelectWorkQueueToService( | |
282 TaskQueue::QUEUE_PRIORITY_COUNT, &chosen_blocked_queue, | |
283 &chose_delayed_over_immediate)) { | |
284 task_queue_selector_observer_->OnTriedToSelectBlockedWorkQueue( | |
285 chosen_blocked_queue); | |
286 } | |
287 } | |
288 | |
289 void TaskQueueSelector::TrySelectingBlockedQueueOverEnabledQueue( | |
290 const WorkQueue& chosen_enabled_queue) { | |
291 DCHECK(main_thread_checker_.CalledOnValidThread()); | |
292 if (!num_blocked_queues_to_report_ || !task_queue_selector_observer_) | |
293 return; | |
294 | |
295 TaskQueue::QueuePriority max_priority = | |
296 NextPriority(chosen_enabled_queue.task_queue()->GetQueuePriority()); | |
297 | |
298 WorkQueue* chosen_blocked_queue; | |
299 bool chose_delayed_over_immediate = false; | |
300 bool found_queue = blocked_selector_.SelectWorkQueueToService( | |
301 max_priority, &chosen_blocked_queue, &chose_delayed_over_immediate); | |
302 if (!found_queue) | |
303 return; | |
304 | |
305 // Check if the chosen blocked queue has a lower numerical priority than the | |
306 // chosen enabled queue. If so we would have chosen the blocked queue (since | |
307 // zero is the highest priority). | |
308 if (chosen_blocked_queue->task_queue()->GetQueuePriority() < | |
309 chosen_enabled_queue.task_queue()->GetQueuePriority()) { | |
310 task_queue_selector_observer_->OnTriedToSelectBlockedWorkQueue( | |
311 chosen_blocked_queue); | |
312 return; | |
313 } | |
314 DCHECK_EQ(chosen_blocked_queue->task_queue()->GetQueuePriority(), | |
315 chosen_enabled_queue.task_queue()->GetQueuePriority()); | |
316 // Otherwise there was an enabled and a blocked task with the same priority. | |
317 // The one with the older enqueue order wins. | |
318 if (chosen_blocked_queue->ShouldRunBefore(&chosen_enabled_queue)) { | |
319 task_queue_selector_observer_->OnTriedToSelectBlockedWorkQueue( | |
320 chosen_blocked_queue); | |
321 } | |
322 } | |
323 | |
324 void TaskQueueSelector::DidSelectQueueWithPriority( | |
325 TaskQueue::QueuePriority priority, | |
326 bool chose_delayed_over_immediate) { | |
327 switch (priority) { | |
328 case TaskQueue::CONTROL_PRIORITY: | |
329 break; | |
330 case TaskQueue::HIGH_PRIORITY: | |
331 high_priority_starvation_count_++; | |
332 break; | |
333 case TaskQueue::NORMAL_PRIORITY: | |
334 case TaskQueue::BEST_EFFORT_PRIORITY: | |
335 high_priority_starvation_count_ = 0; | |
336 break; | |
337 default: | |
338 NOTREACHED(); | |
339 } | |
340 if (chose_delayed_over_immediate) { | |
341 immediate_starvation_count_++; | |
342 } else { | |
343 immediate_starvation_count_ = 0; | |
344 } | |
345 } | |
346 | |
347 void TaskQueueSelector::AsValueInto( | |
348 base::trace_event::TracedValue* state) const { | |
349 DCHECK(main_thread_checker_.CalledOnValidThread()); | |
350 state->SetInteger("high_priority_starvation_count", | |
351 high_priority_starvation_count_); | |
352 state->SetInteger("immediate_starvation_count", immediate_starvation_count_); | |
353 state->SetInteger("num_blocked_queues_to_report", | |
354 num_blocked_queues_to_report_); | |
355 } | |
356 | |
357 void TaskQueueSelector::SetTaskQueueSelectorObserver(Observer* observer) { | |
358 task_queue_selector_observer_ = observer; | |
359 } | |
360 | |
361 bool TaskQueueSelector::EnabledWorkQueuesEmpty() const { | |
362 DCHECK(main_thread_checker_.CalledOnValidThread()); | |
363 for (TaskQueue::QueuePriority priority = TaskQueue::CONTROL_PRIORITY; | |
364 priority < TaskQueue::QUEUE_PRIORITY_COUNT; | |
365 priority = NextPriority(priority)) { | |
366 if (!enabled_selector_.delayed_work_queue_sets()->IsSetEmpty(priority) || | |
367 !enabled_selector_.immediate_work_queue_sets()->IsSetEmpty(priority)) { | |
368 return false; | |
369 } | |
370 } | |
371 return true; | |
372 } | |
373 | |
374 void TaskQueueSelector::SetImmediateStarvationCountForTest( | |
375 size_t immediate_starvation_count) { | |
376 immediate_starvation_count_ = immediate_starvation_count; | |
377 } | |
378 | |
379 } // namespace internal | |
380 } // namespace scheduler | |
OLD | NEW |