/************************************************************************ * EOS - the CERN Disk Storage System * * Copyright (C) 2016 CERN/Switzerland * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 3 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see .* ************************************************************************/ //------------------------------------------------------------------------------ //! @author Elvin-Alin Sindrilaru //! @brief LRU cache for namespace objects making sure we never evict an entry //! which is still referenced in other parts of the program. //------------------------------------------------------------------------------ #ifndef __EOS_NS_LRU_HH__ #define __EOS_NS_LRU_HH__ #include "common/AssistedThread.hh" #include "common/ConcurrentQueue.hh" #include "common/Murmur3.hh" #include "namespace/Namespace.hh" #include #include #include #include #include #include EOSNSNAMESPACE_BEGIN //------------------------------------------------------------------------------ //! Helper struct to test if EntryT implements the getId method. If EntryT //! implements getId method then hasGetId::value will be true, otherwise false. //! This struct is to be used in other template definitions. //------------------------------------------------------------------------------ template struct hasGetId { template static constexpr decltype(std::declval().getId(), bool()) test(int) { return true; } template static constexpr bool test(...) { return false; } // int is used to give precedence! static constexpr bool value = test(int()); }; //------------------------------------------------------------------------------ //! LRU cache for namespace entries //------------------------------------------------------------------------------ template class LRU { public: //---------------------------------------------------------------------------- //! Constructor //! //! @param maxSize maximum number of entries in the cache //---------------------------------------------------------------------------- LRU(std::uint64_t maxSize); //---------------------------------------------------------------------------- //! Destructor //---------------------------------------------------------------------------- virtual ~LRU(); //---------------------------------------------------------------------------- //! Get entry //! //! @param id entry id //! //! @return shared ptr to requested object or nullptr if not found //---------------------------------------------------------------------------- std::shared_ptr get(IdT id); //---------------------------------------------------------------------------- //! Put entry //! //! @param id entry id //! @param entry entry object //! //! @return true if successfully added to the cache, false otherwise. If //! cache is full then the least recently used entry is evicted //! provided that it's not referenced anywhere else in the program. //---------------------------------------------------------------------------- typename std::enable_if::value, std::shared_ptr>::type put(IdT id, std::shared_ptr obj); //---------------------------------------------------------------------------- //! Remove entry from cache //! //! @param id entry id //! //! @return true if successfully removed from the cache, false otherwise //---------------------------------------------------------------------------- bool remove(IdT id); //---------------------------------------------------------------------------- //! Get cache size //! //! @return cache size //---------------------------------------------------------------------------- inline std::uint64_t size() const { std::unique_lock lock(mMutex); return mMap.size(); } //---------------------------------------------------------------------------- //! Get maximim number of entries in the cache //! //! @return maximum cache num entries //---------------------------------------------------------------------------- inline std::uint64_t get_max_num() const { std::unique_lock lock(mMutex); return mMaxNum; } //---------------------------------------------------------------------------- //! Set max num entries //! //! @param max_num new maximum number of entries, if 0 then just drop the //! the current cache //---------------------------------------------------------------------------- inline void set_max_num(const std::uint64_t max_num) { std::unique_lock lock(mMutex); if (max_num == 0ull) { // Flush and disable cache Purge(0.0); mMaxNum = 0ull; } else if (max_num == UINT64_MAX) { Purge(0.0); // Flush cache } else { mMaxNum = max_num; } } //---------------------------------------------------------------------------- //! Forbid copying or moving LRU objects //---------------------------------------------------------------------------- LRU(const LRU& other) = delete; LRU& operator=(const LRU& other) = delete; LRU(LRU&& other) = delete; LRU& operator=(LRU&& other) = delete; private: //---------------------------------------------------------------------------- //! Cleaner job taking care of deallocating entries that are passed through //! the queue to delete //---------------------------------------------------------------------------- void CleanerJob(ThreadAssistant& assistant); //---------------------------------------------------------------------------- //! Purge entries until stop ratio is achieved //! //! @param stop_ratio stop purge ratio //! @note This method must be called with the mutex protecting the map and //! the list locked. //---------------------------------------------------------------------------- void Purge(double stop_ratio); //! Percentage at which the cache purging stops static constexpr double sPurgeStopRatio = 0.9; using ListT = std::list>; typename std::list>::iterator ListIterT; //using MapT = std::map; using MapT = google::dense_hash_map>; MapT mMap; ///< Internal map pointing to obj in list ListT mList; ///< Internal list of objects where new/used objects are at the ///< end of the list //! Mutext to protect access to the map and list mutable std::mutex mMutex; std::uint64_t mMaxNum; ///< Maximum number of entries eos::common::ConcurrentQueue< std::shared_ptr > mToDelete; AssistedThread mCleanerThread; ///< Thread doing the deallocations }; // Definition of class static member template constexpr double LRU::sPurgeStopRatio; //------------------------------------------------------------------------------ // Constructor //------------------------------------------------------------------------------ template LRU::LRU(std::uint64_t max_num) : mMap(), mList(), mMutex(), mMaxNum(max_num), mToDelete() { mMap.set_empty_key(IdT(UINT64_MAX - 1)); mMap.set_deleted_key(IdT(UINT64_MAX)); mCleanerThread.reset(&LRU::CleanerJob, this); } //------------------------------------------------------------------------------ // Destructor //------------------------------------------------------------------------------ template LRU::~LRU() { std::shared_ptr sentinel(nullptr); mCleanerThread.stop(); mToDelete.push(sentinel); mCleanerThread.join(); std::unique_lock lock(mMutex); mMap.clear(); mList.clear(); } //------------------------------------------------------------------------------ // Get object //------------------------------------------------------------------------------ template std::shared_ptr LRU::get(IdT id) { std::unique_lock lock(mMutex); auto iter_map = mMap.find(id); if (iter_map == mMap.end()) { return nullptr; } // Move object to the end of the list i.e. recently accessed auto iter_new = mList.insert(mList.end(), *iter_map->second); mList.erase(iter_map->second); iter_map->second = iter_new; return *iter_new; } //------------------------------------------------------------------------------ // Put object //------------------------------------------------------------------------------ template typename std::enable_if::value, std::shared_ptr>::type LRU::put(IdT id, std::shared_ptr obj) { std::unique_lock lock(mMutex); if (mMaxNum == 0ull) { return obj; } auto iter_map = mMap.find(id); if (iter_map != mMap.end()) { return *(iter_map->second); } // Check if map full and purge some entries if necessary 10% of max size if (mMap.size() >= mMaxNum) { Purge(sPurgeStopRatio); } // @todo (esindril): add time based and for a fixed number of entries purging auto iter = mList.insert(mList.end(), obj); mMap[id] = iter; return *iter; } //------------------------------------------------------------------------------ // Remove object //------------------------------------------------------------------------------ template bool LRU::remove(IdT id) { std::unique_lock lock(mMutex); auto iter_map = mMap.find(id); if (iter_map == mMap.end()) { return false; } (void)mList.erase(iter_map->second); mMap.erase(iter_map); return true; } //---------------------------------------------------------------------------- // Cleaner job taking care of deallocating entries that are passed through // the queue to delete //---------------------------------------------------------------------------- template void LRU::CleanerJob(ThreadAssistant& assistant) { std::shared_ptr tmp; while (!assistant.terminationRequested()) { while (true) { mToDelete.wait_pop(tmp); if (tmp == nullptr) { break; } else { tmp.reset(); } } } } //------------------------------------------------------------------------------ // Purge entries until stop ratio is achieved //------------------------------------------------------------------------------ template void LRU::Purge(double stop_ratio) { auto iter = mList.begin(); while ((iter != mList.end()) && (mMap.size() > stop_ratio * mMaxNum)) { // If object is referenced also by someone else then skip it if (iter->use_count() > 1) { ++iter; continue; } mMap.erase(IdT((*iter)->getId())); mToDelete.push(*iter); iter = mList.erase(iter); } mMap.resize(0); // compact after deletion } EOSNSNAMESPACE_END #endif // __EOS_NS_LRU_HH__