blob: 48bdb3cd8c13dbb7f0ff7e966191f33b7e861860 [file] [log] [blame]
// 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/task_scheduler/task_tracker.h"
#include <atomic>
#include <string>
#include <vector>
#include "base/base_switches.h"
#include "base/callback.h"
#include "base/command_line.h"
#include "base/json/json_writer.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/histogram_macros.h"
#include "base/optional.h"
#include "base/sequence_token.h"
#include "base/synchronization/condition_variable.h"
#include "base/task/scoped_set_task_priority_for_current_thread.h"
#include "base/threading/sequence_local_storage_map.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "base/threading/thread_restrictions.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/time/time.h"
#include "base/trace_event/trace_event.h"
#include "base/values.h"
namespace base {
namespace internal {
namespace {
constexpr char kParallelExecutionMode[] = "parallel";
constexpr char kSequencedExecutionMode[] = "sequenced";
constexpr char kSingleThreadExecutionMode[] = "single thread";
// An immutable copy of a scheduler task's info required by tracing.
class TaskTracingInfo : public trace_event::ConvertableToTraceFormat {
public:
TaskTracingInfo(const TaskTraits& task_traits,
const char* execution_mode,
const SequenceToken& sequence_token)
: task_traits_(task_traits),
execution_mode_(execution_mode),
sequence_token_(sequence_token) {}
// trace_event::ConvertableToTraceFormat implementation.
void AppendAsTraceFormat(std::string* out) const override;
private:
const TaskTraits task_traits_;
const char* const execution_mode_;
const SequenceToken sequence_token_;
DISALLOW_COPY_AND_ASSIGN(TaskTracingInfo);
};
void TaskTracingInfo::AppendAsTraceFormat(std::string* out) const {
DictionaryValue dict;
dict.SetString("task_priority",
base::TaskPriorityToString(task_traits_.priority()));
dict.SetString("execution_mode", execution_mode_);
if (execution_mode_ != kParallelExecutionMode)
dict.SetInteger("sequence_token", sequence_token_.ToInternalValue());
std::string tmp;
JSONWriter::Write(dict, &tmp);
out->append(tmp);
}
// These name conveys that a Task is posted to/run by the task scheduler without
// revealing its implementation details.
constexpr char kQueueFunctionName[] = "TaskScheduler PostTask";
constexpr char kRunFunctionName[] = "TaskScheduler RunTask";
constexpr char kTaskSchedulerFlowTracingCategory[] =
TRACE_DISABLED_BY_DEFAULT("task_scheduler.flow");
// Constructs a histogram to track latency which is logging to
// "TaskScheduler.{histogram_name}.{histogram_label}.{task_type_suffix}".
HistogramBase* GetLatencyHistogram(StringPiece histogram_name,
StringPiece histogram_label,
StringPiece task_type_suffix) {
DCHECK(!histogram_name.empty());
DCHECK(!histogram_label.empty());
DCHECK(!task_type_suffix.empty());
// Mimics the UMA_HISTOGRAM_HIGH_RESOLUTION_CUSTOM_TIMES macro. The minimums
// and maximums were chosen to place the 1ms mark at around the 70% range
// coverage for buckets giving us good info for tasks that have a latency
// below 1ms (most of them) and enough info to assess how bad the latency is
// for tasks that exceed this threshold.
const std::string histogram = JoinString(
{"TaskScheduler", histogram_name, histogram_label, task_type_suffix},
".");
return Histogram::FactoryMicrosecondsTimeGet(
histogram, TimeDelta::FromMicroseconds(1),
TimeDelta::FromMilliseconds(20), 50,
HistogramBase::kUmaTargetedHistogramFlag);
}
// Constructs a histogram to track task count which is logging to
// "TaskScheduler.{histogram_name}.{histogram_label}.{task_type_suffix}".
HistogramBase* GetCountHistogram(StringPiece histogram_name,
StringPiece histogram_label,
StringPiece task_type_suffix) {
DCHECK(!histogram_name.empty());
DCHECK(!histogram_label.empty());
DCHECK(!task_type_suffix.empty());
// Mimics the UMA_HISTOGRAM_CUSTOM_COUNTS macro.
const std::string histogram = JoinString(
{"TaskScheduler", histogram_name, histogram_label, task_type_suffix},
".");
// 500 was chosen as the maximum number of tasks run while queuing because
// values this high would likely indicate an error, beyond which knowing the
// actual number of tasks is not informative.
return Histogram::FactoryGet(histogram, 1, 500, 50,
HistogramBase::kUmaTargetedHistogramFlag);
}
// Returns a histogram stored in a 2D array indexed by task priority and
// whether it may block.
// TODO(jessemckenna): use the STATIC_HISTOGRAM_POINTER_GROUP macro from
// histogram_macros.h instead.
HistogramBase* GetHistogramForTaskTraits(
TaskTraits task_traits,
HistogramBase* const (*histograms)[2]) {
return histograms[static_cast<int>(task_traits.priority())]
[task_traits.may_block() ||
task_traits.with_base_sync_primitives()
? 1
: 0];
}
// Upper bound for the
// TaskScheduler.BlockShutdownTasksPostedDuringShutdown histogram.
constexpr HistogramBase::Sample kMaxBlockShutdownTasksPostedDuringShutdown =
1000;
void RecordNumBlockShutdownTasksPostedDuringShutdown(
HistogramBase::Sample value) {
UMA_HISTOGRAM_CUSTOM_COUNTS(
"TaskScheduler.BlockShutdownTasksPostedDuringShutdown", value, 1,
kMaxBlockShutdownTasksPostedDuringShutdown, 50);
}
// Returns the maximum number of TaskPriority::BEST_EFFORT sequences that can be
// scheduled concurrently based on command line flags.
int GetMaxNumScheduledBestEffortSequences() {
// The CommandLine might not be initialized if TaskScheduler is initialized
// in a dynamic library which doesn't have access to argc/argv.
if (CommandLine::InitializedForCurrentProcess() &&
CommandLine::ForCurrentProcess()->HasSwitch(
switches::kDisableBackgroundTasks)) {
return 0;
}
return std::numeric_limits<int>::max();
}
// Returns shutdown behavior based on |traits|; returns SKIP_ON_SHUTDOWN if
// shutdown behavior is BLOCK_SHUTDOWN and |is_delayed|, because delayed tasks
// are not allowed to block shutdown.
TaskShutdownBehavior GetEffectiveShutdownBehavior(const TaskTraits& traits,
bool is_delayed) {
const TaskShutdownBehavior shutdown_behavior = traits.shutdown_behavior();
if (shutdown_behavior == TaskShutdownBehavior::BLOCK_SHUTDOWN && is_delayed) {
return TaskShutdownBehavior::SKIP_ON_SHUTDOWN;
}
return shutdown_behavior;
}
} // namespace
// Atomic internal state used by TaskTracker. Sequential consistency shouldn't
// be assumed from these calls (i.e. a thread reading
// |HasShutdownStarted() == true| isn't guaranteed to see all writes made before
// |StartShutdown()| on the thread that invoked it).
class TaskTracker::State {
public:
State() = default;
// Sets a flag indicating that shutdown has started. Returns true if there are
// tasks blocking shutdown. Can only be called once.
bool StartShutdown() {
const auto new_value =
subtle::NoBarrier_AtomicIncrement(&bits_, kShutdownHasStartedMask);
// Check that the "shutdown has started" bit isn't zero. This would happen
// if it was incremented twice.
DCHECK(new_value & kShutdownHasStartedMask);
const auto num_tasks_blocking_shutdown =
new_value >> kNumTasksBlockingShutdownBitOffset;
return num_tasks_blocking_shutdown != 0;
}
// Returns true if shutdown has started.
bool HasShutdownStarted() const {
return subtle::NoBarrier_Load(&bits_) & kShutdownHasStartedMask;
}
// Returns true if there are tasks blocking shutdown.
bool AreTasksBlockingShutdown() const {
const auto num_tasks_blocking_shutdown =
subtle::NoBarrier_Load(&bits_) >> kNumTasksBlockingShutdownBitOffset;
DCHECK_GE(num_tasks_blocking_shutdown, 0);
return num_tasks_blocking_shutdown != 0;
}
// Increments the number of tasks blocking shutdown. Returns true if shutdown
// has started.
bool IncrementNumTasksBlockingShutdown() {
#if DCHECK_IS_ON()
// Verify that no overflow will occur.
const auto num_tasks_blocking_shutdown =
subtle::NoBarrier_Load(&bits_) >> kNumTasksBlockingShutdownBitOffset;
DCHECK_LT(num_tasks_blocking_shutdown,
std::numeric_limits<subtle::Atomic32>::max() -
kNumTasksBlockingShutdownIncrement);
#endif
const auto new_bits = subtle::NoBarrier_AtomicIncrement(
&bits_, kNumTasksBlockingShutdownIncrement);
return new_bits & kShutdownHasStartedMask;
}
// Decrements the number of tasks blocking shutdown. Returns true if shutdown
// has started and the number of tasks blocking shutdown becomes zero.
bool DecrementNumTasksBlockingShutdown() {
const auto new_bits = subtle::NoBarrier_AtomicIncrement(
&bits_, -kNumTasksBlockingShutdownIncrement);
const bool shutdown_has_started = new_bits & kShutdownHasStartedMask;
const auto num_tasks_blocking_shutdown =
new_bits >> kNumTasksBlockingShutdownBitOffset;
DCHECK_GE(num_tasks_blocking_shutdown, 0);
return shutdown_has_started && num_tasks_blocking_shutdown == 0;
}
private:
static constexpr subtle::Atomic32 kShutdownHasStartedMask = 1;
static constexpr subtle::Atomic32 kNumTasksBlockingShutdownBitOffset = 1;
static constexpr subtle::Atomic32 kNumTasksBlockingShutdownIncrement =
1 << kNumTasksBlockingShutdownBitOffset;
// The LSB indicates whether shutdown has started. The other bits count the
// number of tasks blocking shutdown.
// No barriers are required to read/write |bits_| as this class is only used
// as an atomic state checker, it doesn't provide sequential consistency
// guarantees w.r.t. external state. Sequencing of the TaskTracker::State
// operations themselves is guaranteed by the AtomicIncrement RMW (read-
// modify-write) semantics however. For example, if two threads are racing to
// call IncrementNumTasksBlockingShutdown() and StartShutdown() respectively,
// either the first thread will win and the StartShutdown() call will see the
// blocking task or the second thread will win and
// IncrementNumTasksBlockingShutdown() will know that shutdown has started.
subtle::Atomic32 bits_ = 0;
DISALLOW_COPY_AND_ASSIGN(State);
};
struct TaskTracker::PreemptedSequence {
PreemptedSequence() = default;
PreemptedSequence(scoped_refptr<Sequence> sequence_in,
TimeTicks next_task_sequenced_time_in,
CanScheduleSequenceObserver* observer_in)
: sequence(std::move(sequence_in)),
next_task_sequenced_time(next_task_sequenced_time_in),
observer(observer_in) {}
PreemptedSequence(PreemptedSequence&& other) = default;
~PreemptedSequence() = default;
PreemptedSequence& operator=(PreemptedSequence&& other) = default;
bool operator<(const PreemptedSequence& other) const {
return next_task_sequenced_time < other.next_task_sequenced_time;
}
bool operator>(const PreemptedSequence& other) const {
return next_task_sequenced_time > other.next_task_sequenced_time;
}
// A sequence waiting to be scheduled.
scoped_refptr<Sequence> sequence;
// The sequenced time of the next task in |sequence|.
TimeTicks next_task_sequenced_time;
// An observer to notify when |sequence| can be scheduled.
CanScheduleSequenceObserver* observer = nullptr;
private:
DISALLOW_COPY_AND_ASSIGN(PreemptedSequence);
};
TaskTracker::PreemptionState::PreemptionState() = default;
TaskTracker::PreemptionState::~PreemptionState() = default;
TaskTracker::TaskTracker(StringPiece histogram_label)
: TaskTracker(histogram_label, GetMaxNumScheduledBestEffortSequences()) {}
// TODO(jessemckenna): Write a helper function to avoid code duplication below.
TaskTracker::TaskTracker(StringPiece histogram_label,
int max_num_scheduled_best_effort_sequences)
: state_(new State),
flush_cv_(flush_lock_.CreateConditionVariable()),
shutdown_lock_(&flush_lock_),
task_latency_histograms_{
{GetLatencyHistogram("TaskLatencyMicroseconds",
histogram_label,
"BackgroundTaskPriority"),
GetLatencyHistogram("TaskLatencyMicroseconds",
histogram_label,
"BackgroundTaskPriority_MayBlock")},
{GetLatencyHistogram("TaskLatencyMicroseconds",
histogram_label,
"UserVisibleTaskPriority"),
GetLatencyHistogram("TaskLatencyMicroseconds",
histogram_label,
"UserVisibleTaskPriority_MayBlock")},
{GetLatencyHistogram("TaskLatencyMicroseconds",
histogram_label,
"UserBlockingTaskPriority"),
GetLatencyHistogram("TaskLatencyMicroseconds",
histogram_label,
"UserBlockingTaskPriority_MayBlock")}},
heartbeat_latency_histograms_{
{GetLatencyHistogram("HeartbeatLatencyMicroseconds",
histogram_label,
"BackgroundTaskPriority"),
GetLatencyHistogram("HeartbeatLatencyMicroseconds",
histogram_label,
"BackgroundTaskPriority_MayBlock")},
{GetLatencyHistogram("HeartbeatLatencyMicroseconds",
histogram_label,
"UserVisibleTaskPriority"),
GetLatencyHistogram("HeartbeatLatencyMicroseconds",
histogram_label,
"UserVisibleTaskPriority_MayBlock")},
{GetLatencyHistogram("HeartbeatLatencyMicroseconds",
histogram_label,
"UserBlockingTaskPriority"),
GetLatencyHistogram("HeartbeatLatencyMicroseconds",
histogram_label,
"UserBlockingTaskPriority_MayBlock")}},
num_tasks_run_while_queuing_histograms_{
{GetCountHistogram("NumTasksRunWhileQueuing",
histogram_label,
"BackgroundTaskPriority"),
GetCountHistogram("NumTasksRunWhileQueuing",
histogram_label,
"BackgroundTaskPriority_MayBlock")},
{GetCountHistogram("NumTasksRunWhileQueuing",
histogram_label,
"UserVisibleTaskPriority"),
GetCountHistogram("NumTasksRunWhileQueuing",
histogram_label,
"UserVisibleTaskPriority_MayBlock")},
{GetCountHistogram("NumTasksRunWhileQueuing",
histogram_label,
"UserBlockingTaskPriority"),
GetCountHistogram("NumTasksRunWhileQueuing",
histogram_label,
"UserBlockingTaskPriority_MayBlock")}},
tracked_ref_factory_(this) {
// Confirm that all |task_latency_histograms_| have been initialized above.
DCHECK(*(&task_latency_histograms_[static_cast<int>(TaskPriority::HIGHEST) +
1][0] -
1));
preemption_state_[static_cast<int>(TaskPriority::BEST_EFFORT)]
.max_scheduled_sequences = max_num_scheduled_best_effort_sequences;
DETACH_FROM_SEQUENCE(sequence_checker_);
}
TaskTracker::~TaskTracker() = default;
void TaskTracker::SetExecutionFenceEnabled(bool execution_fence_enabled) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
#if DCHECK_IS_ON()
// It is invalid to have two fences at the same time.
DCHECK_NE(execution_fence_enabled_, execution_fence_enabled);
execution_fence_enabled_ = execution_fence_enabled;
#endif
for (int priority_index = static_cast<int>(TaskPriority::HIGHEST);
priority_index >= static_cast<int>(TaskPriority::LOWEST);
--priority_index) {
int max_scheduled_sequences;
if (execution_fence_enabled) {
preemption_state_[priority_index].max_scheduled_sequences_before_fence =
preemption_state_[priority_index].max_scheduled_sequences;
max_scheduled_sequences = 0;
} else {
max_scheduled_sequences = preemption_state_[priority_index]
.max_scheduled_sequences_before_fence;
}
SetMaxNumScheduledSequences(max_scheduled_sequences,
static_cast<TaskPriority>(priority_index));
}
}
int TaskTracker::GetPreemptedSequenceCountForTesting(
TaskPriority task_priority) {
int priority_index = static_cast<int>(task_priority);
AutoSchedulerLock auto_lock(preemption_state_[priority_index].lock);
return preemption_state_[priority_index].preempted_sequences.size();
}
void TaskTracker::Shutdown() {
PerformShutdown();
DCHECK(IsShutdownComplete());
// Unblock FlushForTesting() and perform the FlushAsyncForTesting callback
// when shutdown completes.
{
AutoSchedulerLock auto_lock(flush_lock_);
flush_cv_->Signal();
}
CallFlushCallbackForTesting();
}
void TaskTracker::FlushForTesting() {
AutoSchedulerLock auto_lock(flush_lock_);
while (subtle::Acquire_Load(&num_incomplete_undelayed_tasks_) != 0 &&
!IsShutdownComplete()) {
flush_cv_->Wait();
}
}
void TaskTracker::FlushAsyncForTesting(OnceClosure flush_callback) {
DCHECK(flush_callback);
{
AutoSchedulerLock auto_lock(flush_lock_);
DCHECK(!flush_callback_for_testing_)
<< "Only one FlushAsyncForTesting() may be pending at any time.";
flush_callback_for_testing_ = std::move(flush_callback);
}
if (subtle::Acquire_Load(&num_incomplete_undelayed_tasks_) == 0 ||
IsShutdownComplete()) {
CallFlushCallbackForTesting();
}
}
bool TaskTracker::WillPostTask(Task* task,
TaskShutdownBehavior shutdown_behavior) {
DCHECK(task);
DCHECK(task->task);
if (!BeforePostTask(GetEffectiveShutdownBehavior(shutdown_behavior,
!task->delay.is_zero())))
return false;
if (task->delayed_run_time.is_null())
subtle::NoBarrier_AtomicIncrement(&num_incomplete_undelayed_tasks_, 1);
{
TRACE_EVENT_WITH_FLOW0(
kTaskSchedulerFlowTracingCategory, kQueueFunctionName,
TRACE_ID_MANGLE(task_annotator_.GetTaskTraceID(*task)),
TRACE_EVENT_FLAG_FLOW_OUT);
}
task_annotator_.WillQueueTask(nullptr, task);
return true;
}
scoped_refptr<Sequence> TaskTracker::WillScheduleSequence(
scoped_refptr<Sequence> sequence,
CanScheduleSequenceObserver* observer) {
DCHECK(sequence);
const SequenceSortKey sort_key = sequence->GetSortKey();
const int priority_index = static_cast<int>(sort_key.priority());
AutoSchedulerLock auto_lock(preemption_state_[priority_index].lock);
if (preemption_state_[priority_index].current_scheduled_sequences <
preemption_state_[priority_index].max_scheduled_sequences) {
++preemption_state_[priority_index].current_scheduled_sequences;
return sequence;
}
// It is convenient not to have to specify an observer when scheduling
// foreground sequences in tests.
DCHECK(observer);
preemption_state_[priority_index].preempted_sequences.emplace(
std::move(sequence), sort_key.next_task_sequenced_time(), observer);
return nullptr;
}
scoped_refptr<Sequence> TaskTracker::RunAndPopNextTask(
scoped_refptr<Sequence> sequence,
CanScheduleSequenceObserver* observer) {
DCHECK(sequence);
// Run the next task in |sequence|.
Optional<Task> task = sequence->TakeTask();
// TODO(fdoray): Support TakeTask() returning null. https://crbug.com/783309
DCHECK(task);
const TaskShutdownBehavior effective_shutdown_behavior =
GetEffectiveShutdownBehavior(sequence->traits().shutdown_behavior(),
!task->delay.is_zero());
const bool can_run_task = BeforeRunTask(effective_shutdown_behavior);
RunOrSkipTask(std::move(task.value()), sequence.get(), can_run_task);
if (can_run_task) {
IncrementNumTasksRun();
AfterRunTask(effective_shutdown_behavior);
}
if (task->delayed_run_time.is_null())
DecrementNumIncompleteUndelayedTasks();
const bool sequence_is_empty_after_pop = sequence->Pop();
const TaskPriority priority = sequence->traits().priority();
// Never reschedule a Sequence emptied by Pop(). The contract is such that
// next poster to make it non-empty is responsible to schedule it.
if (sequence_is_empty_after_pop)
sequence = nullptr;
// Allow |sequence| to be rescheduled only if its next task is set to run
// earlier than the earliest currently preempted sequence
return ManageSequencesAfterRunningTask(std::move(sequence), observer,
priority);
}
bool TaskTracker::HasShutdownStarted() const {
return state_->HasShutdownStarted();
}
bool TaskTracker::IsShutdownComplete() const {
AutoSchedulerLock auto_lock(shutdown_lock_);
return shutdown_event_ && shutdown_event_->IsSignaled();
}
void TaskTracker::SetHasShutdownStartedForTesting() {
AutoSchedulerLock auto_lock(shutdown_lock_);
// Create a dummy |shutdown_event_| to satisfy TaskTracker's expectation of
// its existence during shutdown (e.g. in OnBlockingShutdownTasksComplete()).
shutdown_event_ = std::make_unique<WaitableEvent>();
state_->StartShutdown();
}
void TaskTracker::RecordLatencyHistogram(
LatencyHistogramType latency_histogram_type,
TaskTraits task_traits,
TimeTicks posted_time) const {
const TimeDelta task_latency = TimeTicks::Now() - posted_time;
DCHECK(latency_histogram_type == LatencyHistogramType::TASK_LATENCY ||
latency_histogram_type == LatencyHistogramType::HEARTBEAT_LATENCY);
const auto& histograms =
latency_histogram_type == LatencyHistogramType::TASK_LATENCY
? task_latency_histograms_
: heartbeat_latency_histograms_;
GetHistogramForTaskTraits(task_traits, histograms)
->AddTimeMicrosecondsGranularity(task_latency);
}
void TaskTracker::RecordHeartbeatLatencyAndTasksRunWhileQueuingHistograms(
TaskPriority task_priority,
bool may_block,
TimeTicks posted_time,
int num_tasks_run_when_posted) const {
TaskTraits task_traits = {task_priority};
if (may_block)
task_traits = TaskTraits::Override(task_traits, {MayBlock()});
RecordLatencyHistogram(LatencyHistogramType::HEARTBEAT_LATENCY, task_traits,
posted_time);
GetHistogramForTaskTraits(task_traits,
num_tasks_run_while_queuing_histograms_)
->Add(GetNumTasksRun() - num_tasks_run_when_posted);
}
int TaskTracker::GetNumTasksRun() const {
return num_tasks_run_.load(std::memory_order_relaxed);
}
void TaskTracker::IncrementNumTasksRun() {
num_tasks_run_.fetch_add(1, std::memory_order_relaxed);
}
void TaskTracker::RunOrSkipTask(Task task,
Sequence* sequence,
bool can_run_task) {
DCHECK(sequence);
RecordLatencyHistogram(LatencyHistogramType::TASK_LATENCY, sequence->traits(),
task.sequenced_time);
const bool previous_singleton_allowed =
ThreadRestrictions::SetSingletonAllowed(
sequence->traits().shutdown_behavior() !=
TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN);
const bool previous_io_allowed =
ThreadRestrictions::SetIOAllowed(sequence->traits().may_block());
const bool previous_wait_allowed = ThreadRestrictions::SetWaitAllowed(
sequence->traits().with_base_sync_primitives());
{
const SequenceToken& sequence_token = sequence->token();
DCHECK(sequence_token.IsValid());
ScopedSetSequenceTokenForCurrentThread
scoped_set_sequence_token_for_current_thread(sequence_token);
ScopedSetTaskPriorityForCurrentThread
scoped_set_task_priority_for_current_thread(
sequence->traits().priority());
ScopedSetSequenceLocalStorageMapForCurrentThread
scoped_set_sequence_local_storage_map_for_current_thread(
sequence->sequence_local_storage());
// Set up TaskRunnerHandle as expected for the scope of the task.
Optional<SequencedTaskRunnerHandle> sequenced_task_runner_handle;
Optional<ThreadTaskRunnerHandle> single_thread_task_runner_handle;
DCHECK(!task.sequenced_task_runner_ref ||
!task.single_thread_task_runner_ref);
if (task.sequenced_task_runner_ref) {
sequenced_task_runner_handle.emplace(task.sequenced_task_runner_ref);
} else if (task.single_thread_task_runner_ref) {
single_thread_task_runner_handle.emplace(
task.single_thread_task_runner_ref);
}
if (can_run_task) {
TRACE_TASK_EXECUTION(kRunFunctionName, task);
const char* const execution_mode =
task.single_thread_task_runner_ref
? kSingleThreadExecutionMode
: (task.sequenced_task_runner_ref ? kSequencedExecutionMode
: kParallelExecutionMode);
// TODO(gab): In a better world this would be tacked on as an extra arg
// to the trace event generated above. This is not possible however until
// http://crbug.com/652692 is resolved.
TRACE_EVENT1("task_scheduler", "TaskTracker::RunTask", "task_info",
std::make_unique<TaskTracingInfo>(
sequence->traits(), execution_mode, sequence_token));
{
// Put this in its own scope so it preceeds rather than overlaps with
// RunTask() in the trace view.
TRACE_EVENT_WITH_FLOW0(
kTaskSchedulerFlowTracingCategory, kQueueFunctionName,
TRACE_ID_MANGLE(task_annotator_.GetTaskTraceID(task)),
TRACE_EVENT_FLAG_FLOW_IN);
}
task_annotator_.RunTask(nullptr, &task);
}
// Make sure the arguments bound to the callback are deleted within the
// scope in which the callback runs.
task.task = OnceClosure();
}
ThreadRestrictions::SetWaitAllowed(previous_wait_allowed);
ThreadRestrictions::SetIOAllowed(previous_io_allowed);
ThreadRestrictions::SetSingletonAllowed(previous_singleton_allowed);
}
void TaskTracker::PerformShutdown() {
{
AutoSchedulerLock auto_lock(shutdown_lock_);
// This method can only be called once.
DCHECK(!shutdown_event_);
DCHECK(!num_block_shutdown_tasks_posted_during_shutdown_);
DCHECK(!state_->HasShutdownStarted());
shutdown_event_ = std::make_unique<WaitableEvent>();
const bool tasks_are_blocking_shutdown = state_->StartShutdown();
// From now, if a thread causes the number of tasks blocking shutdown to
// become zero, it will call OnBlockingShutdownTasksComplete().
if (!tasks_are_blocking_shutdown) {
// If another thread posts a BLOCK_SHUTDOWN task at this moment, it will
// block until this method releases |shutdown_lock_|. Then, it will fail
// DCHECK(!shutdown_event_->IsSignaled()). This is the desired behavior
// because posting a BLOCK_SHUTDOWN task when TaskTracker::Shutdown() has
// started and no tasks are blocking shutdown isn't allowed.
shutdown_event_->Signal();
return;
}
}
// Remove the cap on the maximum number of sequences that can be scheduled
// concurrently. Done after starting shutdown to ensure that non-
// BLOCK_SHUTDOWN sequences don't get a chance to run and that BLOCK_SHUTDOWN
// sequences run on threads running with a normal priority.
for (int priority_index = static_cast<int>(TaskPriority::HIGHEST);
priority_index >= static_cast<int>(TaskPriority::LOWEST);
--priority_index) {
SetMaxNumScheduledSequences(std::numeric_limits<int>::max(),
static_cast<TaskPriority>(priority_index));
}
// It is safe to access |shutdown_event_| without holding |lock_| because the
// pointer never changes after being set above.
{
base::ThreadRestrictions::ScopedAllowWait allow_wait;
shutdown_event_->Wait();
}
{
AutoSchedulerLock auto_lock(shutdown_lock_);
// Record 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_);
}
}
}
void TaskTracker::SetMaxNumScheduledSequences(int max_scheduled_sequences,
TaskPriority task_priority) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
std::vector<PreemptedSequence> sequences_to_schedule;
int priority_index = static_cast<int>(task_priority);
{
AutoSchedulerLock auto_lock(preemption_state_[priority_index].lock);
preemption_state_[priority_index].max_scheduled_sequences =
max_scheduled_sequences;
while (preemption_state_[priority_index].current_scheduled_sequences <
max_scheduled_sequences &&
!preemption_state_[priority_index].preempted_sequences.empty()) {
sequences_to_schedule.push_back(
GetPreemptedSequenceToScheduleLockRequired(task_priority));
}
}
for (auto& sequence_to_schedule : sequences_to_schedule)
SchedulePreemptedSequence(std::move(sequence_to_schedule));
}
TaskTracker::PreemptedSequence
TaskTracker::GetPreemptedSequenceToScheduleLockRequired(
TaskPriority task_priority) {
int priority_index = static_cast<int>(task_priority);
preemption_state_[priority_index].lock.AssertAcquired();
DCHECK(!preemption_state_[priority_index].preempted_sequences.empty());
++preemption_state_[priority_index].current_scheduled_sequences;
DCHECK_LE(preemption_state_[priority_index].current_scheduled_sequences,
preemption_state_[priority_index].max_scheduled_sequences);
// The const_cast on top is okay since the PreemptedSequence is
// transactionnaly being popped from
// |preemption_state_[priority_index].preempted_sequences| right after and the
// move doesn't alter the sort order (a requirement for the Windows STL's
// consistency debug-checks for std::priority_queue::top()).
PreemptedSequence popped_sequence = std::move(const_cast<PreemptedSequence&>(
preemption_state_[priority_index].preempted_sequences.top()));
preemption_state_[priority_index].preempted_sequences.pop();
return popped_sequence;
}
void TaskTracker::SchedulePreemptedSequence(
PreemptedSequence sequence_to_schedule) {
DCHECK(sequence_to_schedule.observer);
sequence_to_schedule.observer->OnCanScheduleSequence(
std::move(sequence_to_schedule.sequence));
}
#if DCHECK_IS_ON()
bool TaskTracker::IsPostingBlockShutdownTaskAfterShutdownAllowed() {
return false;
}
#endif
bool TaskTracker::HasIncompleteUndelayedTasksForTesting() const {
return subtle::Acquire_Load(&num_incomplete_undelayed_tasks_) != 0;
}
bool TaskTracker::BeforePostTask(
TaskShutdownBehavior effective_shutdown_behavior) {
if (effective_shutdown_behavior == TaskShutdownBehavior::BLOCK_SHUTDOWN) {
// BLOCK_SHUTDOWN tasks block shutdown between the moment they are posted
// and the moment they complete their execution.
const bool shutdown_started = state_->IncrementNumTasksBlockingShutdown();
if (shutdown_started) {
AutoSchedulerLock auto_lock(shutdown_lock_);
// A BLOCK_SHUTDOWN task posted after shutdown has completed is an
// ordering bug. This aims to catch those early.
DCHECK(shutdown_event_);
if (shutdown_event_->IsSignaled()) {
#if DCHECK_IS_ON()
// clang-format off
// TODO(robliao): http://crbug.com/698140. Since the service thread
// doesn't stop processing its own tasks at shutdown, we may still
// attempt to post a BLOCK_SHUTDOWN task in response to a
// FileDescriptorWatcher. Same is true for FilePathWatcher
// (http://crbug.com/728235). Until it's possible for such services to
// post to non-BLOCK_SHUTDOWN sequences which are themselves funneled to
// the main execution sequence (a future plan for the post_task.h API),
// this DCHECK will be flaky and must be disabled.
// DCHECK(IsPostingBlockShutdownTaskAfterShutdownAllowed());
// clang-format on
#endif
state_->DecrementNumTasksBlockingShutdown();
return false;
}
++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_);
}
}
return true;
}
// A non BLOCK_SHUTDOWN task is allowed to be posted iff shutdown hasn't
// started.
return !state_->HasShutdownStarted();
}
bool TaskTracker::BeforeRunTask(
TaskShutdownBehavior effective_shutdown_behavior) {
switch (effective_shutdown_behavior) {
case TaskShutdownBehavior::BLOCK_SHUTDOWN: {
// The number of tasks blocking shutdown has been incremented when the
// task was posted.
DCHECK(state_->AreTasksBlockingShutdown());
// 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(!state_->HasShutdownStarted() || !IsShutdownComplete());
return true;
}
case TaskShutdownBehavior::SKIP_ON_SHUTDOWN: {
// SKIP_ON_SHUTDOWN tasks block shutdown while they are running.
const bool shutdown_started = state_->IncrementNumTasksBlockingShutdown();
if (shutdown_started) {
// The SKIP_ON_SHUTDOWN task isn't allowed to run during shutdown.
// Decrement the number of tasks blocking shutdown that was wrongly
// incremented.
const bool shutdown_started_and_no_tasks_block_shutdown =
state_->DecrementNumTasksBlockingShutdown();
if (shutdown_started_and_no_tasks_block_shutdown)
OnBlockingShutdownTasksComplete();
return false;
}
return true;
}
case TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN: {
return !state_->HasShutdownStarted();
}
}
NOTREACHED();
return false;
}
void TaskTracker::AfterRunTask(
TaskShutdownBehavior effective_shutdown_behavior) {
if (effective_shutdown_behavior == TaskShutdownBehavior::BLOCK_SHUTDOWN ||
effective_shutdown_behavior == TaskShutdownBehavior::SKIP_ON_SHUTDOWN) {
const bool shutdown_started_and_no_tasks_block_shutdown =
state_->DecrementNumTasksBlockingShutdown();
if (shutdown_started_and_no_tasks_block_shutdown)
OnBlockingShutdownTasksComplete();
}
}
void TaskTracker::OnBlockingShutdownTasksComplete() {
AutoSchedulerLock auto_lock(shutdown_lock_);
// This method can only be called after shutdown has started.
DCHECK(state_->HasShutdownStarted());
DCHECK(shutdown_event_);
shutdown_event_->Signal();
}
void TaskTracker::DecrementNumIncompleteUndelayedTasks() {
const auto new_num_incomplete_undelayed_tasks =
subtle::Barrier_AtomicIncrement(&num_incomplete_undelayed_tasks_, -1);
DCHECK_GE(new_num_incomplete_undelayed_tasks, 0);
if (new_num_incomplete_undelayed_tasks == 0) {
{
AutoSchedulerLock auto_lock(flush_lock_);
flush_cv_->Signal();
}
CallFlushCallbackForTesting();
}
}
scoped_refptr<Sequence> TaskTracker::ManageSequencesAfterRunningTask(
scoped_refptr<Sequence> just_ran_sequence,
CanScheduleSequenceObserver* observer,
TaskPriority task_priority) {
const TimeTicks next_task_sequenced_time =
just_ran_sequence
? just_ran_sequence->GetSortKey().next_task_sequenced_time()
: TimeTicks();
PreemptedSequence sequence_to_schedule;
int priority_index = static_cast<int>(task_priority);
{
AutoSchedulerLock auto_lock(preemption_state_[priority_index].lock);
--preemption_state_[priority_index].current_scheduled_sequences;
const bool can_schedule_sequence =
preemption_state_[priority_index].current_scheduled_sequences <
preemption_state_[priority_index].max_scheduled_sequences;
if (just_ran_sequence) {
if (can_schedule_sequence &&
(preemption_state_[priority_index].preempted_sequences.empty() ||
preemption_state_[priority_index]
.preempted_sequences.top()
.next_task_sequenced_time > next_task_sequenced_time)) {
++preemption_state_[priority_index].current_scheduled_sequences;
return just_ran_sequence;
}
preemption_state_[priority_index].preempted_sequences.emplace(
std::move(just_ran_sequence), next_task_sequenced_time, observer);
}
if (can_schedule_sequence &&
!preemption_state_[priority_index].preempted_sequences.empty()) {
sequence_to_schedule =
GetPreemptedSequenceToScheduleLockRequired(task_priority);
}
}
// |sequence_to_schedule.sequence| may be null if there was no preempted
// sequence.
if (sequence_to_schedule.sequence)
SchedulePreemptedSequence(std::move(sequence_to_schedule));
return nullptr;
}
void TaskTracker::CallFlushCallbackForTesting() {
OnceClosure flush_callback;
{
AutoSchedulerLock auto_lock(flush_lock_);
flush_callback = std::move(flush_callback_for_testing_);
}
if (flush_callback)
std::move(flush_callback).Run();
}
} // namespace internal
} // namespace base