Skip to content

Commit ef211dd

Browse files
authored
LRU enumeration should not return expired/removed items (#534)
1 parent 6339ed9 commit ef211dd

File tree

2 files changed

+63
-21
lines changed

2 files changed

+63
-21
lines changed

BitFaster.Caching.UnitTests/Lru/ConcurrentTLruTests.cs

Lines changed: 58 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@
77

88
namespace BitFaster.Caching.UnitTests.Lru
99
{
10-
public abstract class ConcurrentTLruTests
10+
public class ConcurrentTLruTests
1111
{
1212
private readonly TimeSpan timeToLive = TimeSpan.FromMilliseconds(10);
1313
private readonly ICapacityPartition capacity = new EqualCapacityPartition(9);
14-
private ICache<int, string> lru;
14+
private ConcurrentTLru<int, string> lru;
1515

1616
private ValueFactory valueFactory = new ValueFactory();
1717

@@ -25,7 +25,10 @@ private void OnLruItemRemoved(object sender, ItemRemovedEventArgs<int, int> e)
2525
removedItems.Add(e);
2626
}
2727

28-
protected abstract ICache<K, V> CreateTLru<K, V>(ICapacityPartition capacity, TimeSpan timeToLive);
28+
public ConcurrentTLru<K, V> CreateTLru<K, V>(ICapacityPartition capacity, TimeSpan timeToLive)
29+
{
30+
return new ConcurrentTLru<K, V>(1, capacity, EqualityComparer<K>.Default, timeToLive);
31+
}
2932

3033
public ConcurrentTLruTests()
3134
{
@@ -159,7 +162,9 @@ public void WhenItemsAreExpiredExpireRemovesExpiredItems()
159162
{
160163
lru.Policy.ExpireAfterWrite.Value.TrimExpired();
161164

162-
lru.Count.Should().Be(0);
165+
lru.HotCount.Should().Be(0);
166+
lru.WarmCount.Should().Be(0);
167+
lru.ColdCount.Should().Be(0);
163168
}
164169
);
165170
}
@@ -199,6 +204,9 @@ public void WhenExpiredItemsAreTrimmedCacheMarkedCold()
199204
}
200205

201206
lru.Count.Should().Be(lru.Policy.Eviction.Value.Capacity);
207+
208+
var total = lru.HotCount + lru.WarmCount + lru.ColdCount;
209+
total.Should().Be(lru.Policy.Eviction.Value.Capacity);
202210
}
203211
);
204212
}
@@ -230,6 +238,9 @@ public void WhenCacheHasExpiredAndFreshItemsExpireRemovesOnlyExpiredItems()
230238
lru.Policy.ExpireAfterWrite.Value.TrimExpired();
231239

232240
lru.Count.Should().Be(3);
241+
242+
var total = lru.HotCount + lru.WarmCount + lru.ColdCount;
243+
total.Should().Be(3);
233244
}
234245
);
235246
}
@@ -253,17 +264,54 @@ public void WhenItemsAreExpiredTrimRemovesExpiredItems()
253264
lru.Policy.Eviction.Value.Trim(1);
254265

255266
lru.Count.Should().Be(0);
267+
268+
lru.HotCount.Should().Be(0);
269+
lru.WarmCount.Should().Be(0);
270+
lru.ColdCount.Should().Be(0);
256271
}
257272
);
258273
}
259-
}
260274

261-
public class ConcurrentTLruDefaultClockTests : ConcurrentTLruTests
262-
{
263-
protected override ICache<K, V> CreateTLru<K, V>(ICapacityPartition capacity, TimeSpan timeToLive)
275+
[Fact]
276+
public void WhenItemsAreExpiredCountFiltersExpiredItems()
264277
{
265-
// backcompat: use TLruTickCount64Policy
266-
return new ConcurrentTLru<K, V>(1, capacity, EqualityComparer<K>.Default, timeToLive);
278+
Timed.Execute(
279+
lru,
280+
lru =>
281+
{
282+
lru.AddOrUpdate(1, "1");
283+
lru.AddOrUpdate(2, "2");
284+
lru.AddOrUpdate(3, "3");
285+
286+
return lru;
287+
},
288+
timeToLive.MultiplyBy(ttlWaitMlutiplier),
289+
lru =>
290+
{
291+
lru.Count.Should().Be(0);
292+
}
293+
);
294+
}
295+
296+
[Fact]
297+
public void WhenItemsAreExpiredEnumerateFiltersExpiredItems()
298+
{
299+
Timed.Execute(
300+
lru,
301+
lru =>
302+
{
303+
lru.AddOrUpdate(1, "1");
304+
lru.AddOrUpdate(2, "2");
305+
lru.AddOrUpdate(3, "3");
306+
307+
return lru;
308+
},
309+
timeToLive.MultiplyBy(ttlWaitMlutiplier),
310+
lru =>
311+
{
312+
lru.Should().BeEquivalentTo(Array.Empty<KeyValuePair<int, string>>());
313+
}
314+
);
267315
}
268316

269317
[Fact]
@@ -290,13 +338,4 @@ public void ConstructPartitionCtorReturnsCapacity()
290338
x.Capacity.Should().Be(3);
291339
}
292340
}
293-
294-
public class ConcurrentTLruHighResClockTests : ConcurrentTLruTests
295-
{
296-
protected override ICache<K, V> CreateTLru<K, V>(ICapacityPartition capacity, TimeSpan timeToLive)
297-
{
298-
// backcompat: use TlruStopwatchPolicy
299-
return new ConcurrentLruCore<K, V, LongTickCountLruItem<K, V>, TLruLongTicksPolicy<K, V>, TelemetryPolicy<K, V>>(1, capacity, EqualityComparer<K>.Default, new TLruLongTicksPolicy<K, V>(timeToLive), default);
300-
}
301-
}
302341
}

BitFaster.Caching/Lru/ConcurrentLruCore.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ public ConcurrentLruCore(
100100

101101
// No lock count: https://arbel.net/2013/02/03/best-practices-for-using-concurrentdictionary/
102102
///<inheritdoc/>
103-
public int Count => this.dictionary.Skip(0).Count();
103+
public int Count => this.dictionary.Where(i => !itemPolicy.ShouldDiscard(i.Value)).Count();
104104

105105
///<inheritdoc/>
106106
public int Capacity => this.capacity.Hot + this.capacity.Warm + this.capacity.Cold;
@@ -146,7 +146,10 @@ public IEnumerator<KeyValuePair<K, V>> GetEnumerator()
146146
{
147147
foreach (var kvp in this.dictionary)
148148
{
149-
yield return new KeyValuePair<K, V>(kvp.Key, kvp.Value.Value);
149+
if (!itemPolicy.ShouldDiscard(kvp.Value))
150+
{
151+
yield return new KeyValuePair<K, V>(kvp.Key, kvp.Value.Value);
152+
}
150153
}
151154
}
152155

0 commit comments

Comments
 (0)