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.
5 #include "cc/raster/task_graph_runner.h"
9 #include "base/containers/hash_tables.h"
10 #include "base/strings/stringprintf.h"
11 #include "base/threading/thread_restrictions.h"
12 #include "base/trace_event/trace_event.h"
17 // Helper class for iterating over all dependents of a task.
18 class DependentIterator
{
20 DependentIterator(TaskGraph
* graph
, const Task
* task
)
23 current_index_(static_cast<size_t>(-1)),
28 TaskGraph::Node
& operator->() const {
29 DCHECK_LT(current_index_
, graph_
->edges
.size());
30 DCHECK_EQ(graph_
->edges
[current_index_
].task
, task_
);
31 DCHECK(current_node_
);
32 return *current_node_
;
35 TaskGraph::Node
& operator*() const {
36 DCHECK_LT(current_index_
, graph_
->edges
.size());
37 DCHECK_EQ(graph_
->edges
[current_index_
].task
, task_
);
38 DCHECK(current_node_
);
39 return *current_node_
;
42 // Note: Performance can be improved by keeping edges sorted.
43 DependentIterator
& operator++() {
44 // Find next dependency edge for |task_|.
47 if (current_index_
== graph_
->edges
.size())
49 } while (graph_
->edges
[current_index_
].task
!= task_
);
51 // Now find the node for the dependent of this edge.
52 TaskGraph::Node::Vector::iterator it
=
53 std::find_if(graph_
->nodes
.begin(),
55 TaskGraph::Node::TaskComparator(
56 graph_
->edges
[current_index_
].dependent
));
57 DCHECK(it
!= graph_
->nodes
.end());
58 current_node_
= &(*it
);
63 operator bool() const { return current_index_
< graph_
->edges
.size(); }
68 size_t current_index_
;
69 TaskGraph::Node
* current_node_
;
72 bool DependencyMismatch(const TaskGraph
* graph
) {
73 // Value storage will be 0-initialized.
74 base::hash_map
<const Task
*, size_t> dependents
;
75 for (const TaskGraph::Edge
& edge
: graph
->edges
)
76 dependents
[edge
.dependent
]++;
78 for (const TaskGraph::Node
& node
: graph
->nodes
) {
79 if (dependents
[node
.task
] != node
.dependencies
)
88 Task::Task() : will_run_(false), did_run_(false) {
95 void Task::WillRun() {
101 void Task::DidRun() {
107 bool Task::HasFinishedRunning() const { return did_run_
; }
109 TaskGraph::TaskGraph() {}
111 TaskGraph::~TaskGraph() {}
113 void TaskGraph::Swap(TaskGraph
* other
) {
114 nodes
.swap(other
->nodes
);
115 edges
.swap(other
->edges
);
118 void TaskGraph::Reset() {
123 TaskGraphRunner::TaskNamespace::TaskNamespace() {}
125 TaskGraphRunner::TaskNamespace::~TaskNamespace() {}
127 TaskGraphRunner::TaskGraphRunner()
129 has_ready_to_run_tasks_cv_(&lock_
),
130 has_namespaces_with_finished_running_tasks_cv_(&lock_
),
131 next_namespace_id_(1),
134 TaskGraphRunner::~TaskGraphRunner() {
136 base::AutoLock
lock(lock_
);
138 DCHECK_EQ(0u, ready_to_run_namespaces_
.size());
139 DCHECK_EQ(0u, namespaces_
.size());
143 NamespaceToken
TaskGraphRunner::GetNamespaceToken() {
144 base::AutoLock
lock(lock_
);
146 NamespaceToken
token(next_namespace_id_
++);
147 DCHECK(namespaces_
.find(token
.id_
) == namespaces_
.end());
151 void TaskGraphRunner::ScheduleTasks(NamespaceToken token
, TaskGraph
* graph
) {
153 "TaskGraphRunner::ScheduleTasks",
157 graph
->edges
.size());
159 DCHECK(token
.IsValid());
160 DCHECK(!DependencyMismatch(graph
));
163 base::AutoLock
lock(lock_
);
167 TaskNamespace
& task_namespace
= namespaces_
[token
.id_
];
169 // First adjust number of dependencies to reflect completed tasks.
170 for (Task::Vector::iterator it
= task_namespace
.completed_tasks
.begin();
171 it
!= task_namespace
.completed_tasks
.end();
173 for (DependentIterator
node_it(graph
, it
->get()); node_it
; ++node_it
) {
174 TaskGraph::Node
& node
= *node_it
;
175 DCHECK_LT(0u, node
.dependencies
);
180 // Build new "ready to run" queue and remove nodes from old graph.
181 task_namespace
.ready_to_run_tasks
.clear();
182 for (TaskGraph::Node::Vector::iterator it
= graph
->nodes
.begin();
183 it
!= graph
->nodes
.end();
185 TaskGraph::Node
& node
= *it
;
187 // Remove any old nodes that are associated with this task. The result is
188 // that the old graph is left with all nodes not present in this graph,
189 // which we use below to determine what tasks need to be canceled.
190 TaskGraph::Node::Vector::iterator old_it
=
191 std::find_if(task_namespace
.graph
.nodes
.begin(),
192 task_namespace
.graph
.nodes
.end(),
193 TaskGraph::Node::TaskComparator(node
.task
));
194 if (old_it
!= task_namespace
.graph
.nodes
.end()) {
195 std::swap(*old_it
, task_namespace
.graph
.nodes
.back());
196 task_namespace
.graph
.nodes
.pop_back();
199 // Task is not ready to run if dependencies are not yet satisfied.
200 if (node
.dependencies
)
203 // Skip if already finished running task.
204 if (node
.task
->HasFinishedRunning())
207 // Skip if already running.
208 if (std::find(task_namespace
.running_tasks
.begin(),
209 task_namespace
.running_tasks
.end(),
210 node
.task
) != task_namespace
.running_tasks
.end())
213 task_namespace
.ready_to_run_tasks
.push_back(
214 PrioritizedTask(node
.task
, node
.priority
));
217 // Rearrange the elements in |ready_to_run_tasks| in such a way that they
219 std::make_heap(task_namespace
.ready_to_run_tasks
.begin(),
220 task_namespace
.ready_to_run_tasks
.end(),
221 CompareTaskPriority
);
224 task_namespace
.graph
.Swap(graph
);
226 // Determine what tasks in old graph need to be canceled.
227 for (TaskGraph::Node::Vector::iterator it
= graph
->nodes
.begin();
228 it
!= graph
->nodes
.end();
230 TaskGraph::Node
& node
= *it
;
232 // Skip if already finished running task.
233 if (node
.task
->HasFinishedRunning())
236 // Skip if already running.
237 if (std::find(task_namespace
.running_tasks
.begin(),
238 task_namespace
.running_tasks
.end(),
239 node
.task
) != task_namespace
.running_tasks
.end())
242 DCHECK(std::find(task_namespace
.completed_tasks
.begin(),
243 task_namespace
.completed_tasks
.end(),
244 node
.task
) == task_namespace
.completed_tasks
.end());
245 task_namespace
.completed_tasks
.push_back(node
.task
);
248 // Build new "ready to run" task namespaces queue.
249 ready_to_run_namespaces_
.clear();
250 for (TaskNamespaceMap::iterator it
= namespaces_
.begin();
251 it
!= namespaces_
.end();
253 if (!it
->second
.ready_to_run_tasks
.empty())
254 ready_to_run_namespaces_
.push_back(&it
->second
);
257 // Rearrange the task namespaces in |ready_to_run_namespaces_| in such a way
258 // that they form a heap.
259 std::make_heap(ready_to_run_namespaces_
.begin(),
260 ready_to_run_namespaces_
.end(),
261 CompareTaskNamespacePriority
);
263 // If there is more work available, wake up worker thread.
264 if (!ready_to_run_namespaces_
.empty())
265 has_ready_to_run_tasks_cv_
.Signal();
269 void TaskGraphRunner::WaitForTasksToFinishRunning(NamespaceToken token
) {
270 TRACE_EVENT0("cc", "TaskGraphRunner::WaitForTasksToFinishRunning");
272 DCHECK(token
.IsValid());
275 base::AutoLock
lock(lock_
);
276 base::ThreadRestrictions::ScopedAllowWait allow_wait
;
278 TaskNamespaceMap::const_iterator it
= namespaces_
.find(token
.id_
);
279 if (it
== namespaces_
.end())
282 const TaskNamespace
& task_namespace
= it
->second
;
284 while (!HasFinishedRunningTasksInNamespace(&task_namespace
))
285 has_namespaces_with_finished_running_tasks_cv_
.Wait();
287 // There may be other namespaces that have finished running tasks, so wake
288 // up another origin thread.
289 has_namespaces_with_finished_running_tasks_cv_
.Signal();
293 void TaskGraphRunner::CollectCompletedTasks(NamespaceToken token
,
294 Task::Vector
* completed_tasks
) {
295 TRACE_EVENT0("cc", "TaskGraphRunner::CollectCompletedTasks");
297 DCHECK(token
.IsValid());
300 base::AutoLock
lock(lock_
);
302 TaskNamespaceMap::iterator it
= namespaces_
.find(token
.id_
);
303 if (it
== namespaces_
.end())
306 TaskNamespace
& task_namespace
= it
->second
;
308 DCHECK_EQ(0u, completed_tasks
->size());
309 completed_tasks
->swap(task_namespace
.completed_tasks
);
310 if (!HasFinishedRunningTasksInNamespace(&task_namespace
))
313 // Remove namespace if finished running tasks.
314 DCHECK_EQ(0u, task_namespace
.completed_tasks
.size());
315 DCHECK_EQ(0u, task_namespace
.ready_to_run_tasks
.size());
316 DCHECK_EQ(0u, task_namespace
.running_tasks
.size());
317 namespaces_
.erase(it
);
321 void TaskGraphRunner::Shutdown() {
322 base::AutoLock
lock(lock_
);
324 DCHECK_EQ(0u, ready_to_run_namespaces_
.size());
325 DCHECK_EQ(0u, namespaces_
.size());
330 // Wake up a worker so it knows it should exit. This will cause all workers
331 // to exit as each will wake up another worker before exiting.
332 has_ready_to_run_tasks_cv_
.Signal();
335 void TaskGraphRunner::FlushForTesting() {
336 base::AutoLock
lock(lock_
);
338 while (std::find_if(namespaces_
.begin(), namespaces_
.end(),
339 [](const TaskNamespaceMap::value_type
& entry
) {
340 return !HasFinishedRunningTasksInNamespace(
342 }) != namespaces_
.end()) {
343 has_namespaces_with_finished_running_tasks_cv_
.Wait();
347 void TaskGraphRunner::Run() {
348 base::AutoLock
lock(lock_
);
351 if (ready_to_run_namespaces_
.empty()) {
352 // Exit when shutdown is set and no more tasks are pending.
356 // Wait for more tasks.
357 has_ready_to_run_tasks_cv_
.Wait();
361 RunTaskWithLockAcquired();
364 // We noticed we should exit. Wake up the next worker so it knows it should
365 // exit as well (because the Shutdown() code only signals once).
366 has_ready_to_run_tasks_cv_
.Signal();
369 void TaskGraphRunner::RunUntilIdle() {
370 base::AutoLock
lock(lock_
);
372 while (!ready_to_run_namespaces_
.empty())
373 RunTaskWithLockAcquired();
376 void TaskGraphRunner::RunTaskWithLockAcquired() {
377 TRACE_EVENT0("toplevel", "TaskGraphRunner::RunTask");
379 lock_
.AssertAcquired();
380 DCHECK(!ready_to_run_namespaces_
.empty());
382 // Take top priority TaskNamespace from |ready_to_run_namespaces_|.
383 std::pop_heap(ready_to_run_namespaces_
.begin(),
384 ready_to_run_namespaces_
.end(),
385 CompareTaskNamespacePriority
);
386 TaskNamespace
* task_namespace
= ready_to_run_namespaces_
.back();
387 ready_to_run_namespaces_
.pop_back();
388 DCHECK(!task_namespace
->ready_to_run_tasks
.empty());
390 // Take top priority task from |ready_to_run_tasks|.
391 std::pop_heap(task_namespace
->ready_to_run_tasks
.begin(),
392 task_namespace
->ready_to_run_tasks
.end(),
393 CompareTaskPriority
);
394 scoped_refptr
<Task
> task(task_namespace
->ready_to_run_tasks
.back().task
);
395 task_namespace
->ready_to_run_tasks
.pop_back();
397 // Add task namespace back to |ready_to_run_namespaces_| if not empty after
398 // taking top priority task.
399 if (!task_namespace
->ready_to_run_tasks
.empty()) {
400 ready_to_run_namespaces_
.push_back(task_namespace
);
401 std::push_heap(ready_to_run_namespaces_
.begin(),
402 ready_to_run_namespaces_
.end(),
403 CompareTaskNamespacePriority
);
406 // Add task to |running_tasks|.
407 task_namespace
->running_tasks
.push_back(task
.get());
409 // There may be more work available, so wake up another worker thread.
410 has_ready_to_run_tasks_cv_
.Signal();
412 // Call WillRun() before releasing |lock_| and running task.
416 base::AutoUnlock
unlock(lock_
);
418 task
->RunOnWorkerThread();
421 // This will mark task as finished running.
424 // Remove task from |running_tasks|.
425 TaskVector::iterator it
= std::find(task_namespace
->running_tasks
.begin(),
426 task_namespace
->running_tasks
.end(),
428 DCHECK(it
!= task_namespace
->running_tasks
.end());
429 std::swap(*it
, task_namespace
->running_tasks
.back());
430 task_namespace
->running_tasks
.pop_back();
432 // Now iterate over all dependents to decrement dependencies and check if they
434 bool ready_to_run_namespaces_has_heap_properties
= true;
435 for (DependentIterator
it(&task_namespace
->graph
, task
.get()); it
; ++it
) {
436 TaskGraph::Node
& dependent_node
= *it
;
438 DCHECK_LT(0u, dependent_node
.dependencies
);
439 dependent_node
.dependencies
--;
440 // Task is ready if it has no dependencies. Add it to |ready_to_run_tasks_|.
441 if (!dependent_node
.dependencies
) {
442 bool was_empty
= task_namespace
->ready_to_run_tasks
.empty();
443 task_namespace
->ready_to_run_tasks
.push_back(
444 PrioritizedTask(dependent_node
.task
, dependent_node
.priority
));
445 std::push_heap(task_namespace
->ready_to_run_tasks
.begin(),
446 task_namespace
->ready_to_run_tasks
.end(),
447 CompareTaskPriority
);
448 // Task namespace is ready if it has at least one ready to run task. Add
449 // it to |ready_to_run_namespaces_| if it just become ready.
451 DCHECK(std::find(ready_to_run_namespaces_
.begin(),
452 ready_to_run_namespaces_
.end(),
453 task_namespace
) == ready_to_run_namespaces_
.end());
454 ready_to_run_namespaces_
.push_back(task_namespace
);
456 ready_to_run_namespaces_has_heap_properties
= false;
460 // Rearrange the task namespaces in |ready_to_run_namespaces_| in such a way
461 // that they yet again form a heap.
462 if (!ready_to_run_namespaces_has_heap_properties
) {
463 std::make_heap(ready_to_run_namespaces_
.begin(),
464 ready_to_run_namespaces_
.end(),
465 CompareTaskNamespacePriority
);
468 // Finally add task to |completed_tasks_|.
469 task_namespace
->completed_tasks
.push_back(task
);
471 // If namespace has finished running all tasks, wake up origin thread.
472 if (HasFinishedRunningTasksInNamespace(task_namespace
))
473 has_namespaces_with_finished_running_tasks_cv_
.Signal();