diff --git a/BitFaster.Caching.UnitTests/Lru/ConcurrentLruTests.cs b/BitFaster.Caching.UnitTests/Lru/ConcurrentLruTests.cs index 71b850ff..73ebe2c5 100644 --- a/BitFaster.Caching.UnitTests/Lru/ConcurrentLruTests.cs +++ b/BitFaster.Caching.UnitTests/Lru/ConcurrentLruTests.cs @@ -953,6 +953,25 @@ public void WhenWarmThenClearedIsWarmIsReset() lru.Count.Should().Be(capacity.Hot + capacity.Warm + capacity.Cold); } + [Fact] + public void WhenWarmThenTrimIsWarmIsReset() + { + for (int i = 0; i < 20; i++) + { + lru.GetOrAdd(i, k => k.ToString()); + } + + lru.Trim(6); + lru.Count.Should().Be(3); + + for (int i = 0; i < 20; i++) + { + lru.GetOrAdd(i, k => k.ToString()); + } + + lru.Count.Should().Be(capacity.Hot + capacity.Warm + capacity.Cold); + } + [Fact] public void WhenItemsAreDisposableClearDisposesItemsOnRemove() { diff --git a/BitFaster.Caching.UnitTests/Lru/ConcurrentTLruTests.cs b/BitFaster.Caching.UnitTests/Lru/ConcurrentTLruTests.cs index 38447f91..fa9099b1 100644 --- a/BitFaster.Caching.UnitTests/Lru/ConcurrentTLruTests.cs +++ b/BitFaster.Caching.UnitTests/Lru/ConcurrentTLruTests.cs @@ -2,10 +2,8 @@ using BitFaster.Caching.Lru; using System; using System.Collections.Generic; -using System.Threading.Tasks; using Xunit; using System.Runtime.InteropServices; -using System.Threading; namespace BitFaster.Caching.UnitTests.Lru { @@ -166,6 +164,45 @@ public void WhenItemsAreExpiredExpireRemovesExpiredItems() ); } + [Fact] + public void WhenExpiredItemsAreTrimmedCacheMarkedCold() + { + Timed.Execute( + lru, + lru => + { + lru.AddOrUpdate(1, "1"); + lru.AddOrUpdate(2, "2"); + lru.AddOrUpdate(3, "3"); + lru.GetOrAdd(1, valueFactory.Create); + lru.GetOrAdd(2, valueFactory.Create); + lru.GetOrAdd(3, valueFactory.Create); + + lru.AddOrUpdate(4, "4"); + lru.AddOrUpdate(5, "5"); + lru.AddOrUpdate(6, "6"); + + lru.AddOrUpdate(7, "7"); + lru.AddOrUpdate(8, "8"); + lru.AddOrUpdate(9, "9"); + + return lru; + }, + timeToLive.MultiplyBy(ttlWaitMlutiplier), + lru => + { + lru.Policy.ExpireAfterWrite.Value.TrimExpired(); + + for (int i = 0; i < lru.Policy.Eviction.Value.Capacity; i++) + { + lru.GetOrAdd(i, k => k.ToString()); + } + + lru.Count.Should().Be(lru.Policy.Eviction.Value.Capacity); + } + ); + } + [Fact] public void WhenCacheHasExpiredAndFreshItemsExpireRemovesOnlyExpiredItems() { diff --git a/BitFaster.Caching/Lru/ConcurrentLruCore.cs b/BitFaster.Caching/Lru/ConcurrentLruCore.cs index b3b75593..ac35e27d 100644 --- a/BitFaster.Caching/Lru/ConcurrentLruCore.cs +++ b/BitFaster.Caching/Lru/ConcurrentLruCore.cs @@ -423,7 +423,6 @@ public void AddOrUpdate(K key, V value) public void Clear() { this.TrimLiveItems(itemsRemoved: 0, this.Count, ItemRemovedReason.Cleared); - Volatile.Write(ref this.isWarm, false); } /// @@ -468,10 +467,9 @@ private void TrimExpired() // backcompat: make internal protected int TrimAllDiscardedItems() { - int itemsRemoved = 0; - - void RemoveDiscardableItems(ConcurrentQueue q, ref int queueCounter) + int RemoveDiscardableItems(ConcurrentQueue q, ref int queueCounter) { + int itemsRemoved = 0; int localCount = queueCounter; for (int i = 0; i < localCount; i++) @@ -490,13 +488,20 @@ void RemoveDiscardableItems(ConcurrentQueue q, ref int queueCounter) } } } + + return itemsRemoved; } - RemoveDiscardableItems(coldQueue, ref this.counter.cold); - RemoveDiscardableItems(warmQueue, ref this.counter.warm); - RemoveDiscardableItems(hotQueue, ref this.counter.hot); + int coldRem = RemoveDiscardableItems(coldQueue, ref this.counter.cold); + int warmRem = RemoveDiscardableItems(warmQueue, ref this.counter.warm); + int hotRem = RemoveDiscardableItems(hotQueue, ref this.counter.hot); + + if (warmRem > 0) + { + Volatile.Write(ref this.isWarm, false); + } - return itemsRemoved; + return coldRem + warmRem + hotRem; } private void TrimLiveItems(int itemsRemoved, int itemCount, ItemRemovedReason reason) @@ -526,6 +531,11 @@ private void TrimLiveItems(int itemsRemoved, int itemCount, ItemRemovedReason re trimWarmAttempts++; } } + + if (Volatile.Read(ref this.counter.warm) < this.capacity.Warm) + { + Volatile.Write(ref this.isWarm, false); + } } private void TrimWarmOrHot(ItemRemovedReason reason)