910e62b5创建于 1月15日历史提交
// 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