| // Copyright 2016 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "base/task_scheduler/task_tracker.h" |
| |
| #include "base/callback.h" |
| #include "base/debug/task_annotator.h" |
| #include "base/metrics/histogram_macros.h" |
| |
| namespace base { |
| namespace internal { |
| |
| namespace { |
| |
| const char kQueueFunctionName[] = "base::PostTask"; |
| |
| // Upper bound for the |
| // TaskScheduler.BlockShutdownTasksPostedDuringShutdown histogram. |
| const HistogramBase::Sample kMaxBlockShutdownTasksPostedDuringShutdown = 1000; |
| |
| void RecordNumBlockShutdownTasksPostedDuringShutdown( |
| HistogramBase::Sample value) { |
| UMA_HISTOGRAM_CUSTOM_COUNTS( |
| "TaskScheduler.BlockShutdownTasksPostedDuringShutdown", value, 1, |
| kMaxBlockShutdownTasksPostedDuringShutdown, 50); |
| } |
| |
| } // namespace |
| |
| TaskTracker::TaskTracker() = default; |
| TaskTracker::~TaskTracker() = default; |
| |
| void TaskTracker::Shutdown() { |
| AutoSchedulerLock auto_lock(lock_); |
| |
| // This method should only be called once. |
| DCHECK(!shutdown_completed_); |
| DCHECK(!shutdown_cv_); |
| |
| shutdown_cv_ = lock_.CreateConditionVariable(); |
| |
| // Wait until the number of tasks blocking shutdown is zero. |
| while (num_tasks_blocking_shutdown_ != 0) |
| shutdown_cv_->Wait(); |
| |
| shutdown_cv_.reset(); |
| shutdown_completed_ = true; |
| |
| // Record the TaskScheduler.BlockShutdownTasksPostedDuringShutdown if less |
| // than |kMaxBlockShutdownTasksPostedDuringShutdown| BLOCK_SHUTDOWN tasks were |
| // posted during shutdown. Otherwise, the histogram has already been recorded |
| // in BeforePostTask(). |
| if (num_block_shutdown_tasks_posted_during_shutdown_ < |
| kMaxBlockShutdownTasksPostedDuringShutdown) { |
| RecordNumBlockShutdownTasksPostedDuringShutdown( |
| num_block_shutdown_tasks_posted_during_shutdown_); |
| } |
| } |
| |
| bool TaskTracker::WillPostTask(const Task* task) { |
| DCHECK(task); |
| |
| if (!BeforePostTask(task->traits.shutdown_behavior())) |
| return false; |
| |
| debug::TaskAnnotator task_annotator; |
| task_annotator.DidQueueTask(kQueueFunctionName, *task); |
| |
| return true; |
| } |
| |
| void TaskTracker::RunTask(const Task* task) { |
| DCHECK(task); |
| |
| const TaskShutdownBehavior shutdown_behavior = |
| task->traits.shutdown_behavior(); |
| if (!BeforeRunTask(shutdown_behavior)) |
| return; |
| |
| debug::TaskAnnotator task_annotator; |
| task_annotator.RunTask(kQueueFunctionName, *task); |
| |
| AfterRunTask(shutdown_behavior); |
| } |
| |
| bool TaskTracker::IsShuttingDownForTesting() const { |
| AutoSchedulerLock auto_lock(lock_); |
| return !!shutdown_cv_; |
| } |
| |
| bool TaskTracker::BeforePostTask(TaskShutdownBehavior shutdown_behavior) { |
| AutoSchedulerLock auto_lock(lock_); |
| |
| if (shutdown_completed_) { |
| // A BLOCK_SHUTDOWN task posted after shutdown has completed is an ordering |
| // bug. This DCHECK aims to catch those early. |
| DCHECK_NE(shutdown_behavior, TaskShutdownBehavior::BLOCK_SHUTDOWN); |
| |
| // No task is allowed to be posted after shutdown. |
| return false; |
| } |
| |
| if (shutdown_behavior == TaskShutdownBehavior::BLOCK_SHUTDOWN) { |
| // BLOCK_SHUTDOWN tasks block shutdown between the moment they are posted |
| // and the moment they complete their execution. |
| ++num_tasks_blocking_shutdown_; |
| |
| if (shutdown_cv_) { |
| ++num_block_shutdown_tasks_posted_during_shutdown_; |
| |
| if (num_block_shutdown_tasks_posted_during_shutdown_ == |
| kMaxBlockShutdownTasksPostedDuringShutdown) { |
| // Record the TaskScheduler.BlockShutdownTasksPostedDuringShutdown |
| // histogram as soon as its upper bound is hit. That way, a value will |
| // be recorded even if an infinite number of BLOCK_SHUTDOWN tasks are |
| // posted, preventing shutdown to complete. |
| RecordNumBlockShutdownTasksPostedDuringShutdown( |
| num_block_shutdown_tasks_posted_during_shutdown_); |
| } |
| } |
| |
| // A BLOCK_SHUTDOWN task is allowed to be posted iff shutdown hasn't |
| // completed. |
| return true; |
| } |
| |
| // A non BLOCK_SHUTDOWN task is allowed to be posted iff shutdown hasn't |
| // started. |
| return !shutdown_cv_; |
| } |
| |
| bool TaskTracker::BeforeRunTask(TaskShutdownBehavior shutdown_behavior) { |
| AutoSchedulerLock auto_lock(lock_); |
| |
| if (shutdown_completed_) { |
| // Trying to run a BLOCK_SHUTDOWN task after shutdown has completed is |
| // unexpected as it either shouldn't have been posted if shutdown completed |
| // or should be blocking shutdown if it was posted before it did. |
| DCHECK_NE(shutdown_behavior, TaskShutdownBehavior::BLOCK_SHUTDOWN); |
| |
| // A WorkerThread might extract a non BLOCK_SHUTDOWN task from a |
| // PriorityQueue after shutdown. It shouldn't be allowed to run it. |
| return false; |
| } |
| |
| switch (shutdown_behavior) { |
| case TaskShutdownBehavior::BLOCK_SHUTDOWN: |
| DCHECK_GT(num_tasks_blocking_shutdown_, 0U); |
| return true; |
| |
| case TaskShutdownBehavior::SKIP_ON_SHUTDOWN: |
| if (shutdown_cv_) |
| return false; |
| |
| // SKIP_ON_SHUTDOWN tasks block shutdown while they are running. |
| ++num_tasks_blocking_shutdown_; |
| return true; |
| |
| case TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN: |
| return !shutdown_cv_; |
| } |
| |
| NOTREACHED(); |
| return false; |
| } |
| |
| void TaskTracker::AfterRunTask(TaskShutdownBehavior shutdown_behavior) { |
| if (shutdown_behavior == TaskShutdownBehavior::BLOCK_SHUTDOWN || |
| shutdown_behavior == TaskShutdownBehavior::SKIP_ON_SHUTDOWN) { |
| AutoSchedulerLock auto_lock(lock_); |
| DCHECK_GT(num_tasks_blocking_shutdown_, 0U); |
| --num_tasks_blocking_shutdown_; |
| if (num_tasks_blocking_shutdown_ == 0 && shutdown_cv_) |
| shutdown_cv_->Signal(); |
| } |
| } |
| |
| } // namespace internal |
| } // namespace base |