Skip to content

Commit 053a6f5

Browse files
authored
Merge pull request #19 from Leefrost/memory-cache-extension
Extend Memory cache entry with options
2 parents 2eeec46 + 2b040ed commit 053a6f5

17 files changed

+2082
-54
lines changed

HttpClient.Cache.graphml

Lines changed: 1671 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,38 @@
11
namespace HttpClient.Cache;
22

3-
internal static class CacheEntryExtensions
3+
public static class CacheEntryExtensions
44
{
5-
internal static ICacheEntry AddExpirationToken(this ICacheEntry entry, IChangeToken token)
5+
public static ICacheEntry SetValue(this ICacheEntry entry, object value)
6+
{
7+
entry.Value = value;
8+
return entry;
9+
}
10+
11+
public static ICacheEntry SetPriority(this ICacheEntry entry, CacheEntryPriority priority)
12+
{
13+
entry.Priority = priority;
14+
return entry;
15+
}
16+
17+
public static ICacheEntry SetAbsoluteExpiration(this ICacheEntry entry, DateTimeOffset expiredAt)
18+
{
19+
entry.AbsoluteExpiration = expiredAt;
20+
return entry;
21+
}
22+
23+
public static ICacheEntry SetAbsoluteExpiration(this ICacheEntry entry, TimeSpan expiredAt)
24+
{
25+
entry.AbsoluteExpirationRelativeToNow = expiredAt;
26+
return entry;
27+
}
28+
29+
public static ICacheEntry SetSlidingExpiration(this ICacheEntry entry, TimeSpan slidingExpiration)
30+
{
31+
entry.SlidingExpiration = slidingExpiration;
32+
return entry;
33+
}
34+
35+
public static ICacheEntry AddExpirationToken(this ICacheEntry entry, IChangeToken? token)
636
{
737
if (token == null)
838
{
@@ -12,4 +42,16 @@ internal static ICacheEntry AddExpirationToken(this ICacheEntry entry, IChangeTo
1242
entry.ExpirationTokens.Add(token);
1343
return entry;
1444
}
45+
46+
public static ICacheEntry RegisterPostEvictionCallback(this ICacheEntry entry, PostEvictionDelegate? callback,
47+
object? state)
48+
{
49+
if (callback == null)
50+
{
51+
throw new ArgumentNullException(nameof(callback));
52+
}
53+
54+
entry.PostEvictionCallbacks.Add(new PostEvictionCallbackRegistration(callback, state));
55+
return entry;
56+
}
1557
}

src/HttpClient.Cache/HttpClient.Cache.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747
</PropertyGroup>
4848

4949
<ItemGroup>
50-
<None Include="README.md" Pack="true" PackagePath=""/>
50+
<None Include="README.md" Pack="true" PackagePath="" />
5151
</ItemGroup>
5252

5353
<ItemGroup>
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
namespace HttpClient.Cache.InMemory.Clock;
2+
3+
/// <summary>
4+
/// Presents current UTC time
5+
/// </summary>
6+
internal class DefaultSystemClock: ISystemClock
7+
{
8+
/// <summary>
9+
/// Current UTC now offset.
10+
/// </summary>
11+
public DateTimeOffset UtcNow => DateTimeOffset.UtcNow;
12+
}

src/HttpClient.Cache/InMemory/Clock/SystemClock.cs

Lines changed: 0 additions & 6 deletions
This file was deleted.

src/HttpClient.Cache/InMemory/MemoryCache.cs

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,16 @@ namespace HttpClient.Cache.InMemory;
66
public sealed class MemoryCache : IMemoryCache
77
{
88
private readonly ISystemClock _clock;
9-
private readonly ConcurrentDictionary<object, CacheEntry> _cacheEntries;
10-
private readonly Action<CacheEntry> _entryExpirationNotification;
9+
private readonly ConcurrentDictionary<object, MemoryCacheEntry> _cacheEntries;
10+
private readonly Action<MemoryCacheEntry> _entryExpirationNotification;
1111

1212
private readonly TimeSpan _expirationScanFrequency;
13-
private readonly Action<CacheEntry> _setEntry;
13+
private readonly Action<MemoryCacheEntry> _setEntry;
1414

1515
private bool _isDisposed;
1616
private DateTimeOffset _lastExpirationScan;
1717

18-
private ICollection<KeyValuePair<object, CacheEntry>> CacheEntries => _cacheEntries;
18+
private ICollection<KeyValuePair<object, MemoryCacheEntry>> CacheEntries => _cacheEntries;
1919

2020
public MemoryCache()
2121
: this(new MemoryCacheOptions())
@@ -29,11 +29,11 @@ public MemoryCache(MemoryCacheOptions options)
2929
throw new ArgumentNullException(nameof(options));
3030
}
3131

32-
_cacheEntries = new ConcurrentDictionary<object, CacheEntry>();
32+
_cacheEntries = new ConcurrentDictionary<object, MemoryCacheEntry>();
3333
_setEntry = SetEntry;
3434
_entryExpirationNotification = EntryExpired;
3535

36-
_clock = options.Clock ?? new SystemClock();
36+
_clock = options.Clock ?? new DefaultSystemClock();
3737
_expirationScanFrequency = options.ExpirationScanFrequency;
3838
_lastExpirationScan = _clock.UtcNow;
3939
}
@@ -52,7 +52,7 @@ public ICacheEntry CreateEntry(object key)
5252
throw new ObjectDisposedException(typeof(MemoryCache).FullName);
5353
}
5454

55-
return new CacheEntry(key, _setEntry, _entryExpirationNotification);
55+
return new MemoryCacheEntry(key, _setEntry, _entryExpirationNotification);
5656
}
5757

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

7373
bool isEntryFound = false;
74-
if (_cacheEntries.TryGetValue(key, out CacheEntry? entry))
74+
if (_cacheEntries.TryGetValue(key, out MemoryCacheEntry? entry))
7575
{
7676
if (entry.IsExpired(currentTime) && entry.EvictionReason != EvictionReason.Replaced)
7777
{
@@ -101,7 +101,7 @@ public void Remove(object key)
101101
throw new ArgumentNullException(nameof(key));
102102
}
103103

104-
if (_cacheEntries.TryRemove(key, out CacheEntry? cacheEntry))
104+
if (_cacheEntries.TryRemove(key, out MemoryCacheEntry? cacheEntry))
105105
{
106106
cacheEntry.ExpireEntryByReason(EvictionReason.Removed);
107107
cacheEntry.InvokeEvictionCallbacks();
@@ -120,7 +120,7 @@ public void Clear()
120120
var keys = _cacheEntries.Keys.ToList();
121121
foreach (object key in keys)
122122
{
123-
if (_cacheEntries.TryRemove(key, out CacheEntry? cacheEntry))
123+
if (_cacheEntries.TryRemove(key, out MemoryCacheEntry? cacheEntry))
124124
{
125125
cacheEntry.ExpireEntryByReason(EvictionReason.Removed);
126126
cacheEntry.InvokeEvictionCallbacks();
@@ -135,7 +135,7 @@ public void Dispose()
135135
Dispose(true);
136136
}
137137

138-
private void SetEntry(CacheEntry entry)
138+
private void SetEntry(MemoryCacheEntry entry)
139139
{
140140
if (_isDisposed)
141141
{
@@ -162,7 +162,7 @@ private void SetEntry(CacheEntry entry)
162162

163163
entry.LastAccessed = currentTime;
164164

165-
if (_cacheEntries.TryGetValue(entry.Key, out CacheEntry? cacheEntry))
165+
if (_cacheEntries.TryGetValue(entry.Key, out MemoryCacheEntry? cacheEntry))
166166
{
167167
cacheEntry.ExpireEntryByReason(EvictionReason.Replaced);
168168
}
@@ -207,17 +207,17 @@ private void SetEntry(CacheEntry entry)
207207
StartScanForExpiredItems();
208208
}
209209

210-
private void RemoveEntry(CacheEntry entry)
210+
private void RemoveEntry(MemoryCacheEntry entry)
211211
{
212-
if (!CacheEntries.Remove(new KeyValuePair<object, CacheEntry>(entry.Key, entry)))
212+
if (!CacheEntries.Remove(new KeyValuePair<object, MemoryCacheEntry>(entry.Key, entry)))
213213
{
214214
return;
215215
}
216216

217217
entry.InvokeEvictionCallbacks();
218218
}
219219

220-
private void EntryExpired(CacheEntry entry)
220+
private void EntryExpired(MemoryCacheEntry entry)
221221
{
222222
RemoveEntry(entry);
223223
StartScanForExpiredItems();
@@ -240,7 +240,7 @@ private void StartScanForExpiredItems()
240240
private static void ScanForExpiredItems(MemoryCache cache)
241241
{
242242
var currentTime = cache._clock.UtcNow;
243-
foreach (CacheEntry entry in cache._cacheEntries.Values)
243+
foreach (MemoryCacheEntry entry in cache._cacheEntries.Values)
244244
{
245245
if (entry.IsExpired(currentTime))
246246
{

src/HttpClient.Cache/InMemory/CacheEntry.cs renamed to src/HttpClient.Cache/InMemory/MemoryCacheEntry.cs

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@
22

33
namespace HttpClient.Cache.InMemory;
44

5-
internal class CacheEntry : ICacheEntry
5+
internal class MemoryCacheEntry : ICacheEntry
66
{
77
private readonly object _lock = new();
88
private static readonly Action<object> ExpirationCallback = ExpirationTokensExpired;
99

10-
private readonly Action<CacheEntry> _notifyCacheEntryDisposed;
11-
private readonly Action<CacheEntry> _notifyCacheOfExpiration;
10+
private readonly Action<MemoryCacheEntry> _notifyCacheEntryDisposed;
11+
private readonly Action<MemoryCacheEntry> _notifyCacheOfExpiration;
1212
private DateTimeOffset? _absoluteExpiration;
1313
private TimeSpan? _absoluteExpirationRelativeToNow;
1414
private TimeSpan? _slidingExpiration;
@@ -20,10 +20,10 @@ internal class CacheEntry : ICacheEntry
2020
private bool _isDisposed;
2121
private bool _isExpired;
2222

23-
internal CacheEntry(
23+
internal MemoryCacheEntry(
2424
object key,
25-
Action<CacheEntry> notifyCacheEntryDisposed,
26-
Action<CacheEntry> notifyCacheOfExpiration)
25+
Action<MemoryCacheEntry> notifyCacheEntryDisposed,
26+
Action<MemoryCacheEntry> notifyCacheOfExpiration)
2727
{
2828
Key = key ?? throw new ArgumentNullException(nameof(key));
2929

@@ -82,9 +82,14 @@ public IList<IChangeToken> ExpirationTokens
8282
}
8383
}
8484

85-
public IList<PostEvictionCallbackRegistration> PostEvictionCallbacks =>
86-
_postEvictionCallbacks ?? new List<PostEvictionCallbackRegistration>();
87-
85+
public IList<PostEvictionCallbackRegistration> PostEvictionCallbacks
86+
{
87+
get
88+
{
89+
return _postEvictionCallbacks ??= new List<PostEvictionCallbackRegistration>();
90+
}
91+
}
92+
8893
internal DateTimeOffset LastAccessed { get; set; }
8994

9095
internal EvictionReason EvictionReason { get; private set; }
@@ -158,7 +163,7 @@ private static void ExpirationTokensExpired(object entry)
158163
{
159164
Task.Factory.StartNew(state =>
160165
{
161-
CacheEntry? cacheEntry = state as CacheEntry;
166+
MemoryCacheEntry? cacheEntry = state as MemoryCacheEntry;
162167
cacheEntry?.ExpireEntryByReason(EvictionReason.TokenExpired);
163168
cacheEntry?._notifyCacheOfExpiration(cacheEntry);
164169
}, entry, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
@@ -222,11 +227,11 @@ internal void InvokeEvictionCallbacks()
222227
return;
223228
}
224229

225-
Task.Factory.StartNew(state => InvokeCallbacks((CacheEntry)state!), this, CancellationToken.None,
230+
Task.Factory.StartNew(state => InvokeCallbacks((MemoryCacheEntry)state!), this, CancellationToken.None,
226231
TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
227232
}
228233

229-
private static void InvokeCallbacks(CacheEntry entry)
234+
private static void InvokeCallbacks(MemoryCacheEntry entry)
230235
{
231236
var evictionCallbacks =
232237
Interlocked.Exchange(ref entry._postEvictionCallbacks, null);
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
namespace HttpClient.Cache.InMemory;
2+
3+
public static class MemoryCacheEntryExtensions
4+
{
5+
public static ICacheEntry SetOptions(this ICacheEntry entry, MemoryCacheEntryOptions options)
6+
{
7+
entry.AbsoluteExpiration = options.AbsoluteExpiration;
8+
entry.AbsoluteExpirationRelativeToNow = options.AbsoluteExpirationRelativeToNow;
9+
entry.SlidingExpiration = options.SlidingExpiration;
10+
entry.Priority = options.Priority;
11+
12+
foreach (var expirationToken in options.ExpirationTokens)
13+
{
14+
entry.ExpirationTokens.Add(expirationToken);
15+
}
16+
17+
foreach (var evictionCallback in options.PostEvictionCallbacks)
18+
{
19+
entry.PostEvictionCallbacks.Add(new PostEvictionCallbackRegistration(evictionCallback.EvictionCallback, evictionCallback.State));
20+
}
21+
22+
return entry;
23+
}
24+
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
namespace HttpClient.Cache.InMemory;
2+
3+
/// <summary>
4+
/// Provides cache entry configuration
5+
/// </summary>
6+
public class MemoryCacheEntryOptions
7+
{
8+
private TimeSpan? _absoluteExpirationRelativeToNow;
9+
10+
private TimeSpan? _slidingExpiration;
11+
12+
private DateTimeOffset? _absoluteExpiration;
13+
14+
/// <summary>
15+
/// The absolute expiration for cache entry. The point in time where cache entry will no longer be available
16+
/// </summary>
17+
/// <exception cref="ArgumentOutOfRangeException">Time point must not be in the past</exception>
18+
public DateTimeOffset? AbsoluteExpiration
19+
{
20+
get { return _absoluteExpiration; }
21+
set
22+
{
23+
if (value.HasValue && value.Value < DateTimeOffset.Now)
24+
throw new ArgumentOutOfRangeException(nameof(AbsoluteExpiration), value,
25+
"The absolute expiration can not be in the past");
26+
27+
_absoluteExpiration = value;
28+
}
29+
}
30+
31+
/// <summary>
32+
/// The absolute expiration due to now. The time period while entity will be available from now.
33+
/// </summary>
34+
/// <exception cref="ArgumentOutOfRangeException">Time point should be positive and bigger to <see cref="TimeSpan.Zero"/> value</exception>
35+
public TimeSpan? AbsoluteExpirationRelativeToNow
36+
{
37+
get
38+
{
39+
return _absoluteExpirationRelativeToNow;
40+
}
41+
set
42+
{
43+
if ((value.HasValue ? (value.GetValueOrDefault() <= TimeSpan.Zero ? 1 : 0) : 0) != 0)
44+
throw new ArgumentOutOfRangeException(nameof(AbsoluteExpirationRelativeToNow), value,
45+
"Relative expiration must be a positive");
46+
47+
_absoluteExpirationRelativeToNow = value;
48+
}
49+
}
50+
51+
/// <summary>
52+
/// Cache entry sliding expiration. The un-active time for cache entry. Do not extends the <see cref="AbsoluteExpiration"/> if it set.
53+
/// </summary>
54+
/// <exception cref="ArgumentOutOfRangeException">Time point should be positive and bigger to <see cref="TimeSpan.Zero"/> value</exception>
55+
public TimeSpan? SlidingExpiration
56+
{
57+
get { return _slidingExpiration; }
58+
set
59+
{
60+
if ((value.HasValue ? (value.GetValueOrDefault() <= TimeSpan.Zero ? 1 : 0) : 0) != 0)
61+
throw new ArgumentOutOfRangeException(nameof(SlidingExpiration), value,
62+
"Sliding expiration must be positive");
63+
64+
_slidingExpiration = value;
65+
}
66+
}
67+
68+
/// <summary>
69+
/// The cache entry priority. Default value is <see cref="CacheEntryPriority.Normal"/>
70+
/// </summary>
71+
public CacheEntryPriority Priority { get; set; } = CacheEntryPriority.Normal;
72+
73+
/// <summary>
74+
/// The collection of <see cref="IChangeToken"/> expiration tokens
75+
/// </summary>
76+
public IList<IChangeToken> ExpirationTokens { get; } = new List<IChangeToken>();
77+
78+
/// <summary>
79+
/// The collection of <see cref="PostEvictionCallbackRegistration"/> eviction callbacks
80+
/// </summary>
81+
public IList<PostEvictionCallbackRegistration> PostEvictionCallbacks { get; } =
82+
new List<PostEvictionCallbackRegistration>();
83+
}

0 commit comments

Comments
 (0)