blob: 015a61e5fea483260989fb5de38e7e09975e465b [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/browser/resource_coordinator/leveldb_site_characteristics_database.h"
#include <string>
#include "base/files/file_util.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/task_runner_util.h"
#include "base/threading/thread_restrictions.h"
#include "chrome/browser/resource_coordinator/utils.h"
#include "third_party/leveldatabase/env_chromium.h"
#include "third_party/leveldatabase/leveldb_chrome.h"
#include "third_party/leveldatabase/src/include/leveldb/write_batch.h"
namespace resource_coordinator {
namespace {
const char kInitStatusHistogramLabel[] =
"ResourceCoordinator.LocalDB.DatabaseInit";
const char kInitStatusAfterRepairHistogramLabel[] =
"ResourceCoordinator.LocalDB.DatabaseInitAfterRepair";
const char kInitStatusAfterDeleteHistogramLabel[] =
"ResourceCoordinator.LocalDB.DatabaseInitAfterDelete";
enum class InitStatus {
kInitStatusOk,
kInitStatusCorruption,
kInitStatusIOError,
kInitStatusUnknownError,
kInitStatusMax
};
// Report the database's initialization status metrics.
void ReportInitStatus(const char* histogram_name,
const leveldb::Status& status) {
if (status.ok()) {
base::UmaHistogramEnumeration(histogram_name, InitStatus::kInitStatusOk,
InitStatus::kInitStatusMax);
} else if (status.IsCorruption()) {
base::UmaHistogramEnumeration(histogram_name,
InitStatus::kInitStatusCorruption,
InitStatus::kInitStatusMax);
} else if (status.IsIOError()) {
base::UmaHistogramEnumeration(histogram_name,
InitStatus::kInitStatusIOError,
InitStatus::kInitStatusMax);
} else {
base::UmaHistogramEnumeration(histogram_name,
InitStatus::kInitStatusUnknownError,
InitStatus::kInitStatusMax);
}
}
// Attempt to repair the database stored in |db_path|.
bool RepairDatabase(const std::string& db_path) {
leveldb_env::Options options;
options.reuse_logs = false;
options.max_open_files = 0;
bool repair_succeeded = leveldb::RepairDB(db_path, options).ok();
UMA_HISTOGRAM_BOOLEAN("ResourceCoordinator.LocalDB.DatabaseRepair",
repair_succeeded);
return repair_succeeded;
}
bool ShouldAttemptDbRepair(const leveldb::Status& status) {
// A corrupt database might be repaired (some data might be loss but it's
// better than losing everything).
if (status.IsCorruption())
return true;
// An I/O error might be caused by a missing manifest, it's sometime possible
// to repair this (some data might be loss).
if (status.IsIOError())
return true;
return false;
}
} // namespace
// Helper class used to run all the blocking operations posted by
// LocalSiteCharacteristicDatabase on a TaskScheduler sequence with the
// |MayBlock()| trait.
//
// Instances of this class should only be destructed once all the posted tasks
// have been run, in practice it means that they should ideally be stored in a
// std::unique_ptr<AsyncHelper, base::OnTaskRunnerDeleter>.
class LevelDBSiteCharacteristicsDatabase::AsyncHelper {
public:
explicit AsyncHelper(const base::FilePath& db_path) : db_path_(db_path) {
DETACH_FROM_SEQUENCE(sequence_checker_);
// Setting |sync| to false might cause some data loss if the system crashes
// but it'll make the write operations faster (no data will be lost if only
// the process crashes).
write_options_.sync = false;
}
~AsyncHelper() = default;
// Open the database from |db_path_| after creating it if it didn't exist.
void OpenOrCreateDatabase();
// Implementations of the DB manipulation functions of
// LevelDBSiteCharacteristicsDatabase that run on a blocking sequence.
base::Optional<SiteCharacteristicsProto> ReadSiteCharacteristicsFromDB(
const url::Origin& origin);
void WriteSiteCharacteristicsIntoDB(
const url::Origin& origin,
const SiteCharacteristicsProto& site_characteristic_proto);
void RemoveSiteCharacteristicsFromDB(
const std::vector<url::Origin>& site_origin);
void ClearDatabase();
bool DBIsInitialized() { return db_ != nullptr; }
private:
// The on disk location of the database.
const base::FilePath db_path_;
// The connection to the LevelDB database.
std::unique_ptr<leveldb::DB> db_;
// The options to be used for all database read operations.
leveldb::ReadOptions read_options_;
// The options to be used for all database write operations.
leveldb::WriteOptions write_options_;
SEQUENCE_CHECKER(sequence_checker_);
DISALLOW_COPY_AND_ASSIGN(AsyncHelper);
};
void LevelDBSiteCharacteristicsDatabase::AsyncHelper::OpenOrCreateDatabase() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!db_) << "Database already open";
base::AssertBlockingAllowed();
// Report the on disk size of the database if it already exists.
if (base::DirectoryExists(db_path_)) {
int64_t db_ondisk_size_in_bytes = base::ComputeDirectorySize(db_path_);
UMA_HISTOGRAM_MEMORY_KB("ResourceCoordinator.LocalDB.OnDiskSize",
db_ondisk_size_in_bytes / 1024);
}
leveldb_env::Options options;
options.create_if_missing = true;
leveldb::Status status =
leveldb_env::OpenDB(options, db_path_.AsUTF8Unsafe(), &db_);
ReportInitStatus(kInitStatusHistogramLabel, status);
if (status.ok())
return;
if (!ShouldAttemptDbRepair(status))
return;
if (RepairDatabase(db_path_.AsUTF8Unsafe())) {
status = leveldb_env::OpenDB(options, db_path_.AsUTF8Unsafe(), &db_);
ReportInitStatus(kInitStatusAfterRepairHistogramLabel, status);
if (status.ok())
return;
}
// Delete the database and try to open it one last time.
if (leveldb_chrome::DeleteDB(db_path_, options).ok()) {
status = leveldb_env::OpenDB(options, db_path_.AsUTF8Unsafe(), &db_);
ReportInitStatus(kInitStatusAfterDeleteHistogramLabel, status);
if (!status.ok())
db_.reset();
}
}
base::Optional<SiteCharacteristicsProto>
LevelDBSiteCharacteristicsDatabase::AsyncHelper::ReadSiteCharacteristicsFromDB(
const url::Origin& origin) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
base::AssertBlockingAllowed();
if (!db_)
return base::nullopt;
std::string protobuf_value;
leveldb::Status s = db_->Get(
read_options_, SerializeOriginIntoDatabaseKey(origin), &protobuf_value);
base::Optional<SiteCharacteristicsProto> site_characteristic_proto;
if (s.ok()) {
site_characteristic_proto = SiteCharacteristicsProto();
if (!site_characteristic_proto->ParseFromString(protobuf_value)) {
site_characteristic_proto = base::nullopt;
LOG(ERROR) << "Error while trying to parse a SiteCharacteristicsProto "
<< "protobuf.";
}
}
return site_characteristic_proto;
}
void LevelDBSiteCharacteristicsDatabase::AsyncHelper::
WriteSiteCharacteristicsIntoDB(
const url::Origin& origin,
const SiteCharacteristicsProto& site_characteristic_proto) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
base::AssertBlockingAllowed();
if (!db_)
return;
leveldb::Status s =
db_->Put(write_options_, SerializeOriginIntoDatabaseKey(origin),
site_characteristic_proto.SerializeAsString());
if (!s.ok()) {
LOG(ERROR) << "Error while inserting an element in the site characteristic "
<< "database: " << s.ToString();
}
}
void LevelDBSiteCharacteristicsDatabase::AsyncHelper::
RemoveSiteCharacteristicsFromDB(
const std::vector<url::Origin>& site_origins) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
base::AssertBlockingAllowed();
if (!db_)
return;
leveldb::WriteBatch batch;
for (const auto& iter : site_origins)
batch.Delete(SerializeOriginIntoDatabaseKey(iter));
leveldb::Status status = db_->Write(write_options_, &batch);
if (!status.ok()) {
LOG(WARNING) << "Failed to remove some entries from the site "
<< "characteristics database: " << status.ToString();
}
}
void LevelDBSiteCharacteristicsDatabase::AsyncHelper::ClearDatabase() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
base::AssertBlockingAllowed();
if (!db_)
return;
leveldb_env::Options options;
db_.reset();
leveldb::Status status = leveldb::DestroyDB(db_path_.AsUTF8Unsafe(), options);
if (status.ok()) {
OpenOrCreateDatabase();
} else {
LOG(WARNING) << "Failed to destroy the site characteristics database: "
<< status.ToString();
}
}
LevelDBSiteCharacteristicsDatabase::LevelDBSiteCharacteristicsDatabase(
const base::FilePath& db_path)
: blocking_task_runner_(base::CreateSequencedTaskRunnerWithTraits(
// The |BLOCK_SHUTDOWN| trait is required to ensure that a clearing of
// the database won't be skipped.
{base::MayBlock(), base::TaskShutdownBehavior::BLOCK_SHUTDOWN})),
async_helper_(new AsyncHelper(db_path),
base::OnTaskRunnerDeleter(blocking_task_runner_)) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
blocking_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&LevelDBSiteCharacteristicsDatabase::
AsyncHelper::OpenOrCreateDatabase,
base::Unretained(async_helper_.get())));
}
LevelDBSiteCharacteristicsDatabase::~LevelDBSiteCharacteristicsDatabase() =
default;
void LevelDBSiteCharacteristicsDatabase::ReadSiteCharacteristicsFromDB(
const url::Origin& origin,
LocalSiteCharacteristicsDatabase::ReadSiteCharacteristicsFromDBCallback
callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Trigger the asynchronous task and make it run the callback on this thread
// once it returns.
base::PostTaskAndReplyWithResult(
blocking_task_runner_.get(), FROM_HERE,
base::BindOnce(&LevelDBSiteCharacteristicsDatabase::AsyncHelper::
ReadSiteCharacteristicsFromDB,
base::Unretained(async_helper_.get()), origin),
base::BindOnce(std::move(callback)));
}
void LevelDBSiteCharacteristicsDatabase::WriteSiteCharacteristicsIntoDB(
const url::Origin& origin,
const SiteCharacteristicsProto& site_characteristic_proto) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
blocking_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&LevelDBSiteCharacteristicsDatabase::
AsyncHelper::WriteSiteCharacteristicsIntoDB,
base::Unretained(async_helper_.get()), origin,
std::move(site_characteristic_proto)));
}
void LevelDBSiteCharacteristicsDatabase::RemoveSiteCharacteristicsFromDB(
const std::vector<url::Origin>& site_origins) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
blocking_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&LevelDBSiteCharacteristicsDatabase::AsyncHelper::
RemoveSiteCharacteristicsFromDB,
base::Unretained(async_helper_.get()),
std::move(site_origins)));
}
void LevelDBSiteCharacteristicsDatabase::ClearDatabase() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
blocking_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(
&LevelDBSiteCharacteristicsDatabase::AsyncHelper::ClearDatabase,
base::Unretained(async_helper_.get())));
}
bool LevelDBSiteCharacteristicsDatabase::DatabaseIsInitializedForTesting() {
return async_helper_->DBIsInitialized();
}
} // namespace resource_coordinator