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
14 changes: 14 additions & 0 deletions BitFaster.Caching.UnitTests/Lfu/ConcurrentLfuSoakTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,20 @@ await Threaded.Run(threads, () =>
await RunIntegrityCheckAsync(lfu, iteration);
}

[Theory]
[Repeat(soakIterations)]
public async Task WhenConcurrentTryGetAddOrUpdateAndTrimCacheEndsInConsistentState(int iteration)
{
const long trimAfter = 100;
string value = "x";

var trimmingCache = new TrimmingLfuCache(maxItems: 1_000_000, trimAfter: trimAfter);
Parallel.For(0, 5000, i => trimmingCache.AddWithTrim("x" + i, value));
trimmingCache.AddWithTrim("y", value);

await RunIntegrityCheckAsync(trimmingCache._cache, iteration);
}

#if NET9_0_OR_GREATER
[Theory]
[Repeat(soakIterations)]
Expand Down
74 changes: 74 additions & 0 deletions BitFaster.Caching.UnitTests/Lfu/TrimmingLfuCache.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
using System;
using System.Collections.Generic;
using System.Threading;
using BitFaster.Caching.Lfu;
using BitFaster.Caching.Scheduler;

namespace BitFaster.Caching.UnitTests.Lfu
{
// simple wrapper around ConcurrentLfu to trim the cache after a certain number of items have been added: repros reported bug
internal class TrimmingLfuCache
{
private const int MinTrimBatch = 16;
private const int MaxTrimIterations = 64;

private readonly long trimAfter;

private int trimInProgress;

public readonly ConcurrentLfu<string, string> _cache;

internal TrimmingLfuCache(int maxItems, long trimAfter)
{
this.trimAfter = trimAfter;

_cache = new ConcurrentLfu<string, string>(
Environment.ProcessorCount,
maxItems ,
new ForegroundScheduler(),
EqualityComparer<string>.Default);
}

public void AddWithTrim(string key, string value)
{
if (_cache.TryGet(key, out _))
{
return;
}

_cache.AddOrUpdate(key, value);

this.Trim();
}

private void Trim()
{
if (Interlocked.CompareExchange(ref trimInProgress, 1, 0) != 0)
{
return;
}

try
{
if (_cache.Policy.Eviction.HasValue)
{
IBoundedPolicy eviction = _cache.Policy.Eviction.Value!;
int iterations = 0;

long currentCount = _cache.Count;
while (_cache.Count > trimAfter && currentCount > 0 && iterations++ < MaxTrimIterations)
{
long over = currentCount - trimAfter;
int toTrim = (int)Math.Min(currentCount, Math.Max(MinTrimBatch, (over) + 1));
eviction.Trim(toTrim);
currentCount = _cache.Count;
}
}
}
finally
{
Volatile.Write(ref trimInProgress, 0);
}
}
}
}
8 changes: 4 additions & 4 deletions BitFaster.Caching/Lfu/ConcurrentLfuCore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -200,15 +200,15 @@ private void Trim(int itemCount, ItemRemovedReason reason)
TakeCandidatesInLruOrder(this.probationLru, candidates, itemCount);
TakeCandidatesInLruOrder(this.protectedLru, candidates, itemCount);
TakeCandidatesInLruOrder(this.windowLru, candidates, itemCount);
}

#if NET6_0_OR_GREATER
foreach (var candidate in CollectionsMarshal.AsSpan(candidates))
#else
foreach (var candidate in candidates)
foreach (var candidate in candidates)
#endif
{
Evict(candidate, reason);
{
Evict(candidate, reason);
}
}
}

Expand Down
Loading