| // Copyright 2023 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "components/unexportable_keys/background_long_task_scheduler.h" |
| |
| #include <string_view> |
| |
| #include "base/check_op.h" |
| #include "base/containers/circular_deque.h" |
| #include "base/memory/scoped_refptr.h" |
| #include "base/memory/weak_ptr.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/no_destructor.h" |
| #include "base/notreached.h" |
| #include "base/strings/strcat.h" |
| #include "base/task/sequenced_task_runner.h" |
| #include "base/time/time.h" |
| #include "base/trace_event/typed_macros.h" |
| #include "components/unexportable_keys/background_task.h" |
| #include "components/unexportable_keys/background_task_priority.h" |
| #include "components/unexportable_keys/background_task_type.h" |
| |
| namespace unexportable_keys { |
| |
| namespace { |
| |
| void RecordDurationHistogramWithAndWithoutSuffix( |
| const char* base_histogram_name, |
| std::string_view suffix, |
| base::TimeDelta duration) { |
| base::UmaHistogramMediumTimes(base_histogram_name, duration); |
| base::UmaHistogramMediumTimes(base::StrCat({base_histogram_name, suffix}), |
| duration); |
| } |
| |
| } // namespace |
| |
| BackgroundLongTaskScheduler::BackgroundLongTaskScheduler( |
| scoped_refptr<base::SequencedTaskRunner> background_task_runner) |
| : background_task_runner_(std::move(background_task_runner)) { |
| DCHECK(background_task_runner_); |
| } |
| |
| BackgroundLongTaskScheduler::~BackgroundLongTaskScheduler() = default; |
| |
| void BackgroundLongTaskScheduler::PostTask( |
| std::unique_ptr<BackgroundTask> task) { |
| TRACE_EVENT("browser", |
| "unexportable_keys::BackgroundLongTaskScheduler::PostTask", |
| perfetto::Flow::FromPointer(task.get()), "type", task->GetType(), |
| "priority", task->GetPriority()); |
| BackgroundTaskPriority priority = task->GetPriority(); |
| GetTaskQueueForPriority(priority).push_back(std::move(task)); |
| // If no task is running, schedule `task` immediately. |
| if (!running_task_) { |
| MaybeRunNextPendingTask(); |
| } |
| } |
| |
| void BackgroundLongTaskScheduler::OnTaskCompleted(BackgroundTask* task) { |
| DCHECK_EQ(running_task_.get(), task); |
| TRACE_EVENT("browser", |
| "unexportable_keys::BackgroundLongTaskScheduler::OnTaskCompleted", |
| perfetto::TerminatingFlow::FromPointer(running_task_.get())); |
| |
| std::optional<base::TimeDelta> elapsed_time_since_run = |
| task->GetElapsedTimeSinceRun(); |
| // Task must have been run before being completed. |
| CHECK(elapsed_time_since_run.has_value()); |
| RecordDurationHistogramWithAndWithoutSuffix( |
| "Crypto.UnexportableKeys.BackgroundTaskRunDuration", |
| GetBackgroundTaskTypeSuffixForHistograms(task->GetType()), |
| *elapsed_time_since_run); |
| RecordDurationHistogramWithAndWithoutSuffix( |
| "Crypto.UnexportableKeys.BackgroundTaskDuration", |
| GetBackgroundTaskPrioritySuffixForHistograms(task->GetPriority()), |
| task->GetElapsedTimeSinceScheduled()); |
| |
| if (running_task_->ShouldRetry()) { |
| running_task_->ResetStateBeforeRetry(); |
| // TODO(crbug.com/390145655): Consider adding retry with back off. |
| // `PostTask()` will schedule the next task so it's not necessary to call |
| // `MaybeRunNextPendingTask()` here. |
| PostTask(std::move(running_task_)); |
| } else { |
| running_task_->ReplyWithResult(); |
| running_task_.reset(); |
| MaybeRunNextPendingTask(); |
| } |
| } |
| |
| void BackgroundLongTaskScheduler::MaybeRunNextPendingTask() { |
| DCHECK(!running_task_); |
| |
| running_task_ = TakeNextPendingTask(); |
| if (!running_task_) { |
| // There is no more pending tasks. Nothing to do. |
| return; |
| } |
| |
| TRACE_EVENT( |
| "browser", |
| "unexportable_keys::BackgroundLongTaskScheduler::MaybeRunNextPendingTask", |
| perfetto::Flow::FromPointer(running_task_.get())); |
| RecordDurationHistogramWithAndWithoutSuffix( |
| "Crypto.UnexportableKeys.BackgroundTaskQueueWaitDuration", |
| GetBackgroundTaskPrioritySuffixForHistograms( |
| running_task_->GetPriority()), |
| running_task_->GetElapsedTimeSinceScheduled()); |
| running_task_->Run( |
| background_task_runner_, |
| base::BindOnce(&BackgroundLongTaskScheduler::OnTaskCompleted, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| BackgroundLongTaskScheduler::TaskQueue& |
| BackgroundLongTaskScheduler::GetTaskQueueForPriority( |
| BackgroundTaskPriority priority) { |
| size_t index = static_cast<size_t>(priority); |
| CHECK_LT(index, kNumTaskPriorities); |
| return task_queue_by_priority_[index]; |
| } |
| |
| BackgroundLongTaskScheduler::TaskQueue* |
| BackgroundLongTaskScheduler::GetHighestPriorityNonEmptyTaskQueue() { |
| // Highest priority has the highest value. |
| for (int i = kNumTaskPriorities - 1; i >= 0; --i) { |
| TaskQueue& queue = task_queue_by_priority_[i]; |
| if (!queue.empty()) { |
| return &queue; |
| } |
| } |
| return nullptr; |
| } |
| |
| std::unique_ptr<BackgroundTask> |
| BackgroundLongTaskScheduler::TakeNextPendingTask() { |
| std::unique_ptr<BackgroundTask> next_task; |
| while (!next_task) { |
| TaskQueue* next_queue = GetHighestPriorityNonEmptyTaskQueue(); |
| if (!next_queue) { |
| return nullptr; |
| } |
| |
| next_task = std::move(next_queue->front()); |
| next_queue->pop_front(); |
| if (next_task->GetStatus() == BackgroundTask::Status::kCanceled) { |
| TRACE_EVENT( |
| "browser", |
| "unexportable_keys::BackgroundLongTaskScheduler::OnTaskCanceled", |
| perfetto::TerminatingFlow::FromPointer(next_task.get())); |
| // Dismiss a canceled task and try the next one. |
| next_task.reset(); |
| } else { |
| DCHECK_EQ(next_task->GetStatus(), BackgroundTask::Status::kPending); |
| } |
| } |
| return next_task; |
| } |
| |
| } // namespace unexportable_keys |