diff --git a/BitFaster.Caching.UnitTests/Lfu/ConcurrentLfuSoakTests.cs b/BitFaster.Caching.UnitTests/Lfu/ConcurrentLfuSoakTests.cs index c1f1122f..7f84e9d2 100644 --- a/BitFaster.Caching.UnitTests/Lfu/ConcurrentLfuSoakTests.cs +++ b/BitFaster.Caching.UnitTests/Lfu/ConcurrentLfuSoakTests.cs @@ -108,6 +108,29 @@ await Threaded.RunAsync(4, async () => { scheduler.Dispose(); await scheduler.Completion; + RunIntegrityCheck(lfu); + } + + [Theory] + [Repeat(iterations)] + public async Task WhenConcurrentGetAndUpdateCacheEndsInConsistentState(int iteration) + { + var scheduler = new BackgroundThreadScheduler(); + var lfu = new ConcurrentLfuBuilder().WithCapacity(9).WithScheduler(scheduler).Build() as ConcurrentLfu; + + await Threaded.Run(4, () => { + for (int i = 0; i < 100000; i++) + { + lfu.TryUpdate(i + 1, i.ToString()); + lfu.GetOrAdd(i + 1, i => i.ToString()); + } + }); + + this.output.WriteLine($"iteration {iteration} keys={string.Join(" ", lfu.Keys)}"); + + scheduler.Dispose(); + await scheduler.Completion; + RunIntegrityCheck(lfu); } diff --git a/BitFaster.Caching/Lfu/ConcurrentLfu.cs b/BitFaster.Caching/Lfu/ConcurrentLfu.cs index 3c780958..c98657de 100644 --- a/BitFaster.Caching/Lfu/ConcurrentLfu.cs +++ b/BitFaster.Caching/Lfu/ConcurrentLfu.cs @@ -691,7 +691,13 @@ private void OnWrite(LfuNode node) } private void PromoteProbation(LfuNode node) - { + { + if (node.list == null) + { + // Ignore stale accesses for an entry that is no longer present + return; + } + this.probationLru.Remove(node); this.protectedLru.AddLast(node); node.Position = Position.Protected;