blob: ecfae4e951017c0bd362c978b9e4ca07e8bb18d8 [file] [log] [blame]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/private_aggregation/private_aggregation_budget_storage.h"
#include <stdint.h>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#include "base/check.h"
#include "base/check_op.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/location.h"
#include "base/memory/ptr_util.h"
#include "base/memory/scoped_refptr.h"
#include "base/metrics/histogram_functions.h"
#include "base/numerics/clamped_math.h"
#include "base/task/sequenced_task_runner.h"
#include "base/timer/elapsed_timer.h"
#include "components/sqlite_proto/key_value_data.h"
#include "components/sqlite_proto/key_value_table.h"
#include "components/sqlite_proto/proto_table_manager.h"
#include "content/browser/private_aggregation/proto/private_aggregation_budgets.pb.h"
#include "sql/database.h"
namespace content {
namespace {
constexpr base::FilePath::CharType kDatabaseFilename[] =
FILE_PATH_LITERAL("PrivateAggregation");
constexpr char kBudgetsTableName[] = "private_aggregation_api_budgets";
// When updating the database's schema, please increment the schema version.
// This will raze the database. This is not necessary for backwards-compatible
// updates to the proto format.
constexpr int kCurrentSchemaVersion = 2;
void RecordInitializationStatus(
PrivateAggregationBudgetStorage::InitStatus status) {
base::UmaHistogramEnumeration(
"PrivacySandbox.PrivateAggregation.BudgetStorage.InitStatus", status);
}
void RecordFileSizeHistogram(const base::FilePath& path_to_database) {
std::optional<int64_t> size_bytes = base::GetFileSize(path_to_database);
if (size_bytes.has_value()) {
base::UmaHistogramCounts1M(
"PrivacySandbox.PrivateAggregation.BudgetStorage.DbSize",
base::MakeClampedNum(size_bytes.value() / 1024));
}
}
} // namespace
// static
base::OnceClosure PrivateAggregationBudgetStorage::CreateAsync(
scoped_refptr<base::SequencedTaskRunner> db_task_runner,
bool exclusively_run_in_memory,
base::FilePath path_to_db_dir,
base::OnceCallback<void(std::unique_ptr<PrivateAggregationBudgetStorage>)>
on_done_initializing) {
CHECK(on_done_initializing);
base::UmaHistogramBoolean(
"PrivacySandbox.PrivateAggregation.BudgetStorage."
"BeginInitializationCount",
/*sample=*/true);
auto storage =
base::WrapUnique(new PrivateAggregationBudgetStorage(db_task_runner));
auto* raw_storage = storage.get();
// `base::Unretained` is safe here as it is impossible for `storage` to be
// deleted before initialization finishes as it is now owned by the reply
// callback below, i.e. passed to `FinishInitializationOnMainSequence()`.
// Similarly, passing the database raw pointer is safe as it can only be
// destroyed on the database sequence after `InitializeOnDbSequence()`.
db_task_runner->PostTaskAndReplyWithResult(
FROM_HERE,
base::BindOnce(&PrivateAggregationBudgetStorage::InitializeOnDbSequence,
base::Unretained(raw_storage),
/*db=*/raw_storage->db_.get(), exclusively_run_in_memory,
std::move(path_to_db_dir)),
base::BindOnce(
&PrivateAggregationBudgetStorage::FinishInitializationOnMainSequence,
base::Unretained(raw_storage), std::move(storage),
std::move(on_done_initializing), base::ElapsedTimer()));
return base::BindOnce(&PrivateAggregationBudgetStorage::Shutdown,
raw_storage->weak_factory_.GetWeakPtr());
}
PrivateAggregationBudgetStorage::PrivateAggregationBudgetStorage(
scoped_refptr<base::SequencedTaskRunner> db_task_runner)
: table_manager_(base::MakeRefCounted<sqlite_proto::ProtoTableManager>(
db_task_runner)),
budgets_table_(
std::make_unique<
sqlite_proto::KeyValueTable<proto::PrivateAggregationBudgets>>(
kBudgetsTableName)),
budgets_data_(table_manager_,
budgets_table_.get(),
/*max_num_entries=*/std::nullopt,
kFlushDelay),
db_task_runner_(std::move(db_task_runner)),
db_(std::make_unique<sql::Database>(
sql::DatabaseOptions().set_cache_size(32),
sql::Database::Tag("PrivateAggregation"))) {}
PrivateAggregationBudgetStorage::~PrivateAggregationBudgetStorage() {
Shutdown();
}
bool PrivateAggregationBudgetStorage::InitializeOnDbSequence(
sql::Database* db,
bool exclusively_run_in_memory,
base::FilePath path_to_db_dir) {
CHECK(db_task_runner_->RunsTasksInCurrentSequence());
CHECK(db);
// TODO(crbug.com/40224647): Record histograms for the different
// outcomes/errors.
if (exclusively_run_in_memory) {
if (!db->OpenInMemory()) {
RecordInitializationStatus(InitStatus::kFailedToOpenDbInMemory);
return false;
}
} else {
const bool dir_exists_or_was_created =
base::DirectoryExists(path_to_db_dir) ||
base::CreateDirectory(path_to_db_dir);
if (!dir_exists_or_was_created) {
RecordInitializationStatus(InitStatus::kFailedToCreateDir);
return false;
}
base::FilePath path_to_database = path_to_db_dir.Append(kDatabaseFilename);
if (!db->Open(path_to_database)) {
RecordInitializationStatus(InitStatus::kFailedToOpenDbFile);
return false;
}
RecordFileSizeHistogram(path_to_database);
}
table_manager_->InitializeOnDbSequence(
db, std::vector<std::string>{kBudgetsTableName}, kCurrentSchemaVersion);
budgets_data_.InitializeOnDBSequence();
RecordInitializationStatus(InitStatus::kSuccess);
return true;
}
void PrivateAggregationBudgetStorage::Shutdown() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK_EQ(!!db_, !!budgets_table_);
// Guard against `Shutdown()` being called multiple times.
if (db_) {
// Schedules `budgets_table_` tasks on the DB sequence for all pending
// updates. Since they are scheduled before the `DeleteSoon()` commands, the
// tasks will be able to complete before `budgets_table_` gets destroyed.
budgets_data_.FlushDataToDisk();
// The following protects `table_manager_` from holding a dangling pointer
// to `db_`. This is possible in the case that the
// PrivateAggregationBudgeter is deleted before `this` is finished
// initializing. In that case, we can delete the `db_` before the
// `table_manager_` is deleted.
// TODO(alexmt,csharrison): Consider refactoring the ownership here.
db_task_runner_->PostTaskAndReply(
FROM_HERE,
base::BindOnce(&sqlite_proto::ProtoTableManager::WillShutdown,
base::Unretained(table_manager_.get())),
base::BindOnce([](sqlite_proto::ProtoTableManager*) {},
base::RetainedRef(table_manager_)));
// `budgets_table_` must be deleted on the database sequence.
db_task_runner_->DeleteSoon(FROM_HERE, budgets_table_.release());
// The sequenced task runner will ensure that this `db_` destruction task
// doesn't run until after `InitializeOnDbSequence()` runs.
db_task_runner_->DeleteSoon(FROM_HERE, db_.release());
}
}
void PrivateAggregationBudgetStorage::FinishInitializationOnMainSequence(
std::unique_ptr<PrivateAggregationBudgetStorage> owned_this,
base::OnceCallback<void(std::unique_ptr<PrivateAggregationBudgetStorage>)>
on_done_initializing,
base::ElapsedTimer elapsed_timer,
bool was_successful) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK(owned_this);
base::UmaHistogramBoolean(
"PrivacySandbox.PrivateAggregation.BudgetStorage."
"ShutdownBeforeFinishingInitialization",
!db_);
base::UmaHistogramTimes(
"PrivacySandbox.PrivateAggregation.BudgetStorage.InitTime",
elapsed_timer.Elapsed());
// If the initialization failed, `this` will be destroyed after its unique_ptr
// passes out of scope here.
std::move(on_done_initializing)
.Run(was_successful ? std::move(owned_this) : nullptr);
}
} // namespace content