-
Notifications
You must be signed in to change notification settings - Fork 36
ConcurrentLfu time-based expiry #516
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
Merged
Merged
Changes from all commits
Commits
Show all changes
34 commits
Select commit
Hold shift + click to select a range
518bc9f
wheel+nodes
d7207d8
fix typo
51d8b34
64bit
acd21e4
merge
deb2ecf
+ConcurrentLfuCore
6cfa3cc
undo bitops
5c8c38c
undo bitops
31eb6b7
node
1b7f5ce
merge
73cdbae
cleanup merge
1fc15fa
simplify generics
4b35e07
outline tests
dc49f73
more tests
ff36c1a
comments
728f14a
merge
e6fcbc1
use Duration, cleanup
f65a95e
rough end to end
97cc90a
merge
e4a81fc
nullability static analysis
a7ba4c8
schedule
4634517
assert wheel pos
d486add
port all tests
52ee735
test e2e
5950e1a
policy
2e3056a
cleanup
94bcf44
test coverage
462e515
explicit interface impl
50cb404
rem comment
1f64737
Merge branch 'main' into users/alexpeck/lfuexpire
44ebb9b
Merge branch 'main' into users/alexpeck/lfuexpire
24fcff6
Merge branch 'main' into users/alexpeck/lfuexpire
bitfaster 769d9c1
Merge branch 'main' into users/alexpeck/lfuexpire
bitfaster 3196417
merge
3e24514
mem layout
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
using System; | ||
using FluentAssertions; | ||
using Xunit; | ||
|
||
namespace BitFaster.Caching.UnitTests | ||
{ | ||
public class ExpireAfterAccessTests | ||
{ | ||
private readonly Duration expiry = Duration.FromMinutes(1); | ||
private readonly ExpireAfterAccess<int, int> expiryCalculator; | ||
|
||
public ExpireAfterAccessTests() | ||
{ | ||
expiryCalculator = new(expiry.ToTimeSpan()); | ||
} | ||
|
||
[Fact] | ||
public void TimeToExpireReturnsCtorArg() | ||
{ | ||
expiryCalculator.TimeToExpire.Should().Be(expiry.ToTimeSpan()); | ||
} | ||
|
||
[Fact] | ||
public void AfterCreateReturnsTimeToExpire() | ||
{ | ||
expiryCalculator.GetExpireAfterCreate(1, 2).Should().Be(expiry); | ||
} | ||
|
||
[Fact] | ||
public void AfteReadReturnsTimeToExpire() | ||
{ | ||
expiryCalculator.GetExpireAfterRead(1, 2, Duration.SinceEpoch()).Should().Be(expiry); | ||
} | ||
|
||
[Fact] | ||
public void AfteUpdateReturnsTimeToExpire() | ||
{ | ||
expiryCalculator.GetExpireAfterUpdate(1, 2, Duration.SinceEpoch()).Should().Be(expiry); | ||
} | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
using System; | ||
using FluentAssertions; | ||
using Xunit; | ||
|
||
namespace BitFaster.Caching.UnitTests | ||
{ | ||
public class ExpireAfterWriteTests | ||
{ | ||
private readonly Duration expiry = Duration.FromMinutes(1); | ||
private readonly ExpireAfterWrite<int, int> expiryCalculator; | ||
|
||
public ExpireAfterWriteTests() | ||
{ | ||
expiryCalculator = new(expiry.ToTimeSpan()); | ||
} | ||
|
||
[Fact] | ||
public void TimeToExpireReturnsCtorArg() | ||
{ | ||
expiryCalculator.TimeToExpire.Should().Be(expiry.ToTimeSpan()); | ||
} | ||
|
||
[Fact] | ||
public void AfterCreateReturnsTimeToExpire() | ||
{ | ||
expiryCalculator.GetExpireAfterCreate(1, 2).Should().Be(expiry); | ||
} | ||
|
||
[Fact] | ||
public void AfteReadReturnsCurrentTimeToExpire() | ||
{ | ||
var current = new Duration(123); | ||
expiryCalculator.GetExpireAfterRead(1, 2, current).Should().Be(current); | ||
} | ||
|
||
[Fact] | ||
public void AfteUpdateReturnsTimeToExpire() | ||
{ | ||
expiryCalculator.GetExpireAfterUpdate(1, 2, Duration.SinceEpoch()).Should().Be(expiry); | ||
} | ||
} | ||
} |
202 changes: 202 additions & 0 deletions
202
BitFaster.Caching.UnitTests/Lfu/ConcurrentLfuCoreTests.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,202 @@ | ||
using System; | ||
using System.Collections; | ||
using System.Collections.Generic; | ||
using System.Threading.Tasks; | ||
using BitFaster.Caching.Lfu; | ||
using FluentAssertions; | ||
using Xunit; | ||
|
||
namespace BitFaster.Caching.UnitTests.Lfu | ||
{ | ||
public abstract class ConcurrentLfuCoreTests | ||
{ | ||
protected readonly TimeSpan timeToLive = TimeSpan.FromMilliseconds(200); | ||
protected readonly int capacity = 20; | ||
|
||
private ConcurrentLfuTests.ValueFactory valueFactory = new(); | ||
|
||
private ICache<int, int> lfu; | ||
|
||
public abstract ICache<K, V> Create<K,V>(); | ||
public abstract void DoMaintenance<K, V>(ICache<K, V> cache); | ||
|
||
public ConcurrentLfuCoreTests() | ||
{ | ||
lfu = Create<int, int>(); | ||
} | ||
|
||
[Fact] | ||
public void EvictionPolicyCapacityReturnsCapacity() | ||
{ | ||
lfu.Policy.Eviction.Value.Capacity.Should().Be(capacity); | ||
} | ||
|
||
[Fact] | ||
public void WhenKeyIsRequestedItIsCreatedAndCached() | ||
{ | ||
var result1 = lfu.GetOrAdd(1, valueFactory.Create); | ||
var result2 = lfu.GetOrAdd(1, valueFactory.Create); | ||
|
||
valueFactory.timesCalled.Should().Be(1); | ||
result1.Should().Be(result2); | ||
} | ||
#if NETCOREAPP3_0_OR_GREATER | ||
[Fact] | ||
public void WhenKeyIsRequestedWithArgItIsCreatedAndCached() | ||
{ | ||
var result1 = lfu.GetOrAdd(1, valueFactory.Create, 9); | ||
var result2 = lfu.GetOrAdd(1, valueFactory.Create, 17); | ||
|
||
valueFactory.timesCalled.Should().Be(1); | ||
result1.Should().Be(result2); | ||
} | ||
#endif | ||
[Fact] | ||
public async Task WhenKeyIsRequesteItIsCreatedAndCachedAsync() | ||
{ | ||
var asyncLfu = lfu as IAsyncCache<int, int>; | ||
var result1 = await asyncLfu.GetOrAddAsync(1, valueFactory.CreateAsync); | ||
var result2 = await asyncLfu.GetOrAddAsync(1, valueFactory.CreateAsync); | ||
|
||
valueFactory.timesCalled.Should().Be(1); | ||
result1.Should().Be(result2); | ||
} | ||
|
||
#if NETCOREAPP3_0_OR_GREATER | ||
[Fact] | ||
public async Task WhenKeyIsRequestedWithArgItIsCreatedAndCachedAsync() | ||
{ | ||
var asyncLfu = lfu as IAsyncCache<int, int>; | ||
var result1 = await asyncLfu.GetOrAddAsync(1, valueFactory.CreateAsync, 9); | ||
var result2 = await asyncLfu.GetOrAddAsync(1, valueFactory.CreateAsync, 17); | ||
|
||
valueFactory.timesCalled.Should().Be(1); | ||
result1.Should().Be(result2); | ||
} | ||
#endif | ||
|
||
[Fact] | ||
public void WhenItemIsUpdatedItIsUpdated() | ||
{ | ||
lfu.GetOrAdd(1, k => k); | ||
lfu.AddOrUpdate(1, 2); | ||
|
||
lfu.TryGet(1, out var value).Should().BeTrue(); | ||
value.Should().Be(2); | ||
} | ||
|
||
[Fact] | ||
public void WhenItemDoesNotExistUpdatedAddsItem() | ||
{ | ||
lfu.AddOrUpdate(1, 2); | ||
|
||
lfu.TryGet(1, out var value).Should().BeTrue(); | ||
value.Should().Be(2); | ||
} | ||
|
||
|
||
[Fact] | ||
public void WhenKeyExistsTryRemoveRemovesItem() | ||
{ | ||
lfu.GetOrAdd(1, k => k); | ||
|
||
lfu.TryRemove(1).Should().BeTrue(); | ||
lfu.TryGet(1, out _).Should().BeFalse(); | ||
} | ||
|
||
#if NETCOREAPP3_0_OR_GREATER | ||
[Fact] | ||
public void WhenKeyExistsTryRemoveReturnsValue() | ||
{ | ||
lfu.GetOrAdd(1, valueFactory.Create); | ||
|
||
lfu.TryRemove(1, out var value).Should().BeTrue(); | ||
value.Should().Be(1); | ||
} | ||
|
||
[Fact] | ||
public void WhenItemExistsTryRemoveRemovesItem() | ||
{ | ||
lfu.GetOrAdd(1, k => k); | ||
|
||
lfu.TryRemove(new KeyValuePair<int, int>(1, 1)).Should().BeTrue(); | ||
lfu.TryGet(1, out _).Should().BeFalse(); | ||
} | ||
|
||
[Fact] | ||
public void WhenItemDoesntMatchTryRemoveDoesNotRemove() | ||
{ | ||
lfu.GetOrAdd(1, k => k); | ||
|
||
lfu.TryRemove(new KeyValuePair<int, int>(1, 2)).Should().BeFalse(); | ||
lfu.TryGet(1, out var value).Should().BeTrue(); | ||
} | ||
#endif | ||
|
||
[Fact] | ||
public void WhenClearedCacheIsEmpty() | ||
{ | ||
lfu.GetOrAdd(1, k => k); | ||
lfu.GetOrAdd(2, k => k); | ||
|
||
lfu.Clear(); | ||
|
||
lfu.Count.Should().Be(0); | ||
lfu.TryGet(1, out var _).Should().BeFalse(); | ||
} | ||
|
||
[Fact] | ||
public void TrimRemovesNItems() | ||
{ | ||
for (int i = 0; i < 25; i++) | ||
{ | ||
lfu.GetOrAdd(i, k => k); | ||
} | ||
DoMaintenance<int, int>(lfu); | ||
|
||
lfu.Count.Should().Be(20); | ||
|
||
lfu.Policy.Eviction.Value.Trim(5); | ||
DoMaintenance<int, int>(lfu); | ||
|
||
lfu.Count.Should().Be(15); | ||
} | ||
|
||
[Fact] | ||
public void WhenItemsAddedGenericEnumerateContainsKvps() | ||
{ | ||
lfu.GetOrAdd(1, k => k); | ||
lfu.GetOrAdd(2, k => k); | ||
|
||
var enumerator = lfu.GetEnumerator(); | ||
enumerator.MoveNext().Should().BeTrue(); | ||
enumerator.Current.Should().Be(new KeyValuePair<int, int>(1, 1)); | ||
enumerator.MoveNext().Should().BeTrue(); | ||
enumerator.Current.Should().Be(new KeyValuePair<int, int>(2, 2)); | ||
} | ||
|
||
[Fact] | ||
public void WhenItemsAddedEnumerateContainsKvps() | ||
{ | ||
lfu.GetOrAdd(1, k => k); | ||
lfu.GetOrAdd(2, k => k); | ||
|
||
var enumerable = (IEnumerable)lfu; | ||
enumerable.Should().BeEquivalentTo(new[] { new KeyValuePair<int, int>(1, 1), new KeyValuePair<int, int>(2, 2) }); | ||
} | ||
} | ||
|
||
public class ConcurrentTLfuWrapperTests : ConcurrentLfuCoreTests | ||
{ | ||
public override ICache<K, V> Create<K,V>() | ||
{ | ||
return new ConcurrentTLfu<K, V>(capacity, new ExpireAfterWrite<K, V>(timeToLive)); | ||
} | ||
|
||
public override void DoMaintenance<K, V>(ICache<K, V> cache) | ||
{ | ||
var tlfu = cache as ConcurrentTLfu<K, V>; | ||
tlfu?.DoMaintenance(); | ||
} | ||
} | ||
} |
44 changes: 44 additions & 0 deletions
44
BitFaster.Caching.UnitTests/Lfu/ConcurrentTLfuSoakTests.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
} | ||
} |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.