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
1,671 changes: 1,671 additions & 0 deletions HttpClient.Cache.graphml

Large diffs are not rendered by default.

46 changes: 44 additions & 2 deletions src/HttpClient.Cache/CacheEntryExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,38 @@
namespace HttpClient.Cache;

internal static class CacheEntryExtensions
public static class CacheEntryExtensions
{
internal static ICacheEntry AddExpirationToken(this ICacheEntry entry, IChangeToken token)
public static ICacheEntry SetValue(this ICacheEntry entry, object value)
{
entry.Value = value;
return entry;
}

public static ICacheEntry SetPriority(this ICacheEntry entry, CacheEntryPriority priority)
{
entry.Priority = priority;
return entry;
}

public static ICacheEntry SetAbsoluteExpiration(this ICacheEntry entry, DateTimeOffset expiredAt)
{
entry.AbsoluteExpiration = expiredAt;
return entry;
}

public static ICacheEntry SetAbsoluteExpiration(this ICacheEntry entry, TimeSpan expiredAt)
{
entry.AbsoluteExpirationRelativeToNow = expiredAt;
return entry;
}

public static ICacheEntry SetSlidingExpiration(this ICacheEntry entry, TimeSpan slidingExpiration)
{
entry.SlidingExpiration = slidingExpiration;
return entry;
}

public static ICacheEntry AddExpirationToken(this ICacheEntry entry, IChangeToken? token)
{
if (token == null)
{
Expand All @@ -12,4 +42,16 @@ internal static ICacheEntry AddExpirationToken(this ICacheEntry entry, IChangeTo
entry.ExpirationTokens.Add(token);
return entry;
}

public static ICacheEntry RegisterPostEvictionCallback(this ICacheEntry entry, PostEvictionDelegate? callback,
object? state)
{
if (callback == null)
{
throw new ArgumentNullException(nameof(callback));
}

entry.PostEvictionCallbacks.Add(new PostEvictionCallbackRegistration(callback, state));
return entry;
}
}
2 changes: 1 addition & 1 deletion src/HttpClient.Cache/HttpClient.Cache.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
</PropertyGroup>

<ItemGroup>
<None Include="README.md" Pack="true" PackagePath=""/>
<None Include="README.md" Pack="true" PackagePath="" />
</ItemGroup>

<ItemGroup>
Expand Down
12 changes: 12 additions & 0 deletions src/HttpClient.Cache/InMemory/Clock/DefaultSystemClock.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace HttpClient.Cache.InMemory.Clock;

/// <summary>
/// Presents current UTC time
/// </summary>
internal class DefaultSystemClock: ISystemClock
{
/// <summary>
/// Current UTC now offset.
/// </summary>
public DateTimeOffset UtcNow => DateTimeOffset.UtcNow;
}
6 changes: 0 additions & 6 deletions src/HttpClient.Cache/InMemory/Clock/SystemClock.cs

This file was deleted.

32 changes: 16 additions & 16 deletions src/HttpClient.Cache/InMemory/MemoryCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,16 @@ namespace HttpClient.Cache.InMemory;
public sealed class MemoryCache : IMemoryCache
{
private readonly ISystemClock _clock;
private readonly ConcurrentDictionary<object, CacheEntry> _cacheEntries;
private readonly Action<CacheEntry> _entryExpirationNotification;
private readonly ConcurrentDictionary<object, MemoryCacheEntry> _cacheEntries;
private readonly Action<MemoryCacheEntry> _entryExpirationNotification;

private readonly TimeSpan _expirationScanFrequency;
private readonly Action<CacheEntry> _setEntry;
private readonly Action<MemoryCacheEntry> _setEntry;

private bool _isDisposed;
private DateTimeOffset _lastExpirationScan;

private ICollection<KeyValuePair<object, CacheEntry>> CacheEntries => _cacheEntries;
private ICollection<KeyValuePair<object, MemoryCacheEntry>> CacheEntries => _cacheEntries;

public MemoryCache()
: this(new MemoryCacheOptions())
Expand All @@ -29,11 +29,11 @@ public MemoryCache(MemoryCacheOptions options)
throw new ArgumentNullException(nameof(options));
}

_cacheEntries = new ConcurrentDictionary<object, CacheEntry>();
_cacheEntries = new ConcurrentDictionary<object, MemoryCacheEntry>();
_setEntry = SetEntry;
_entryExpirationNotification = EntryExpired;

_clock = options.Clock ?? new SystemClock();
_clock = options.Clock ?? new DefaultSystemClock();
_expirationScanFrequency = options.ExpirationScanFrequency;
_lastExpirationScan = _clock.UtcNow;
}
Expand All @@ -52,7 +52,7 @@ public ICacheEntry CreateEntry(object key)
throw new ObjectDisposedException(typeof(MemoryCache).FullName);
}

return new CacheEntry(key, _setEntry, _entryExpirationNotification);
return new MemoryCacheEntry(key, _setEntry, _entryExpirationNotification);
}

public bool TryGetValue(object key, out object? value)
Expand All @@ -71,7 +71,7 @@ public bool TryGetValue(object key, out object? value)
var currentTime = _clock.UtcNow;

bool isEntryFound = false;
if (_cacheEntries.TryGetValue(key, out CacheEntry? entry))
if (_cacheEntries.TryGetValue(key, out MemoryCacheEntry? entry))
{
if (entry.IsExpired(currentTime) && entry.EvictionReason != EvictionReason.Replaced)
{
Expand Down Expand Up @@ -101,7 +101,7 @@ public void Remove(object key)
throw new ArgumentNullException(nameof(key));
}

if (_cacheEntries.TryRemove(key, out CacheEntry? cacheEntry))
if (_cacheEntries.TryRemove(key, out MemoryCacheEntry? cacheEntry))
{
cacheEntry.ExpireEntryByReason(EvictionReason.Removed);
cacheEntry.InvokeEvictionCallbacks();
Expand All @@ -120,7 +120,7 @@ public void Clear()
var keys = _cacheEntries.Keys.ToList();
foreach (object key in keys)
{
if (_cacheEntries.TryRemove(key, out CacheEntry? cacheEntry))
if (_cacheEntries.TryRemove(key, out MemoryCacheEntry? cacheEntry))
{
cacheEntry.ExpireEntryByReason(EvictionReason.Removed);
cacheEntry.InvokeEvictionCallbacks();
Expand All @@ -135,7 +135,7 @@ public void Dispose()
Dispose(true);
}

private void SetEntry(CacheEntry entry)
private void SetEntry(MemoryCacheEntry entry)
{
if (_isDisposed)
{
Expand All @@ -162,7 +162,7 @@ private void SetEntry(CacheEntry entry)

entry.LastAccessed = currentTime;

if (_cacheEntries.TryGetValue(entry.Key, out CacheEntry? cacheEntry))
if (_cacheEntries.TryGetValue(entry.Key, out MemoryCacheEntry? cacheEntry))
{
cacheEntry.ExpireEntryByReason(EvictionReason.Replaced);
}
Expand Down Expand Up @@ -207,17 +207,17 @@ private void SetEntry(CacheEntry entry)
StartScanForExpiredItems();
}

private void RemoveEntry(CacheEntry entry)
private void RemoveEntry(MemoryCacheEntry entry)
{
if (!CacheEntries.Remove(new KeyValuePair<object, CacheEntry>(entry.Key, entry)))
if (!CacheEntries.Remove(new KeyValuePair<object, MemoryCacheEntry>(entry.Key, entry)))
{
return;
}

entry.InvokeEvictionCallbacks();
}

private void EntryExpired(CacheEntry entry)
private void EntryExpired(MemoryCacheEntry entry)
{
RemoveEntry(entry);
StartScanForExpiredItems();
Expand All @@ -240,7 +240,7 @@ private void StartScanForExpiredItems()
private static void ScanForExpiredItems(MemoryCache cache)
{
var currentTime = cache._clock.UtcNow;
foreach (CacheEntry entry in cache._cacheEntries.Values)
foreach (MemoryCacheEntry entry in cache._cacheEntries.Values)
{
if (entry.IsExpired(currentTime))
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@

namespace HttpClient.Cache.InMemory;

internal class CacheEntry : ICacheEntry
internal class MemoryCacheEntry : ICacheEntry
{
private readonly object _lock = new();
private static readonly Action<object> ExpirationCallback = ExpirationTokensExpired;

private readonly Action<CacheEntry> _notifyCacheEntryDisposed;
private readonly Action<CacheEntry> _notifyCacheOfExpiration;
private readonly Action<MemoryCacheEntry> _notifyCacheEntryDisposed;
private readonly Action<MemoryCacheEntry> _notifyCacheOfExpiration;
private DateTimeOffset? _absoluteExpiration;
private TimeSpan? _absoluteExpirationRelativeToNow;
private TimeSpan? _slidingExpiration;
Expand All @@ -20,10 +20,10 @@ internal class CacheEntry : ICacheEntry
private bool _isDisposed;
private bool _isExpired;

internal CacheEntry(
internal MemoryCacheEntry(
object key,
Action<CacheEntry> notifyCacheEntryDisposed,
Action<CacheEntry> notifyCacheOfExpiration)
Action<MemoryCacheEntry> notifyCacheEntryDisposed,
Action<MemoryCacheEntry> notifyCacheOfExpiration)
{
Key = key ?? throw new ArgumentNullException(nameof(key));

Expand Down Expand Up @@ -82,9 +82,14 @@ public IList<IChangeToken> ExpirationTokens
}
}

public IList<PostEvictionCallbackRegistration> PostEvictionCallbacks =>
_postEvictionCallbacks ?? new List<PostEvictionCallbackRegistration>();

public IList<PostEvictionCallbackRegistration> PostEvictionCallbacks
{
get
{
return _postEvictionCallbacks ??= new List<PostEvictionCallbackRegistration>();
}
}

internal DateTimeOffset LastAccessed { get; set; }

internal EvictionReason EvictionReason { get; private set; }
Expand Down Expand Up @@ -158,7 +163,7 @@ private static void ExpirationTokensExpired(object entry)
{
Task.Factory.StartNew(state =>
{
CacheEntry? cacheEntry = state as CacheEntry;
MemoryCacheEntry? cacheEntry = state as MemoryCacheEntry;
cacheEntry?.ExpireEntryByReason(EvictionReason.TokenExpired);
cacheEntry?._notifyCacheOfExpiration(cacheEntry);
}, entry, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
Expand Down Expand Up @@ -222,11 +227,11 @@ internal void InvokeEvictionCallbacks()
return;
}

Task.Factory.StartNew(state => InvokeCallbacks((CacheEntry)state!), this, CancellationToken.None,
Task.Factory.StartNew(state => InvokeCallbacks((MemoryCacheEntry)state!), this, CancellationToken.None,
TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
}

private static void InvokeCallbacks(CacheEntry entry)
private static void InvokeCallbacks(MemoryCacheEntry entry)
{
var evictionCallbacks =
Interlocked.Exchange(ref entry._postEvictionCallbacks, null);
Expand Down
24 changes: 24 additions & 0 deletions src/HttpClient.Cache/InMemory/MemoryCacheEntryExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
namespace HttpClient.Cache.InMemory;

public static class MemoryCacheEntryExtensions
{
public static ICacheEntry SetOptions(this ICacheEntry entry, MemoryCacheEntryOptions options)
{
entry.AbsoluteExpiration = options.AbsoluteExpiration;
entry.AbsoluteExpirationRelativeToNow = options.AbsoluteExpirationRelativeToNow;
entry.SlidingExpiration = options.SlidingExpiration;
entry.Priority = options.Priority;

foreach (var expirationToken in options.ExpirationTokens)
{
entry.ExpirationTokens.Add(expirationToken);
}

foreach (var evictionCallback in options.PostEvictionCallbacks)
{
entry.PostEvictionCallbacks.Add(new PostEvictionCallbackRegistration(evictionCallback.EvictionCallback, evictionCallback.State));
}

return entry;
}
}
83 changes: 83 additions & 0 deletions src/HttpClient.Cache/InMemory/MemoryCacheEntryOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
namespace HttpClient.Cache.InMemory;

/// <summary>
/// Provides cache entry configuration
/// </summary>
public class MemoryCacheEntryOptions
{
private TimeSpan? _absoluteExpirationRelativeToNow;

private TimeSpan? _slidingExpiration;

private DateTimeOffset? _absoluteExpiration;

/// <summary>
/// The absolute expiration for cache entry. The point in time where cache entry will no longer be available
/// </summary>
/// <exception cref="ArgumentOutOfRangeException">Time point must not be in the past</exception>
public DateTimeOffset? AbsoluteExpiration
{
get { return _absoluteExpiration; }
set
{
if (value.HasValue && value.Value < DateTimeOffset.Now)
throw new ArgumentOutOfRangeException(nameof(AbsoluteExpiration), value,
"The absolute expiration can not be in the past");

_absoluteExpiration = value;
}
}

/// <summary>
/// The absolute expiration due to now. The time period while entity will be available from now.
/// </summary>
/// <exception cref="ArgumentOutOfRangeException">Time point should be positive and bigger to <see cref="TimeSpan.Zero"/> value</exception>
public TimeSpan? AbsoluteExpirationRelativeToNow
{
get
{
return _absoluteExpirationRelativeToNow;
}
set
{
if ((value.HasValue ? (value.GetValueOrDefault() <= TimeSpan.Zero ? 1 : 0) : 0) != 0)
throw new ArgumentOutOfRangeException(nameof(AbsoluteExpirationRelativeToNow), value,
"Relative expiration must be a positive");

_absoluteExpirationRelativeToNow = value;
}
}

/// <summary>
/// Cache entry sliding expiration. The un-active time for cache entry. Do not extends the <see cref="AbsoluteExpiration"/> if it set.
/// </summary>
/// <exception cref="ArgumentOutOfRangeException">Time point should be positive and bigger to <see cref="TimeSpan.Zero"/> value</exception>
public TimeSpan? SlidingExpiration
{
get { return _slidingExpiration; }
set
{
if ((value.HasValue ? (value.GetValueOrDefault() <= TimeSpan.Zero ? 1 : 0) : 0) != 0)
throw new ArgumentOutOfRangeException(nameof(SlidingExpiration), value,
"Sliding expiration must be positive");

_slidingExpiration = value;
}
}

/// <summary>
/// The cache entry priority. Default value is <see cref="CacheEntryPriority.Normal"/>
/// </summary>
public CacheEntryPriority Priority { get; set; } = CacheEntryPriority.Normal;

/// <summary>
/// The collection of <see cref="IChangeToken"/> expiration tokens
/// </summary>
public IList<IChangeToken> ExpirationTokens { get; } = new List<IChangeToken>();

/// <summary>
/// The collection of <see cref="PostEvictionCallbackRegistration"/> eviction callbacks
/// </summary>
public IList<PostEvictionCallbackRegistration> PostEvictionCallbacks { get; } =
new List<PostEvictionCallbackRegistration>();
}
Loading