#pragma once #ifndef MESSMER_PARALLELACCESSSTORE_PARALLELACCESSSTORE_H_ #define MESSMER_PARALLELACCESSSTORE_PARALLELACCESSSTORE_H_ #include #include #include #include #include #include #include #include #include "ParallelAccessBaseStore.h" #include //TODO Refactor //TODO Test cases namespace parallelaccessstore { template class ParallelAccessStore { public: explicit ParallelAccessStore(cpputils::unique_ref> baseStore); ~ParallelAccessStore() { ASSERT(_openResources.size() == 0, "Still resources open when trying to destruct"); ASSERT(_resourcesToRemove.size() == 0, "Still resources to remove when trying to destruct"); }; class ResourceRefBase { public: //TODO Better way to initialize ResourceRefBase(): _parallelAccessStore(nullptr), _key(Key::Null()) {} void init(ParallelAccessStore *parallelAccessStore, const Key &key) { _parallelAccessStore = parallelAccessStore; _key = key; } virtual ~ResourceRefBase() { _parallelAccessStore->release(_key); } private: ParallelAccessStore *_parallelAccessStore; //TODO We're storing Key twice (here and in the base resource). Rather use getKey() on the base resource if possible somehow. Key _key; DISALLOW_COPY_AND_ASSIGN(ResourceRefBase); }; bool isOpened(const Key &key) const; cpputils::unique_ref add(const Key &key, cpputils::unique_ref resource); template cpputils::unique_ref add(const Key &key, cpputils::unique_ref resource, std::function(Resource*)> createResourceRef); boost::optional> load(const Key &key); boost::optional> load(const Key &key, std::function(Resource*)> createResourceRef); void remove(const Key &key, cpputils::unique_ref block); private: class OpenResource { public: OpenResource(cpputils::unique_ref resource): _resource(std::move(resource)), _refCount(0) {} Resource *getReference() { ++_refCount; return _resource.get(); } void releaseReference() { --_refCount; } bool refCountIsZero() const { return 0 == _refCount; } cpputils::unique_ref moveResourceOut() { return std::move(_resource); } private: cpputils::unique_ref _resource; uint32_t _refCount; }; mutable std::mutex _mutex; cpputils::unique_ref> _baseStore; std::unordered_map _openResources; std::map>> _resourcesToRemove; template cpputils::unique_ref _add(const Key &key, cpputils::unique_ref resource, std::function(Resource*)> createResourceRef); void release(const Key &key); friend class CachedResource; DISALLOW_COPY_AND_ASSIGN(ParallelAccessStore); }; template ParallelAccessStore::ParallelAccessStore(cpputils::unique_ref> baseStore) : _mutex(), _baseStore(std::move(baseStore)), _openResources(), _resourcesToRemove() { static_assert(std::is_base_of::value, "ResourceRef must inherit from ResourceRefBase"); } template bool ParallelAccessStore::isOpened(const Key &key) const { std::lock_guard lock(_mutex); return _openResources.find(key) != _openResources.end(); }; template cpputils::unique_ref ParallelAccessStore::add(const Key &key, cpputils::unique_ref resource) { return add(key, std::move(resource), [] (Resource *resource) { return cpputils::make_unique_ref(resource); }); } template template cpputils::unique_ref ParallelAccessStore::add(const Key &key, cpputils::unique_ref resource, std::function(Resource*)> createResourceRef) { static_assert(std::is_base_of::value, "Wrong ResourceRef type"); std::lock_guard lock(_mutex); return _add(key, std::move(resource), createResourceRef); } template template cpputils::unique_ref ParallelAccessStore::_add(const Key &key, cpputils::unique_ref resource, std::function(Resource*)> createResourceRef) { static_assert(std::is_base_of::value, "Wrong ResourceRef type"); auto insertResult = _openResources.emplace(key, std::move(resource)); ASSERT(true == insertResult.second, "Inserting failed. Already exists."); auto resourceRef = createResourceRef(insertResult.first->second.getReference()); resourceRef->init(this, key); return resourceRef; } template boost::optional> ParallelAccessStore::load(const Key &key) { return load(key, [] (Resource *res) { return cpputils::make_unique_ref(res); }); }; template boost::optional> ParallelAccessStore::load(const Key &key, std::function(Resource*)> createResourceRef) { //TODO This lock doesn't allow loading different blocks in parallel. Can we only lock the requested key? std::lock_guard lock(_mutex); auto found = _openResources.find(key); if (found == _openResources.end()) { auto resource = _baseStore->loadFromBaseStore(key); if (resource == boost::none) { return boost::none; } return _add(key, std::move(*resource), createResourceRef); } else { auto resourceRef = createResourceRef(found->second.getReference()); resourceRef->init(this, key); return std::move(resourceRef); } } template void ParallelAccessStore::remove(const Key &key, cpputils::unique_ref resource) { std::future> resourceToRemoveFuture; { std::lock_guard lock(_mutex); // TODO Lock needed for _resourcesToRemove? auto insertResult = _resourcesToRemove.emplace(key, std::promise < cpputils::unique_ref < Resource >> ()); ASSERT(true == insertResult.second, "Inserting failed"); resourceToRemoveFuture = insertResult.first->second.get_future(); } cpputils::destruct(std::move(resource)); //Wait for last resource user to release it auto resourceToRemove = resourceToRemoveFuture.get(); std::lock_guard lock(_mutex); // TODO Just added this as a precaution on a whim, but I seriously need to rethink locking here. _resourcesToRemove.erase(key); //TODO Is this erase causing a race condition? _baseStore->removeFromBaseStore(std::move(resourceToRemove)); } template void ParallelAccessStore::release(const Key &key) { std::lock_guard lock(_mutex); auto found = _openResources.find(key); ASSERT(found != _openResources.end(), "Didn't find key"); found->second.releaseReference(); if (found->second.refCountIsZero()) { auto foundToRemove = _resourcesToRemove.find(key); if (foundToRemove != _resourcesToRemove.end()) { foundToRemove->second.set_value(found->second.moveResourceOut()); } _openResources.erase(found); } } } #endif