# HG changeset patch # User Sebastien Jodogne # Date 1376655834 -7200 # Node ID e7841864c97c7e19d227f8d3a29b65df993d8e97 # Parent c4122c3a47c19ed16ddd0bb887cbb8897b876c31 StableResourcesMonitor diff -r c4122c3a47c1 -r e7841864c97c Core/Cache/LeastRecentlyUsedIndex.h --- a/Core/Cache/LeastRecentlyUsedIndex.h Fri Aug 16 10:24:49 2013 +0200 +++ b/Core/Cache/LeastRecentlyUsedIndex.h Fri Aug 16 14:23:54 2013 +0200 @@ -73,12 +73,16 @@ **/ void Add(T id, Payload payload = Payload()); + void AddOrMakeMostRecent(T id, Payload payload = Payload()); + /** * When accessing an element of the cache, this method tags the * element as the most recently used. * \param id The most recently accessed item. **/ - void TagAsMostRecent(T id); + void MakeMostRecent(T id); + + void MakeMostRecent(T id, Payload updatedPayload); /** * Remove an element from the cache index. @@ -90,11 +94,7 @@ * Get the oldest element in the cache and remove it. * \return The oldest item. **/ - T RemoveOldest() - { - Payload p; - return RemoveOldest(p); - } + T RemoveOldest(); /** * Get the oldest element in the cache, remove it and return the @@ -191,7 +191,7 @@ template - void LeastRecentlyUsedIndex::TagAsMostRecent(T id) + void LeastRecentlyUsedIndex::MakeMostRecent(T id) { if (!Contains(id)) { @@ -212,6 +212,53 @@ template + void LeastRecentlyUsedIndex::AddOrMakeMostRecent(T id, Payload payload) + { + typename Index::iterator it = index_.find(id); + + if (it != index_.end()) + { + // Already existing. Make it most recent. + std::pair item = *(it->second); + item.second = payload; + queue_.erase(it->second); + queue_.push_front(item); + } + else + { + // New item + queue_.push_front(std::make_pair(id, payload)); + } + + index_[id] = queue_.begin(); + + CheckInvariants(); + } + + + template + void LeastRecentlyUsedIndex::MakeMostRecent(T id, Payload updatedPayload) + { + if (!Contains(id)) + { + throw OrthancException(ErrorCode_InexistentItem); + } + + typename Index::iterator it = index_.find(id); + assert(it != index_.end()); + + std::pair item = *(it->second); + item.second = updatedPayload; + + queue_.erase(it->second); + queue_.push_front(item); + index_[id] = queue_.begin(); + + CheckInvariants(); + } + + + template Payload LeastRecentlyUsedIndex::Invalidate(T id) { if (!Contains(id)) @@ -254,6 +301,27 @@ template + T LeastRecentlyUsedIndex::RemoveOldest() + { + if (IsEmpty()) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + + std::pair item = queue_.back(); + T oldest = item.first; + + queue_.pop_back(); + assert(index_.find(oldest) != index_.end()); + index_.erase(oldest); + + CheckInvariants(); + + return oldest; + } + + + template const T& LeastRecentlyUsedIndex::GetOldest() const { if (IsEmpty()) diff -r c4122c3a47c1 -r e7841864c97c Core/Cache/MemoryCache.cpp --- a/Core/Cache/MemoryCache.cpp Fri Aug 16 10:24:49 2013 +0200 +++ b/Core/Cache/MemoryCache.cpp Fri Aug 16 14:23:54 2013 +0200 @@ -46,7 +46,7 @@ { VLOG(1) << "Reusing a cache page"; assert(p != NULL); - index_.TagAsMostRecent(id); + index_.MakeMostRecent(id); return *p; } diff -r c4122c3a47c1 -r e7841864c97c UnitTests/MemoryCache.cpp --- a/UnitTests/MemoryCache.cpp Fri Aug 16 10:24:49 2013 +0200 +++ b/UnitTests/MemoryCache.cpp Fri Aug 16 14:23:54 2013 +0200 @@ -17,12 +17,12 @@ r.Add("c"); r.Add("b"); - r.TagAsMostRecent("a"); - r.TagAsMostRecent("d"); - r.TagAsMostRecent("b"); - r.TagAsMostRecent("c"); - r.TagAsMostRecent("d"); - r.TagAsMostRecent("c"); + r.MakeMostRecent("a"); + r.MakeMostRecent("d"); + r.MakeMostRecent("b"); + r.MakeMostRecent("c"); + r.MakeMostRecent("d"); + r.MakeMostRecent("c"); ASSERT_EQ("a", r.GetOldest()); ASSERT_EQ("a", r.RemoveOldest()); @@ -49,12 +49,12 @@ r.Add("c", 422); r.Add("d", 423); - r.TagAsMostRecent("a"); - r.TagAsMostRecent("d"); - r.TagAsMostRecent("b"); - r.TagAsMostRecent("c"); - r.TagAsMostRecent("d"); - r.TagAsMostRecent("c"); + r.MakeMostRecent("a"); + r.MakeMostRecent("d"); + r.MakeMostRecent("b"); + r.MakeMostRecent("c"); + r.MakeMostRecent("d"); + r.MakeMostRecent("c"); ASSERT_TRUE(r.Contains("b")); ASSERT_EQ(421, r.Invalidate("b")); @@ -81,6 +81,60 @@ } +TEST(LRU, PayloadUpdate) +{ + Orthanc::LeastRecentlyUsedIndex r; + + r.Add("a", 420); + r.Add("b", 421); + r.Add("d", 423); + + r.MakeMostRecent("a", 424); + r.MakeMostRecent("d", 421); + + ASSERT_EQ("b", r.GetOldest()); + ASSERT_EQ(421, r.GetOldestPayload()); + r.RemoveOldest(); + + ASSERT_EQ("a", r.GetOldest()); + ASSERT_EQ(424, r.GetOldestPayload()); + r.RemoveOldest(); + + ASSERT_EQ("d", r.GetOldest()); + ASSERT_EQ(421, r.GetOldestPayload()); + r.RemoveOldest(); + + ASSERT_TRUE(r.IsEmpty()); +} + + + +TEST(LRU, PayloadUpdateBis) +{ + Orthanc::LeastRecentlyUsedIndex r; + + r.AddOrMakeMostRecent("a", 420); + r.AddOrMakeMostRecent("b", 421); + r.AddOrMakeMostRecent("d", 423); + r.AddOrMakeMostRecent("a", 424); + r.AddOrMakeMostRecent("d", 421); + + ASSERT_EQ("b", r.GetOldest()); + ASSERT_EQ(421, r.GetOldestPayload()); + r.RemoveOldest(); + + ASSERT_EQ("a", r.GetOldest()); + ASSERT_EQ(424, r.GetOldestPayload()); + r.RemoveOldest(); + + ASSERT_EQ("d", r.GetOldest()); + ASSERT_EQ(421, r.GetOldestPayload()); + r.RemoveOldest(); + + ASSERT_TRUE(r.IsEmpty()); +} + + namespace @@ -141,3 +195,105 @@ ASSERT_EQ("45 42 43 47 44 42 ", provider.log_); } + + +#include "../OrthancServer/ServerEnumerations.h" + +namespace +{ + struct Payload + { + Orthanc::ResourceType type_; + boost::posix_time::ptime time_; + + Payload() : type_(Orthanc::ResourceType_Instance) + { + } + + Payload(Orthanc::ResourceType type) : type_(type) + { + time_ = boost::posix_time::second_clock::local_time(); + } + + unsigned int GetAge() const + { + return (boost::posix_time::second_clock::local_time() - time_).total_seconds(); + } + }; + + + class StableResourcesMonitor + { + private: + bool done_; + boost::mutex mutex_; + unsigned int stableTimeout_; + Orthanc::LeastRecentlyUsedIndex unstableResources_; + boost::thread thread_; + + static void Thread(StableResourcesMonitor* that) + { + static const unsigned int SLEEP = 1; // Check for stable resources each second + + while (!that->done_) + { + boost::this_thread::sleep(boost::posix_time::seconds(SLEEP)); + + boost::mutex::scoped_lock lock(that->mutex_); + while (!that->unstableResources_.IsEmpty() && + that->unstableResources_.GetOldestPayload().GetAge() > that->stableTimeout_) + { + // This DICOM resource has not received any new instance for + // some time. It can be considered as stable. + + Payload payload; + std::string id = that->unstableResources_.RemoveOldest(payload); + + LOG(INFO) << "Stable resource: " << id << " (type " << payload.type_ << ")"; + } + } + + LOG(INFO) << "Closing the monitor for stable resources"; + } + + public: + StableResourcesMonitor(unsigned int stableTimeout) + { + done_ = false; + stableTimeout_ = stableTimeout; + thread_ = boost::thread(Thread, this); + } + + ~StableResourcesMonitor() + { + done_ = true; + + if (thread_.joinable()) + { + thread_.join(); + } + } + + void ResourceUpdated(const std::string& id, + Orthanc::ResourceType type) + { + assert(type == Orthanc::ResourceType_Patient || + type == Orthanc::ResourceType_Study || + type == Orthanc::ResourceType_Series); + + boost::mutex::scoped_lock lock(mutex_); + unstableResources_.AddOrMakeMostRecent(id, type); + } + }; +} + +TEST(LRU, Hello) +{ + StableResourcesMonitor m(5); + boost::this_thread::sleep(boost::posix_time::seconds(1)); + m.ResourceUpdated("Hello", Orthanc::ResourceType_Study); + m.ResourceUpdated("World", Orthanc::ResourceType_Series); + boost::this_thread::sleep(boost::posix_time::seconds(2)); + m.ResourceUpdated("Hello", Orthanc::ResourceType_Study); + boost::this_thread::sleep(boost::posix_time::seconds(10)); +}