| 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 "cc/resources/task_graph_runner.h" | |
| 6 | |
| 7 #include <algorithm> | |
| 8 | |
| 9 #include "base/strings/stringprintf.h" | |
| 10 #include "base/threading/thread_restrictions.h" | |
| 11 #include "base/trace_event/trace_event.h" | |
| 12 | |
| 13 namespace cc { | |
| 14 namespace { | |
| 15 | |
| 16 // Helper class for iterating over all dependents of a task. | |
| 17 class DependentIterator { | |
| 18 public: | |
| 19 DependentIterator(TaskGraph* graph, const Task* task) | |
| 20 : graph_(graph), | |
| 21 task_(task), | |
| 22 current_index_(static_cast<size_t>(-1)), | |
| 23 current_node_(NULL) { | |
| 24 ++(*this); | |
| 25 } | |
| 26 | |
| 27 TaskGraph::Node& operator->() const { | |
| 28 DCHECK_LT(current_index_, graph_->edges.size()); | |
| 29 DCHECK_EQ(graph_->edges[current_index_].task, task_); | |
| 30 DCHECK(current_node_); | |
| 31 return *current_node_; | |
| 32 } | |
| 33 | |
| 34 TaskGraph::Node& operator*() const { | |
| 35 DCHECK_LT(current_index_, graph_->edges.size()); | |
| 36 DCHECK_EQ(graph_->edges[current_index_].task, task_); | |
| 37 DCHECK(current_node_); | |
| 38 return *current_node_; | |
| 39 } | |
| 40 | |
| 41 // Note: Performance can be improved by keeping edges sorted. | |
| 42 DependentIterator& operator++() { | |
| 43 // Find next dependency edge for |task_|. | |
| 44 do { | |
| 45 ++current_index_; | |
| 46 if (current_index_ == graph_->edges.size()) | |
| 47 return *this; | |
| 48 } while (graph_->edges[current_index_].task != task_); | |
| 49 | |
| 50 // Now find the node for the dependent of this edge. | |
| 51 TaskGraph::Node::Vector::iterator it = | |
| 52 std::find_if(graph_->nodes.begin(), | |
| 53 graph_->nodes.end(), | |
| 54 TaskGraph::Node::TaskComparator( | |
| 55 graph_->edges[current_index_].dependent)); | |
| 56 DCHECK(it != graph_->nodes.end()); | |
| 57 current_node_ = &(*it); | |
| 58 | |
| 59 return *this; | |
| 60 } | |
| 61 | |
| 62 operator bool() const { return current_index_ < graph_->edges.size(); } | |
| 63 | |
| 64 private: | |
| 65 TaskGraph* graph_; | |
| 66 const Task* task_; | |
| 67 size_t current_index_; | |
| 68 TaskGraph::Node* current_node_; | |
| 69 }; | |
| 70 | |
| 71 class DependencyMismatchComparator { | |
| 72 public: | |
| 73 explicit DependencyMismatchComparator(const TaskGraph* graph) | |
| 74 : graph_(graph) {} | |
| 75 | |
| 76 bool operator()(const TaskGraph::Node& node) const { | |
| 77 return static_cast<size_t>(std::count_if(graph_->edges.begin(), | |
| 78 graph_->edges.end(), | |
| 79 DependentComparator(node.task))) != | |
| 80 node.dependencies; | |
| 81 } | |
| 82 | |
| 83 private: | |
| 84 class DependentComparator { | |
| 85 public: | |
| 86 explicit DependentComparator(const Task* dependent) | |
| 87 : dependent_(dependent) {} | |
| 88 | |
| 89 bool operator()(const TaskGraph::Edge& edge) const { | |
| 90 return edge.dependent == dependent_; | |
| 91 } | |
| 92 | |
| 93 private: | |
| 94 const Task* dependent_; | |
| 95 }; | |
| 96 | |
| 97 const TaskGraph* graph_; | |
| 98 }; | |
| 99 | |
| 100 } // namespace | |
| 101 | |
| 102 Task::Task() : will_run_(false), did_run_(false) { | |
| 103 } | |
| 104 | |
| 105 Task::~Task() { | |
| 106 DCHECK(!will_run_); | |
| 107 } | |
| 108 | |
| 109 void Task::WillRun() { | |
| 110 DCHECK(!will_run_); | |
| 111 DCHECK(!did_run_); | |
| 112 will_run_ = true; | |
| 113 } | |
| 114 | |
| 115 void Task::DidRun() { | |
| 116 DCHECK(will_run_); | |
| 117 will_run_ = false; | |
| 118 did_run_ = true; | |
| 119 } | |
| 120 | |
| 121 bool Task::HasFinishedRunning() const { return did_run_; } | |
| 122 | |
| 123 TaskGraph::TaskGraph() {} | |
| 124 | |
| 125 TaskGraph::~TaskGraph() {} | |
| 126 | |
| 127 void TaskGraph::Swap(TaskGraph* other) { | |
| 128 nodes.swap(other->nodes); | |
| 129 edges.swap(other->edges); | |
| 130 } | |
| 131 | |
| 132 void TaskGraph::Reset() { | |
| 133 nodes.clear(); | |
| 134 edges.clear(); | |
| 135 } | |
| 136 | |
| 137 TaskGraphRunner::TaskNamespace::TaskNamespace() {} | |
| 138 | |
| 139 TaskGraphRunner::TaskNamespace::~TaskNamespace() {} | |
| 140 | |
| 141 TaskGraphRunner::TaskGraphRunner() | |
| 142 : lock_(), | |
| 143 has_ready_to_run_tasks_cv_(&lock_), | |
| 144 has_namespaces_with_finished_running_tasks_cv_(&lock_), | |
| 145 next_namespace_id_(1), | |
| 146 shutdown_(false) {} | |
| 147 | |
| 148 TaskGraphRunner::~TaskGraphRunner() { | |
| 149 { | |
| 150 base::AutoLock lock(lock_); | |
| 151 | |
| 152 DCHECK_EQ(0u, ready_to_run_namespaces_.size()); | |
| 153 DCHECK_EQ(0u, namespaces_.size()); | |
| 154 } | |
| 155 } | |
| 156 | |
| 157 NamespaceToken TaskGraphRunner::GetNamespaceToken() { | |
| 158 base::AutoLock lock(lock_); | |
| 159 | |
| 160 NamespaceToken token(next_namespace_id_++); | |
| 161 DCHECK(namespaces_.find(token.id_) == namespaces_.end()); | |
| 162 return token; | |
| 163 } | |
| 164 | |
| 165 void TaskGraphRunner::ScheduleTasks(NamespaceToken token, TaskGraph* graph) { | |
| 166 TRACE_EVENT2("cc", | |
| 167 "TaskGraphRunner::ScheduleTasks", | |
| 168 "num_nodes", | |
| 169 graph->nodes.size(), | |
| 170 "num_edges", | |
| 171 graph->edges.size()); | |
| 172 | |
| 173 DCHECK(token.IsValid()); | |
| 174 DCHECK(std::find_if(graph->nodes.begin(), | |
| 175 graph->nodes.end(), | |
| 176 DependencyMismatchComparator(graph)) == | |
| 177 graph->nodes.end()); | |
| 178 | |
| 179 { | |
| 180 base::AutoLock lock(lock_); | |
| 181 | |
| 182 DCHECK(!shutdown_); | |
| 183 | |
| 184 TaskNamespace& task_namespace = namespaces_[token.id_]; | |
| 185 | |
| 186 // First adjust number of dependencies to reflect completed tasks. | |
| 187 for (Task::Vector::iterator it = task_namespace.completed_tasks.begin(); | |
| 188 it != task_namespace.completed_tasks.end(); | |
| 189 ++it) { | |
| 190 for (DependentIterator node_it(graph, it->get()); node_it; ++node_it) { | |
| 191 TaskGraph::Node& node = *node_it; | |
| 192 DCHECK_LT(0u, node.dependencies); | |
| 193 node.dependencies--; | |
| 194 } | |
| 195 } | |
| 196 | |
| 197 // Build new "ready to run" queue and remove nodes from old graph. | |
| 198 task_namespace.ready_to_run_tasks.clear(); | |
| 199 for (TaskGraph::Node::Vector::iterator it = graph->nodes.begin(); | |
| 200 it != graph->nodes.end(); | |
| 201 ++it) { | |
| 202 TaskGraph::Node& node = *it; | |
| 203 | |
| 204 // Remove any old nodes that are associated with this task. The result is | |
| 205 // that the old graph is left with all nodes not present in this graph, | |
| 206 // which we use below to determine what tasks need to be canceled. | |
| 207 TaskGraph::Node::Vector::iterator old_it = | |
| 208 std::find_if(task_namespace.graph.nodes.begin(), | |
| 209 task_namespace.graph.nodes.end(), | |
| 210 TaskGraph::Node::TaskComparator(node.task)); | |
| 211 if (old_it != task_namespace.graph.nodes.end()) { | |
| 212 std::swap(*old_it, task_namespace.graph.nodes.back()); | |
| 213 task_namespace.graph.nodes.pop_back(); | |
| 214 } | |
| 215 | |
| 216 // Task is not ready to run if dependencies are not yet satisfied. | |
| 217 if (node.dependencies) | |
| 218 continue; | |
| 219 | |
| 220 // Skip if already finished running task. | |
| 221 if (node.task->HasFinishedRunning()) | |
| 222 continue; | |
| 223 | |
| 224 // Skip if already running. | |
| 225 if (std::find(task_namespace.running_tasks.begin(), | |
| 226 task_namespace.running_tasks.end(), | |
| 227 node.task) != task_namespace.running_tasks.end()) | |
| 228 continue; | |
| 229 | |
| 230 task_namespace.ready_to_run_tasks.push_back( | |
| 231 PrioritizedTask(node.task, node.priority)); | |
| 232 } | |
| 233 | |
| 234 // Rearrange the elements in |ready_to_run_tasks| in such a way that they | |
| 235 // form a heap. | |
| 236 std::make_heap(task_namespace.ready_to_run_tasks.begin(), | |
| 237 task_namespace.ready_to_run_tasks.end(), | |
| 238 CompareTaskPriority); | |
| 239 | |
| 240 // Swap task graph. | |
| 241 task_namespace.graph.Swap(graph); | |
| 242 | |
| 243 // Determine what tasks in old graph need to be canceled. | |
| 244 for (TaskGraph::Node::Vector::iterator it = graph->nodes.begin(); | |
| 245 it != graph->nodes.end(); | |
| 246 ++it) { | |
| 247 TaskGraph::Node& node = *it; | |
| 248 | |
| 249 // Skip if already finished running task. | |
| 250 if (node.task->HasFinishedRunning()) | |
| 251 continue; | |
| 252 | |
| 253 // Skip if already running. | |
| 254 if (std::find(task_namespace.running_tasks.begin(), | |
| 255 task_namespace.running_tasks.end(), | |
| 256 node.task) != task_namespace.running_tasks.end()) | |
| 257 continue; | |
| 258 | |
| 259 DCHECK(std::find(task_namespace.completed_tasks.begin(), | |
| 260 task_namespace.completed_tasks.end(), | |
| 261 node.task) == task_namespace.completed_tasks.end()); | |
| 262 task_namespace.completed_tasks.push_back(node.task); | |
| 263 } | |
| 264 | |
| 265 // Build new "ready to run" task namespaces queue. | |
| 266 ready_to_run_namespaces_.clear(); | |
| 267 for (TaskNamespaceMap::iterator it = namespaces_.begin(); | |
| 268 it != namespaces_.end(); | |
| 269 ++it) { | |
| 270 if (!it->second.ready_to_run_tasks.empty()) | |
| 271 ready_to_run_namespaces_.push_back(&it->second); | |
| 272 } | |
| 273 | |
| 274 // Rearrange the task namespaces in |ready_to_run_namespaces_| in such a way | |
| 275 // that they form a heap. | |
| 276 std::make_heap(ready_to_run_namespaces_.begin(), | |
| 277 ready_to_run_namespaces_.end(), | |
| 278 CompareTaskNamespacePriority); | |
| 279 | |
| 280 // If there is more work available, wake up worker thread. | |
| 281 if (!ready_to_run_namespaces_.empty()) | |
| 282 has_ready_to_run_tasks_cv_.Signal(); | |
| 283 } | |
| 284 } | |
| 285 | |
| 286 void TaskGraphRunner::WaitForTasksToFinishRunning(NamespaceToken token) { | |
| 287 TRACE_EVENT0("cc", "TaskGraphRunner::WaitForTasksToFinishRunning"); | |
| 288 | |
| 289 DCHECK(token.IsValid()); | |
| 290 | |
| 291 { | |
| 292 base::AutoLock lock(lock_); | |
| 293 base::ThreadRestrictions::ScopedAllowWait allow_wait; | |
| 294 | |
| 295 TaskNamespaceMap::const_iterator it = namespaces_.find(token.id_); | |
| 296 if (it == namespaces_.end()) | |
| 297 return; | |
| 298 | |
| 299 const TaskNamespace& task_namespace = it->second; | |
| 300 | |
| 301 while (!HasFinishedRunningTasksInNamespace(&task_namespace)) | |
| 302 has_namespaces_with_finished_running_tasks_cv_.Wait(); | |
| 303 | |
| 304 // There may be other namespaces that have finished running tasks, so wake | |
| 305 // up another origin thread. | |
| 306 has_namespaces_with_finished_running_tasks_cv_.Signal(); | |
| 307 } | |
| 308 } | |
| 309 | |
| 310 void TaskGraphRunner::CollectCompletedTasks(NamespaceToken token, | |
| 311 Task::Vector* completed_tasks) { | |
| 312 TRACE_EVENT0("cc", "TaskGraphRunner::CollectCompletedTasks"); | |
| 313 | |
| 314 DCHECK(token.IsValid()); | |
| 315 | |
| 316 { | |
| 317 base::AutoLock lock(lock_); | |
| 318 | |
| 319 TaskNamespaceMap::iterator it = namespaces_.find(token.id_); | |
| 320 if (it == namespaces_.end()) | |
| 321 return; | |
| 322 | |
| 323 TaskNamespace& task_namespace = it->second; | |
| 324 | |
| 325 DCHECK_EQ(0u, completed_tasks->size()); | |
| 326 completed_tasks->swap(task_namespace.completed_tasks); | |
| 327 if (!HasFinishedRunningTasksInNamespace(&task_namespace)) | |
| 328 return; | |
| 329 | |
| 330 // Remove namespace if finished running tasks. | |
| 331 DCHECK_EQ(0u, task_namespace.completed_tasks.size()); | |
| 332 DCHECK_EQ(0u, task_namespace.ready_to_run_tasks.size()); | |
| 333 DCHECK_EQ(0u, task_namespace.running_tasks.size()); | |
| 334 namespaces_.erase(it); | |
| 335 } | |
| 336 } | |
| 337 | |
| 338 void TaskGraphRunner::Shutdown() { | |
| 339 base::AutoLock lock(lock_); | |
| 340 | |
| 341 DCHECK_EQ(0u, ready_to_run_namespaces_.size()); | |
| 342 DCHECK_EQ(0u, namespaces_.size()); | |
| 343 | |
| 344 DCHECK(!shutdown_); | |
| 345 shutdown_ = true; | |
| 346 | |
| 347 // Wake up a worker so it knows it should exit. This will cause all workers | |
| 348 // to exit as each will wake up another worker before exiting. | |
| 349 has_ready_to_run_tasks_cv_.Signal(); | |
| 350 } | |
| 351 | |
| 352 void TaskGraphRunner::Run() { | |
| 353 base::AutoLock lock(lock_); | |
| 354 | |
| 355 while (true) { | |
| 356 if (ready_to_run_namespaces_.empty()) { | |
| 357 // Exit when shutdown is set and no more tasks are pending. | |
| 358 if (shutdown_) | |
| 359 break; | |
| 360 | |
| 361 // Wait for more tasks. | |
| 362 has_ready_to_run_tasks_cv_.Wait(); | |
| 363 continue; | |
| 364 } | |
| 365 | |
| 366 RunTaskWithLockAcquired(); | |
| 367 } | |
| 368 | |
| 369 // We noticed we should exit. Wake up the next worker so it knows it should | |
| 370 // exit as well (because the Shutdown() code only signals once). | |
| 371 has_ready_to_run_tasks_cv_.Signal(); | |
| 372 } | |
| 373 | |
| 374 void TaskGraphRunner::RunUntilIdle() { | |
| 375 base::AutoLock lock(lock_); | |
| 376 | |
| 377 while (!ready_to_run_namespaces_.empty()) | |
| 378 RunTaskWithLockAcquired(); | |
| 379 } | |
| 380 | |
| 381 void TaskGraphRunner::RunTaskWithLockAcquired() { | |
| 382 TRACE_EVENT0("toplevel", "TaskGraphRunner::RunTask"); | |
| 383 | |
| 384 lock_.AssertAcquired(); | |
| 385 DCHECK(!ready_to_run_namespaces_.empty()); | |
| 386 | |
| 387 // Take top priority TaskNamespace from |ready_to_run_namespaces_|. | |
| 388 std::pop_heap(ready_to_run_namespaces_.begin(), | |
| 389 ready_to_run_namespaces_.end(), | |
| 390 CompareTaskNamespacePriority); | |
| 391 TaskNamespace* task_namespace = ready_to_run_namespaces_.back(); | |
| 392 ready_to_run_namespaces_.pop_back(); | |
| 393 DCHECK(!task_namespace->ready_to_run_tasks.empty()); | |
| 394 | |
| 395 // Take top priority task from |ready_to_run_tasks|. | |
| 396 std::pop_heap(task_namespace->ready_to_run_tasks.begin(), | |
| 397 task_namespace->ready_to_run_tasks.end(), | |
| 398 CompareTaskPriority); | |
| 399 scoped_refptr<Task> task(task_namespace->ready_to_run_tasks.back().task); | |
| 400 task_namespace->ready_to_run_tasks.pop_back(); | |
| 401 | |
| 402 // Add task namespace back to |ready_to_run_namespaces_| if not empty after | |
| 403 // taking top priority task. | |
| 404 if (!task_namespace->ready_to_run_tasks.empty()) { | |
| 405 ready_to_run_namespaces_.push_back(task_namespace); | |
| 406 std::push_heap(ready_to_run_namespaces_.begin(), | |
| 407 ready_to_run_namespaces_.end(), | |
| 408 CompareTaskNamespacePriority); | |
| 409 } | |
| 410 | |
| 411 // Add task to |running_tasks|. | |
| 412 task_namespace->running_tasks.push_back(task.get()); | |
| 413 | |
| 414 // There may be more work available, so wake up another worker thread. | |
| 415 has_ready_to_run_tasks_cv_.Signal(); | |
| 416 | |
| 417 // Call WillRun() before releasing |lock_| and running task. | |
| 418 task->WillRun(); | |
| 419 | |
| 420 { | |
| 421 base::AutoUnlock unlock(lock_); | |
| 422 | |
| 423 task->RunOnWorkerThread(); | |
| 424 } | |
| 425 | |
| 426 // This will mark task as finished running. | |
| 427 task->DidRun(); | |
| 428 | |
| 429 // Remove task from |running_tasks|. | |
| 430 TaskVector::iterator it = std::find(task_namespace->running_tasks.begin(), | |
| 431 task_namespace->running_tasks.end(), | |
| 432 task.get()); | |
| 433 DCHECK(it != task_namespace->running_tasks.end()); | |
| 434 std::swap(*it, task_namespace->running_tasks.back()); | |
| 435 task_namespace->running_tasks.pop_back(); | |
| 436 | |
| 437 // Now iterate over all dependents to decrement dependencies and check if they | |
| 438 // are ready to run. | |
| 439 bool ready_to_run_namespaces_has_heap_properties = true; | |
| 440 for (DependentIterator it(&task_namespace->graph, task.get()); it; ++it) { | |
| 441 TaskGraph::Node& dependent_node = *it; | |
| 442 | |
| 443 DCHECK_LT(0u, dependent_node.dependencies); | |
| 444 dependent_node.dependencies--; | |
| 445 // Task is ready if it has no dependencies. Add it to |ready_to_run_tasks_|. | |
| 446 if (!dependent_node.dependencies) { | |
| 447 bool was_empty = task_namespace->ready_to_run_tasks.empty(); | |
| 448 task_namespace->ready_to_run_tasks.push_back( | |
| 449 PrioritizedTask(dependent_node.task, dependent_node.priority)); | |
| 450 std::push_heap(task_namespace->ready_to_run_tasks.begin(), | |
| 451 task_namespace->ready_to_run_tasks.end(), | |
| 452 CompareTaskPriority); | |
| 453 // Task namespace is ready if it has at least one ready to run task. Add | |
| 454 // it to |ready_to_run_namespaces_| if it just become ready. | |
| 455 if (was_empty) { | |
| 456 DCHECK(std::find(ready_to_run_namespaces_.begin(), | |
| 457 ready_to_run_namespaces_.end(), | |
| 458 task_namespace) == ready_to_run_namespaces_.end()); | |
| 459 ready_to_run_namespaces_.push_back(task_namespace); | |
| 460 } | |
| 461 ready_to_run_namespaces_has_heap_properties = false; | |
| 462 } | |
| 463 } | |
| 464 | |
| 465 // Rearrange the task namespaces in |ready_to_run_namespaces_| in such a way | |
| 466 // that they yet again form a heap. | |
| 467 if (!ready_to_run_namespaces_has_heap_properties) { | |
| 468 std::make_heap(ready_to_run_namespaces_.begin(), | |
| 469 ready_to_run_namespaces_.end(), | |
| 470 CompareTaskNamespacePriority); | |
| 471 } | |
| 472 | |
| 473 // Finally add task to |completed_tasks_|. | |
| 474 task_namespace->completed_tasks.push_back(task); | |
| 475 | |
| 476 // If namespace has finished running all tasks, wake up origin thread. | |
| 477 if (HasFinishedRunningTasksInNamespace(task_namespace)) | |
| 478 has_namespaces_with_finished_running_tasks_cv_.Signal(); | |
| 479 } | |
| 480 | |
| 481 } // namespace cc | |
| OLD | NEW |