diff --git a/BitFaster.Caching.UnitTests/Lru/ConcurrentLruTests.cs b/BitFaster.Caching.UnitTests/Lru/ConcurrentLruTests.cs index 07bb606d..1a9b32f1 100644 --- a/BitFaster.Caching.UnitTests/Lru/ConcurrentLruTests.cs +++ b/BitFaster.Caching.UnitTests/Lru/ConcurrentLruTests.cs @@ -1300,11 +1300,14 @@ public class ConcurrentLruIntegrityChecker where T : struct, ITelemetryPolicy { private readonly ConcurrentLruCore cache; - + + private readonly ConcurrentDictionary dictionary; private readonly ConcurrentQueue hotQueue; private readonly ConcurrentQueue warmQueue; private readonly ConcurrentQueue coldQueue; + private static FieldInfo dictionaryField = typeof(ConcurrentLruCore).GetField("dictionary", BindingFlags.NonPublic | BindingFlags.Instance); + private static FieldInfo hotQueueField = typeof(ConcurrentLruCore).GetField("hotQueue", BindingFlags.NonPublic | BindingFlags.Instance); private static FieldInfo warmQueueField = typeof(ConcurrentLruCore).GetField("warmQueue", BindingFlags.NonPublic | BindingFlags.Instance); private static FieldInfo coldQueueField = typeof(ConcurrentLruCore).GetField("coldQueue", BindingFlags.NonPublic | BindingFlags.Instance); @@ -1314,6 +1317,7 @@ public ConcurrentLruIntegrityChecker(ConcurrentLruCore cache) this.cache = cache; // get queues via reflection + this.dictionary = (ConcurrentDictionary)dictionaryField.GetValue(cache); this.hotQueue = (ConcurrentQueue)hotQueueField.GetValue(cache); this.warmQueue = (ConcurrentQueue)warmQueueField.GetValue(cache); this.coldQueue = (ConcurrentQueue)coldQueueField.GetValue(cache); @@ -1344,7 +1348,7 @@ private void ValidateQueue(ConcurrentLruCore cache, ConcurrentQue // It is possible for the queues to contain 2 (or more) instances of the same key/item. One that was removed, // and one that was added after the other was removed. // In this case, the dictionary may contain the value only if the queues contain an entry for that key marked as WasRemoved == false. - if (cache.TryGet(item.Key, out var value)) + if (dictionary.TryGetValue(item.Key, out var value)) { hotQueue.Union(warmQueue).Union(coldQueue) .Any(i => i.Key.Equals(item.Key) && !i.WasRemoved) @@ -1353,7 +1357,7 @@ private void ValidateQueue(ConcurrentLruCore cache, ConcurrentQue } else { - cache.TryGet(item.Key, out var value).Should().BeTrue($"{queueName} item {item.Key} was not present"); + dictionary.TryGetValue(item.Key, out var value).Should().BeTrue($"{queueName} item {item.Key} was not present"); } } } diff --git a/BitFaster.Caching.UnitTests/Lru/ConcurrentTLruSoakTests.cs b/BitFaster.Caching.UnitTests/Lru/ConcurrentTLruSoakTests.cs new file mode 100644 index 00000000..c8a1dda7 --- /dev/null +++ b/BitFaster.Caching.UnitTests/Lru/ConcurrentTLruSoakTests.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using BitFaster.Caching.Lru; +using Xunit; +using Xunit.Abstractions; + +namespace BitFaster.Caching.UnitTests.Lru +{ + [Collection("Soak")] + public class ConcurrentTLruSoakTests + { + private readonly ITestOutputHelper testOutputHelper; + private const int hotCap = 33; + private const int warmCap = 33; + private const int coldCap = 33; + private static readonly ICapacityPartition capacity = new EqualCapacityPartition(hotCap + warmCap + coldCap); + + private ConcurrentTLru lru = new ConcurrentTLru(1, capacity, EqualityComparer.Default, TimeSpan.FromMilliseconds(10)); + + public ConcurrentTLruSoakTests(ITestOutputHelper testOutputHelper) + { + this.testOutputHelper = testOutputHelper; + } + + [Theory] + [Repeat(10)] + public async Task WhenSoakConcurrentGetCacheEndsInConsistentState(int iteration) + { + await Threaded.Run(4, () => { + for (int j = 0; j < 100000; j++) + { + for (int i = 0; i < lru.Capacity; i++) + { + lru.GetOrAdd(i + 1, i => i.ToString()); + } + } + }); + + this.testOutputHelper.WriteLine($"iteration{iteration}: {lru.HotCount} {lru.WarmCount} {lru.ColdCount}"); + this.testOutputHelper.WriteLine(string.Join(" ", lru.Keys)); + + RunIntegrityCheck(); + } + + private void RunIntegrityCheck() + { + new ConcurrentLruIntegrityChecker, TLruLongTicksPolicy, TelemetryPolicy>(this.lru).Validate(); + } + } +}