// Copyright (c) 2010 Martin Knafve / hMailServer.com. // http://www.hmailserver.com #include #include #include #include using boost::multi_index_container; using namespace boost::multi_index; #include "../Util/VariantDateTime.h" #include "CachedObject.h" #pragma once namespace HM { struct id {}; struct name {}; struct timestamp {}; template class Cache : public Singleton> { public: Cache(); std::shared_ptr GetObject(const String &sName); // Retrieves an object using the object name. std::shared_ptr GetObject(__int64 iID); // Retrieves an object using the ID void RemoveObject(std::shared_ptr pObject); void RemoveObject(const String &sName); void RemoveObject(__int64 iID); void SetTTL(int iNewVal); int GetHitRate(); void SetEnabled(bool bEnabled); void Clear(); void AdjustEstimatedSize(bool increase, size_t size_change); void SetMaxSize(size_t max_size); size_t GetMaxSize(); size_t GetSize(); void Add(std::shared_ptr pObject); private: void ResetEstimatedSizeIfEmpty_(); template std::shared_ptr GetItemBy_(const MultiIndexContainer& s, TagValue value) { typedef index::type items_by_tag; items_by_tag& items = get(objects_); auto item = items.find(value); if (item != items.end()) { CachedObject cached_object = (*item); if (GetObjectIsWithinTTL_(cached_object)) { return cached_object.object_; } items.erase(item); } std::shared_ptr empty; return empty; } template void RemoveBy_(const MultiIndexContainer& s, TagValue value) { typedef index::type items_by_tag; items_by_tag& items = get(objects_); auto item_iter = items.find(value); if (item_iter != items.end()) { auto item = (*item_iter); if (current_estimated_size_ >= item.GetEstimatedSize()) current_estimated_size_ -= item.GetEstimatedSize(); items.erase(item_iter); } ResetEstimatedSizeIfEmpty_(); } bool GetObjectIsWithinTTL_(CachedObject pObject); boost::recursive_mutex _mutex; typedef multi_index_container< CachedObject, indexed_by< hashed_unique< tag, BOOST_MULTI_INDEX_MEMBER(CachedObject, __int64, id_)>, hashed_non_unique< tag, BOOST_MULTI_INDEX_MEMBER(CachedObject, std::wstring, name_)>, ordered_non_unique< tag, BOOST_MULTI_INDEX_MEMBER(CachedObject, int, creation_time_)> > > container_type; container_type objects_; int no_of_misses_; int no_of_hits_; int ttl_; bool enabled_; size_t max_size_; size_t current_estimated_size_; }; template Cache::Cache() : max_size_(0), current_estimated_size_(0), no_of_misses_(0), no_of_hits_(0), ttl_(0), enabled_(false) { } template void Cache::Clear() { boost::lock_guard guard(_mutex); objects_.clear(); no_of_misses_ = 0; no_of_hits_ = 0; } template void Cache::SetTTL(int iNewVal) { ttl_ = iNewVal; no_of_misses_ = 0; no_of_hits_ = 0; } template void Cache::SetEnabled(bool bEnabled) { enabled_ = bEnabled; if (!enabled_) Clear(); } template void Cache::SetMaxSize(size_t max_size) { max_size_ = max_size; } template size_t Cache::GetMaxSize() { return max_size_; } template size_t Cache::GetSize() { return current_estimated_size_; } template int Cache::GetHitRate() { boost::lock_guard guard(_mutex); if (no_of_hits_ == 0) return 0; int iHitRate = (int) (((float) no_of_hits_ / (float) (no_of_hits_ + no_of_misses_)) * 100); return iHitRate; } template void Cache::RemoveObject(std::shared_ptr pObject) { boost::lock_guard guard(_mutex); RemoveBy_(objects_, pObject->GetName()); } template void Cache::RemoveObject(const String &sName) { boost::lock_guard guard(_mutex); RemoveBy_(objects_, sName); } template void Cache::RemoveObject(__int64 iID) { boost::lock_guard guard(_mutex); RemoveBy_(objects_, iID); } template std::shared_ptr Cache::GetObject(const String &sName) { boost::lock_guard guard(_mutex); if (enabled_) { return GetItemBy_(objects_, sName); } return nullptr; } template std::shared_ptr Cache::GetObject(__int64 iID) { boost::lock_guard guard(_mutex); if (enabled_) { return GetItemBy_(objects_, iID); } return nullptr; } template void Cache::Add(std::shared_ptr pObject) { boost::lock_guard guard(_mutex); if (!enabled_) return; // Object must be saved before it can be cached. #ifdef DEBUG if (pObject->GetID() == 0) { assert(0); } #endif typedef index::type items_by_timestamp; items_by_timestamp& items = get(objects_); CachedObject object(pObject); if (max_size_ > 0 && current_estimated_size_ + object.GetEstimatedSize() > max_size_) { // We've reached the cache max size. Remove items until we're 10% free. size_t target_size = (size_t) (max_size_ * 0.9); while (current_estimated_size_ > target_size) { auto item_iter = items.begin(); if (item_iter == items.end()) break; auto item = (*item_iter); if (current_estimated_size_ >= item.GetEstimatedSize()) current_estimated_size_ -= item.GetEstimatedSize(); items.erase(item_iter); } ResetEstimatedSizeIfEmpty_(); } no_of_misses_++; current_estimated_size_ += object.GetEstimatedSize(); items.insert(object); } template bool Cache::GetObjectIsWithinTTL_(CachedObject pObject) { if (pObject.SecondsOld() < ttl_) { // A fresh object was found in the cache. no_of_hits_++; return true; } return false; } template void Cache::ResetEstimatedSizeIfEmpty_() { if (objects_.size() == 0) { current_estimated_size_ = 0; } } template void Cache::AdjustEstimatedSize(bool increase, size_t size_change) { if (increase) { current_estimated_size_ += size_change; } else { if (size_change > current_estimated_size_) current_estimated_size_ -= size_change; else current_estimated_size_ = 0; } } }