#ifndef MESSMER_PARALLELACCESSSTORE_IMPLEMENTATIONS_PARALLELACCESS_PARALLELACCESSSTORE_H_ #define MESSMER_PARALLELACCESSSTORE_IMPLEMENTATIONS_PARALLELACCESS_PARALLELACCESSSTORE_H_ #include #include #include #include #include #include #include #include #include "ParallelAccessBaseStore.h" //TODO Refactor //TODO Test cases namespace parallelaccessstore { template class ParallelAccessStore { public: explicit ParallelAccessStore(cpputils::unique_ref> baseStore); class ResourceRefBase { public: //TODO Better way to initialize ResourceRefBase(): _cachingStore(nullptr), _key(Key::CreatePseudoRandom()) {} void init(ParallelAccessStore *cachingStore, const Key &key) { _cachingStore = cachingStore; _key = key; } virtual ~ResourceRefBase() { _cachingStore->release(_key); } private: ParallelAccessStore *_cachingStore; //TODO We're storing Key twice (here and in the base resource). Rather use getKey() on the base resource if possible somehow. Key _key; }; cpputils::unique_ref add(const Key &key, cpputils::unique_ref resource); boost::optional> load(const Key &key); 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; }; std::mutex _mutex; cpputils::unique_ref> _baseStore; std::unordered_map _openResources; std::map>> _resourcesToRemove; cpputils::unique_ref _add(const Key &key, cpputils::unique_ref resource); cpputils::unique_ref _createResourceRef(Resource *resource, const Key &key); 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 cpputils::unique_ref ParallelAccessStore::add(const Key &key, cpputils::unique_ref resource) { std::lock_guard lock(_mutex); return _add(key, std::move(resource)); } template cpputils::unique_ref ParallelAccessStore::_add(const Key &key, cpputils::unique_ref resource) { auto insertResult = _openResources.emplace(key, std::move(resource)); assert(true == insertResult.second); return _createResourceRef(insertResult.first->second.getReference(), key); } template cpputils::unique_ref ParallelAccessStore::_createResourceRef(Resource *resource, const Key &key) { auto resourceRef = cpputils::make_unique_ref(resource); resourceRef->init(this, key); return std::move(resourceRef); } template boost::optional> ParallelAccessStore::load(const Key &key) { //TODO This lock doesn't allow loading different blocks in parallel. Can we do something with futures maybe? 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)); } else { return _createResourceRef(found->second.getReference(), key); } } template void ParallelAccessStore::remove(const Key &key, cpputils::unique_ref resource) { auto insertResult = _resourcesToRemove.emplace(key, std::promise>()); assert(true == insertResult.second); cpputils::to_unique_ptr(std::move(resource)).reset(); // Call destructor //Wait for last resource user to release it auto resourceToRemove = insertResult.first->second.get_future().get(); _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()); 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