blob: d4107c665701fd45b24fba33b2fff4a7e6acfa36 [file] [log] [blame]
// Copyright 2018 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 "chrome/chrome_cleaner/components/component_manager.h"
#include <memory>
#include <utility>
#include "base/bind.h"
#include "base/run_loop.h"
#include "base/task/post_task.h"
#include "base/threading/thread_task_runner_handle.h"
#include "chrome/chrome_cleaner/components/component_api.h"
namespace chrome_cleaner {
namespace {
// A set of factory functions to create the calls to code to be executed in
// worker threads. Since the code to loop through the components is the same
// for all calls, it was extracted in the |PostComponentTasks| method. So the
// public API methods must specify which function to call on the components.
// Since the component pointer must be the first argument, and we can't pre-pend
// args to bound calls, the public API methods sends a bound version of these
// factory functions so that |PostComponentTasks| can call them to get the
// closure by passing the component pointer to them. The component pointer
// must be the last argument to these factory functions so that the other
// arguments are pre-bound by the public API methods when they call
// |PostComponentTasks| (e.g., |found_pups|).
base::OnceClosure BindPreScan(ComponentAPI* component) {
return base::BindOnce(&ComponentAPI::PreScan, base::Unretained(component));
}
base::OnceClosure BindPostScan(const std::vector<UwSId>& found_pups,
ComponentAPI* component) {
return base::BindOnce(&ComponentAPI::PostScan, base::Unretained(component),
found_pups);
}
base::OnceClosure BindPreCleanup(ComponentAPI* component) {
return base::BindOnce(&ComponentAPI::PreCleanup, base::Unretained(component));
}
base::OnceClosure BindPostCleanup(ResultCode result_code,
RebooterAPI* rebooter,
ComponentAPI* component) {
return base::BindOnce(&ComponentAPI::PostCleanup, base::Unretained(component),
result_code, rebooter);
}
} // namespace
ComponentManager::ComponentManager(ComponentManagerDelegate* delegate)
: delegate_(delegate) {
DCHECK(delegate_);
}
ComponentManager::~ComponentManager() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Make sure |CloseAllComponents| was called. IT can't be called from here
// because it needs the result code. The components will still be released by
// the scoped vector, but their OnClose won't be called if
// |CloseAllComponents| wasn't called.
DCHECK(!cancelable_task_tracker_.HasTrackedTasks());
DCHECK(components_.empty());
DCHECK(done_callback_.is_null());
}
void ComponentManager::AddComponent(std::unique_ptr<ComponentAPI> component) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Components can't be added while tasks are in flight.
DCHECK(!cancelable_task_tracker_.HasTrackedTasks());
DCHECK(done_callback_.is_null());
DCHECK_EQ(0UL, num_tasks_pending_);
components_.push_back(std::move(component));
}
void ComponentManager::PreScan() {
// No task should be pending when any method from the ComponentsAPI are
// called.
DCHECK(done_callback_.is_null());
done_callback_ = base::BindOnce(&ComponentManagerDelegate::PreScanDone,
base::Unretained(delegate_));
PostComponentTasks(base::BindRepeating(&BindPreScan), "PreScan");
}
void ComponentManager::PostScan(const std::vector<UwSId>& found_pups) {
DCHECK(done_callback_.is_null());
done_callback_ = base::BindOnce(&ComponentManagerDelegate::PostScanDone,
base::Unretained(delegate_));
PostComponentTasks(base::BindRepeating(&BindPostScan, found_pups),
"PostScan");
}
void ComponentManager::PreCleanup() {
DCHECK(done_callback_.is_null());
done_callback_ = base::BindOnce(&ComponentManagerDelegate::PreCleanupDone,
base::Unretained(delegate_));
PostComponentTasks(base::BindRepeating(&BindPreCleanup), "PreCleanup");
}
void ComponentManager::PostCleanup(ResultCode result_code,
RebooterAPI* rebooter) {
DCHECK(done_callback_.is_null());
done_callback_ = base::BindOnce(&ComponentManagerDelegate::PostCleanupDone,
base::Unretained(delegate_));
PostComponentTasks(
base::BindRepeating(&BindPostCleanup, result_code, rebooter),
"PostCleanup");
}
void ComponentManager::PostValidation(ResultCode result_code) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!cancelable_task_tracker_.HasTrackedTasks());
DCHECK_EQ(0UL, num_tasks_pending_);
for (auto& component : components_)
component->PostValidation(result_code);
}
void ComponentManager::CloseAllComponents(ResultCode result_code) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (num_tasks_pending_ > 0) {
cancelable_task_tracker_.TryCancelAll();
while (cancelable_task_tracker_.HasTrackedTasks())
base::RunLoop().RunUntilIdle();
done_callback_.Reset();
} else {
DCHECK(!cancelable_task_tracker_.HasTrackedTasks());
}
for (auto& component : components_)
component->OnClose(result_code);
components_.clear();
}
void ComponentManager::PostComponentTasks(
const base::RepeatingCallback<base::OnceClosure(ComponentAPI*)>
component_task,
const char* method_name) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!cancelable_task_tracker_.HasTrackedTasks());
DCHECK_EQ(0UL, num_tasks_pending_);
if (components_.empty()) {
DCHECK(!done_callback_.is_null());
base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
std::move(done_callback_));
return;
}
// Allow blocking operations, such as file operations, in the component task.
// This is allowed since there is no UI for it to block.
auto task_runner = base::CreateTaskRunnerWithTraits(base::MayBlock());
for (auto& component : components_) {
if (cancelable_task_tracker_.PostTaskAndReply(
task_runner.get(), FROM_HERE,
component_task.Run(component.get()), // This returns a Closure.
base::BindOnce(&ComponentManager::TaskCompleted,
base::Unretained(this))) ==
base::CancelableTaskTracker::kBadTaskId) {
PLOG(ERROR) << "Failed to run component's method: " << method_name;
} else {
// The reply task is never called synchronously, so it's OK to increment
// the number of tasks pending after the task was posted.
++num_tasks_pending_;
}
}
}
void ComponentManager::TaskCompleted() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK_GT(num_tasks_pending_, 0UL);
if (--num_tasks_pending_ == 0) {
// The callback must be run asynchronously so that the task tracker is not
// on the call stack anymore in cases where the callback ends up calling
// CloseAllComponents.
base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
std::move(done_callback_));
}
}
} // namespace chrome_cleaner