blob: 46e1263fb591b3b469f8a2770f8cad1ec96d4fce [file] [log] [blame]
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/files/important_file_writer_cleaner.h"
#include <algorithm>
#include <functional>
#include <iterator>
#include <utility>
#include "base/files/file_enumerator.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/no_destructor.h"
#include "base/process/process.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "base/time/time.h"
#include "build/build_config.h"
namespace base {
namespace {
base::Time GetUpperBoundTime() {
#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS) || BUILDFLAG(IS_FUCHSIA)
// If process creation time is not available then use instance creation
// time as the upper-bound for old files. Modification times may be
// rounded-down to coarse-grained increments, e.g. FAT has 2s granularity,
// so it is necessary to set the upper-bound earlier than Now() by at least
// that margin to account for modification times being rounded-down.
return Time::Now() - Seconds(2);
#else
return Process::Current().CreationTime() - Seconds(2);
#endif
}
} // namespace
// static
ImportantFileWriterCleaner& ImportantFileWriterCleaner::GetInstance() {
static NoDestructor<ImportantFileWriterCleaner> instance;
return *instance;
}
// static
void ImportantFileWriterCleaner::AddDirectory(const FilePath& directory) {
auto& instance = GetInstance();
scoped_refptr<SequencedTaskRunner> task_runner;
{
AutoLock scoped_lock(instance.task_runner_lock_);
task_runner = instance.task_runner_;
}
if (!task_runner)
return;
if (task_runner->RunsTasksInCurrentSequence()) {
instance.AddDirectoryImpl(directory);
} else {
// Unretained is safe here since the cleaner instance is never destroyed.
task_runner->PostTask(
FROM_HERE, BindOnce(&ImportantFileWriterCleaner::AddDirectoryImpl,
Unretained(&instance), directory));
}
}
void ImportantFileWriterCleaner::Initialize() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
AutoLock scoped_lock(task_runner_lock_);
DCHECK(!task_runner_ ||
task_runner_ == SequencedTaskRunner::GetCurrentDefault());
task_runner_ = SequencedTaskRunner::GetCurrentDefault();
}
void ImportantFileWriterCleaner::Start() {
#if DCHECK_IS_ON()
{
AutoLock scoped_lock(task_runner_lock_);
DCHECK(task_runner_);
}
#endif
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (is_started())
return;
started_ = true;
if (!pending_directories_.empty())
ScheduleTask();
}
void ImportantFileWriterCleaner::Stop() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!is_started())
return;
if (is_running())
stop_flag_.store(true, std::memory_order_relaxed);
else
DoStop();
}
void ImportantFileWriterCleaner::UninitializeForTesting() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!is_started());
{
AutoLock scoped_lock(task_runner_lock_);
task_runner_ = nullptr;
}
// AddDirectory may have been called after Stop. Clear the containers just in
// case.
important_directories_.clear();
pending_directories_.clear();
DETACH_FROM_SEQUENCE(sequence_checker_);
}
base::Time ImportantFileWriterCleaner::GetUpperBoundTimeForTest() const {
return upper_bound_time_;
}
ImportantFileWriterCleaner::ImportantFileWriterCleaner()
: upper_bound_time_(GetUpperBoundTime()) {
DETACH_FROM_SEQUENCE(sequence_checker_);
}
void ImportantFileWriterCleaner::AddDirectoryImpl(const FilePath& directory) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!important_directories_.insert(directory).second)
return; // This directory has already been seen.
pending_directories_.push_back(directory);
if (!is_started())
return; // Nothing more to do if Start() has not been called.
// Start the background task if it's not already running. If it is running, a
// new task will be posted on completion of the current one by
// OnBackgroundTaskFinished to handle all directories added while it was
// running.
if (!is_running())
ScheduleTask();
}
void ImportantFileWriterCleaner::ScheduleTask() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(is_started());
DCHECK(!is_running());
DCHECK(!pending_directories_.empty());
DCHECK(!stop_flag_.load(std::memory_order_relaxed));
// Pass the set of directories to be processed.
running_ = ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE,
{TaskPriority::BEST_EFFORT, TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN,
MayBlock()},
BindOnce(&ImportantFileWriterCleaner::CleanInBackground,
upper_bound_time_, std::move(pending_directories_),
std::ref(stop_flag_)),
// Unretained is safe here since the cleaner instance is never destroyed.
BindOnce(&ImportantFileWriterCleaner::OnBackgroundTaskFinished,
Unretained(this)));
}
// static
bool ImportantFileWriterCleaner::CleanInBackground(
Time upper_bound_time,
std::vector<FilePath> directories,
std::atomic_bool& stop_flag) {
DCHECK(!directories.empty());
for (auto scan = directories.begin(), end = directories.end(); scan != end;
++scan) {
const auto& directory = *scan;
FileEnumerator file_enum(
directory, /*recursive=*/false, FileEnumerator::FILES,
FormatTemporaryFileName(FILE_PATH_LITERAL("*")).value());
for (FilePath path = file_enum.Next(); !path.empty();
path = file_enum.Next()) {
const FileEnumerator::FileInfo info = file_enum.GetInfo();
if (info.GetLastModifiedTime() >= upper_bound_time)
continue;
// Cleanup is a best-effort process, so ignore any failures here and
// continue to clean as much as possible. Metrics tell us that ~98.4% of
// directories are cleaned with no failures.
DeleteFile(path);
// Break out without checking for the next file if a stop is requested.
if (stop_flag.load(std::memory_order_relaxed))
return false;
}
}
return true;
}
void ImportantFileWriterCleaner::OnBackgroundTaskFinished(
bool processing_completed) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
running_ = false;
// There are no other accessors of |stop_flag_| at this point, so atomic
// operations aren't needed. There is no way to read it without such, so use
// the same (relaxed) ordering as elsewhere.
const bool stop = stop_flag_.exchange(false, std::memory_order_relaxed);
DCHECK(stop || processing_completed);
if (stop) {
DoStop();
} else if (!pending_directories_.empty()) {
// Run the task again with the new directories.
ScheduleTask();
} // else do nothing until a new directory is added.
}
void ImportantFileWriterCleaner::DoStop() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(is_started());
DCHECK(!is_running());
important_directories_.clear();
pending_directories_.clear();
started_ = false;
}
} // namespace base