Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 23 additions & 14 deletions BitFaster.Caching.UnitTests/Lfu/ConcurrentLfuSoakTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
using BitFaster.Caching.Lfu;
using BitFaster.Caching.Scheduler;
using FluentAssertions;
using FluentAssertions.Equivalency;
using Xunit;
using Xunit.Abstractions;

Expand Down Expand Up @@ -520,18 +519,23 @@ private void Log<K>(ConcurrentLfu<K, string> lfu, int iteration, string stage)

private static void RunIntegrityCheck<K, V>(ConcurrentLfu<K, V> cache, ITestOutputHelper output)
{
new ConcurrentLfuIntegrityChecker<K, V, AccessOrderNode<K, V>, AccessOrderPolicy<K, V>>(cache.Core).Validate(output);
new ConcurrentLfuIntegrityChecker<K, V, AccessOrderNode<K, V>, AccessOrderPolicy<K, V, EventPolicy<K, V>>, EventPolicy<K, V>>(cache.Core).Validate(output);
}
}

internal class ConcurrentLfuIntegrityChecker<K, V, N, P>
internal class ConcurrentLfuIntegrityChecker<K, V, N, P, E>
where N : LfuNode<K, V>
where P : struct, INodePolicy<K, V, N>
where P : struct, INodePolicy<K, V, N, E>
where E : struct, IEventPolicy<K, V>
{
private readonly ConcurrentLfuCore<K, V, N, P> cache;

private readonly ConcurrentLfuCore<K, V, N, P, E> cache;
private readonly ConcurrentDictionary<K, N> dictionary;

#if NET9_0_OR_GREATER
private readonly Lock maintenanceLock;
#else
private readonly object maintenanceLock;
#endif

private readonly LfuNodeList<K, V> windowLru;
private readonly LfuNodeList<K, V> probationLru;
Expand All @@ -540,22 +544,27 @@ internal class ConcurrentLfuIntegrityChecker<K, V, N, P>
private readonly StripedMpscBuffer<N> readBuffer;
private readonly MpscBoundedBuffer<N> writeBuffer;

private static FieldInfo dictionaryField = typeof(ConcurrentLfuCore<K, V, N, P>).GetField("dictionary", BindingFlags.NonPublic | BindingFlags.Instance);
private static FieldInfo lockField = typeof(ConcurrentLfuCore<K, V, N, P>).GetField("maintenanceLock", BindingFlags.NonPublic | BindingFlags.Instance);
private static FieldInfo dictionaryField = typeof(ConcurrentLfuCore<K, V, N, P, E>).GetField("dictionary", BindingFlags.NonPublic | BindingFlags.Instance);
private static FieldInfo lockField = typeof(ConcurrentLfuCore<K, V, N, P, E>).GetField("maintenanceLock", BindingFlags.NonPublic | BindingFlags.Instance);

private static FieldInfo windowLruField = typeof(ConcurrentLfuCore<K, V, N, P>).GetField("windowLru", BindingFlags.NonPublic | BindingFlags.Instance);
private static FieldInfo probationLruField = typeof(ConcurrentLfuCore<K, V, N, P>).GetField("probationLru", BindingFlags.NonPublic | BindingFlags.Instance);
private static FieldInfo protectedLruField = typeof(ConcurrentLfuCore<K, V, N, P>).GetField("protectedLru", BindingFlags.NonPublic | BindingFlags.Instance);
private static FieldInfo windowLruField = typeof(ConcurrentLfuCore<K, V, N, P, E>).GetField("windowLru", BindingFlags.NonPublic | BindingFlags.Instance);
private static FieldInfo probationLruField = typeof(ConcurrentLfuCore<K, V, N, P, E>).GetField("probationLru", BindingFlags.NonPublic | BindingFlags.Instance);
private static FieldInfo protectedLruField = typeof(ConcurrentLfuCore<K, V, N, P, E>).GetField("protectedLru", BindingFlags.NonPublic | BindingFlags.Instance);

private static FieldInfo readBufferField = typeof(ConcurrentLfuCore<K, V, N, P>).GetField("readBuffer", BindingFlags.NonPublic | BindingFlags.Instance);
private static FieldInfo writeBufferField = typeof(ConcurrentLfuCore<K, V, N, P>).GetField("writeBuffer", BindingFlags.NonPublic | BindingFlags.Instance);
private static FieldInfo readBufferField = typeof(ConcurrentLfuCore<K, V, N, P, E>).GetField("readBuffer", BindingFlags.NonPublic | BindingFlags.Instance);
private static FieldInfo writeBufferField = typeof(ConcurrentLfuCore<K, V, N, P, E>).GetField("writeBuffer", BindingFlags.NonPublic | BindingFlags.Instance);

public ConcurrentLfuIntegrityChecker(ConcurrentLfuCore<K, V, N, P> cache)
public ConcurrentLfuIntegrityChecker(ConcurrentLfuCore<K, V, N, P, E> cache)
{
this.cache = cache;

this.dictionary = (ConcurrentDictionary<K, N>)dictionaryField.GetValue(cache);

#if NET9_0_OR_GREATER
this.maintenanceLock = (Lock)lockField.GetValue(cache);
#else
this.maintenanceLock = lockField.GetValue(cache);
#endif

// get lrus via reflection
this.windowLru = (LfuNodeList<K, V>)windowLruField.GetValue(cache);
Expand Down
168 changes: 165 additions & 3 deletions BitFaster.Caching.UnitTests/Lfu/ConcurrentLfuTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,19 @@ public class ConcurrentLfuTests
private ConcurrentLfu<int, int> cache = new ConcurrentLfu<int, int>(1, 20, new BackgroundThreadScheduler(), EqualityComparer<int>.Default);
private ValueFactory valueFactory = new ValueFactory();

private List<ItemRemovedEventArgs<int, int>> removedItems = new List<ItemRemovedEventArgs<int, int>>();
private List<ItemUpdatedEventArgs<int, int>> updatedItems = new List<ItemUpdatedEventArgs<int, int>>();

private void OnLfuItemRemoved(object sender, ItemRemovedEventArgs<int, int> e)
{
removedItems.Add(e);
}

private void OnLfuItemUpdated(object sender, ItemUpdatedEventArgs<int, int> e)
{
updatedItems.Add(e);
}

public ConcurrentLfuTests(ITestOutputHelper output)
{
this.output = output;
Expand Down Expand Up @@ -525,9 +538,9 @@ public void ExpireAfterWriteIsDisabled()
}

[Fact]
public void EventsAreDisabled()
public void EventsAreEnabled()
{
cache.Events.HasValue.Should().BeFalse();
cache.Events.HasValue.Should().BeTrue();
}

[Fact]
Expand Down Expand Up @@ -815,9 +828,158 @@ private void LogLru()
{
#if DEBUG
this.output.WriteLine(cache.FormatLfuString());
#endif
#endif
}

[Fact]
public void WhenItemIsRemovedRemovedEventIsFired()
{
removedItems.Clear();
var lfuEvents = new ConcurrentLfu<int, int>(20);
lfuEvents.Events.Value.ItemRemoved += OnLfuItemRemoved;

lfuEvents.GetOrAdd(1, i => i + 2);

lfuEvents.TryRemove(1).Should().BeTrue();

// Maintenance is needed for events to be processed
lfuEvents.DoMaintenance();

removedItems.Count.Should().Be(1);
removedItems[0].Key.Should().Be(1);
removedItems[0].Value.Should().Be(3);
removedItems[0].Reason.Should().Be(ItemRemovedReason.Removed);
}

[Fact]
public void WhenItemRemovedEventIsUnregisteredEventIsNotFired()
{
removedItems.Clear();
var lfuEvents = new ConcurrentLfu<int, int>(20);

lfuEvents.Events.Value.ItemRemoved += OnLfuItemRemoved;
lfuEvents.Events.Value.ItemRemoved -= OnLfuItemRemoved;

lfuEvents.GetOrAdd(1, i => i + 1);
lfuEvents.TryRemove(1);
lfuEvents.DoMaintenance();

removedItems.Count.Should().Be(0);
}

[Fact]
public void WhenValueEvictedItemRemovedEventIsFired()
{
removedItems.Clear();
var lfuEvents = new ConcurrentLfu<int, int>(6);
lfuEvents.Events.Value.ItemRemoved += OnLfuItemRemoved;

// Fill cache to capacity
for (int i = 0; i < 6; i++)
{
lfuEvents.GetOrAdd(i, i => i);
}

// This should trigger eviction
lfuEvents.GetOrAdd(100, i => i);
lfuEvents.DoMaintenance();

// At least one item should be evicted
removedItems.Count.Should().Be(1);
removedItems.All(r => r.Reason == ItemRemovedReason.Evicted).Should().BeTrue();
}

[Fact]
public void WhenItemsAreTrimmedAnEventIsFired()
{
removedItems.Clear();
var lfuEvents = new ConcurrentLfu<int, int>(20);
lfuEvents.Events.Value.ItemRemoved += OnLfuItemRemoved;

for (int i = 0; i < 6; i++)
{
lfuEvents.GetOrAdd(i, i => i);
}

lfuEvents.Trim(2);

removedItems.Count.Should().Be(2);
removedItems.All(r => r.Reason == ItemRemovedReason.Trimmed).Should().BeTrue();
}

[Fact]
public void WhenItemsAreClearedAnEventIsFired()
{
removedItems.Clear();
var lfuEvents = new ConcurrentLfu<int, int>(20);
lfuEvents.Events.Value.ItemRemoved += OnLfuItemRemoved;

for (int i = 0; i < 6; i++)
{
lfuEvents.GetOrAdd(i, i => i);
}

lfuEvents.Clear();

removedItems.Count.Should().Be(6);
removedItems.All(r => r.Reason == ItemRemovedReason.Cleared).Should().BeTrue();
}

// backcompat: remove conditional compile
#if NETCOREAPP3_0_OR_GREATER
[Fact]
public void WhenItemExistsAddOrUpdateFiresUpdateEvent()
{
updatedItems.Clear();
var lfuEvents = new ConcurrentLfu<int, int>(20);
lfuEvents.Events.Value.ItemUpdated += OnLfuItemUpdated;

lfuEvents.AddOrUpdate(1, 2);
lfuEvents.AddOrUpdate(2, 3);

lfuEvents.AddOrUpdate(1, 3);

this.updatedItems.Count.Should().Be(1);
this.updatedItems[0].Key.Should().Be(1);
this.updatedItems[0].OldValue.Should().Be(2);
this.updatedItems[0].NewValue.Should().Be(3);
}

[Fact]
public void WhenItemExistsTryUpdateFiresUpdateEvent()
{
updatedItems.Clear();
var lfuEvents = new ConcurrentLfu<int, int>(20);
lfuEvents.Events.Value.ItemUpdated += OnLfuItemUpdated;

lfuEvents.AddOrUpdate(1, 2);
lfuEvents.AddOrUpdate(2, 3);

lfuEvents.TryUpdate(1, 3);

this.updatedItems.Count.Should().Be(1);
this.updatedItems[0].Key.Should().Be(1);
this.updatedItems[0].OldValue.Should().Be(2);
this.updatedItems[0].NewValue.Should().Be(3);
}

[Fact]
public void WhenItemUpdatedEventIsUnregisteredEventIsNotFired()
{
updatedItems.Clear();
var lfuEvents = new ConcurrentLfu<int, int>(20);

lfuEvents.Events.Value.ItemUpdated += OnLfuItemUpdated;
lfuEvents.Events.Value.ItemUpdated -= OnLfuItemUpdated;

lfuEvents.AddOrUpdate(1, 2);
lfuEvents.AddOrUpdate(1, 2);
lfuEvents.AddOrUpdate(1, 2);

updatedItems.Count.Should().Be(0);
}
#endif

public class ValueFactory
{
public int timesCalled;
Expand Down
Loading
Loading