Commit 7ea6f1cd authored by Troy Hildebrandt's avatar Troy Hildebrandt Committed by Commit Bot

Add SharedProtoDatabase and SharedProtoDatabaseClient.

Adds a SharedProtoDatabase that owns a LevelDB instance. The shared
database provides a way to get SharedProtoDatabaseClients to access
the shared DB with a unique namespace/prefix. The SharedProtoDatabase
instance is provided through the KeyedService ProtoDatabaseProvider
so the shared database is tied to the appropriate profile directory.

Bug: 870813
Change-Id: I433518c3fee4d37abcaa0c7efa94e7912da521ec
Reviewed-on: https://chromium-review.googlesource.com/c/1332009Reviewed-by: default avatarTommy Nyquist <nyquist@chromium.org>
Commit-Queue: Troy Hildebrandt <thildebr@chromium.org>
Cr-Commit-Position: refs/heads/master@{#612000}
parent f9ac141a
......@@ -39,10 +39,9 @@
namespace feature_engagement {
namespace {
const base::FilePath::CharType kEventDBStorageDir[] =
FILE_PATH_LITERAL("EventDB");
const base::FilePath::CharType kAvailabilityDBStorageDir[] =
FILE_PATH_LITERAL("AvailabilityDB");
const char kFeatureName[] = "FeatureEngagement";
const char kEventDBName[] = "EventDB";
const char kAvailabilityDBName[] = "AvailabilityDB";
// Creates a TrackerImpl that is usable for a demo mode.
std::unique_ptr<Tracker> CreateDemoModeTracker() {
......@@ -98,9 +97,11 @@ Tracker* Tracker::Create(
if (base::FeatureList::IsEnabled(kIPHDemoMode))
return CreateDemoModeTracker().release();
base::FilePath event_storage_dir = storage_dir.Append(kEventDBStorageDir);
auto event_db =
db_provider->GetDB<Event>(event_storage_dir, background_task_runner);
base::FilePath event_storage_dir =
storage_dir.AppendASCII(std::string(kEventDBName));
auto event_db = db_provider->GetDB<Event>(
std::string(kFeatureName), std::string(kEventDBName), event_storage_dir,
background_task_runner);
auto event_store =
std::make_unique<PersistentEventStore>(std::move(event_db));
......@@ -122,8 +123,9 @@ Tracker* Tracker::Create(
auto time_provider = std::make_unique<SystemTimeProvider>();
base::FilePath availability_storage_dir =
storage_dir.Append(kAvailabilityDBStorageDir);
storage_dir.AppendASCII(std::string(kAvailabilityDBName));
auto availability_db = db_provider->GetDB<Availability>(
std::string(kFeatureName), std::string(kAvailabilityDBName),
availability_storage_dir, background_task_runner);
auto availability_store_loader = base::BindOnce(
&PersistentAvailabilityStore::LoadAndUpdateStore,
......
......@@ -16,6 +16,10 @@ static_library("leveldb_proto") {
"proto_leveldb_wrapper.h",
"proto_leveldb_wrapper_metrics.cc",
"proto_leveldb_wrapper_metrics.h",
"shared_proto_database.cc",
"shared_proto_database.h",
"shared_proto_database_client.cc",
"shared_proto_database_client.h",
"unique_proto_database.h",
]
......@@ -43,6 +47,8 @@ source_set("test_support") {
source_set("unit_tests") {
testonly = true
sources = [
"shared_proto_database_client_unittest.cc",
"shared_proto_database_unittest.cc",
"unique_proto_database_unittest.cc",
]
deps = [
......
......@@ -5,11 +5,27 @@
#include "components/leveldb_proto/proto_database_provider.h"
#include "base/files/file_path.h"
#include "base/sequenced_task_runner.h"
#include "base/synchronization/lock.h"
#include "components/leveldb_proto/shared_proto_database.h"
namespace {
const char kSharedProtoDatabaseClientName[] = "SharedProtoDB";
const char kSharedProtoDatabaseDirectory[] = "shared_proto_db";
} // namespace
namespace leveldb_proto {
ProtoDatabaseProvider::ProtoDatabaseProvider(const base::FilePath& profile_dir)
: profile_dir_(profile_dir) {}
: profile_dir_(profile_dir),
task_runner_(base::CreateSequencedTaskRunnerWithTraits(
{base::MayBlock(), base::TaskPriority::BEST_EFFORT,
base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN})),
weak_factory_(this) {}
ProtoDatabaseProvider::~ProtoDatabaseProvider() = default;
// static
ProtoDatabaseProvider* ProtoDatabaseProvider::Create(
......@@ -17,4 +33,32 @@ ProtoDatabaseProvider* ProtoDatabaseProvider::Create(
return new ProtoDatabaseProvider(profile_dir);
}
void ProtoDatabaseProvider::GetSharedDBInstance(
GetSharedDBInstanceCallback callback) {
task_runner_->PostTaskAndReply(
FROM_HERE,
base::BindOnce(
&ProtoDatabaseProvider::PrepareSharedDBInstanceOnTaskRunner,
weak_factory_.GetWeakPtr()),
base::BindOnce(&ProtoDatabaseProvider::RunGetSharedDBInstanceCallback,
weak_factory_.GetWeakPtr(), std::move(callback)));
}
void ProtoDatabaseProvider::PrepareSharedDBInstanceOnTaskRunner() {
if (db_)
return;
db_ = base::WrapRefCounted(new SharedProtoDatabase(
base::CreateSequencedTaskRunnerWithTraits(
{base::MayBlock(), base::TaskPriority::BEST_EFFORT,
base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN}),
kSharedProtoDatabaseClientName,
profile_dir_.AppendASCII(std::string(kSharedProtoDatabaseDirectory))));
}
void ProtoDatabaseProvider::RunGetSharedDBInstanceCallback(
GetSharedDBInstanceCallback callback) {
std::move(callback).Run(db_);
}
} // namespace leveldb_proto
......@@ -6,41 +6,76 @@
#define COMPONENTS_LEVELDB_PROTO_PROTO_DATABASE_PROVIDER_H_
#include "base/files/file_path.h"
#include "base/sequenced_task_runner.h"
#include "components/keyed_service/core/keyed_service.h"
#include "components/leveldb_proto/proto_database.h"
#include "components/leveldb_proto/proto_database_wrapper.h"
namespace leveldb_proto {
class SharedProtoDatabase;
// A KeyedService that provides instances of ProtoDatabase tied to the current
// profile directory.
class ProtoDatabaseProvider : public KeyedService {
public:
using GetSharedDBInstanceCallback =
base::OnceCallback<void(scoped_refptr<SharedProtoDatabase>)>;
static ProtoDatabaseProvider* Create(const base::FilePath& profile_dir);
// |client_namespace| is the unique prefix to be used in the shared database
// if the database returned is a SharedDatabaseClient<T>.
// |type_prefix| is a unique prefix within the |client_namespace| to be used
// in the shared database if the database returned is a
// SharedProtoDatabaseClient<T>.
// |unique_db_dir|: the subdirectory this database should live in within
// the profile directory.
// |task_runner|: the SequencedTaskRunner to run all database operations on.
// This isn't used by SharedProtoDatabaseClients since all calls using
// the SharedProtoDatabase will run on its TaskRunner.
template <typename T>
std::unique_ptr<ProtoDatabase<T>> GetDB(
const std::string& client_namespace,
const std::string& type_prefix,
const base::FilePath& unique_db_dir,
const scoped_refptr<base::SequencedTaskRunner>& task_runner);
~ProtoDatabaseProvider() override = default;
void GetSharedDBInstance(GetSharedDBInstanceCallback callback);
~ProtoDatabaseProvider() override;
private:
ProtoDatabaseProvider(const base::FilePath& profile_dir);
// The shared DB is lazy-initialized, and this ensures that we have a valid
// instance before triggering the GetSharedDBInstanceCallback on the original
// calling sequence.
void PrepareSharedDBInstanceOnTaskRunner();
void RunGetSharedDBInstanceCallback(GetSharedDBInstanceCallback callback);
base::FilePath profile_dir_;
scoped_refptr<SharedProtoDatabase> db_;
// The SequencedTaskRunner used to ensure thread-safe behaviour for
// GetSharedDBInstance.
scoped_refptr<base::SequencedTaskRunner> task_runner_;
base::WeakPtrFactory<ProtoDatabaseProvider> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(ProtoDatabaseProvider);
};
template <typename T>
std::unique_ptr<ProtoDatabase<T>> ProtoDatabaseProvider::GetDB(
const std::string& client_namespace,
const std::string& type_prefix,
const base::FilePath& unique_db_dir,
const scoped_refptr<base::SequencedTaskRunner>& task_runner) {
return std::make_unique<ProtoDatabaseWrapper<T>>(unique_db_dir, task_runner);
return std::make_unique<ProtoDatabaseWrapper<T>>(
client_namespace, type_prefix, unique_db_dir, task_runner);
}
} // namespace leveldb_proto
#endif // COMPONENTS_LEVELDB_PROTO_PROTO_DATABASE_PROVIDER_H_
\ No newline at end of file
#endif // COMPONENTS_LEVELDB_PROTO_PROTO_DATABASE_PROVIDER_H_
......@@ -16,12 +16,16 @@ namespace leveldb_proto {
// The ProtoDatabaseWrapper<T> owns a ProtoDatabase<T> instance, and allows the
// underlying ProtoDatabase<T> implementation to change without users of the
// wrapper needing to know.
// This allows clients to request a DB instance without knowing whether or not
// it's a UniqueDatabase<T> or a SharedProtoDatabaseClient<T>.
template <typename T>
class ProtoDatabaseWrapper : public UniqueProtoDatabase<T> {
public:
using InitCallback = base::OnceCallback<void(bool success)>;
ProtoDatabaseWrapper(
const std::string& client_namespace,
const std::string& type_prefix,
const base::FilePath& db_dir,
const scoped_refptr<base::SequencedTaskRunner>& task_runner);
......@@ -100,6 +104,8 @@ class ProtoDatabaseWrapper : public UniqueProtoDatabase<T> {
InitCallback callback,
bool success);
std::string client_namespace_;
std::string type_prefix_;
base::FilePath db_dir_;
std::unique_ptr<ProtoDatabase<T>> db_;
scoped_refptr<base::SequencedTaskRunner> task_runner_;
......@@ -118,9 +124,13 @@ void ProtoDatabaseWrapper<T>::RunCallbackOnCallingSequence(
template <typename T>
ProtoDatabaseWrapper<T>::ProtoDatabaseWrapper(
const std::string& client_namespace,
const std::string& type_prefix,
const base::FilePath& db_dir,
const scoped_refptr<base::SequencedTaskRunner>& task_runner)
: UniqueProtoDatabase<T>(task_runner) {
client_namespace_ = client_namespace;
type_prefix_ = type_prefix;
db_dir_ = db_dir;
db_ = nullptr;
task_runner_ = task_runner;
......@@ -154,7 +164,7 @@ void ProtoDatabaseWrapper<T>::Init(
const std::string& client_name,
typename ProtoDatabase<T>::InitCallback callback) {
auto unique_db = std::make_unique<UniqueProtoDatabase<T>>(
db_dir_, CreateSimpleOptions(), task_runner_);
db_dir_, CreateSimpleOptions(), this->task_runner_);
unique_db->Init(client_name,
base::BindOnce(&ProtoDatabaseWrapper<T>::OnInitUniqueDB,
weak_ptr_factory_->GetWeakPtr(),
......
......@@ -58,7 +58,7 @@ inline void LoadKeysFromTaskRunner(LevelDB* database,
DCHECK(success);
DCHECK(keys);
keys->clear();
*success = database->LoadKeys(keys);
*success = database->LoadKeys(target_prefix, keys);
ProtoLevelDBWrapperMetrics::RecordLoadKeys(client_id, *success);
}
......@@ -66,12 +66,16 @@ inline void LoadKeysFromTaskRunner(LevelDB* database,
ProtoLevelDBWrapper::ProtoLevelDBWrapper(
const scoped_refptr<base::SequencedTaskRunner>& task_runner)
: task_runner_(task_runner) {}
: task_runner_(task_runner) {
DETACH_FROM_SEQUENCE(sequence_checker_);
}
ProtoLevelDBWrapper::ProtoLevelDBWrapper(
const scoped_refptr<base::SequencedTaskRunner>& task_runner,
LevelDB* db)
: task_runner_(task_runner), db_(db) {}
: task_runner_(task_runner), db_(db) {
DETACH_FROM_SEQUENCE(sequence_checker_);
}
ProtoLevelDBWrapper::~ProtoLevelDBWrapper() = default;
......@@ -81,7 +85,7 @@ void ProtoLevelDBWrapper::InitWithDatabase(
const leveldb_env::Options& options,
bool destroy_on_corruption,
typename ProtoLevelDBWrapper::InitCallback callback) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!db_);
DCHECK(database);
db_ = database;
......@@ -96,7 +100,7 @@ void ProtoLevelDBWrapper::InitWithDatabase(
void ProtoLevelDBWrapper::Destroy(
typename ProtoLevelDBWrapper::DestroyCallback callback) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(db_);
bool* success = new bool(false);
......@@ -116,7 +120,7 @@ void ProtoLevelDBWrapper::LoadKeys(
void ProtoLevelDBWrapper::LoadKeys(
const std::string& target_prefix,
typename ProtoLevelDBWrapper::LoadKeysCallback callback) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
auto success = std::make_unique<bool>(false);
auto keys = std::make_unique<std::vector<std::string>>();
bool* success_ptr = success.get();
......
......@@ -147,7 +147,7 @@ class ProtoLevelDBWrapper {
const scoped_refptr<base::SequencedTaskRunner>& task_runner();
private:
THREAD_CHECKER(thread_checker_);
SEQUENCE_CHECKER(sequence_checker_);
// Used to run blocking tasks in-order, must be the TaskRunner that |db_|
// relies on.
......@@ -223,6 +223,7 @@ void UpdateEntriesWithRemoveFilterFromTaskRunner(
std::unique_ptr<typename ProtoLevelDBWrapper::Internal<T>::KeyEntryVector>
entries_to_save,
const LevelDB::KeyFilter& delete_key_filter,
const std::string& target_prefix,
bool* success,
const std::string& client_id) {
DCHECK(success);
......@@ -236,7 +237,7 @@ void UpdateEntriesWithRemoveFilterFromTaskRunner(
leveldb::Status status;
*success = database->UpdateWithRemoveFilter(pairs_to_save, delete_key_filter,
&status);
target_prefix, &status);
ProtoLevelDBWrapperMetrics::RecordUpdate(client_id, *success, status);
}
......@@ -331,7 +332,7 @@ void ProtoLevelDBWrapper::UpdateEntries(
entries_to_save,
std::unique_ptr<KeyVector> keys_to_remove,
typename ProtoLevelDBWrapper::UpdateCallback callback) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
bool* success = new bool(false);
task_runner_->PostTaskAndReply(
FROM_HERE,
......@@ -360,13 +361,13 @@ void ProtoLevelDBWrapper::UpdateEntriesWithRemoveFilter(
const LevelDB::KeyFilter& delete_key_filter,
const std::string& target_prefix,
typename ProtoLevelDBWrapper::UpdateCallback callback) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
bool* success = new bool(false);
task_runner_->PostTaskAndReply(
FROM_HERE,
base::BindOnce(UpdateEntriesWithRemoveFilterFromTaskRunner<T>,
base::Unretained(db_), std::move(entries_to_save),
delete_key_filter, success, metrics_id_),
delete_key_filter, target_prefix, success, metrics_id_),
base::BindOnce(RunUpdateCallback<T>, std::move(callback),
base::Owned(success)));
}
......@@ -391,7 +392,7 @@ void ProtoLevelDBWrapper::LoadEntriesWithFilter(
const leveldb::ReadOptions& options,
const std::string& target_prefix,
typename ProtoLevelDBWrapper::Internal<T>::LoadCallback callback) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
bool* success = new bool(false);
std::unique_ptr<std::vector<T>> entries(new std::vector<T>());
......@@ -430,7 +431,7 @@ void ProtoLevelDBWrapper::LoadKeysAndEntriesWithFilter(
const std::string& target_prefix,
typename ProtoLevelDBWrapper::Internal<T>::LoadKeysAndEntriesCallback
callback) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
bool* success = new bool(false);
auto keys_entries = std::make_unique<std::map<std::string, T>>();
......@@ -450,7 +451,7 @@ template <typename T>
void ProtoLevelDBWrapper::GetEntry(
const std::string& key,
typename ProtoLevelDBWrapper::Internal<T>::GetCallback callback) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
bool* success = new bool(false);
bool* found = new bool(false);
......
// 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 "components/leveldb_proto/shared_proto_database.h"
#include "base/sequence_checker.h"
#include "base/sequenced_task_runner.h"
#include "base/synchronization/lock.h"
#include "base/task/post_task.h"
#include "components/leveldb_proto/leveldb_database.h"
#include "components/leveldb_proto/proto_database.h"
#include "components/leveldb_proto/proto_leveldb_wrapper.h"
namespace leveldb_proto {
SharedProtoDatabase::SharedProtoDatabase(
const scoped_refptr<base::SequencedTaskRunner>& task_runner,
const std::string& client_name,
const base::FilePath& db_dir)
: task_runner_(task_runner),
db_dir_(db_dir),
db_wrapper_(std::make_unique<ProtoLevelDBWrapper>(task_runner_)),
db_(std::make_unique<LevelDB>(client_name.c_str())),
weak_factory_(this) {
DETACH_FROM_SEQUENCE(on_task_runner_);
}
// All init functionality runs on the same SequencedTaskRunner, so any caller of
// this after a database Init will receive the correct status of the database.
// PostTaskAndReply is used to ensure that we call the Init callback on its
// original calling thread.
void SharedProtoDatabase::GetDatabaseInitStateAsync(
ProtoLevelDBWrapper::InitCallback callback) {
task_runner_->PostTaskAndReply(
FROM_HERE, base::DoNothing(),
base::BindOnce(&SharedProtoDatabase::RunInitCallback,
weak_factory_.GetWeakPtr(), std::move(callback)));
}
void SharedProtoDatabase::RunInitCallback(
ProtoLevelDBWrapper::InitCallback callback) {
std::move(callback).Run(init_state_ == InitState::kSuccess);
}
// Setting |create_if_missing| to false allows us to test whether or not the
// shared database already exists, useful for migrating data from the shared
// database to a unique database if it exists.
// All clients planning to use the shared database should be setting
// |create_if_missing| to true. Setting this to false can result in unexpected
// behaviour since the ordering of Init calls may matter if some calls are made
// with this set to true, and others false.
void SharedProtoDatabase::Init(
bool create_if_missing,
ProtoLevelDBWrapper::InitCallback callback,
scoped_refptr<base::SequencedTaskRunner> callback_task_runner) {
DCHECK_CALLED_ON_VALID_SEQUENCE(on_task_runner_);
// If we succeeded previously, just let the callback know. Otherwise, we'll
// continue to try initialization for every new request.
if (init_state_ == InitState::kSuccess) {
callback_task_runner->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), true /* success */));
return;
}
init_state_ = InitState::kInProgress;
auto options = CreateSimpleOptions();
options.create_if_missing = create_if_missing;
// |db_wrapper_| uses the same SequencedTaskRunner that Init is called on,
// so OnDatabaseInit will be called on the same sequence after Init.
// This means any callers to Init using the same TaskRunner can guarantee that
// the InitState will be final after Init is called.
db_wrapper_->InitWithDatabase(
db_.get(), db_dir_, options, false /* destroy_on_corruption */,
base::BindOnce(&SharedProtoDatabase::OnDatabaseInit,
weak_factory_.GetWeakPtr(), std::move(callback),
callback_task_runner));
}
void SharedProtoDatabase::OnDatabaseInit(
ProtoLevelDBWrapper::InitCallback callback,
scoped_refptr<base::SequencedTaskRunner> callback_task_runner,
bool success) {
DCHECK_CALLED_ON_VALID_SEQUENCE(on_task_runner_);
init_state_ = success ? InitState::kSuccess : InitState::kFailure;
callback_task_runner->PostTask(FROM_HERE,
base::BindOnce(std::move(callback), success));
}
SharedProtoDatabase::~SharedProtoDatabase() {
DCHECK_CALLED_ON_VALID_SEQUENCE(on_creation_sequence_);
}
LevelDB* SharedProtoDatabase::GetLevelDBForTesting() const {
return db_.get();
}
} // namespace leveldb_proto
\ No newline at end of file
// 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.
#ifndef COMPONENTS_LEVELDB_PROTO_SHARED_PROTO_DATABASE_H_
#define COMPONENTS_LEVELDB_PROTO_SHARED_PROTO_DATABASE_H_
#include <queue>
#include "base/bind_helpers.h"
#include "base/memory/ref_counted.h"
#include "base/sequence_checker.h"
#include "base/sequenced_task_runner.h"
#include "base/task/post_task.h"
#include "base/task_runner_util.h"
#include "components/leveldb_proto/leveldb_database.h"
#include "components/leveldb_proto/proto_database.h"
#include "components/leveldb_proto/proto_leveldb_wrapper.h"
#include "components/leveldb_proto/shared_proto_database_client.h"
namespace leveldb_proto {
// Controls a single LevelDB database to be used by many clients, and provides
// a way to get SharedProtoDatabaseClients that allow shared access to the
// underlying single database.
class SharedProtoDatabase : public base::RefCounted<SharedProtoDatabase> {
public:
void GetDatabaseInitStateAsync(ProtoLevelDBWrapper::InitCallback callback);
// Always returns a SharedProtoDatabaseClient pointer, but that should ONLY
// be used if the callback returns success.
template <typename T>
std::unique_ptr<SharedProtoDatabaseClient<T>> GetClient(
const std::string& client_namespace,
const std::string& type_prefix,
bool create_if_missing,
ProtoLevelDBWrapper::InitCallback callback);
private:
friend class base::RefCounted<SharedProtoDatabase>;
friend class ProtoDatabaseProvider;
friend class SharedProtoDatabaseTest;
friend class SharedProtoDatabaseClientTest;
enum InitState {
kNone,
kInProgress,
kSuccess,
kFailure,
};
// Private since we only want to create a singleton of it.
SharedProtoDatabase(
const scoped_refptr<base::SequencedTaskRunner>& task_runner,
const std::string& client_name,
const base::FilePath& db_dir);
virtual ~SharedProtoDatabase();
// |callback_task_runner| should be the same sequence that Init was called
// from.
void Init(bool create_if_missing,
ProtoLevelDBWrapper::InitCallback callback,
scoped_refptr<base::SequencedTaskRunner> callback_task_runner);
void OnDatabaseInit(
ProtoLevelDBWrapper::InitCallback callback,
scoped_refptr<base::SequencedTaskRunner> callback_task_runner,
bool success);
void RunInitCallback(ProtoLevelDBWrapper::InitCallback callback);
LevelDB* GetLevelDBForTesting() const;
SEQUENCE_CHECKER(on_creation_sequence_);
SEQUENCE_CHECKER(on_task_runner_);
InitState init_state_ = InitState::kNone;
// This TaskRunner is used to properly sequence Init calls and checks for the
// current init state. When clients request the current InitState as part of
// their call to their Init function, the request is put into this TaskRunner.
scoped_refptr<base::SequencedTaskRunner> task_runner_;
base::FilePath db_dir_;
std::unique_ptr<ProtoLevelDBWrapper> db_wrapper_;
std::unique_ptr<LevelDB> db_;
base::WeakPtrFactory<SharedProtoDatabase> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(SharedProtoDatabase);
};
template <typename T>
std::unique_ptr<SharedProtoDatabaseClient<T>> SharedProtoDatabase::GetClient(
const std::string& client_namespace,
const std::string& type_prefix,
bool create_if_missing,
ProtoLevelDBWrapper::InitCallback callback) {
DCHECK(base::SequencedTaskRunnerHandle::IsSet());
auto current_task_runner = base::SequencedTaskRunnerHandle::Get();
task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&SharedProtoDatabase::Init, weak_factory_.GetWeakPtr(),
create_if_missing, std::move(callback),
std::move(current_task_runner)));
return base::WrapUnique(new SharedProtoDatabaseClient<T>(
std::make_unique<ProtoLevelDBWrapper>(task_runner_, db_.get()),
client_namespace, type_prefix, this));
}
} // namespace leveldb_proto
#endif // COMPONENTS_LEVELDB_PROTO_SHARED_PROTO_DATABASE_H_
// 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 "components/leveldb_proto/shared_proto_database_client.h"
#include "base/strings/strcat.h"
#include "components/leveldb_proto/proto_leveldb_wrapper.h"
#include "components/leveldb_proto/shared_proto_database.h"
namespace leveldb_proto {
std::string StripPrefix(const std::string& key, const std::string& prefix) {
return base::StartsWith(key, prefix, base::CompareCase::SENSITIVE)
? key.substr(prefix.length())
: key;
}
std::unique_ptr<std::vector<std::string>> PrefixStrings(
std::unique_ptr<std::vector<std::string>> strings,
const std::string& prefix) {
for (auto& str : *strings)
str.assign(base::StrCat({prefix, str}));
return strings;
}
bool KeyFilterStripPrefix(const LevelDB::KeyFilter& key_filter,
const std::string& prefix,
const std::string& key) {
if (key_filter.is_null())
return true;
return key_filter.Run(StripPrefix(key, prefix));
}
void GetSharedDatabaseInitStateAsync(
const scoped_refptr<SharedProtoDatabase>& shared_db,
ProtoLevelDBWrapper::InitCallback callback) {
shared_db->GetDatabaseInitStateAsync(std::move(callback));
}
} // namespace leveldb_proto
This diff is collapsed.
// 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 "components/leveldb_proto/shared_proto_database.h"
#include "base/bind.h"
#include "base/files/scoped_temp_dir.h"
#include "base/message_loop/message_loop.h"
#include "base/test/scoped_task_environment.h"
#include "base/threading/thread.h"
#include "components/leveldb_proto/proto_leveldb_wrapper.h"
#include "components/leveldb_proto/testing/proto/test_db.pb.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace leveldb_proto {
namespace {
const std::string kDefaultNamespace = "ns";
const std::string kDefaultNamespace2 = "ns2";
const std::string kDefaultTypePrefix = "tp";
} // namespace
class SharedProtoDatabaseTest : public testing::Test {
public:
void SetUp() override {
temp_dir_ = std::make_unique<base::ScopedTempDir>();
ASSERT_TRUE(temp_dir_->CreateUniqueTempDir());
db_thread_ = std::make_unique<base::Thread>("db_thread");
ASSERT_TRUE(db_thread_->Start());
db_ = base::WrapRefCounted(new SharedProtoDatabase(
db_thread_->task_runner(), "client", temp_dir_->GetPath()));
}
void TearDown() override {}
void InitDB(bool create_if_missing,
ProtoLevelDBWrapper::InitCallback callback) {
db_->Init(create_if_missing, std::move(callback),
scoped_task_environment_.GetMainThreadTaskRunner());
}
void KillDB() { db_.reset(); }
scoped_refptr<SharedProtoDatabase> CreateDatabase(
const scoped_refptr<base::SequencedTaskRunner>& task_runner,
const char* client_name,
const base::FilePath& db_dir) {
return base::WrapRefCounted(
new SharedProtoDatabase(task_runner, client_name, db_dir));
}
bool IsDatabaseInitialized(SharedProtoDatabase* db) {
return db->init_state_ == SharedProtoDatabase::InitState::kSuccess;
}
template <typename T>
std::unique_ptr<SharedProtoDatabaseClient<T>> GetClientAndWait(
SharedProtoDatabase* db,
const std::string& client_namespace,
const std::string& type_prefix,
bool create_if_missing,
bool* success) {
base::RunLoop loop;
auto client = db->GetClient<T>(
client_namespace, type_prefix, create_if_missing,
base::BindOnce(
[](bool* success_out, base::OnceClosure closure, bool success) {
*success_out = success;
std::move(closure).Run();
},
success, loop.QuitClosure()));
loop.Run();
return client;
}
scoped_refptr<base::SequencedTaskRunner> GetMainThreadTaskRunner() {
return scoped_task_environment_.GetMainThreadTaskRunner();
}
SharedProtoDatabase* db() { return db_.get(); }
ProtoLevelDBWrapper* wrapper() { return db_->db_wrapper_.get(); }
private:
base::test::ScopedTaskEnvironment scoped_task_environment_;
std::unique_ptr<base::ScopedTempDir> temp_dir_;
std::unique_ptr<base::Thread> db_thread_;
scoped_refptr<SharedProtoDatabase> db_;
};
inline void GetClientFromTaskRunner(SharedProtoDatabase* db,
const std::string& client_namespace,
const std::string& type_prefix,
base::OnceClosure closure) {
db->GetClient<TestProto>(
client_namespace, type_prefix, true /* create_if_missing */,
base::BindOnce([](base::OnceClosure closure,
bool success) { std::move(closure).Run(); },
std::move(closure)));
}
TEST_F(SharedProtoDatabaseTest, CreateClient_SucceedsWithCreate) {
bool success = false;
GetClientAndWait<TestProto>(db(), kDefaultNamespace, kDefaultTypePrefix,
true /* create_if_missing */, &success);
ASSERT_TRUE(success);
}
TEST_F(SharedProtoDatabaseTest, CreateClient_FailsWithoutCreate) {
bool success = false;
GetClientAndWait<TestProto>(db(), kDefaultNamespace, kDefaultTypePrefix,
false /* create_if_missing */, &success);
ASSERT_FALSE(success);
}
TEST_F(SharedProtoDatabaseTest,
CreateClient_SucceedsWithoutCreateIfAlreadyCreated) {
bool success = false;
GetClientAndWait<TestProto>(db(), kDefaultNamespace2, kDefaultTypePrefix,
true /* create_if_missing */, &success);
ASSERT_TRUE(success);
GetClientAndWait<TestProto>(db(), kDefaultNamespace, kDefaultTypePrefix,
false /* create_if_missing */, &success);
ASSERT_TRUE(success);
}
TEST_F(SharedProtoDatabaseTest, GetClient_DifferentThreads) {
bool success = false;
GetClientAndWait<TestProto>(db(), kDefaultNamespace, kDefaultTypePrefix,
true /* create_if_missing */, &success);
ASSERT_TRUE(success);
base::Thread t("test_thread");
ASSERT_TRUE(t.Start());
base::RunLoop run_loop;
t.task_runner()->PostTask(
FROM_HERE, base::BindOnce(&GetClientFromTaskRunner,
base::Unretained(db()), kDefaultNamespace2,
kDefaultTypePrefix, run_loop.QuitClosure()));
run_loop.Run();
base::RunLoop quit_cooldown;
GetMainThreadTaskRunner()->PostDelayedTask(
FROM_HERE, quit_cooldown.QuitClosure(), base::TimeDelta::FromSeconds(3));
}
// Tests that the shared DB's destructor behaves appropriately once the
// backing LevelDB has been initialized on another thread.
TEST_F(SharedProtoDatabaseTest, TestDBDestructionAfterInit) {
base::RunLoop run_init_loop;
InitDB(true /* create_if_missing */,
base::BindOnce(
[](base::OnceClosure signal, bool success) {
ASSERT_TRUE(success);
std::move(signal).Run();
},
run_init_loop.QuitClosure()));
run_init_loop.Run();
KillDB();
}
} // namespace leveldb_proto
\ No newline at end of file
......@@ -96,12 +96,15 @@ class UniqueProtoDatabase : public ProtoDatabase<T> {
bool GetApproximateMemoryUse(uint64_t* approx_mem_use);
protected:
std::unique_ptr<ProtoLevelDBWrapper> db_wrapper_;
scoped_refptr<base::SequencedTaskRunner> task_runner_;
private:
THREAD_CHECKER(thread_checker_);
base::FilePath database_dir_;
leveldb_env::Options options_;
std::unique_ptr<ProtoLevelDBWrapper> db_wrapper_;
std::unique_ptr<LevelDB> db_;
};
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment