Skip to content

Commit da00197

Browse files
authored
Eagerly purge deleted items from internal LRU queues (#664)
* removed * fix during warmup * policy tests * handle warm * handle trim ---------
1 parent 01805a5 commit da00197

14 files changed

+427
-131
lines changed

BitFaster.Caching.UnitTests/Lru/ConcurrentLruTests.cs

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -678,6 +678,113 @@ public void WhenItemIsRemovedRemovedEventIsFired()
678678
removedItems[0].Reason.Should().Be(ItemRemovedReason.Removed);
679679
}
680680

681+
[Fact]
682+
public void WhenItemRemovedFromHotDuringWarmupItIsEagerlyCycledOut()
683+
{
684+
lru.GetOrAdd(1, valueFactory.Create);
685+
686+
lru.TryRemove(1);
687+
Print(); // Hot [1] Warm [] Cold []
688+
689+
lru.GetOrAdd(1, valueFactory.Create);
690+
lru.GetOrAdd(2, valueFactory.Create);
691+
lru.GetOrAdd(3, valueFactory.Create);
692+
Print(); // Hot [1,2,3] Warm [] Cold []
693+
694+
lru.WarmCount.Should().Be(0);
695+
lru.ColdCount.Should().Be(0);
696+
}
697+
698+
[Fact]
699+
public void WhenItemRemovedFromHotAfterWarmupItIsEagerlyCycledOut()
700+
{
701+
for (int i = 0; i < lru.Capacity; i++)
702+
{
703+
lru.GetOrAdd(i, valueFactory.Create);
704+
}
705+
706+
Print(); // Hot [6,7,8] Warm [1,2,3] Cold [0,4,5]
707+
lru.Metrics.Value.Evicted.Should().Be(0);
708+
709+
lru.GetOrAdd(-1, valueFactory.Create);
710+
711+
lru.TryRemove(-1);
712+
Print(); // Hot[7, 8, -1] Warm[1, 2, 3] Cold[4, 5, 6]
713+
714+
// fully cycle hot, which is 3 items
715+
lru.GetOrAdd(-2, valueFactory.Create);
716+
lru.GetOrAdd(-3, valueFactory.Create);
717+
lru.GetOrAdd(-4, valueFactory.Create);
718+
719+
Print(); // Hot [-2,-3,-4] Warm [1,2,3] Cold [6,7,8]
720+
721+
// without eager eviction as -1 is purged from hot, a 4th item will pushed out since hot queue is full
722+
lru.Metrics.Value.Evicted.Should().Be(3);
723+
}
724+
725+
[Fact]
726+
public void WhenItemRemovedFromWarmDuringWarmupItIsEagerlyCycledOut()
727+
{
728+
lru.GetOrAdd(1, valueFactory.Create);
729+
lru.GetOrAdd(2, valueFactory.Create);
730+
lru.GetOrAdd(3, valueFactory.Create);
731+
lru.GetOrAdd(4, valueFactory.Create);
732+
Print(); // Hot [2,3,4] Warm [1] Cold []
733+
734+
lru.TryRemove(1);
735+
736+
lru.GetOrAdd(5, valueFactory.Create);
737+
lru.GetOrAdd(6, valueFactory.Create);
738+
lru.GetOrAdd(7, valueFactory.Create);
739+
Print(); // Hot [5,6,7] Warm [2,3,4] Cold []
740+
741+
lru.WarmCount.Should().Be(3);
742+
lru.ColdCount.Should().Be(0);
743+
}
744+
745+
746+
[Fact]
747+
public void WhenItemRemovedFromWarmAfterWarmupItIsEagerlyCycledOut()
748+
{
749+
for (int i = 0; i < lru.Capacity; i++)
750+
{
751+
lru.GetOrAdd(i, valueFactory.Create);
752+
}
753+
754+
Print(); // Hot [6,7,8] Warm [1,2,3] Cold [0,4,5]
755+
lru.Metrics.Value.Evicted.Should().Be(0);
756+
757+
lru.TryRemove(1);
758+
759+
lru.GetOrAdd(6, valueFactory.Create); // 6 -> W
760+
lru.GetOrAdd(9, valueFactory.Create);
761+
762+
Print(); // Hot [7,8,9] Warm [2,3,6] Cold [0,4,5]
763+
764+
lru.Metrics.Value.Evicted.Should().Be(0);
765+
}
766+
767+
[Fact]
768+
public void WhenItemRemovedFromColdAfterWarmupItIsEagerlyCycledOut()
769+
{
770+
for (int i = 0; i < lru.Capacity; i++)
771+
{
772+
lru.GetOrAdd(i, valueFactory.Create);
773+
}
774+
775+
Print(); // Hot [6,7,8] Warm [1,2,3] Cold [0,4,5]
776+
lru.Metrics.Value.Evicted.Should().Be(0);
777+
778+
lru.GetOrAdd(0, valueFactory.Create);
779+
lru.TryRemove(0);
780+
781+
lru.GetOrAdd(9, valueFactory.Create);
782+
783+
Print(); // Hot [7,8,9] Warm [1,2,3] Cold [4,5,6]
784+
785+
lru.Metrics.Value.Evicted.Should().Be(0);
786+
}
787+
681788
[Fact]
682789
public void WhenKeyDoesNotExistTryRemoveReturnsFalse()
683790
{
@@ -686,6 +793,29 @@ public void WhenKeyDoesNotExistTryRemoveReturnsFalse()
686793
lru.TryRemove(2).Should().BeFalse();
687794
}
688795

796+
[Fact]
797+
public void WhenItemsAreRemovedTrimRemovesDeletedItemsFromQueues()
798+
{
799+
for (int i = 0; i < lru.Capacity; i++)
800+
{
801+
lru.GetOrAdd(i, valueFactory.Create);
802+
}
803+
804+
Print(); // Hot [6,7,8] Warm [1,2,3] Cold [0,4,5]
805+
806+
lru.TryRemove(0);
807+
lru.TryRemove(1);
808+
lru.TryRemove(6);
809+
810+
lru.Policy.Eviction.Value.Trim(1);
811+
812+
Print(); // Hot [7,8] Warm [2,3] Cold [5]
813+
814+
lru.HotCount.Should().Be(2);
815+
lru.WarmCount.Should().Be(2);
816+
lru.ColdCount.Should().Be(1);
817+
}
818+
689819
[Fact]
690820
public void WhenRepeatedlyAddingAndRemovingSameValueLruRemainsInConsistentState()
691821
{
@@ -1305,6 +1435,14 @@ private void Warmup()
13051435
lru.GetOrAdd(-8, valueFactory.Create);
13061436
lru.GetOrAdd(-9, valueFactory.Create);
13071437
}
1438+
1439+
1440+
private void Print()
1441+
{
1442+
#if DEBUG
1443+
this.testOutputHelper.WriteLine(this.lru.FormatLruString());
1444+
#endif
1445+
}
13081446
}
13091447

13101448
public class ConcurrentLruIntegrityChecker<K, V, I, P, T>

BitFaster.Caching.UnitTests/Lru/ConcurrentTLruTests.cs

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@
55
using Xunit;
66
using System.Runtime.InteropServices;
77
using BitFaster.Caching.UnitTests.Retry;
8+
using Xunit.Abstractions;
89

910
namespace BitFaster.Caching.UnitTests.Lru
1011
{
1112
public class ConcurrentTLruTests
1213
{
14+
private readonly ITestOutputHelper testOutputHelper;
1315
private readonly TimeSpan timeToLive = TimeSpan.FromMilliseconds(10);
1416
private readonly ICapacityPartition capacity = new EqualCapacityPartition(9);
1517
private ConcurrentTLru<int, string> lru;
@@ -31,8 +33,9 @@ public ConcurrentTLru<K, V> CreateTLru<K, V>(ICapacityPartition capacity, TimeSp
3133
return new ConcurrentTLru<K, V>(1, capacity, EqualityComparer<K>.Default, timeToLive);
3234
}
3335

34-
public ConcurrentTLruTests()
36+
public ConcurrentTLruTests(ITestOutputHelper testOutputHelper)
3537
{
38+
this.testOutputHelper = testOutputHelper;
3639
lru = CreateTLru<int, string>(capacity, timeToLive);
3740
}
3841

@@ -315,6 +318,31 @@ public void WhenItemsAreExpiredEnumerateFiltersExpiredItems()
315318
);
316319
}
317320

321+
[Fact]
322+
public void WhenItemsAreRemovedTrimExpiredRemovesDeletedItemsFromQueues()
323+
{
324+
lru = CreateTLru<int, string>(capacity, TimeSpan.FromMinutes(1));
325+
326+
for (int i = 0; i < lru.Capacity; i++)
327+
{
328+
lru.GetOrAdd(i, valueFactory.Create);
329+
}
330+
331+
Print(); // Hot [6,7,8] Warm [1,2,3] Cold [0,4,5]
332+
333+
lru.TryRemove(0);
334+
lru.TryRemove(1);
335+
lru.TryRemove(6);
336+
337+
lru.Policy.ExpireAfterWrite.Value.TrimExpired();
338+
339+
Print(); // Hot [7,8] Warm [2,3] Cold [4,5]
340+
341+
lru.HotCount.Should().Be(2);
342+
lru.WarmCount.Should().Be(2);
343+
lru.ColdCount.Should().Be(2);
344+
}
345+
318346
[Fact]
319347
public void ConstructWithDefaultCtorReturnsCapacity()
320348
{
@@ -338,5 +366,12 @@ public void ConstructPartitionCtorReturnsCapacity()
338366

339367
x.Capacity.Should().Be(3);
340368
}
369+
370+
private void Print()
371+
{
372+
#if DEBUG
373+
this.testOutputHelper.WriteLine(this.lru.FormatLruString());
374+
#endif
375+
}
341376
}
342377
}

BitFaster.Caching.UnitTests/Lru/DiscretePolicyTests.cs

Lines changed: 32 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -114,48 +114,60 @@ public void CanDiscardIsTrue()
114114
this.policy.CanDiscard().Should().BeTrue();
115115
}
116116

117-
118117
[Theory]
119-
[InlineData(false, true, ItemDestination.Remove)]
120-
[InlineData(true, true, ItemDestination.Remove)]
121-
[InlineData(true, false, ItemDestination.Warm)]
122-
[InlineData(false, false, ItemDestination.Cold)]
123-
public void RouteHot(bool wasAccessed, bool isExpired, ItemDestination expectedDestination)
118+
[InlineData(false, false, true, ItemDestination.Remove)]
119+
[InlineData(true, false, true, ItemDestination.Remove)]
120+
[InlineData(true, false, false, ItemDestination.Warm)]
121+
[InlineData(false, false, false, ItemDestination.Cold)]
122+
[InlineData(false, true, true, ItemDestination.Remove)]
123+
[InlineData(true, true, true, ItemDestination.Remove)]
124+
[InlineData(true, true, false, ItemDestination.Remove)]
125+
[InlineData(false, true, false, ItemDestination.Remove)]
126+
public void RouteHot(bool wasAccessed, bool wasRemoved, bool isExpired, ItemDestination expectedDestination)
124127
{
125-
var item = CreateItem(wasAccessed, isExpired);
128+
var item = CreateItem(wasAccessed, wasRemoved, isExpired);
126129

127130
this.policy.RouteHot(item).Should().Be(expectedDestination);
128131
}
129132

130133
[Theory]
131-
[InlineData(false, true, ItemDestination.Remove)]
132-
[InlineData(true, true, ItemDestination.Remove)]
133-
[InlineData(true, false, ItemDestination.Warm)]
134-
[InlineData(false, false, ItemDestination.Cold)]
135-
public void RouteWarm(bool wasAccessed, bool isExpired, ItemDestination expectedDestination)
134+
[InlineData(false, false, true, ItemDestination.Remove)]
135+
[InlineData(true, false, true, ItemDestination.Remove)]
136+
[InlineData(true, false, false, ItemDestination.Warm)]
137+
[InlineData(false, false, false, ItemDestination.Cold)]
138+
[InlineData(false, true, true, ItemDestination.Remove)]
139+
[InlineData(true, true, true, ItemDestination.Remove)]
140+
[InlineData(true, true, false, ItemDestination.Remove)]
141+
[InlineData(false, true, false, ItemDestination.Remove)]
142+
public void RouteWarm(bool wasAccessed, bool wasRemoved, bool isExpired, ItemDestination expectedDestination)
136143
{
137-
var item = CreateItem(wasAccessed, isExpired);
144+
var item = CreateItem(wasAccessed, wasRemoved, isExpired);
138145

139146
this.policy.RouteWarm(item).Should().Be(expectedDestination);
140147
}
141148

142149
[Theory]
143-
[InlineData(false, true, ItemDestination.Remove)]
144-
[InlineData(true, true, ItemDestination.Remove)]
145-
[InlineData(true, false, ItemDestination.Warm)]
146-
[InlineData(false, false, ItemDestination.Remove)]
147-
public void RouteCold(bool wasAccessed, bool isExpired, ItemDestination expectedDestination)
150+
[InlineData(false, false, true, ItemDestination.Remove)]
151+
[InlineData(true, false, true, ItemDestination.Remove)]
152+
[InlineData(true, false, false, ItemDestination.Warm)]
153+
[InlineData(false, false, false, ItemDestination.Remove)]
154+
[InlineData(false, true, true, ItemDestination.Remove)]
155+
[InlineData(true, true, true, ItemDestination.Remove)]
156+
[InlineData(true, true, false, ItemDestination.Remove)]
157+
[InlineData(false, true, false, ItemDestination.Remove)]
158+
public void RouteCold(bool wasAccessed, bool wasRemoved, bool isExpired, ItemDestination expectedDestination)
148159
{
149-
var item = CreateItem(wasAccessed, isExpired);
160+
var item = CreateItem(wasAccessed, wasRemoved, isExpired);
150161

151162
this.policy.RouteCold(item).Should().Be(expectedDestination);
152163
}
153164

154-
private LongTickCountLruItem<int, int> CreateItem(bool wasAccessed, bool isExpired)
165+
private LongTickCountLruItem<int, int> CreateItem(bool wasAccessed, bool wasRemoved, bool isExpired)
155166
{
156167
var item = this.policy.CreateItem(1, 2);
157168

158169
item.WasAccessed = wasAccessed;
170+
item.WasRemoved = wasRemoved;
159171

160172
if (isExpired)
161173
{

BitFaster.Caching.UnitTests/Lru/LruPolicyTests.cs

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -69,40 +69,47 @@ public void CanDiscardIsFalse()
6969
}
7070

7171
[Theory]
72-
[InlineData(true, ItemDestination.Warm)]
73-
[InlineData(false, ItemDestination.Cold)]
74-
public void RouteHot(bool wasAccessed, ItemDestination expectedDestination)
72+
[InlineData(true, false, ItemDestination.Warm)]
73+
[InlineData(false, false, ItemDestination.Cold)]
74+
[InlineData(false, true, ItemDestination.Remove)]
75+
[InlineData(true, true, ItemDestination.Remove)]
76+
public void RouteHot(bool wasAccessed, bool wasRemoved, ItemDestination expectedDestination)
7577
{
76-
var item = CreateItem(wasAccessed);
78+
var item = CreateItem(wasAccessed, wasRemoved);
7779

7880
this.policy.RouteHot(item).Should().Be(expectedDestination);
7981
}
8082

8183
[Theory]
82-
[InlineData(true, ItemDestination.Warm)]
83-
[InlineData(false, ItemDestination.Cold)]
84-
public void RouteWarm(bool wasAccessed, ItemDestination expectedDestination)
84+
[InlineData(true, false, ItemDestination.Warm)]
85+
[InlineData(false, false, ItemDestination.Cold)]
86+
[InlineData(true, true, ItemDestination.Remove)]
87+
[InlineData(false, true, ItemDestination.Remove)]
88+
public void RouteWarm(bool wasAccessed, bool wasRemoved, ItemDestination expectedDestination)
8589
{
86-
var item = CreateItem(wasAccessed);
90+
var item = CreateItem(wasAccessed, wasRemoved);
8791

8892
this.policy.RouteWarm(item).Should().Be(expectedDestination);
8993
}
9094

9195
[Theory]
92-
[InlineData(true, ItemDestination.Warm)]
93-
[InlineData(false, ItemDestination.Remove)]
94-
public void RouteCold(bool wasAccessed, ItemDestination expectedDestination)
96+
[InlineData(true, false, ItemDestination.Warm)]
97+
[InlineData(false, false, ItemDestination.Remove)]
98+
[InlineData(true, true, ItemDestination.Remove)]
99+
[InlineData(false, true, ItemDestination.Remove)]
100+
public void RouteCold(bool wasAccessed, bool wasRemoved, ItemDestination expectedDestination)
95101
{
96-
var item = CreateItem(wasAccessed);
102+
var item = CreateItem(wasAccessed, wasRemoved);
97103

98104
this.policy.RouteCold(item).Should().Be(expectedDestination);
99105
}
100106

101-
private LruItem<int, int> CreateItem(bool wasAccessed)
107+
private LruItem<int, int> CreateItem(bool wasAccessed, bool wasRemoved)
102108
{
103109
var item = this.policy.CreateItem(1, 2);
104110

105111
item.WasAccessed = wasAccessed;
112+
item.WasRemoved = wasRemoved;
106113

107114
return item;
108115
}

0 commit comments

Comments
 (0)