Skip to content

Commit ece7a3a

Browse files
authored
Soak ConcurrentTLru with rapid expiry (#493)
1 parent 866d8b2 commit ece7a3a

File tree

2 files changed

+58
-3
lines changed

2 files changed

+58
-3
lines changed

BitFaster.Caching.UnitTests/Lru/ConcurrentLruTests.cs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1300,11 +1300,14 @@ public class ConcurrentLruIntegrityChecker<K, V, I, P, T>
13001300
where T : struct, ITelemetryPolicy<K, V>
13011301
{
13021302
private readonly ConcurrentLruCore<K, V, I, P, T> cache;
1303-
1303+
1304+
private readonly ConcurrentDictionary<K, I> dictionary;
13041305
private readonly ConcurrentQueue<I> hotQueue;
13051306
private readonly ConcurrentQueue<I> warmQueue;
13061307
private readonly ConcurrentQueue<I> coldQueue;
13071308

1309+
private static FieldInfo dictionaryField = typeof(ConcurrentLruCore<K, V, I, P, T>).GetField("dictionary", BindingFlags.NonPublic | BindingFlags.Instance);
1310+
13081311
private static FieldInfo hotQueueField = typeof(ConcurrentLruCore<K, V, I, P, T>).GetField("hotQueue", BindingFlags.NonPublic | BindingFlags.Instance);
13091312
private static FieldInfo warmQueueField = typeof(ConcurrentLruCore<K, V, I, P, T>).GetField("warmQueue", BindingFlags.NonPublic | BindingFlags.Instance);
13101313
private static FieldInfo coldQueueField = typeof(ConcurrentLruCore<K, V, I, P, T>).GetField("coldQueue", BindingFlags.NonPublic | BindingFlags.Instance);
@@ -1314,6 +1317,7 @@ public ConcurrentLruIntegrityChecker(ConcurrentLruCore<K, V, I, P, T> cache)
13141317
this.cache = cache;
13151318

13161319
// get queues via reflection
1320+
this.dictionary = (ConcurrentDictionary<K, I>)dictionaryField.GetValue(cache);
13171321
this.hotQueue = (ConcurrentQueue<I>)hotQueueField.GetValue(cache);
13181322
this.warmQueue = (ConcurrentQueue<I>)warmQueueField.GetValue(cache);
13191323
this.coldQueue = (ConcurrentQueue<I>)coldQueueField.GetValue(cache);
@@ -1344,7 +1348,7 @@ private void ValidateQueue(ConcurrentLruCore<K, V, I, P, T> cache, ConcurrentQue
13441348
// It is possible for the queues to contain 2 (or more) instances of the same key/item. One that was removed,
13451349
// and one that was added after the other was removed.
13461350
// In this case, the dictionary may contain the value only if the queues contain an entry for that key marked as WasRemoved == false.
1347-
if (cache.TryGet(item.Key, out var value))
1351+
if (dictionary.TryGetValue(item.Key, out var value))
13481352
{
13491353
hotQueue.Union(warmQueue).Union(coldQueue)
13501354
.Any(i => i.Key.Equals(item.Key) && !i.WasRemoved)
@@ -1353,7 +1357,7 @@ private void ValidateQueue(ConcurrentLruCore<K, V, I, P, T> cache, ConcurrentQue
13531357
}
13541358
else
13551359
{
1356-
cache.TryGet(item.Key, out var value).Should().BeTrue($"{queueName} item {item.Key} was not present");
1360+
dictionary.TryGetValue(item.Key, out var value).Should().BeTrue($"{queueName} item {item.Key} was not present");
13571361
}
13581362
}
13591363
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Threading.Tasks;
4+
using BitFaster.Caching.Lru;
5+
using Xunit;
6+
using Xunit.Abstractions;
7+
8+
namespace BitFaster.Caching.UnitTests.Lru
9+
{
10+
[Collection("Soak")]
11+
public class ConcurrentTLruSoakTests
12+
{
13+
private readonly ITestOutputHelper testOutputHelper;
14+
private const int hotCap = 33;
15+
private const int warmCap = 33;
16+
private const int coldCap = 33;
17+
private static readonly ICapacityPartition capacity = new EqualCapacityPartition(hotCap + warmCap + coldCap);
18+
19+
private ConcurrentTLru<int, string> lru = new ConcurrentTLru<int, string>(1, capacity, EqualityComparer<int>.Default, TimeSpan.FromMilliseconds(10));
20+
21+
public ConcurrentTLruSoakTests(ITestOutputHelper testOutputHelper)
22+
{
23+
this.testOutputHelper = testOutputHelper;
24+
}
25+
26+
[Theory]
27+
[Repeat(10)]
28+
public async Task WhenSoakConcurrentGetCacheEndsInConsistentState(int iteration)
29+
{
30+
await Threaded.Run(4, () => {
31+
for (int j = 0; j < 100000; j++)
32+
{
33+
for (int i = 0; i < lru.Capacity; i++)
34+
{
35+
lru.GetOrAdd(i + 1, i => i.ToString());
36+
}
37+
}
38+
});
39+
40+
this.testOutputHelper.WriteLine($"iteration{iteration}: {lru.HotCount} {lru.WarmCount} {lru.ColdCount}");
41+
this.testOutputHelper.WriteLine(string.Join(" ", lru.Keys));
42+
43+
RunIntegrityCheck();
44+
}
45+
46+
private void RunIntegrityCheck()
47+
{
48+
new ConcurrentLruIntegrityChecker<int, string, LongTickCountLruItem<int, string>, TLruLongTicksPolicy<int, string>, TelemetryPolicy<int, string>>(this.lru).Validate();
49+
}
50+
}
51+
}

0 commit comments

Comments
 (0)