blob: 41a48d643ee5c2c315fadf8d87550cc62ec6c5da [file] [log] [blame]
// Copyright 2021 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/metrics/structured/persistent_proto.h"
#include <memory>
#include "base/files/file_util.h"
#include "base/files/important_file_writer.h"
#include "base/logging.h"
#include "base/rand_util.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "base/threading/scoped_blocking_call.h"
#include "components/metrics/structured/histogram_util.h"
#include "components/metrics/structured/storage.pb.h"
namespace metrics {
namespace structured {
namespace {
template <class T>
std::pair<ReadStatus, std::unique_ptr<T>> Read(const base::FilePath& filepath) {
base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
base::BlockingType::MAY_BLOCK);
if (!base::PathExists(filepath)) {
return {ReadStatus::kMissing, nullptr};
}
std::string proto_str;
if (!base::ReadFileToString(filepath, &proto_str)) {
return {ReadStatus::kReadError, nullptr};
}
auto proto = std::make_unique<T>();
if (!proto->ParseFromString(proto_str)) {
return {ReadStatus::kParseError, nullptr};
}
return {ReadStatus::kOk, std::move(proto)};
}
WriteStatus Write(const base::FilePath& filepath,
const std::string& proto_str) {
const auto directory = filepath.DirName();
if (!base::DirectoryExists(directory)) {
base::CreateDirectory(directory);
}
bool write_result;
{
base::ScopedBlockingCall scoped_blocking_call(
FROM_HERE, base::BlockingType::MAY_BLOCK);
write_result = base::ImportantFileWriter::WriteFileAtomically(
filepath, proto_str, "StructuredMetricsPersistentProto");
}
if (!write_result) {
return WriteStatus::kWriteError;
}
return WriteStatus::kOk;
}
} // namespace
template <class T>
PersistentProto<T>::PersistentProto(
const base::FilePath& path,
const base::TimeDelta write_delay,
typename PersistentProto<T>::ReadCallback on_read,
typename PersistentProto<T>::WriteCallback on_write)
: path_(path),
write_delay_(write_delay),
on_read_(std::move(on_read)),
on_write_(std::move(on_write)) {
task_runner_ = base::ThreadPool::CreateSequencedTaskRunner(
{base::TaskPriority::BEST_EFFORT, base::MayBlock(),
base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN});
task_runner_->PostTaskAndReplyWithResult(
FROM_HERE, base::BindOnce(&Read<T>, path_),
base::BindOnce(&PersistentProto<T>::OnReadComplete,
weak_factory_.GetWeakPtr()));
}
template <class T>
PersistentProto<T>::~PersistentProto() {
if (has_value()) {
std::string proto_str;
if (!proto_->SerializeToString(&proto_str)) {
OnWriteComplete(WriteStatus::kSerializationError);
}
Write(path_, proto_str);
}
}
template <class T>
void PersistentProto<T>::OnReadComplete(
std::pair<ReadStatus, std::unique_ptr<T>> result) {
if (result.first == ReadStatus::kOk) {
proto_ = std::move(result.second);
} else {
proto_ = std::make_unique<T>();
QueueWrite();
}
if (purge_after_reading_) {
proto_.reset();
proto_ = std::make_unique<T>();
StartWrite();
purge_after_reading_ = false;
}
std::move(on_read_).Run(result.first);
}
template <class T>
void PersistentProto<T>::QueueWrite() {
DCHECK(proto_);
if (!proto_) {
return;
}
// If a save is already queued, do nothing.
if (write_is_queued_) {
return;
}
write_is_queued_ = true;
base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&PersistentProto<T>::OnQueueWrite,
weak_factory_.GetWeakPtr()),
write_delay_);
}
template <class T>
void PersistentProto<T>::OnQueueWrite() {
// Reset the queued flag before posting the task. Last-moment updates to
// |proto_| will post another task to write the proto, avoiding race
// conditions.
write_is_queued_ = false;
StartWrite();
}
template <class T>
void PersistentProto<T>::StartWrite() {
DCHECK(proto_);
if (!proto_) {
return;
}
// Serialize the proto outside of the posted task, because otherwise we need
// to pass a proto pointer into the task. This causes a rare race condition
// during destruction where the proto can be destroyed before serialization,
// causing a crash.
std::string proto_str;
if (!proto_->SerializeToString(&proto_str)) {
OnWriteComplete(WriteStatus::kSerializationError);
}
// The SequentialTaskRunner ensures the writes won't trip over each other, so
// we can schedule without checking whether another write is currently active.
task_runner_->PostTaskAndReplyWithResult(
FROM_HERE, base::BindOnce(&Write, path_, proto_str),
base::BindOnce(&PersistentProto<T>::OnWriteComplete,
weak_factory_.GetWeakPtr()));
}
template <class T>
void PersistentProto<T>::OnWriteComplete(const WriteStatus status) {
on_write_.Run(status);
}
template <class T>
void PersistentProto<T>::Purge() {
if (proto_) {
proto_.reset();
proto_ = std::make_unique<T>();
StartWrite();
} else {
purge_after_reading_ = true;
}
}
// A list of all types that the PersistentProto can be used with.
template class PersistentProto<EventsProto>;
template class PersistentProto<KeyDataProto>;
template class PersistentProto<KeyProto>;
} // namespace structured
} // namespace metrics