Mercurial > hg > orthanc
diff OrthancFramework/UnitTestsSources/MemoryCacheTests.cpp @ 5420:d37dff2c0028 am-new-cache
Optimized the MemoryStringCache to prevent loading the same file multiple times if multiple users request the same file at the same time
author | Alain Mazy <am@osimis.io> |
---|---|
date | Mon, 13 Nov 2023 17:01:59 +0100 |
parents | 0ea402b4d901 |
children | 48b8dae6dc77 |
line wrap: on
line diff
--- a/OrthancFramework/UnitTestsSources/MemoryCacheTests.cpp Thu Nov 09 08:51:01 2023 +0100 +++ b/OrthancFramework/UnitTestsSources/MemoryCacheTests.cpp Mon Nov 13 17:01:59 2023 +0100 @@ -33,6 +33,7 @@ #include "../Sources/Cache/SharedArchive.h" #include "../Sources/IDynamicObject.h" #include "../Sources/Logging.h" +#include "../Sources/SystemToolbox.h" #include <memory> #include <algorithm> @@ -319,44 +320,260 @@ Orthanc::MemoryStringCache c; ASSERT_THROW(c.SetMaximumSize(0), Orthanc::OrthancException); - c.SetMaximumSize(2); + c.SetMaximumSize(3); std::string v; - ASSERT_FALSE(c.Fetch(v, "hello")); + { + Orthanc::MemoryStringCache::Accessor a(c); + ASSERT_FALSE(a.Fetch(v, "key1")); + } - c.Add("hello", "a"); - ASSERT_TRUE(c.Fetch(v, "hello")); ASSERT_EQ("a", v); - ASSERT_FALSE(c.Fetch(v, "hello2")); - ASSERT_FALSE(c.Fetch(v, "hello3")); + { + 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); - c.Add("hello2", "b"); - ASSERT_TRUE(c.Fetch(v, "hello")); ASSERT_EQ("a", v); - ASSERT_TRUE(c.Fetch(v, "hello2")); ASSERT_EQ("b", v); - ASSERT_FALSE(c.Fetch(v, "hello3")); + 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); - c.Add("hello3", "too large value"); - ASSERT_TRUE(c.Fetch(v, "hello")); ASSERT_EQ("a", v); - ASSERT_TRUE(c.Fetch(v, "hello2")); ASSERT_EQ("b", v); - ASSERT_FALSE(c.Fetch(v, "hello3")); - - c.Add("hello3", "c"); - ASSERT_FALSE(c.Fetch(v, "hello")); // Recycled - ASSERT_TRUE(c.Fetch(v, "hello2")); ASSERT_EQ("b", v); - ASSERT_TRUE(c.Fetch(v, "hello3")); ASSERT_EQ("c", 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; - c.Add("hello", "a"); - c.Add("hello2", "b"); + Orthanc::MemoryStringCache::Accessor a(c); + + a.Add("hello", "a"); + a.Add("hello2", "b"); std::string v; - ASSERT_TRUE(c.Fetch(v, "hello")); ASSERT_EQ("a", v); - ASSERT_TRUE(c.Fetch(v, "hello2")); ASSERT_EQ("b", 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(c.Fetch(v, "hello")); - ASSERT_TRUE(c.Fetch(v, "hello2")); ASSERT_EQ("b", v); + 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(); +}