From a71aa22715365b10e2fcdedd357fa63e6d87268e Mon Sep 17 00:00:00 2001 From: Turnerj Date: Sun, 26 Jun 2022 18:02:22 +0930 Subject: [PATCH 1/2] Remove abstract CacheEntry CacheEntry is now a record class with interfaces that expose the expiry property. All expiry dates are now rounded to the second. --- .../CacheStackBenchmark.cs | 186 +++++++++--------- .../ProtobufCacheSerializer.cs | 2 +- src/CacheTower/CacheEntry.cs | 153 ++++++-------- src/CacheTower/Internal/CacheEntryKeyLock.cs | 12 +- .../Providers/Memory/MemoryCacheLayer.cs | 2 +- 5 files changed, 165 insertions(+), 190 deletions(-) diff --git a/benchmarks/CacheTower.Benchmarks/CacheStackBenchmark.cs b/benchmarks/CacheTower.Benchmarks/CacheStackBenchmark.cs index 0f655077..43b4bd82 100644 --- a/benchmarks/CacheTower.Benchmarks/CacheStackBenchmark.cs +++ b/benchmarks/CacheTower.Benchmarks/CacheStackBenchmark.cs @@ -31,100 +31,100 @@ public ConfigSettings() } } - [Benchmark] - public async Task Set() - { - await using (var cacheStack = new CacheStack(new[] { new MemoryCacheLayer() }, Array.Empty())) - { - for (var i = 0; i < WorkIterations; i++) - { - await cacheStack.SetAsync("Set", 15, TimeSpan.FromDays(1)); - } - } - } - [Benchmark] - public async Task Set_TwoLayers() - { - await using (var cacheStack = new CacheStack(new[] { new MemoryCacheLayer(), new MemoryCacheLayer() }, Array.Empty())) - { - for (var i = 0; i < WorkIterations; i++) - { - await cacheStack.SetAsync("Set", 15, TimeSpan.FromDays(1)); - } - } - } - [Benchmark] - public async Task Evict() - { - await using (var cacheStack = new CacheStack(new[] { new MemoryCacheLayer() }, Array.Empty())) - { - for (var i = 0; i < WorkIterations; i++) - { - await cacheStack.SetAsync("Evict", 15, TimeSpan.FromDays(1)); - await cacheStack.EvictAsync("Evict"); - } - } - } - [Benchmark] - public async Task Evict_TwoLayers() - { - await using (var cacheStack = new CacheStack(new[] { new MemoryCacheLayer(), new MemoryCacheLayer() }, Array.Empty())) - { - for (var i = 0; i < WorkIterations; i++) - { - await cacheStack.SetAsync("Evict", 15, TimeSpan.FromDays(1)); - await cacheStack.EvictAsync("Evict"); - } - } - } - [Benchmark] - public async Task Cleanup() - { - await using (var cacheStack = new CacheStack(new[] { new MemoryCacheLayer() }, Array.Empty())) - { - for (var i = 0; i < WorkIterations; i++) - { - await cacheStack.SetAsync("Cleanup", 15, TimeSpan.FromDays(1)); - await cacheStack.CleanupAsync(); - } - } - } - [Benchmark] - public async Task Cleanup_TwoLayers() - { - await using (var cacheStack = new CacheStack(new[] { new MemoryCacheLayer(), new MemoryCacheLayer() }, Array.Empty())) - { - for (var i = 0; i < WorkIterations; i++) - { - await cacheStack.SetAsync("Cleanup", 15, TimeSpan.FromDays(1)); - await cacheStack.CleanupAsync(); - } - } - } - [Benchmark] - public async Task GetMiss() - { - await using (var cacheStack = new CacheStack(new[] { new MemoryCacheLayer() }, Array.Empty())) - { - for (var i = 0; i < WorkIterations; i++) - { - await cacheStack.GetAsync("GetMiss"); - } - } - } - [Benchmark] - public async Task GetHit() - { - await using (var cacheStack = new CacheStack(new[] { new MemoryCacheLayer() }, Array.Empty())) - { - await cacheStack.SetAsync("GetHit", 15, TimeSpan.FromDays(1)); + //[Benchmark] + //public async Task Set() + //{ + // await using (var cacheStack = new CacheStack(new[] { new MemoryCacheLayer() }, Array.Empty())) + // { + // for (var i = 0; i < WorkIterations; i++) + // { + // await cacheStack.SetAsync("Set", 15, TimeSpan.FromDays(1)); + // } + // } + //} + //[Benchmark] + //public async Task Set_TwoLayers() + //{ + // await using (var cacheStack = new CacheStack(new[] { new MemoryCacheLayer(), new MemoryCacheLayer() }, Array.Empty())) + // { + // for (var i = 0; i < WorkIterations; i++) + // { + // await cacheStack.SetAsync("Set", 15, TimeSpan.FromDays(1)); + // } + // } + //} + //[Benchmark] + //public async Task Evict() + //{ + // await using (var cacheStack = new CacheStack(new[] { new MemoryCacheLayer() }, Array.Empty())) + // { + // for (var i = 0; i < WorkIterations; i++) + // { + // await cacheStack.SetAsync("Evict", 15, TimeSpan.FromDays(1)); + // await cacheStack.EvictAsync("Evict"); + // } + // } + //} + //[Benchmark] + //public async Task Evict_TwoLayers() + //{ + // await using (var cacheStack = new CacheStack(new[] { new MemoryCacheLayer(), new MemoryCacheLayer() }, Array.Empty())) + // { + // for (var i = 0; i < WorkIterations; i++) + // { + // await cacheStack.SetAsync("Evict", 15, TimeSpan.FromDays(1)); + // await cacheStack.EvictAsync("Evict"); + // } + // } + //} + //[Benchmark] + //public async Task Cleanup() + //{ + // await using (var cacheStack = new CacheStack(new[] { new MemoryCacheLayer() }, Array.Empty())) + // { + // for (var i = 0; i < WorkIterations; i++) + // { + // await cacheStack.SetAsync("Cleanup", 15, TimeSpan.FromDays(1)); + // await cacheStack.CleanupAsync(); + // } + // } + //} + //[Benchmark] + //public async Task Cleanup_TwoLayers() + //{ + // await using (var cacheStack = new CacheStack(new[] { new MemoryCacheLayer(), new MemoryCacheLayer() }, Array.Empty())) + // { + // for (var i = 0; i < WorkIterations; i++) + // { + // await cacheStack.SetAsync("Cleanup", 15, TimeSpan.FromDays(1)); + // await cacheStack.CleanupAsync(); + // } + // } + //} + //[Benchmark] + //public async Task GetMiss() + //{ + // await using (var cacheStack = new CacheStack(new[] { new MemoryCacheLayer() }, Array.Empty())) + // { + // for (var i = 0; i < WorkIterations; i++) + // { + // await cacheStack.GetAsync("GetMiss"); + // } + // } + //} + //[Benchmark] + //public async Task GetHit() + //{ + // await using (var cacheStack = new CacheStack(new[] { new MemoryCacheLayer() }, Array.Empty())) + // { + // await cacheStack.SetAsync("GetHit", 15, TimeSpan.FromDays(1)); - for (var i = 0; i < WorkIterations; i++) - { - await cacheStack.GetAsync("GetHit"); - } - } - } + // for (var i = 0; i < WorkIterations; i++) + // { + // await cacheStack.GetAsync("GetHit"); + // } + // } + //} [Benchmark] public async Task GetOrSet_NeverStale() { diff --git a/src/CacheTower.Serializers.Protobuf/ProtobufCacheSerializer.cs b/src/CacheTower.Serializers.Protobuf/ProtobufCacheSerializer.cs index b0ed7cca..aa2e57eb 100644 --- a/src/CacheTower.Serializers.Protobuf/ProtobufCacheSerializer.cs +++ b/src/CacheTower.Serializers.Protobuf/ProtobufCacheSerializer.cs @@ -34,7 +34,7 @@ private static class SerializerConfig { static SerializerConfig() { - if (typeof(T).IsSubclassOf(typeof(CacheEntry))) + if (typeof(ICacheEntry).IsAssignableFrom(typeof(T))) { RuntimeTypeModel.Default.Add(typeof(T)) .Add(1, nameof(CacheEntry.Expiry)) diff --git a/src/CacheTower/CacheEntry.cs b/src/CacheTower/CacheEntry.cs index 12a7eced..3828c463 100644 --- a/src/CacheTower/CacheEntry.cs +++ b/src/CacheTower/CacheEntry.cs @@ -1,109 +1,84 @@ using System; -using System.Collections.Generic; using System.Runtime.CompilerServices; -using System.Text; using CacheTower.Internal; -namespace CacheTower +namespace CacheTower; + +/// +/// Container for the cache entry expiry date. +/// +public interface ICacheEntry { /// - /// Container for the cache entry expiry date. + /// The expiry date for the cache entry. /// - public abstract class CacheEntry - { - /// - /// The expiry date for the cache entry. - /// - public DateTime Expiry { get; } - - /// - /// Creates a new with the given expiry date. - /// - /// The expiry date of the cache entry. This will be rounded down to the second. - protected CacheEntry(DateTime expiry) - { - //Force the resolution of the expiry date to be to the second - Expiry = new DateTime( - expiry.Year, expiry.Month, expiry.Day, expiry.Hour, expiry.Minute, expiry.Second, DateTimeKind.Utc - ); - } + DateTime Expiry { get; } +} - /// - /// Calculates the stale date for the cache entry using the provided . - /// - /// - /// If is not configured, the stale date is the expiry date. - /// - /// The cache settings to use for the calculation. - /// The date that the cache entry can be considered stale. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public DateTime GetStaleDate(CacheSettings cacheSettings) - { - if (cacheSettings.StaleAfter.HasValue) - { - return Expiry - cacheSettings.TimeToLive + cacheSettings.StaleAfter!.Value; - } - else - { - return Expiry; - } - } - } +/// +/// Container for the cached value and its expiry date. +/// +/// +public interface ICacheEntry : ICacheEntry +{ + /// + /// The cached value. + /// + T? Value { get; } +} +/// +/// Extension methods for . +/// +public static class CacheEntryExtensions +{ /// - /// Container for both the cached value and its expiry date. + /// Calculates the stale date for an based on the 's expiry and . /// - /// - public class CacheEntry : CacheEntry, IEquatable?> + /// + /// When is , this will return the 's expiry. + /// + /// The cache entry to get the stale date for. + /// The cache settings to use as part of the stale date calculation. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static DateTime GetStaleDate(this ICacheEntry cacheEntry, CacheSettings cacheSettings) { - /// - /// The cached value. - /// - public T? Value { get; } - - /// - /// Creates a new with the given and an expiry adjusted to the . - /// - /// The value to cache. - /// The amount of time before the cache entry expires. - internal CacheEntry(T? value, TimeSpan timeToLive) : this(value, DateTimeProvider.Now + timeToLive) { } - /// - /// Creates a new with the given and . - /// - /// The value to cache. - /// The expiry date of the cache entry. This will be rounded down to the second. - public CacheEntry(T? value, DateTime expiry) : base(expiry) + if (cacheSettings.StaleAfter.HasValue) { - Value = value; + return cacheEntry.Expiry - cacheSettings.TimeToLive + cacheSettings.StaleAfter!.Value; } - - /// - public bool Equals(CacheEntry? other) + else { - if (other == null) - { - return false; - } - - return Equals(Value, other.Value) && - Expiry == other.Expiry; + return cacheEntry.Expiry; } + } +} - /// - public override bool Equals(object? obj) - { - if (obj is CacheEntry objOfType) - { - return Equals(objOfType); - } +/// +/// Container for both the cached value and its expiry date. +/// +/// +/// The value to cache. +/// The expiry date of the cache entry. This will be rounded down to the second. +public sealed record CacheEntry(T? Value, DateTime Expiry) : ICacheEntry +{ + /// + /// The cached value. + /// + public T? Value { get; } = Value; - return false; - } + /// + /// The expiry date for the cache entry. + /// + public DateTime Expiry { get; } = new DateTime( + Expiry.Year, Expiry.Month, Expiry.Day, Expiry.Hour, Expiry.Minute, Expiry.Second, DateTimeKind.Utc + ); - /// - public override int GetHashCode() - { - return (Value?.GetHashCode() ?? 1) ^ Expiry.GetHashCode(); - } - } + /// + /// Creates a new with the given and an expiry adjusted to the . + /// + /// The value to cache. + /// The amount of time before the cache entry expires. + internal CacheEntry(T? value, TimeSpan timeToLive) : this(value, DateTimeProvider.Now + timeToLive) { } } diff --git a/src/CacheTower/Internal/CacheEntryKeyLock.cs b/src/CacheTower/Internal/CacheEntryKeyLock.cs index 6ec853ff..62287389 100644 --- a/src/CacheTower/Internal/CacheEntryKeyLock.cs +++ b/src/CacheTower/Internal/CacheEntryKeyLock.cs @@ -7,7 +7,7 @@ namespace CacheTower.Internal; internal readonly struct CacheEntryKeyLock { - private readonly Dictionary?> keyLocks = new(StringComparer.Ordinal); + private readonly Dictionary?> keyLocks = new(StringComparer.Ordinal); public CacheEntryKeyLock() { } @@ -28,15 +28,15 @@ public bool AcquireLock(string cacheKey) } } - public Task WaitAsync(string cacheKey) + public Task WaitAsync(string cacheKey) { - TaskCompletionSource? completionSource; + TaskCompletionSource? completionSource; lock (keyLocks) { if (!keyLocks.TryGetValue(cacheKey, out completionSource) || completionSource == null) { - completionSource = new TaskCompletionSource(); + completionSource = new TaskCompletionSource(); keyLocks[cacheKey] = completionSource; } } @@ -45,7 +45,7 @@ public Task WaitAsync(string cacheKey) } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private bool TryRemove(string cacheKey, out TaskCompletionSource? completionSource) + private bool TryRemove(string cacheKey, out TaskCompletionSource? completionSource) { lock (keyLocks) { @@ -62,7 +62,7 @@ private bool TryRemove(string cacheKey, out TaskCompletionSource? co } } - public void ReleaseLock(string cacheKey, CacheEntry cacheEntry) + public void ReleaseLock(string cacheKey, ICacheEntry cacheEntry) { if (TryRemove(cacheKey, out var completionSource)) { diff --git a/src/CacheTower/Providers/Memory/MemoryCacheLayer.cs b/src/CacheTower/Providers/Memory/MemoryCacheLayer.cs index 4d16e42c..abec8f22 100644 --- a/src/CacheTower/Providers/Memory/MemoryCacheLayer.cs +++ b/src/CacheTower/Providers/Memory/MemoryCacheLayer.cs @@ -17,7 +17,7 @@ namespace CacheTower.Providers.Memory /// public class MemoryCacheLayer : ILocalCacheLayer { - private readonly ConcurrentDictionary Cache = new(StringComparer.Ordinal); + private readonly ConcurrentDictionary Cache = new(StringComparer.Ordinal); /// public ValueTask CleanupAsync() From b4878fe3d3f8cc09ced1bcee38d3b2a6b5f78674 Mon Sep 17 00:00:00 2001 From: Turnerj Date: Sun, 26 Jun 2022 18:03:51 +0930 Subject: [PATCH 2/2] Uncomment tests --- .../CacheStackBenchmark.cs | 186 +++++++++--------- 1 file changed, 93 insertions(+), 93 deletions(-) diff --git a/benchmarks/CacheTower.Benchmarks/CacheStackBenchmark.cs b/benchmarks/CacheTower.Benchmarks/CacheStackBenchmark.cs index 43b4bd82..0f655077 100644 --- a/benchmarks/CacheTower.Benchmarks/CacheStackBenchmark.cs +++ b/benchmarks/CacheTower.Benchmarks/CacheStackBenchmark.cs @@ -31,100 +31,100 @@ public ConfigSettings() } } - //[Benchmark] - //public async Task Set() - //{ - // await using (var cacheStack = new CacheStack(new[] { new MemoryCacheLayer() }, Array.Empty())) - // { - // for (var i = 0; i < WorkIterations; i++) - // { - // await cacheStack.SetAsync("Set", 15, TimeSpan.FromDays(1)); - // } - // } - //} - //[Benchmark] - //public async Task Set_TwoLayers() - //{ - // await using (var cacheStack = new CacheStack(new[] { new MemoryCacheLayer(), new MemoryCacheLayer() }, Array.Empty())) - // { - // for (var i = 0; i < WorkIterations; i++) - // { - // await cacheStack.SetAsync("Set", 15, TimeSpan.FromDays(1)); - // } - // } - //} - //[Benchmark] - //public async Task Evict() - //{ - // await using (var cacheStack = new CacheStack(new[] { new MemoryCacheLayer() }, Array.Empty())) - // { - // for (var i = 0; i < WorkIterations; i++) - // { - // await cacheStack.SetAsync("Evict", 15, TimeSpan.FromDays(1)); - // await cacheStack.EvictAsync("Evict"); - // } - // } - //} - //[Benchmark] - //public async Task Evict_TwoLayers() - //{ - // await using (var cacheStack = new CacheStack(new[] { new MemoryCacheLayer(), new MemoryCacheLayer() }, Array.Empty())) - // { - // for (var i = 0; i < WorkIterations; i++) - // { - // await cacheStack.SetAsync("Evict", 15, TimeSpan.FromDays(1)); - // await cacheStack.EvictAsync("Evict"); - // } - // } - //} - //[Benchmark] - //public async Task Cleanup() - //{ - // await using (var cacheStack = new CacheStack(new[] { new MemoryCacheLayer() }, Array.Empty())) - // { - // for (var i = 0; i < WorkIterations; i++) - // { - // await cacheStack.SetAsync("Cleanup", 15, TimeSpan.FromDays(1)); - // await cacheStack.CleanupAsync(); - // } - // } - //} - //[Benchmark] - //public async Task Cleanup_TwoLayers() - //{ - // await using (var cacheStack = new CacheStack(new[] { new MemoryCacheLayer(), new MemoryCacheLayer() }, Array.Empty())) - // { - // for (var i = 0; i < WorkIterations; i++) - // { - // await cacheStack.SetAsync("Cleanup", 15, TimeSpan.FromDays(1)); - // await cacheStack.CleanupAsync(); - // } - // } - //} - //[Benchmark] - //public async Task GetMiss() - //{ - // await using (var cacheStack = new CacheStack(new[] { new MemoryCacheLayer() }, Array.Empty())) - // { - // for (var i = 0; i < WorkIterations; i++) - // { - // await cacheStack.GetAsync("GetMiss"); - // } - // } - //} - //[Benchmark] - //public async Task GetHit() - //{ - // await using (var cacheStack = new CacheStack(new[] { new MemoryCacheLayer() }, Array.Empty())) - // { - // await cacheStack.SetAsync("GetHit", 15, TimeSpan.FromDays(1)); + [Benchmark] + public async Task Set() + { + await using (var cacheStack = new CacheStack(new[] { new MemoryCacheLayer() }, Array.Empty())) + { + for (var i = 0; i < WorkIterations; i++) + { + await cacheStack.SetAsync("Set", 15, TimeSpan.FromDays(1)); + } + } + } + [Benchmark] + public async Task Set_TwoLayers() + { + await using (var cacheStack = new CacheStack(new[] { new MemoryCacheLayer(), new MemoryCacheLayer() }, Array.Empty())) + { + for (var i = 0; i < WorkIterations; i++) + { + await cacheStack.SetAsync("Set", 15, TimeSpan.FromDays(1)); + } + } + } + [Benchmark] + public async Task Evict() + { + await using (var cacheStack = new CacheStack(new[] { new MemoryCacheLayer() }, Array.Empty())) + { + for (var i = 0; i < WorkIterations; i++) + { + await cacheStack.SetAsync("Evict", 15, TimeSpan.FromDays(1)); + await cacheStack.EvictAsync("Evict"); + } + } + } + [Benchmark] + public async Task Evict_TwoLayers() + { + await using (var cacheStack = new CacheStack(new[] { new MemoryCacheLayer(), new MemoryCacheLayer() }, Array.Empty())) + { + for (var i = 0; i < WorkIterations; i++) + { + await cacheStack.SetAsync("Evict", 15, TimeSpan.FromDays(1)); + await cacheStack.EvictAsync("Evict"); + } + } + } + [Benchmark] + public async Task Cleanup() + { + await using (var cacheStack = new CacheStack(new[] { new MemoryCacheLayer() }, Array.Empty())) + { + for (var i = 0; i < WorkIterations; i++) + { + await cacheStack.SetAsync("Cleanup", 15, TimeSpan.FromDays(1)); + await cacheStack.CleanupAsync(); + } + } + } + [Benchmark] + public async Task Cleanup_TwoLayers() + { + await using (var cacheStack = new CacheStack(new[] { new MemoryCacheLayer(), new MemoryCacheLayer() }, Array.Empty())) + { + for (var i = 0; i < WorkIterations; i++) + { + await cacheStack.SetAsync("Cleanup", 15, TimeSpan.FromDays(1)); + await cacheStack.CleanupAsync(); + } + } + } + [Benchmark] + public async Task GetMiss() + { + await using (var cacheStack = new CacheStack(new[] { new MemoryCacheLayer() }, Array.Empty())) + { + for (var i = 0; i < WorkIterations; i++) + { + await cacheStack.GetAsync("GetMiss"); + } + } + } + [Benchmark] + public async Task GetHit() + { + await using (var cacheStack = new CacheStack(new[] { new MemoryCacheLayer() }, Array.Empty())) + { + await cacheStack.SetAsync("GetHit", 15, TimeSpan.FromDays(1)); - // for (var i = 0; i < WorkIterations; i++) - // { - // await cacheStack.GetAsync("GetHit"); - // } - // } - //} + for (var i = 0; i < WorkIterations; i++) + { + await cacheStack.GetAsync("GetHit"); + } + } + } [Benchmark] public async Task GetOrSet_NeverStale() {