Mercurial > hg > orthanc
view OrthancFramework/UnitTestsSources/MemoryCacheTests.cpp @ 5541:5a34160b7627
todo
author | Alain Mazy <am@osimis.io> |
---|---|
date | Wed, 20 Mar 2024 10:08:42 +0100 |
parents | 48b8dae6dc77 |
children | f7adfb22e20e |
line wrap: on
line source
/** * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium * Copyright (C) 2017-2024 Osimis S.A., Belgium * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this program. If not, see * <http://www.gnu.org/licenses/>. **/ #if ORTHANC_UNIT_TESTS_LINK_FRAMEWORK == 1 // Must be the first to be sure to use the Orthanc framework shared library # include <OrthancFramework.h> #endif #include <gtest/gtest.h> #include "../Sources/Cache/MemoryCache.h" #include "../Sources/Cache/MemoryStringCache.h" #include "../Sources/Cache/SharedArchive.h" #include "../Sources/IDynamicObject.h" #include "../Sources/Logging.h" #include "../Sources/SystemToolbox.h" #include <memory> #include <algorithm> #include <boost/thread.hpp> #include <boost/lexical_cast.hpp> TEST(LRU, Basic) { Orthanc::LeastRecentlyUsedIndex<std::string> r; r.Add("d"); r.Add("a"); r.Add("c"); r.Add("b"); 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()); ASSERT_EQ("b", r.GetOldest()); ASSERT_EQ("b", r.RemoveOldest()); ASSERT_EQ("d", r.GetOldest()); ASSERT_EQ("d", r.RemoveOldest()); ASSERT_EQ("c", r.GetOldest()); ASSERT_EQ("c", r.RemoveOldest()); ASSERT_TRUE(r.IsEmpty()); ASSERT_THROW(r.GetOldest(), Orthanc::OrthancException); ASSERT_THROW(r.RemoveOldest(), Orthanc::OrthancException); } TEST(LRU, Payload) { Orthanc::LeastRecentlyUsedIndex<std::string, int> r; r.Add("a", 420); r.Add("b", 421); r.Add("c", 422); r.Add("d", 423); 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")); ASSERT_FALSE(r.Contains("b")); int p; ASSERT_TRUE(r.Contains("a", p)); ASSERT_EQ(420, p); ASSERT_TRUE(r.Contains("c", p)); ASSERT_EQ(422, p); ASSERT_TRUE(r.Contains("d", p)); ASSERT_EQ(423, p); ASSERT_EQ("a", r.GetOldest()); ASSERT_EQ(420, r.GetOldestPayload()); ASSERT_EQ("a", r.RemoveOldest(p)); ASSERT_EQ(420, p); ASSERT_EQ("d", r.GetOldest()); ASSERT_EQ(423, r.GetOldestPayload()); ASSERT_EQ("d", r.RemoveOldest(p)); ASSERT_EQ(423, p); ASSERT_EQ("c", r.GetOldest()); ASSERT_EQ(422, r.GetOldestPayload()); ASSERT_EQ("c", r.RemoveOldest(p)); ASSERT_EQ(422, p); ASSERT_TRUE(r.IsEmpty()); } TEST(LRU, PayloadUpdate) { Orthanc::LeastRecentlyUsedIndex<std::string, int> 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<std::string, int> 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()); } TEST(LRU, GetAllKeys) { Orthanc::LeastRecentlyUsedIndex<std::string, int> r; std::vector<std::string> keys; r.AddOrMakeMostRecent("a", 420); r.GetAllKeys(keys); ASSERT_EQ(1u, keys.size()); ASSERT_EQ("a", keys[0]); r.AddOrMakeMostRecent("b", 421); r.GetAllKeys(keys); ASSERT_EQ(2u, keys.size()); ASSERT_TRUE(std::find(keys.begin(), keys.end(),"a") != keys.end()); ASSERT_TRUE(std::find(keys.begin(), keys.end(),"b") != keys.end()); } namespace { class Integer : public Orthanc::IDynamicObject { private: std::string& log_; int value_; public: Integer(std::string& log, int v) : log_(log), value_(v) { } virtual ~Integer() ORTHANC_OVERRIDE { LOG(INFO) << "Removing cache entry for " << value_; log_ += boost::lexical_cast<std::string>(value_) + " "; } }; class IntegerProvider : public Orthanc::Deprecated::ICachePageProvider { public: std::string log_; virtual Orthanc::IDynamicObject* Provide(const std::string& s) ORTHANC_OVERRIDE { LOG(INFO) << "Providing " << s; return new Integer(log_, boost::lexical_cast<int>(s)); } }; } TEST(MemoryCache, Basic) { IntegerProvider provider; { Orthanc::Deprecated::MemoryCache cache(provider, 3); cache.Access("42"); // 42 -> exit cache.Access("43"); // 43, 42 -> exit cache.Access("45"); // 45, 43, 42 -> exit cache.Access("42"); // 42, 45, 43 -> exit cache.Access("43"); // 43, 42, 45 -> exit cache.Access("47"); // 45 is removed; 47, 43, 42 -> exit cache.Access("44"); // 42 is removed; 44, 47, 43 -> exit cache.Access("42"); // 43 is removed; 42, 44, 47 -> exit // Closing the cache: 47, 44, 42 are successively removed } ASSERT_EQ("45 42 43 47 44 42 ", provider.log_); } namespace { class S : public Orthanc::IDynamicObject { private: std::string value_; public: explicit S(const std::string& value) : value_(value) { } const std::string& GetValue() const { return value_; } }; } TEST(LRU, SharedArchive) { std::string first, second; Orthanc::SharedArchive a(3); first = a.Add(new S("First item")); second = a.Add(new S("Second item")); for (int i = 1; i < 100; i++) { a.Add(new S("Item " + boost::lexical_cast<std::string>(i))); // Continuously protect the two first items { Orthanc::SharedArchive::Accessor accessor(a, first); ASSERT_TRUE(accessor.IsValid()); ASSERT_EQ("First item", dynamic_cast<S&>(accessor.GetItem()).GetValue()); } { Orthanc::SharedArchive::Accessor accessor(a, second); ASSERT_TRUE(accessor.IsValid()); ASSERT_EQ("Second item", dynamic_cast<S&>(accessor.GetItem()).GetValue()); } { Orthanc::SharedArchive::Accessor accessor(a, "nope"); ASSERT_FALSE(accessor.IsValid()); ASSERT_THROW(accessor.GetItem(), Orthanc::OrthancException); } } std::list<std::string> i; a.List(i); size_t count = 0; for (std::list<std::string>::const_iterator it = i.begin(); it != i.end(); ++it) { if (*it == first || *it == second) { count++; } } ASSERT_EQ(2u, count); } TEST(MemoryStringCache, Basic) { Orthanc::MemoryStringCache c; ASSERT_THROW(c.SetMaximumSize(0), Orthanc::OrthancException); c.SetMaximumSize(3); std::string v; { Orthanc::MemoryStringCache::Accessor a(c); ASSERT_FALSE(a.Fetch(v, "key1")); } { Orthanc::MemoryStringCache::Accessor a(c); ASSERT_FALSE(a.Fetch(v, "key1")); a.Add("key1", "a"); ASSERT_TRUE(a.Fetch(v, "key1")); ASSERT_EQ("a", v); ASSERT_FALSE(a.Fetch(v, "key2")); ASSERT_FALSE(a.Fetch(v, "key3")); a.Add("key2", "b"); ASSERT_TRUE(a.Fetch(v, "key1")); ASSERT_EQ("a", v); ASSERT_TRUE(a.Fetch(v, "key2")); ASSERT_EQ("b", v); a.Add("key3", "too-large-value"); ASSERT_TRUE(a.Fetch(v, "key1")); ASSERT_EQ("a", v); ASSERT_TRUE(a.Fetch(v, "key2")); ASSERT_EQ("b", v); ASSERT_FALSE(a.Fetch(v, "key3")); a.Add("key3", "c"); ASSERT_TRUE(a.Fetch(v, "key2")); ASSERT_EQ("b", v); ASSERT_TRUE(a.Fetch(v, "key1")); ASSERT_EQ("a", v); ASSERT_TRUE(a.Fetch(v, "key3")); ASSERT_EQ("c", v); // adding a fourth value should remove the oldest accessed value (key2) a.Add("key4", "d"); ASSERT_FALSE(a.Fetch(v, "key2")); ASSERT_TRUE(a.Fetch(v, "key1")); ASSERT_EQ("a", v); ASSERT_TRUE(a.Fetch(v, "key3")); ASSERT_EQ("c", v); ASSERT_TRUE(a.Fetch(v, "key4")); ASSERT_EQ("d", v); } } TEST(MemoryStringCache, Invalidate) { Orthanc::MemoryStringCache c; Orthanc::MemoryStringCache::Accessor a(c); a.Add("hello", "a"); a.Add("hello2", "b"); std::string v; ASSERT_TRUE(a.Fetch(v, "hello")); ASSERT_EQ("a", v); ASSERT_TRUE(a.Fetch(v, "hello2")); ASSERT_EQ("b", v); c.Invalidate("hello"); ASSERT_FALSE(a.Fetch(v, "hello")); ASSERT_TRUE(a.Fetch(v, "hello2")); ASSERT_EQ("b", v); } static int ThreadingScenarioHappyStep = 0; static Orthanc::MemoryStringCache ThreadingScenarioHappyCache; void ThreadingScenarioHappyThread1() { // the first thread to call Fetch (will be in charge of adding) Orthanc::MemoryStringCache::Accessor a(ThreadingScenarioHappyCache); std::string v; LOG(INFO) << "Thread1 will fetch"; if (!a.Fetch(v, "key1")) { LOG(INFO) << "Thread1 has fetch"; ThreadingScenarioHappyStep = 1; // wait for the other thread to fetch too while (ThreadingScenarioHappyStep < 2) { Orthanc::SystemToolbox::USleep(10000); } LOG(INFO) << "Thread1 will add after a short sleep"; Orthanc::SystemToolbox::USleep(100000); LOG(INFO) << "Thread1 will add"; a.Add("key1", "value1"); LOG(INFO) << "Thread1 has added"; } } void ThreadingScenarioHappyThread2() { Orthanc::MemoryStringCache::Accessor a(ThreadingScenarioHappyCache); std::string v; // nobody has added key2 -> go if (!a.Fetch(v, "key2")) { a.Add("key2", "value2"); } // wait until thread 1 has completed its "Fetch" but not added yet while (ThreadingScenarioHappyStep < 1) { Orthanc::SystemToolbox::USleep(10000); } ThreadingScenarioHappyStep = 2; LOG(INFO) << "Thread2 will fetch"; // this should wait until thread 1 has added if (!a.Fetch(v, "key1")) { ASSERT_FALSE(true); // this thread should not add since thread1 should have done it } LOG(INFO) << "Thread2 has fetched the value"; ASSERT_EQ("value1", v); } TEST(MemoryStringCache, ThreadingScenarioHappy) { boost::thread thread1 = boost::thread(ThreadingScenarioHappyThread1); boost::thread thread2 = boost::thread(ThreadingScenarioHappyThread2); thread1.join(); thread2.join(); } static int ThreadingScenarioFailureStep = 0; static Orthanc::MemoryStringCache ThreadingScenarioFailureCache; void ThreadingScenarioFailureThread1() { // the first thread to call Fetch (will be in charge of adding) Orthanc::MemoryStringCache::Accessor a(ThreadingScenarioFailureCache); std::string v; LOG(INFO) << "Thread1 will fetch"; if (!a.Fetch(v, "key1")) { LOG(INFO) << "Thread1 has fetch"; ThreadingScenarioFailureStep = 1; // wait for the other thread to fetch too while (ThreadingScenarioFailureStep < 2) { Orthanc::SystemToolbox::USleep(10000); } LOG(INFO) << "Thread1 will add after a short sleep"; Orthanc::SystemToolbox::USleep(100000); LOG(INFO) << "Thread1 fails to add because of an error"; } } void ThreadingScenarioFailureThread2() { Orthanc::MemoryStringCache::Accessor a(ThreadingScenarioFailureCache); std::string v; // wait until thread 1 has completed its "Fetch" but not added yet while (ThreadingScenarioFailureStep < 1) { Orthanc::SystemToolbox::USleep(10000); } ThreadingScenarioFailureStep = 2; LOG(INFO) << "Thread2 will fetch and wait for thread1 to add"; // this should wait until thread 1 has added if (!a.Fetch(v, "key1")) { LOG(INFO) << "Thread2 has been awaken and will add since Thread1 has failed to add"; a.Add("key1", "value1"); } LOG(INFO) << "Thread2 has added the value"; } TEST(MemoryStringCache, ThreadingScenarioFailure) { boost::thread thread1 = boost::thread(ThreadingScenarioFailureThread1); boost::thread thread2 = boost::thread(ThreadingScenarioFailureThread2); thread1.join(); thread2.join(); } static int ThreadingScenarioInvalidateStep = 0; static Orthanc::MemoryStringCache ThreadingScenarioInvalidateCache; void ThreadingScenarioInvalidateThread1() { // the first thread to call Fetch (will be in charge of adding) Orthanc::MemoryStringCache::Accessor a(ThreadingScenarioInvalidateCache); std::string v; LOG(INFO) << "Thread1 will fetch"; if (!a.Fetch(v, "key1")) { LOG(INFO) << "Thread1 has fetch"; ThreadingScenarioInvalidateStep = 1; // wait for the other thread to fetch too while (ThreadingScenarioInvalidateStep < 2) { Orthanc::SystemToolbox::USleep(10000); } LOG(INFO) << "Thread1 will invalidate after a short sleep"; Orthanc::SystemToolbox::USleep(100000); LOG(INFO) << "Thread1 is invalidating"; ThreadingScenarioInvalidateCache.Invalidate("key1"); } } void ThreadingScenarioInvalidateThread2() { Orthanc::MemoryStringCache::Accessor a(ThreadingScenarioInvalidateCache); std::string v; // wait until thread 1 has completed its "Fetch" but not added yet while (ThreadingScenarioInvalidateStep < 1) { Orthanc::SystemToolbox::USleep(10000); } ThreadingScenarioInvalidateStep = 2; LOG(INFO) << "Thread2 will fetch and wait for thread1 to add"; // this should wait until thread 1 has added if (!a.Fetch(v, "key1")) { LOG(INFO) << "Thread2 has been awaken because thread1 has invalidated the key"; } } TEST(MemoryStringCache, ThreadingScenarioInvalidate) { boost::thread thread1 = boost::thread(ThreadingScenarioInvalidateThread1); boost::thread thread2 = boost::thread(ThreadingScenarioInvalidateThread2); thread1.join(); thread2.join(); }