// Copyright 2014 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/cancelable_task_tracker.h" #include #include #include "base/bind.h" #include "base/callback_helpers.h" #include "base/feature_list.h" #include "base/location.h" #include "base/memory/ref_counted.h" #include "base/metrics/histogram_macros.h" #include "base/task/scoped_set_task_priority_for_current_thread.h" #include "base/task/sequenced_task_runner.h" #include "base/task/task_runner.h" #include "base/threading/sequenced_task_runner_handle.h" namespace base { namespace { void RunOrPostToTaskRunner(scoped_refptr task_runner, OnceClosure closure) { if (task_runner->RunsTasksInCurrentSequence()) std::move(closure).Run(); else task_runner->PostTask(FROM_HERE, std::move(closure)); } // TODO(https://crbug.com/1009795): Remove these once we have established // whether off-sequence cancelation is worthwhile. // These values are persisted to logs. Entries should not be renumbered and // numeric values should never be reused. enum class TaskStatus { kSameSequenceLive = 0, kOffSequenceLive = 1, kSameSequenceCanceled = 2, kOffSequenceCanceled = 3, kMaxValue = kOffSequenceCanceled, }; void UmaRecordTaskDuration(bool same_sequence, bool background, bool canceled, TimeDelta duration) { #define DECLARE_HISTOGRAM(suffix) \ Histogram::FactoryTimeGet( \ "Scheduler.CancelableTaskTracker.TaskDuration2_" suffix, \ Milliseconds(1), Seconds(10), 50, Histogram::kUmaTargetedHistogramFlag) static HistogramBase* histograms[] = { DECLARE_HISTOGRAM("LiveForegroundOffSequence"), DECLARE_HISTOGRAM("LiveForegroundSameSequence"), DECLARE_HISTOGRAM("LiveBackgroundOffSequence"), DECLARE_HISTOGRAM("LiveBackgroundSameSequence"), DECLARE_HISTOGRAM("CanceledForegroundOffSequence"), DECLARE_HISTOGRAM("CanceledForegroundSameSequence"), DECLARE_HISTOGRAM("CanceledBackgroundOffSequence"), DECLARE_HISTOGRAM("CanceledBackgroundSameSequence")}; int i = (same_sequence ? 1 : 0) + (background ? 2 : 0) + (canceled ? 4 : 0); histograms[i]->AddTimeMillisecondsGranularity(duration); } const base::Feature kAllowOffSequenceTaskCancelation{ "AllowOffSequenceTaskCancelation", base::FEATURE_ENABLED_BY_DEFAULT}; bool AllowOffSequenceTaskCancelation() { if (!base::FeatureList::GetInstance()) return true; return base::FeatureList::IsEnabled(kAllowOffSequenceTaskCancelation); } } // namespace // static const CancelableTaskTracker::TaskId CancelableTaskTracker::kBadTaskId = 0; CancelableTaskTracker::CancelableTaskTracker() { weak_this_ = weak_factory_.GetWeakPtr(); } CancelableTaskTracker::~CancelableTaskTracker() { DCHECK(sequence_checker_.CalledOnValidSequence()); TryCancelAll(); } CancelableTaskTracker::TaskId CancelableTaskTracker::PostTask( TaskRunner* task_runner, const Location& from_here, OnceClosure task) { DCHECK(sequence_checker_.CalledOnValidSequence()); CHECK(weak_this_); return PostTaskAndReply(task_runner, from_here, std::move(task), DoNothing()); } CancelableTaskTracker::TaskId CancelableTaskTracker::PostTaskAndReply( TaskRunner* task_runner, const Location& from_here, OnceClosure task, OnceClosure reply) { DCHECK(sequence_checker_.CalledOnValidSequence()); CHECK(weak_this_); // We need a SequencedTaskRunnerHandle to run |reply|. DCHECK(SequencedTaskRunnerHandle::IsSet()); auto flag = MakeRefCounted(); TaskId id = next_id_; next_id_++; // int64_t is big enough that we ignore the potential overflow. // Unretained(this) is safe because |flag| will have been set to the // "canceled" state after |this| is deleted. OnceClosure untrack_closure = BindOnce(&CancelableTaskTracker::Untrack, Unretained(this), id); bool success = task_runner->PostTaskAndReply( from_here, BindOnce(&RunIfNotCanceled, SequencedTaskRunnerHandle::Get(), flag, std::move(task)), BindOnce(&RunThenUntrackIfNotCanceled, SequencedTaskRunnerHandle::Get(), flag, std::move(reply), std::move(untrack_closure))); if (!success) return kBadTaskId; Track(id, std::move(flag)); return id; } CancelableTaskTracker::TaskId CancelableTaskTracker::NewTrackedTaskId( IsCanceledCallback* is_canceled_cb) { DCHECK(sequence_checker_.CalledOnValidSequence()); DCHECK(SequencedTaskRunnerHandle::IsSet()); TaskId id = next_id_; next_id_++; // int64_t is big enough that we ignore the potential overflow. auto flag = MakeRefCounted(); // Unretained(this) is safe because |flag| will have been set to the // "canceled" state after |this| is deleted. OnceClosure untrack_closure = BindOnce(&CancelableTaskTracker::Untrack, Unretained(this), id); // Will always run |untrack_closure| on current sequence. ScopedClosureRunner untrack_runner( BindOnce(&RunOrPostToTaskRunner, SequencedTaskRunnerHandle::Get(), BindOnce(&RunIfNotCanceled, SequencedTaskRunnerHandle::Get(), flag, std::move(untrack_closure)))); *is_canceled_cb = BindRepeating(&IsCanceled, SequencedTaskRunnerHandle::Get(), flag, std::move(untrack_runner)); Track(id, std::move(flag)); return id; } void CancelableTaskTracker::TryCancel(TaskId id) { DCHECK(sequence_checker_.CalledOnValidSequence()); const auto it = task_flags_.find(id); if (it == task_flags_.end()) { // Two possibilities: // // 1. The task has already been untracked. // 2. The TaskId is bad or unknown. // // Since this function is best-effort, it's OK to ignore these. return; } it->second->data.Set(); // Remove |id| from |task_flags_| immediately, since we have no further // use for tracking it. This allows the reply closures (see // PostTaskAndReply()) for cancelled tasks to be skipped, since they have // no clean-up to perform. task_flags_.erase(it); } void CancelableTaskTracker::TryCancelAll() { DCHECK(sequence_checker_.CalledOnValidSequence()); for (const auto& it : task_flags_) it.second->data.Set(); task_flags_.clear(); } bool CancelableTaskTracker::HasTrackedTasks() const { DCHECK(sequence_checker_.CalledOnValidSequence()); return !task_flags_.empty(); } // static void CancelableTaskTracker::RunIfNotCanceled( const scoped_refptr& origin_task_runner, const scoped_refptr& flag, OnceClosure task) { // TODO(https://crbug.com/1009795): Record durations for executed tasks, // correlated with whether the task runs on a background or foreground // sequence, and whether it is the same sequence as the CancelableTaskTracker. // Also correlate with whether the task was run despite being canceled, to // allow an experiment to assess the value of off-sequence cancelation. // Record canceled & off-sequence status for all tasks. const bool was_canceled = flag->data.IsSet(); const bool same_sequence = origin_task_runner->RunsTasksInCurrentSequence(); const TaskStatus task_status = was_canceled ? (same_sequence ? TaskStatus::kSameSequenceCanceled : TaskStatus::kOffSequenceCanceled) : (same_sequence ? TaskStatus::kSameSequenceLive : TaskStatus::kOffSequenceLive); UMA_HISTOGRAM_ENUMERATION("Scheduler.CancelableTaskTracker.TaskStatus", task_status); // Skip tasks if they are canceled, taking into account the off-sequence // cancelation experiment. const bool skip_task = was_canceled && (AllowOffSequenceTaskCancelation() || same_sequence); if (skip_task) return; // Run the task and record its duration. const TimeTicks before_task_ticks = TimeTicks::Now(); std::move(task).Run(); const TimeDelta duration = TimeTicks::Now() - before_task_ticks; const bool is_background = internal::GetTaskPriorityForCurrentThread() < TaskPriority::USER_VISIBLE; UmaRecordTaskDuration(same_sequence, is_background, was_canceled, duration); } // static void CancelableTaskTracker::RunThenUntrackIfNotCanceled( const scoped_refptr& origin_task_runner, const scoped_refptr& flag, OnceClosure task, OnceClosure untrack) { RunIfNotCanceled(origin_task_runner, flag, std::move(task)); RunIfNotCanceled(origin_task_runner, flag, std::move(untrack)); } // static bool CancelableTaskTracker::IsCanceled( const scoped_refptr& origin_task_runner, const scoped_refptr& flag, const ScopedClosureRunner& cleanup_runner) { return flag->data.IsSet() && (AllowOffSequenceTaskCancelation() || origin_task_runner->RunsTasksInCurrentSequence()); } void CancelableTaskTracker::Track(TaskId id, scoped_refptr flag) { DCHECK(sequence_checker_.CalledOnValidSequence()); CHECK(weak_this_); bool success = task_flags_.insert(std::make_pair(id, std::move(flag))).second; DCHECK(success); } void CancelableTaskTracker::Untrack(TaskId id) { DCHECK(sequence_checker_.CalledOnValidSequence()); CHECK(weak_this_); size_t num = task_flags_.erase(id); DCHECK_EQ(1u, num); } } // namespace base