Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ConcurrentLfu time-based expiry #516

Merged
merged 34 commits into from
May 8, 2024
Merged
Show file tree
Hide file tree
Changes from 25 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
44 changes: 44 additions & 0 deletions BitFaster.Caching.UnitTests/Lfu/ConcurrentTLfuSoakTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using BitFaster.Caching.Lfu;
using Xunit;
using Xunit.Abstractions;

namespace BitFaster.Caching.UnitTests.Lfu
{
[Collection("Soak")]
public class ConcurrentTLfuSoakTests
{
private const int soakIterations = 10;
private const int threads = 4;
private const int loopIterations = 100_000;

private readonly ITestOutputHelper output;

public ConcurrentTLfuSoakTests(ITestOutputHelper testOutputHelper)
{
this.output = testOutputHelper;
}

[Theory]
[Repeat(soakIterations)]
public async Task GetOrAddWithExpiry(int iteration)
{
var lfu = new ConcurrentTLfu<int, string>(20, new ExpireAfterWrite<int, string>(TimeSpan.FromMilliseconds(10)));

await Threaded.RunAsync(threads, async () => {
for (int i = 0; i < loopIterations; i++)
{
await lfu.GetOrAddAsync(i + 1, i => Task.FromResult(i.ToString()));
}
});

this.output.WriteLine($"iteration {iteration} keys={string.Join(" ", lfu.Keys)}");

// TODO: integrity check, including TimerWheel
Dismissed Show dismissed Hide dismissed
}
}
}
123 changes: 123 additions & 0 deletions BitFaster.Caching.UnitTests/Lfu/ConcurrentTLfuTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
using System;
using System.Runtime.InteropServices;
using BitFaster.Caching.Lfu;
using FluentAssertions;
using Xunit;

namespace BitFaster.Caching.UnitTests.Lfu
{
// This could use foreground scheduler to make it more deterministic.
public class ConcurrentTLfuTests
{
private readonly TimeSpan timeToLive = TimeSpan.FromMilliseconds(200);
private readonly int capacity = 9;
private ConcurrentTLfu<int, string> lfu;

private Lru.ValueFactory valueFactory = new Lru.ValueFactory();

// on MacOS time measurement seems to be less stable, give longer pause
private int ttlWaitMlutiplier = RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 8 : 2;

public ConcurrentTLfuTests()
{
lfu = new ConcurrentTLfu<int, string>(capacity, new ExpireAfterWrite<int, string>(timeToLive));
}

[Fact]
public void WhenCalculatorIsAfterWritePolicyIsAfterWrite()
{
lfu.Policy.ExpireAfterWrite.HasValue.Should().BeTrue();
lfu.Policy.ExpireAfterWrite.Value.TimeToLive.Should().Be(timeToLive);
}

[Fact]
public void WhenCalculatorIsAfterAccessPolicyIsAfterAccess()
{
lfu = new ConcurrentTLfu<int, string>(capacity, new ExpireAfterAccess<int, string>(timeToLive));

lfu.Policy.ExpireAfterAccess.HasValue.Should().BeTrue();
lfu.Policy.ExpireAfterAccess.Value.TimeToLive.Should().Be(timeToLive);
}

[Fact]
public void WhenCalculatorIsCustomPolicyIsAfter()
{
lfu = new ConcurrentTLfu<int, string>(capacity, new TestExpiryCalculator<int, string>());

lfu.Policy.ExpireAfter.HasValue.Should().BeTrue();
lfu.TimeToLive.Should().Be(TimeSpan.Zero);
}

// policy can expire after write

[Fact]
public void WhenItemIsNotExpiredItIsNotRemoved()
{
lfu.GetOrAdd(1, valueFactory.Create);

lfu.TryGet(1, out var value).Should().BeTrue();
}

[Fact]
public void WhenItemIsExpiredItIsRemoved()
{
Timed.Execute(
lfu,
lfu =>
{
lfu.GetOrAdd(1, valueFactory.Create);
return lfu;
},
timeToLive.MultiplyBy(ttlWaitMlutiplier),
lfu =>
{
lfu.TryGet(1, out var value).Should().BeFalse();
}
);
}

[Fact]
public void WhenItemIsExpiredItIsRemoved2()
{
Timed.Execute(
lfu,
lfu =>
{
lfu.GetOrAdd(1, valueFactory.Create);
return lfu;
},
TimeSpan.FromSeconds(2),
lfu =>
{
// This is a bit flaky - seems like it doesnt always
// remove the item
lfu.Policy.ExpireAfterWrite.Value.TrimExpired();
lfu.Count.Should().Be(0);
}
);
}

[Fact]
public void WhenItemIsUpdatedTtlIsExtended()
{
Timed.Execute(
lfu,
lfu =>
{
lfu.GetOrAdd(1, valueFactory.Create);
return lfu;
},
timeToLive.MultiplyBy(ttlWaitMlutiplier),
lfu =>
{
lfu.TryUpdate(1, "3");

// If we defer computing time to the maintenance loop, we
// need to call maintenance here for the timestamp to be updated
lfu.DoMaintenance();
lfu.TryGet(1, out var value).Should().BeTrue();

Check failure on line 118 in BitFaster.Caching.UnitTests/Lfu/ConcurrentTLfuTests.cs

View workflow job for this annotation

GitHub Actions / test results (win net4.8)

BitFaster.Caching.UnitTests.Lfu.ConcurrentTLfuTests ► WhenItemIsUpdatedTtlIsExtended

Failed test found in: BitFaster.Caching.UnitTests/TestResults/results4.trx Error: Expected lfu.TryGet(1, out var value) to be true, but found False.
Raw output
Expected lfu.TryGet(1, out var value) to be true, but found False.
   at FluentAssertions.Execution.XUnit2TestFramework.Throw(String message)
   at FluentAssertions.Execution.AssertionScope.FailWith(Func`1 failReasonFunc)
   at FluentAssertions.Primitives.BooleanAssertions`1.BeTrue(String because, Object[] becauseArgs)
   at BitFaster.Caching.UnitTests.Lfu.ConcurrentTLfuTests.<>c.<WhenItemIsUpdatedTtlIsExtended>b__12_1(ConcurrentTLfu`2 lfu) in D:\a\BitFaster.Caching\BitFaster.Caching\BitFaster.Caching.UnitTests\Lfu\ConcurrentTLfuTests.cs:line 118
   at BitFaster.Caching.UnitTests.Timed.Execute[TArg,TState](TArg arg, Func`2 first, TimeSpan pause, Action`1 second) in D:\a\BitFaster.Caching\BitFaster.Caching\BitFaster.Caching.UnitTests\Timed.cs:line 33
   at BitFaster.Caching.UnitTests.Lfu.ConcurrentTLfuTests.WhenItemIsUpdatedTtlIsExtended() in D:\a\BitFaster.Caching\BitFaster.Caching\BitFaster.Caching.UnitTests\Lfu\ConcurrentTLfuTests.cs:line 103
}
);
}
}
}
Loading
Loading