#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: ParallelAccessStore(std::unique_ptr> baseStore); class ResourceRefBase { public: //TODO Better way to initialize ResourceRefBase(): _cachingStore(nullptr), _key(Key::CreateRandom()) {} 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; }; std::unique_ptr add(const Key &key, std::unique_ptr resource); std::unique_ptr load(const Key &key); void remove(const Key &key, std::unique_ptr block); private: class OpenResource { public: OpenResource(std::unique_ptr resource): _resource(std::move(resource)), _refCount(0) {} Resource *getReference() { ++_refCount; return _resource.get(); } void releaseReference() { --_refCount; } bool refCountIsZero() const { return 0 == _refCount; } std::unique_ptr moveResourceOut() { return std::move(_resource); } private: std::unique_ptr _resource; uint32_t _refCount; }; std::mutex _mutex; std::unique_ptr> _baseStore; std::unordered_map _openResources; std::map>> _resourcesToRemove; std::unique_ptr _add(const Key &key, std::unique_ptr resource); std::unique_ptr _createResourceRef(Resource *resource, const Key &key); void release(const Key &key); friend class CachedResource; DISALLOW_COPY_AND_ASSIGN(ParallelAccessStore); }; template ParallelAccessStore::ParallelAccessStore(std::unique_ptr> baseStore) : _mutex(), _baseStore(std::move(baseStore)), _openResources(), _resourcesToRemove() { static_assert(std::is_base_of::value, "ResourceRef must inherit from ResourceRefBase"); } template std::unique_ptr ParallelAccessStore::add(const Key &key, std::unique_ptr resource) { std::lock_guard lock(_mutex); return _add(key, std::move(resource)); } template std::unique_ptr ParallelAccessStore::_add(const Key &key, std::unique_ptr resource) { auto insertResult = _openResources.emplace(key, std::move(resource)); assert(true == insertResult.second); return _createResourceRef(insertResult.first->second.getReference(), key); } template std::unique_ptr ParallelAccessStore::_createResourceRef(Resource *resource, const Key &key) { auto resourceRef = std::make_unique(resource); resourceRef->init(this, key); return std::move(resourceRef); } template std::unique_ptr 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.get() == nullptr) { return nullptr; } return _add(key, std::move(resource)); } else { return _createResourceRef(found->second.getReference(), key); } } template void ParallelAccessStore::remove(const Key &key, std::unique_ptr resource) { auto insertResult = _resourcesToRemove.emplace(key, std::promise>()); assert(true == insertResult.second); resource.reset(); //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