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

Remove Abstract CacheEntry / Use C# Records #206

Merged
merged 2 commits into from
Jun 26, 2022
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
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ private static class SerializerConfig<T>
{
static SerializerConfig()
{
if (typeof(T).IsSubclassOf(typeof(CacheEntry)))
if (typeof(ICacheEntry).IsAssignableFrom(typeof(T)))
{
RuntimeTypeModel.Default.Add(typeof(T))
.Add(1, nameof(CacheEntry<object>.Expiry))
Expand Down
153 changes: 64 additions & 89 deletions src/CacheTower/CacheEntry.cs
Original file line number Diff line number Diff line change
@@ -1,109 +1,84 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Text;
using CacheTower.Internal;

namespace CacheTower
namespace CacheTower;

/// <summary>
/// Container for the cache entry expiry date.
/// </summary>
public interface ICacheEntry
{
/// <summary>
/// Container for the cache entry expiry date.
/// The expiry date for the cache entry.
/// </summary>
public abstract class CacheEntry
{
/// <summary>
/// The expiry date for the cache entry.
/// </summary>
public DateTime Expiry { get; }

/// <summary>
/// Creates a new <see cref="CacheEntry"/> with the given expiry date.
/// </summary>
/// <param name="expiry">The expiry date of the cache entry. This will be rounded down to the second.</param>
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; }
}

/// <summary>
/// Calculates the stale date for the cache entry using the provided <paramref name="cacheSettings"/>.
/// </summary>
/// <remarks>
/// If <see cref="CacheSettings.StaleAfter"/> is not configured, the stale date is the expiry date.
/// </remarks>
/// <param name="cacheSettings">The cache settings to use for the calculation.</param>
/// <returns>The date that the cache entry can be considered stale.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public DateTime GetStaleDate(CacheSettings cacheSettings)
{
if (cacheSettings.StaleAfter.HasValue)
{
return Expiry - cacheSettings.TimeToLive + cacheSettings.StaleAfter!.Value;
}
else
{
return Expiry;
}
}
}
/// <summary>
/// Container for the cached value and its expiry date.
/// </summary>
/// <typeparam name="T"></typeparam>
public interface ICacheEntry<T> : ICacheEntry
{
/// <summary>
/// The cached value.
/// </summary>
T? Value { get; }
}

/// <summary>
/// Extension methods for <see cref="ICacheEntry"/>.
/// </summary>
public static class CacheEntryExtensions
{
/// <summary>
/// Container for both the cached value and its expiry date.
/// Calculates the stale date for an <see cref="ICacheEntry"/> based on the <paramref name="cacheEntry"/>'s expiry and <paramref name="cacheSettings"/>.
/// </summary>
/// <typeparam name="T"></typeparam>
public class CacheEntry<T> : CacheEntry, IEquatable<CacheEntry<T?>?>
/// <remarks>
/// When <see cref="CacheSettings.StaleAfter"/> is <see langword="null"/>, this will return the <paramref name="cacheEntry"/>'s expiry.
/// </remarks>
/// <param name="cacheEntry">The cache entry to get the stale date for.</param>
/// <param name="cacheSettings">The cache settings to use as part of the stale date calculation.</param>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static DateTime GetStaleDate(this ICacheEntry cacheEntry, CacheSettings cacheSettings)
{
/// <summary>
/// The cached value.
/// </summary>
public T? Value { get; }

/// <summary>
/// Creates a new <see cref="CacheEntry"/> with the given <paramref name="value"/> and an expiry adjusted to the <paramref name="timeToLive"/>.
/// </summary>
/// <param name="value">The value to cache.</param>
/// <param name="timeToLive">The amount of time before the cache entry expires.</param>
internal CacheEntry(T? value, TimeSpan timeToLive) : this(value, DateTimeProvider.Now + timeToLive) { }
/// <summary>
/// Creates a new <see cref="CacheEntry"/> with the given <paramref name="value"/> and <paramref name="expiry"/>.
/// </summary>
/// <param name="value">The value to cache.</param>
/// <param name="expiry">The expiry date of the cache entry. This will be rounded down to the second.</param>
public CacheEntry(T? value, DateTime expiry) : base(expiry)
if (cacheSettings.StaleAfter.HasValue)
{
Value = value;
return cacheEntry.Expiry - cacheSettings.TimeToLive + cacheSettings.StaleAfter!.Value;
}

/// <inheritdoc/>
public bool Equals(CacheEntry<T?>? other)
else
{
if (other == null)
{
return false;
}

return Equals(Value, other.Value) &&
Expiry == other.Expiry;
return cacheEntry.Expiry;
}
}
}

/// <inheritdoc/>
public override bool Equals(object? obj)
{
if (obj is CacheEntry<T?> objOfType)
{
return Equals(objOfType);
}
/// <summary>
/// Container for both the cached value and its expiry date.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="Value">The value to cache.</param>
/// <param name="Expiry">The expiry date of the cache entry. This will be rounded down to the second.</param>
public sealed record CacheEntry<T>(T? Value, DateTime Expiry) : ICacheEntry<T>
{
/// <summary>
/// The cached value.
/// </summary>
public T? Value { get; } = Value;

return false;
}
/// <summary>
/// The expiry date for the cache entry.
/// </summary>
public DateTime Expiry { get; } = new DateTime(
Expiry.Year, Expiry.Month, Expiry.Day, Expiry.Hour, Expiry.Minute, Expiry.Second, DateTimeKind.Utc
);

/// <inheritdoc/>
public override int GetHashCode()
{
return (Value?.GetHashCode() ?? 1) ^ Expiry.GetHashCode();
}
}
/// <summary>
/// Creates a new <see cref="ICacheEntry"/> with the given <paramref name="value"/> and an expiry adjusted to the <paramref name="timeToLive"/>.
/// </summary>
/// <param name="value">The value to cache.</param>
/// <param name="timeToLive">The amount of time before the cache entry expires.</param>
internal CacheEntry(T? value, TimeSpan timeToLive) : this(value, DateTimeProvider.Now + timeToLive) { }
}
12 changes: 6 additions & 6 deletions src/CacheTower/Internal/CacheEntryKeyLock.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ namespace CacheTower.Internal;

internal readonly struct CacheEntryKeyLock
{
private readonly Dictionary<string, TaskCompletionSource<CacheEntry>?> keyLocks = new(StringComparer.Ordinal);
private readonly Dictionary<string, TaskCompletionSource<ICacheEntry>?> keyLocks = new(StringComparer.Ordinal);

public CacheEntryKeyLock() { }

Expand All @@ -28,15 +28,15 @@ public bool AcquireLock(string cacheKey)
}
}

public Task<CacheEntry> WaitAsync(string cacheKey)
public Task<ICacheEntry> WaitAsync(string cacheKey)
{
TaskCompletionSource<CacheEntry>? completionSource;
TaskCompletionSource<ICacheEntry>? completionSource;

lock (keyLocks)
{
if (!keyLocks.TryGetValue(cacheKey, out completionSource) || completionSource == null)
{
completionSource = new TaskCompletionSource<CacheEntry>();
completionSource = new TaskCompletionSource<ICacheEntry>();
keyLocks[cacheKey] = completionSource;
}
}
Expand All @@ -45,7 +45,7 @@ public Task<CacheEntry> WaitAsync(string cacheKey)
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private bool TryRemove(string cacheKey, out TaskCompletionSource<CacheEntry>? completionSource)
private bool TryRemove(string cacheKey, out TaskCompletionSource<ICacheEntry>? completionSource)
{
lock (keyLocks)
{
Expand All @@ -62,7 +62,7 @@ private bool TryRemove(string cacheKey, out TaskCompletionSource<CacheEntry>? co
}
}

public void ReleaseLock(string cacheKey, CacheEntry cacheEntry)
public void ReleaseLock(string cacheKey, ICacheEntry cacheEntry)
{
if (TryRemove(cacheKey, out var completionSource))
{
Expand Down
2 changes: 1 addition & 1 deletion src/CacheTower/Providers/Memory/MemoryCacheLayer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ namespace CacheTower.Providers.Memory
/// <inheritdoc cref="ICacheLayer"/>
public class MemoryCacheLayer : ILocalCacheLayer
{
private readonly ConcurrentDictionary<string, CacheEntry> Cache = new(StringComparer.Ordinal);
private readonly ConcurrentDictionary<string, ICacheEntry> Cache = new(StringComparer.Ordinal);

/// <inheritdoc/>
public ValueTask CleanupAsync()
Expand Down