From 334f8ad77f3dce407b28cfeb75183288c5631a00 Mon Sep 17 00:00:00 2001 From: Md Junaed Hossain <169046794+junaed-optimizely@users.noreply.github.com> Date: Mon, 22 Sep 2025 23:46:34 +0600 Subject: [PATCH] [FSSDK-11159] lru cache impl update --- OptimizelySDK.Tests/OdpTests/LruCacheTest.cs | 108 +++++++++++++++++++ OptimizelySDK/Odp/LruCache.cs | 12 +++ 2 files changed, 120 insertions(+) diff --git a/OptimizelySDK.Tests/OdpTests/LruCacheTest.cs b/OptimizelySDK.Tests/OdpTests/LruCacheTest.cs index 426240c5..2ff8071d 100644 --- a/OptimizelySDK.Tests/OdpTests/LruCacheTest.cs +++ b/OptimizelySDK.Tests/OdpTests/LruCacheTest.cs @@ -208,5 +208,113 @@ public void ShouldHandleWhenCacheIsReset() Assert.AreEqual(0, cache.CurrentCacheKeysForTesting().Length); } + + [Test] + public void ShouldHandleRemoveNonExistentKey() + { + var cache = new LruCache>(); + cache.Save("user1", _segments1And2); + cache.Save("user2", _segments3And4); + + // Remove a key that doesn't exist + cache.Remove("user3"); + + // Existing keys should still be there + Assert.AreEqual(_segments1And2, cache.Lookup("user1")); + Assert.AreEqual(_segments3And4, cache.Lookup("user2")); + } + + [Test] + public void ShouldHandleRemoveExistingKey() + { + var cache = new LruCache>(); + + cache.Save("user1", _segments1And2); + cache.Save("user2", _segments3And4); + cache.Save("user3", _segments5And6); + + Assert.AreEqual(_segments1And2, cache.Lookup("user1")); + Assert.AreEqual(_segments3And4, cache.Lookup("user2")); + Assert.AreEqual(_segments5And6, cache.Lookup("user3")); + + cache.Remove("user2"); + + Assert.AreEqual(_segments1And2, cache.Lookup("user1")); + Assert.IsNull(cache.Lookup("user2")); + Assert.AreEqual(_segments5And6, cache.Lookup("user3")); + } + + [Test] + public void ShouldHandleRemoveFromZeroSizedCache() + { + var cache = new LruCache>(0); + + cache.Save("user1", _segments1And2); + cache.Remove("user1"); + + Assert.IsNull(cache.Lookup("user1")); + Assert.AreEqual(0, cache.CurrentCacheKeysForTesting().Length); + } + + [Test] + public void ShouldHandleRemoveAndAddBack() + { + var cache = new LruCache>(); + + cache.Save("user1", _segments1And2); + cache.Save("user2", _segments3And4); + cache.Save("user3", _segments5And6); + + // Remove user2 and add it back with different data + cache.Remove("user2"); + cache.Save("user2", _segments1And2); + + Assert.AreEqual(_segments1And2, cache.Lookup("user1")); + Assert.AreEqual(_segments1And2, cache.Lookup("user2")); + Assert.AreEqual(_segments5And6, cache.Lookup("user3")); + + Assert.AreEqual(3, cache.CurrentCacheKeysForTesting().Length); + } + + [Test] + public void ShouldHandleThreadSafetyWithRemove() + { + var cache = new LruCache(100); + + for (int i = 1; i <= 100; i++) + { + cache.Save($"key{i}", $"value{i}"); + } + + var threads = new List(); + + for (int i = 1; i <= 50; i++) + { + int localI = i; // Capture variable for closure + var thread = new Thread(() => cache.Remove($"key{localI}")); + threads.Add(thread); + thread.Start(); + } + + // Wait for all threads to complete + foreach (var thread in threads) + { + thread.Join(); + } + + for (int i = 1; i <= 100; i++) + { + if (i <= 50) + { + Assert.IsNull(cache.Lookup($"key{i}"), $"key{i} should be removed"); + } + else + { + Assert.AreEqual($"value{i}", cache.Lookup($"key{i}"), $"key{i} should still exist"); + } + } + + Assert.AreEqual(50, cache.CurrentCacheKeysForTesting().Length); + } } } diff --git a/OptimizelySDK/Odp/LruCache.cs b/OptimizelySDK/Odp/LruCache.cs index bf1af65a..45b9be5d 100644 --- a/OptimizelySDK/Odp/LruCache.cs +++ b/OptimizelySDK/Odp/LruCache.cs @@ -165,6 +165,18 @@ public void Reset() } } + /// + /// Remove the element associated with the provided key from the cache + /// + /// Key of element to remove from the cache + public void Remove(string key) + { + lock (_mutex) + { + _cache.Remove(key); + } + } + /// /// Wrapping class around a generic value stored in the cache ///