lmdb: Generalize caching to be able to use it for other types than packages

This commit is contained in:
Martchus 2022-01-19 23:27:14 +01:00
parent a8afccf081
commit 231571f2d1
3 changed files with 198 additions and 162 deletions

View File

@ -365,8 +365,8 @@ StorageID Database::updatePackage(const std::shared_ptr<Package> &package)
if (!res.updated) {
return res.id;
}
if (res.oldPackage) {
removePackageDependencies(res.id, res.oldPackage);
if (res.oldEntry) {
removePackageDependencies(res.id, res.oldEntry);
}
addPackageDependencies(res.id, package);
return res.id;
@ -375,8 +375,8 @@ StorageID Database::updatePackage(const std::shared_ptr<Package> &package)
StorageID Database::forceUpdatePackage(const std::shared_ptr<Package> &package)
{
const auto res = m_storage->packageCache.store(*m_storage, package, true);
if (res.oldPackage) {
removePackageDependencies(res.id, res.oldPackage);
if (res.oldEntry) {
removePackageDependencies(res.id, res.oldEntry);
}
addPackageDependencies(res.id, package);
return res.id;
@ -628,8 +628,8 @@ PackageUpdaterPrivate::PackageUpdaterPrivate(DatabaseStorage &storage)
void PackageUpdaterPrivate::update(const PackageCache::StoreResult &res, const std::shared_ptr<Package> &package)
{
update(res.id, false, package);
if (res.oldPackage) {
update(res.id, true, res.oldPackage);
if (res.oldEntry) {
update(res.id, true, res.oldEntry);
}
}

View File

@ -6,102 +6,132 @@ using namespace CppUtilities;
namespace LibPkg {
StorageDistribution::StorageDistribution(const char *path, std::uint32_t maxDbs)
template <typename StorageEntryType> auto StorageCacheEntries<StorageEntryType>::findOrCreate(const Ref &ref) -> StorageEntry &
{
m_env = LMDBSafe::getMDBEnv(path, MDB_NOSUBDIR, 0600, maxDbs);
const auto &index = m_entries.template get<Ref>();
if (auto i = index.find(ref); i != index.end()) {
m_entries.relocate(m_entries.begin(), m_entries.template project<0>(i));
return i.get_node()->value();
}
const auto [i, newItem] = m_entries.emplace_front(ref);
if (!newItem) {
m_entries.relocate(m_entries.begin(), i);
} else if (m_entries.size() > m_limit) {
m_entries.pop_back();
}
return i.get_node()->value();
}
PackageSpec PackageCache::retrieve(DatabaseStorage &databaseStorage, const std::string &packageName)
template <typename StorageEntryType> std::size_t StorageCacheEntries<StorageEntryType>::clear(const Storage &storage)
{
auto count = std::size_t();
for (auto i = m_entries.begin(); i != m_entries.end();) {
if (i->ref.relatedStorage == &storage) {
i = m_entries.erase(i);
++count;
} else {
++i;
}
}
return count;
}
template <typename StorageEntriesType, typename TransactionType, typename SpecType>
auto StorageCache<StorageEntriesType, TransactionType, SpecType>::retrieve(Storage &storage, const std::string &entryName) -> SpecType
{
// check for package in cache
const auto ref = PackageCacheRef(databaseStorage, packageName);
const auto ref = typename Entries::Ref(storage, entryName);
const auto lock = std::unique_lock(m_mutex);
auto &cacheEntry = m_packages.findOrCreate(ref);
if (cacheEntry.package) {
return PackageSpec(cacheEntry.id, cacheEntry.package);
auto &cacheEntry = m_entries.findOrCreate(ref);
if (cacheEntry.entry) {
return PackageSpec(cacheEntry.id, cacheEntry.entry);
}
// check for package in storage, populate cache entry
cacheEntry.package = std::make_unique<Package>();
auto txn = databaseStorage.packages.getROTransaction();
if ((cacheEntry.id = txn.get<0>(packageName, *cacheEntry.package))) {
cacheEntry.ref.packageName = &cacheEntry.package->name;
return PackageSpec(cacheEntry.id, cacheEntry.package);
cacheEntry.entry = std::make_shared<Entry>();
auto txn = storage.packages.getROTransaction();
if ((cacheEntry.id = txn.template get<0>(entryName, *cacheEntry.entry))) {
cacheEntry.ref.entryName = &cacheEntry.entry->name;
return PackageSpec(cacheEntry.id, cacheEntry.entry);
}
m_packages.undo();
return PackageSpec(0, std::shared_ptr<Package>());
m_entries.undo();
return PackageSpec(0, std::shared_ptr<Entry>());
}
PackageCache::StoreResult PackageCache::store(DatabaseStorage &databaseStorage, const std::shared_ptr<Package> &package, bool force)
template <typename StorageEntriesType, typename TransactionType, typename SpecType>
auto StorageCache<StorageEntriesType, TransactionType, SpecType>::store(Storage &storage, const std::shared_ptr<Entry> &entry, bool force)
-> StoreResult
{
// check for package in cache
const auto ref = PackageCacheRef(databaseStorage, package->name);
auto res = PackageCache::StoreResult();
const auto ref = typename Entries::Ref(storage, entry->name);
auto res = StorageCache::StoreResult();
auto lock = std::unique_lock(m_mutex);
auto &cacheEntry = m_packages.findOrCreate(ref);
if (cacheEntry.package == package && !force) {
auto &cacheEntry = m_entries.findOrCreate(ref);
if (cacheEntry.entry == entry && !force) {
// do nothing if cached package is the same as specified one
res.id = cacheEntry.id;
return res;
} else if (cacheEntry.package) {
} else if (cacheEntry.entry) {
// retain certain information obtained from package contents if this is actually the same package as before
package->addDepsAndProvidesFromOtherPackage(*(res.oldPackage = cacheEntry.package));
entry->addDepsAndProvidesFromOtherPackage(*(res.oldEntry = cacheEntry.entry));
} else {
cacheEntry.package = std::make_shared<Package>();
cacheEntry.entry = std::make_shared<Entry>();
}
// check for package in storage
auto txn = databaseStorage.packages.getRWTransaction();
if (!res.oldPackage && (cacheEntry.id = txn.get<0>(package->name, *cacheEntry.package))) {
package->addDepsAndProvidesFromOtherPackage(*(res.oldPackage = cacheEntry.package));
auto txn = storage.packages.getRWTransaction();
if (!res.oldEntry && (cacheEntry.id = txn.template get<0>(entry->name, *cacheEntry.entry))) {
entry->addDepsAndProvidesFromOtherPackage(*(res.oldEntry = cacheEntry.entry));
}
// update cache entry
cacheEntry.ref.packageName = &package->name;
cacheEntry.package = package;
cacheEntry.ref.entryName = &entry->name;
cacheEntry.entry = entry;
// update package in storage
cacheEntry.id = txn.put(*package, cacheEntry.id);
cacheEntry.id = txn.put(*entry, cacheEntry.id);
txn.commit();
res.id = cacheEntry.id;
res.updated = true;
return res;
}
PackageCache::StoreResult PackageCache::store(
DatabaseStorage &databaseStorage, PackageStorage::RWTransaction &txn, const std::shared_ptr<Package> &package)
template <typename StorageEntriesType, typename TransactionType, typename SpecType>
auto StorageCache<StorageEntriesType, TransactionType, SpecType>::store(Storage &storage, Txn &txn, const std::shared_ptr<Entry> &entry)
-> StoreResult
{
// check for package in cache
const auto ref = PackageCacheRef(databaseStorage, package->name);
auto res = PackageCache::StoreResult();
const auto ref = typename Entries::Ref(storage, entry->name);
auto res = StorageCache::StoreResult();
auto lock = std::unique_lock(m_mutex);
auto &cacheEntry = m_packages.findOrCreate(ref);
if (cacheEntry.package) {
auto &cacheEntry = m_entries.findOrCreate(ref);
if (cacheEntry.entry) {
// retain certain information obtained from package contents if this is actually the same package as before
res.id = cacheEntry.id;
package->addDepsAndProvidesFromOtherPackage(*(res.oldPackage = cacheEntry.package));
entry->addDepsAndProvidesFromOtherPackage(*(res.oldEntry = cacheEntry.entry));
} else {
// check for package in storage
cacheEntry.package = std::make_shared<Package>();
if ((cacheEntry.id = txn.get<0>(package->name, *cacheEntry.package))) {
package->addDepsAndProvidesFromOtherPackage(*(res.oldPackage = cacheEntry.package));
cacheEntry.entry = std::make_shared<Entry>();
if ((cacheEntry.id = txn.template get<0>(entry->name, *cacheEntry.entry))) {
entry->addDepsAndProvidesFromOtherPackage(*(res.oldEntry = cacheEntry.entry));
}
}
// update cache entry
cacheEntry.ref.packageName = &package->name;
cacheEntry.package = package;
cacheEntry.ref.entryName = &entry->name;
cacheEntry.entry = entry;
// update package in storage
res.id = cacheEntry.id = txn.put(*package, cacheEntry.id);
res.id = cacheEntry.id = txn.put(*entry, cacheEntry.id);
res.updated = true;
return res;
}
bool PackageCache::invalidate(DatabaseStorage &databaseStorage, const std::string &packageName)
template <typename StorageEntriesType, typename TransactionType, typename SpecType>
bool StorageCache<StorageEntriesType, TransactionType, SpecType>::invalidate(Storage &storage, const std::string &entryName)
{
// remove package from cache
const auto ref = PackageCacheRef(databaseStorage, packageName);
const auto ref = typename Entries::Ref(storage, entryName);
auto lock = std::unique_lock(m_mutex);
m_packages.erase(ref);
m_entries.erase(ref);
lock.unlock();
// remove package from storage
auto txn = databaseStorage.packages.getRWTransaction();
if (auto i = txn.find<0>(packageName); i != txn.end()) {
auto txn = storage.packages.getRWTransaction();
if (auto i = txn.template find<0>(entryName); i != txn.end()) {
i.del();
txn.commit();
return true;
@ -109,30 +139,42 @@ bool PackageCache::invalidate(DatabaseStorage &databaseStorage, const std::strin
return false;
}
void PackageCache::clear(DatabaseStorage &databaseStorage)
template <typename StorageEntriesType, typename TransactionType, typename SpecType>
void StorageCache<StorageEntriesType, TransactionType, SpecType>::clear(Storage &storage)
{
clearCacheOnly(databaseStorage);
auto packagesTxn = databaseStorage.packages.getRWTransaction();
clearCacheOnly(storage);
auto packagesTxn = storage.packages.getRWTransaction();
packagesTxn.clear();
packagesTxn.commit();
auto providedDepsTxn = databaseStorage.providedDeps.getRWTransaction();
auto providedDepsTxn = storage.providedDeps.getRWTransaction();
providedDepsTxn.clear();
providedDepsTxn.commit();
auto requiredDepsTxn = databaseStorage.requiredDeps.getRWTransaction();
auto requiredDepsTxn = storage.requiredDeps.getRWTransaction();
requiredDepsTxn.clear();
requiredDepsTxn.commit();
auto providedLibsTxn = databaseStorage.providedLibs.getRWTransaction();
auto providedLibsTxn = storage.providedLibs.getRWTransaction();
providedLibsTxn.clear();
providedLibsTxn.commit();
auto requiredLibsTxn = databaseStorage.requiredLibs.getRWTransaction();
auto requiredLibsTxn = storage.requiredLibs.getRWTransaction();
requiredLibsTxn.clear();
requiredLibsTxn.commit();
}
void PackageCache::clearCacheOnly(DatabaseStorage &databaseStorage)
template <typename StorageEntriesType, typename TransactionType, typename SpecType>
void StorageCache<StorageEntriesType, TransactionType, SpecType>::clearCacheOnly(Storage &storage)
{
const auto lock = std::unique_lock(m_mutex);
m_packages.clear(databaseStorage);
m_entries.clear(storage);
}
template struct StorageCacheRef<DatabaseStorage, Package>;
template struct StorageCacheEntry<PackageCacheRef, Package>;
template class StorageCacheEntries<PackageCacheEntry>;
template struct StorageCache<PackageCacheEntries, PackageStorage::RWTransaction, PackageSpec>;
StorageDistribution::StorageDistribution(const char *path, std::uint32_t maxDbs)
{
m_env = LMDBSafe::getMDBEnv(path, MDB_NOSUBDIR, 0600, maxDbs);
}
DatabaseStorage::DatabaseStorage(const std::shared_ptr<LMDBSafe::MDBEnv> &env, PackageCache &packageCache, std::string_view uniqueDatabaseName)
@ -150,37 +192,7 @@ std::size_t hash_value(const PackageCacheRef &ref)
{
const auto hasher1 = boost::hash<const LibPkg::DatabaseStorage *>();
const auto hasher2 = boost::hash<std::string>();
return ((hasher1(ref.databaseStorage) ^ (hasher2(*ref.packageName) << 1)) >> 1);
}
PackageCacheEntry &RecentlyUsedPackages::findOrCreate(const PackageCacheRef &ref)
{
const auto &index = m_packages.get<PackageCacheRef>();
if (auto i = index.find(ref); i != index.end()) {
m_packages.relocate(m_packages.begin(), m_packages.project<0>(i));
return i.get_node()->value();
}
const auto [i, newItem] = m_packages.emplace_front(ref);
if (!newItem) {
m_packages.relocate(m_packages.begin(), i);
} else if (m_packages.size() > m_limit) {
m_packages.pop_back();
}
return i.get_node()->value();
}
std::size_t RecentlyUsedPackages::clear(const DatabaseStorage &databaseStorage)
{
auto count = std::size_t();
for (auto i = m_packages.begin(); i != m_packages.end();) {
if (i->ref.databaseStorage == &databaseStorage) {
i = m_packages.erase(i);
++count;
} else {
++i;
}
}
return count;
return ((hasher1(ref.relatedStorage) ^ (hasher2(*ref.entryName) << 1)) >> 1);
}
} // namespace LibPkg

View File

@ -18,132 +18,141 @@
namespace LibPkg {
using StorageID = std::uint32_t;
using PackageStorage = LMDBSafe::TypedDBI<Package, LMDBSafe::index_on<Package, std::string, &Package::name>>;
using DependencyStorage = LMDBSafe::TypedDBI<DatabaseDependency, LMDBSafe::index_on<Dependency, std::string, &DatabaseDependency::name>>;
using LibraryDependencyStorage
= LMDBSafe::TypedDBI<DatabaseLibraryDependency, LMDBSafe::index_on<DatabaseLibraryDependency, std::string, &DatabaseLibraryDependency::name>>;
struct PackageCache;
struct DatabaseStorage {
explicit DatabaseStorage(const std::shared_ptr<LMDBSafe::MDBEnv> &env, PackageCache &packageCache, std::string_view uniqueDatabaseName);
PackageCache &packageCache;
PackageStorage packages;
DependencyStorage providedDeps;
DependencyStorage requiredDeps;
LibraryDependencyStorage providedLibs;
LibraryDependencyStorage requiredLibs;
private:
std::shared_ptr<LMDBSafe::MDBEnv> m_env;
template <typename StorageType, typename EntryType> struct StorageCacheRef {
using Storage = StorageType;
explicit StorageCacheRef(const StorageType &relatedStorage, const std::shared_ptr<EntryType> &entry);
explicit StorageCacheRef(const StorageType &relatedStorage, const std::string &entryName);
bool operator==(const StorageCacheRef &other) const;
const StorageType *relatedStorage = nullptr;
const std::string *entryName;
};
struct PackageCacheRef {
explicit PackageCacheRef(const DatabaseStorage &databaseStorage, const std::shared_ptr<Package> &package);
explicit PackageCacheRef(const DatabaseStorage &databaseStorage, const std::string &packageName);
bool operator==(const PackageCacheRef &other) const;
const DatabaseStorage *databaseStorage = nullptr;
const std::string *packageName;
};
inline PackageCacheRef::PackageCacheRef(const DatabaseStorage &databaseStorage, const std::shared_ptr<Package> &package)
: databaseStorage(&databaseStorage)
, packageName(&package->name)
template <typename StorageType, typename EntryType>
inline StorageCacheRef<StorageType, EntryType>::StorageCacheRef(const StorageType &relatedStorage, const std::shared_ptr<EntryType> &entry)
: relatedStorage(&relatedStorage)
, entryName(&entry->name)
{
}
inline PackageCacheRef::PackageCacheRef(const DatabaseStorage &databaseStorage, const std::string &packageName)
: databaseStorage(&databaseStorage)
, packageName(&packageName)
template <typename StorageType, typename EntryType>
inline StorageCacheRef<StorageType, EntryType>::StorageCacheRef(const StorageType &relatedStorage, const std::string &entryName)
: relatedStorage(&relatedStorage)
, entryName(&entryName)
{
}
inline bool PackageCacheRef::operator==(const PackageCacheRef &other) const
template <typename StorageType, typename EntryType>
inline bool StorageCacheRef<StorageType, EntryType>::operator==(const StorageCacheRef<StorageType, EntryType> &other) const
{
return databaseStorage == other.databaseStorage && *packageName == *other.packageName;
return relatedStorage == other.relatedStorage && *entryName == *other.entryName;
}
std::size_t hash_value(const PackageCacheRef &ref);
struct PackageCacheEntry {
explicit PackageCacheEntry(const PackageCacheRef &ref);
PackageCacheRef ref;
template <typename StorageRefType, typename EntryType> struct StorageCacheEntry {
using Ref = StorageRefType;
using Entry = EntryType;
using Storage = typename Ref::Storage;
explicit StorageCacheEntry(const StorageRefType &ref);
StorageRefType ref;
StorageID id;
std::shared_ptr<Package> package;
std::shared_ptr<EntryType> entry;
};
inline PackageCacheEntry::PackageCacheEntry(const PackageCacheRef &ref)
template <typename StorageRefType, typename EntryType>
inline StorageCacheEntry<StorageRefType, EntryType>::StorageCacheEntry(const StorageRefType &ref)
: ref(ref)
, id(0)
{
}
class RecentlyUsedPackages {
using PackageList = boost::multi_index::multi_index_container<PackageCacheEntry,
boost::multi_index::indexed_by<boost::multi_index::sequenced<>,
boost::multi_index::hashed_unique<boost::multi_index::tag<PackageCacheRef>,
BOOST_MULTI_INDEX_MEMBER(PackageCacheEntry, PackageCacheRef, ref)>>>;
using iterator = PackageList::iterator;
template <typename StorageEntryType> class StorageCacheEntries {
public:
explicit RecentlyUsedPackages(std::size_t limit = 1000);
using Ref = typename StorageEntryType::Ref;
using Entry = typename StorageEntryType::Entry;
using Storage = typename StorageEntryType::Storage;
using StorageEntry = StorageEntryType;
using EntryList = boost::multi_index::multi_index_container<StorageEntry,
boost::multi_index::indexed_by<boost::multi_index::sequenced<>,
boost::multi_index::hashed_unique<boost::multi_index::tag<Ref>, BOOST_MULTI_INDEX_MEMBER(StorageEntryType, Ref, ref)>>>;
using iterator = typename EntryList::iterator;
PackageCacheEntry &findOrCreate(const PackageCacheRef &ref);
explicit StorageCacheEntries(std::size_t limit = 1000);
StorageEntry &findOrCreate(const Ref &ref);
void undo();
std::size_t erase(const PackageCacheRef &ref);
std::size_t clear(const DatabaseStorage &databaseStorage);
std::size_t erase(const Ref &ref);
std::size_t clear(const Storage &storage);
iterator begin();
iterator end();
private:
PackageList m_packages;
EntryList m_entries;
std::size_t m_limit;
};
inline RecentlyUsedPackages::RecentlyUsedPackages(std::size_t limit)
template <typename StorageEntryType>
inline StorageCacheEntries<StorageEntryType>::StorageCacheEntries(std::size_t limit)
: m_limit(limit)
{
}
inline void RecentlyUsedPackages::undo()
template <typename StorageEntryType> inline void StorageCacheEntries<StorageEntryType>::undo()
{
m_packages.pop_front();
m_entries.pop_front();
}
inline std::size_t RecentlyUsedPackages::erase(const PackageCacheRef &ref)
template <typename StorageEntryType> inline std::size_t StorageCacheEntries<StorageEntryType>::erase(const Ref &ref)
{
return m_packages.get<PackageCacheRef>().erase(ref);
return m_entries.template get<typename StorageEntryType::Ref>().erase(ref);
}
inline RecentlyUsedPackages::iterator RecentlyUsedPackages::begin()
template <typename StorageEntryType> inline auto StorageCacheEntries<StorageEntryType>::begin() -> iterator
{
return m_packages.begin();
return m_entries.begin();
}
inline RecentlyUsedPackages::iterator RecentlyUsedPackages::end()
template <typename StorageEntryType> inline auto StorageCacheEntries<StorageEntryType>::end() -> iterator
{
return m_packages.end();
return m_entries.end();
}
struct PackageCache {
template <typename StorageEntriesType, typename TransactionType, typename SpecType> struct StorageCache {
using Entries = StorageEntriesType;
using Entry = typename Entries::Entry;
using Txn = TransactionType;
using Storage = typename Entries::Storage;
struct StoreResult {
StorageID id = 0;
bool updated = false;
std::shared_ptr<Package> oldPackage;
std::shared_ptr<typename Entries::Entry> oldEntry;
};
PackageSpec retrieve(DatabaseStorage &databaseStorage, const std::string &packageName);
StoreResult store(DatabaseStorage &databaseStorage, const std::shared_ptr<Package> &package, bool force);
StoreResult store(DatabaseStorage &databaseStorage, PackageStorage::RWTransaction &txn, const std::shared_ptr<Package> &package);
bool invalidate(DatabaseStorage &databaseStorage, const std::string &packageName);
void clear(DatabaseStorage &databaseStorage);
void clearCacheOnly(DatabaseStorage &databaseStorage);
SpecType retrieve(Storage &storage, const std::string &entryName);
StoreResult store(Storage &storage, const std::shared_ptr<Entry> &entry, bool force);
StoreResult store(Storage &storage, Txn &txn, const std::shared_ptr<Entry> &entry);
bool invalidate(Storage &storage, const std::string &entryName);
void clear(Storage &storage);
void clearCacheOnly(Storage &storage);
private:
RecentlyUsedPackages m_packages;
Entries m_entries;
std::mutex m_mutex;
};
using PackageStorage = LMDBSafe::TypedDBI<Package, LMDBSafe::index_on<Package, std::string, &Package::name>>;
using DependencyStorage = LMDBSafe::TypedDBI<DatabaseDependency, LMDBSafe::index_on<Dependency, std::string, &DatabaseDependency::name>>;
using LibraryDependencyStorage
= LMDBSafe::TypedDBI<DatabaseLibraryDependency, LMDBSafe::index_on<DatabaseLibraryDependency, std::string, &DatabaseLibraryDependency::name>>;
using PackageCacheRef = StorageCacheRef<DatabaseStorage, Package>;
using PackageCacheEntry = StorageCacheEntry<PackageCacheRef, Package>;
using PackageCacheEntries = StorageCacheEntries<PackageCacheEntry>;
using PackageCache = StorageCache<PackageCacheEntries, PackageStorage::RWTransaction, PackageSpec>;
extern template struct StorageCacheRef<DatabaseStorage, Package>;
extern template struct StorageCacheEntry<PackageCacheRef, Package>;
extern template class StorageCacheEntries<PackageCacheEntry>;
extern template struct StorageCache<PackageCacheEntries, PackageStorage::RWTransaction, PackageSpec>;
struct StorageDistribution {
explicit StorageDistribution(const char *path, std::uint32_t maxDbs);
@ -159,6 +168,21 @@ inline std::unique_ptr<DatabaseStorage> StorageDistribution::forDatabase(std::st
return std::make_unique<DatabaseStorage>(m_env, m_packageCache, uniqueDatabaseName);
}
struct DatabaseStorage {
explicit DatabaseStorage(const std::shared_ptr<LMDBSafe::MDBEnv> &env, PackageCache &packageCache, std::string_view uniqueDatabaseName);
PackageCache &packageCache;
PackageStorage packages;
DependencyStorage providedDeps;
DependencyStorage requiredDeps;
LibraryDependencyStorage providedLibs;
LibraryDependencyStorage requiredLibs;
private:
std::shared_ptr<LMDBSafe::MDBEnv> m_env;
};
std::size_t hash_value(const PackageCacheRef &ref);
} // namespace LibPkg
#endif // LIBPKG_DATA_STORAGE_PRIVATE_H