2
2
// The .NET Foundation licenses this file to you under the MIT license.
3
3
4
4
using System ;
5
+ using System . Collections ;
5
6
using System . Collections . Concurrent ;
6
7
using System . Collections . Generic ;
7
8
using System . Diagnostics ;
8
9
using System . Diagnostics . CodeAnalysis ;
9
10
using System . Runtime . CompilerServices ;
11
+ using System . Runtime . InteropServices ;
10
12
using System . Threading ;
11
13
using System . Threading . Tasks ;
12
14
using Microsoft . Extensions . Logging ;
@@ -81,15 +83,7 @@ public MemoryCache(IOptions<MemoryCacheOptions> optionsAccessor, ILoggerFactory
81
83
/// Gets an enumerable of the all the keys in the <see cref="MemoryCache"/>.
82
84
/// </summary>
83
85
public IEnumerable < object > Keys
84
- {
85
- get
86
- {
87
- foreach ( KeyValuePair < object , CacheEntry > pairs in _coherentState . _entries )
88
- {
89
- yield return pairs . Key ;
90
- }
91
- }
92
- }
86
+ => _coherentState . GetAllKeys ( ) ;
93
87
94
88
/// <summary>
95
89
/// Internal accessor for Size for testing only.
@@ -141,7 +135,7 @@ internal void SetEntry(CacheEntry entry)
141
135
entry . LastAccessed = utcNow ;
142
136
143
137
CoherentState coherentState = _coherentState ; // Clear() can update the reference in the meantime
144
- if ( coherentState . _entries . TryGetValue ( entry . Key , out CacheEntry ? priorEntry ) )
138
+ if ( coherentState . TryGetValue ( entry . Key , out CacheEntry ? priorEntry ) )
145
139
{
146
140
priorEntry . SetExpired ( EvictionReason . Replaced ) ;
147
141
}
@@ -160,19 +154,19 @@ internal void SetEntry(CacheEntry entry)
160
154
if ( priorEntry == null )
161
155
{
162
156
// Try to add the new entry if no previous entries exist.
163
- entryAdded = coherentState . _entries . TryAdd ( entry . Key , entry ) ;
157
+ entryAdded = coherentState . TryAdd ( entry . Key , entry ) ;
164
158
}
165
159
else
166
160
{
167
161
// Try to update with the new entry if a previous entries exist.
168
- entryAdded = coherentState . _entries . TryUpdate ( entry . Key , entry , priorEntry ) ;
162
+ entryAdded = coherentState . TryUpdate ( entry . Key , entry , priorEntry ) ;
169
163
170
164
if ( ! entryAdded )
171
165
{
172
166
// The update will fail if the previous entry was removed after retrieval.
173
167
// Adding the new entry will succeed only if no entry has been added since.
174
168
// This guarantees removing an old entry does not prevent adding a new entry.
175
- entryAdded = coherentState . _entries . TryAdd ( entry . Key , entry ) ;
169
+ entryAdded = coherentState . TryAdd ( entry . Key , entry ) ;
176
170
}
177
171
}
178
172
@@ -217,7 +211,7 @@ public bool TryGetValue(object key, out object? result)
217
211
DateTime utcNow = UtcNow ;
218
212
219
213
CoherentState coherentState = _coherentState ; // Clear() can update the reference in the meantime
220
- if ( coherentState . _entries . TryGetValue ( key , out CacheEntry ? tmp ) )
214
+ if ( coherentState . TryGetValue ( key , out CacheEntry ? tmp ) )
221
215
{
222
216
CacheEntry entry = tmp ;
223
217
// Check if expired due to expiration tokens, timers, etc. and if so, remove it.
@@ -276,7 +270,8 @@ public void Remove(object key)
276
270
CheckDisposed ( ) ;
277
271
278
272
CoherentState coherentState = _coherentState ; // Clear() can update the reference in the meantime
279
- if ( coherentState . _entries . TryRemove ( key , out CacheEntry ? entry ) )
273
+
274
+ if ( coherentState . TryRemove ( key , out CacheEntry ? entry ) )
280
275
{
281
276
if ( _options . HasSizeLimit )
282
277
{
@@ -298,10 +293,10 @@ public void Clear()
298
293
CheckDisposed ( ) ;
299
294
300
295
CoherentState oldState = Interlocked . Exchange ( ref _coherentState , new CoherentState ( ) ) ;
301
- foreach ( KeyValuePair < object , CacheEntry > entry in oldState . _entries )
296
+ foreach ( CacheEntry entry in oldState . GetAllValues ( ) )
302
297
{
303
- entry . Value . SetExpired ( EvictionReason . Removed ) ;
304
- entry . Value . InvokeEvictionCallbacks ( ) ;
298
+ entry . SetExpired ( EvictionReason . Removed ) ;
299
+ entry . InvokeEvictionCallbacks ( ) ;
305
300
}
306
301
}
307
302
@@ -422,10 +417,9 @@ private void ScanForExpiredItems()
422
417
DateTime utcNow = _lastExpirationScan = UtcNow ;
423
418
424
419
CoherentState coherentState = _coherentState ; // Clear() can update the reference in the meantime
425
- foreach ( KeyValuePair < object , CacheEntry > item in coherentState . _entries )
426
- {
427
- CacheEntry entry = item . Value ;
428
420
421
+ foreach ( CacheEntry entry in coherentState . GetAllValues ( ) )
422
+ {
429
423
if ( entry . CheckExpired ( utcNow ) )
430
424
{
431
425
coherentState . RemoveEntry ( entry , _options ) ;
@@ -547,9 +541,8 @@ private void Compact(long removalSizeTarget, Func<CacheEntry, long> computeEntry
547
541
548
542
// Sort items by expired & priority status
549
543
DateTime utcNow = UtcNow ;
550
- foreach ( KeyValuePair < object , CacheEntry > item in coherentState . _entries )
544
+ foreach ( CacheEntry entry in coherentState . GetAllValues ( ) )
551
545
{
552
- CacheEntry entry = item . Value ;
553
546
if ( entry . CheckExpired ( utcNow ) )
554
547
{
555
548
entriesToRemove . Add ( entry ) ;
@@ -676,18 +669,71 @@ private static void ValidateCacheKey(object key)
676
669
/// </summary>
677
670
private sealed class CoherentState
678
671
{
679
- internal ConcurrentDictionary < object , CacheEntry > _entries = new ConcurrentDictionary < object , CacheEntry > ( ) ;
672
+ private readonly ConcurrentDictionary < string , CacheEntry > _stringEntries = new ConcurrentDictionary < string , CacheEntry > ( StringKeyComparer . Instance ) ;
673
+ private readonly ConcurrentDictionary < object , CacheEntry > _nonStringEntries = new ConcurrentDictionary < object , CacheEntry > ( ) ;
680
674
internal long _cacheSize ;
681
675
682
- private ICollection < KeyValuePair < object , CacheEntry > > EntriesCollection => _entries ;
676
+ internal bool TryGetValue ( object key , [ NotNullWhen ( true ) ] out CacheEntry ? entry )
677
+ => key is string s ? _stringEntries . TryGetValue ( s , out entry ) : _nonStringEntries . TryGetValue ( key , out entry ) ;
678
+
679
+ internal bool TryRemove ( object key , [ NotNullWhen ( true ) ] out CacheEntry ? entry )
680
+ => key is string s ? _stringEntries . TryRemove ( s , out entry ) : _nonStringEntries . TryRemove ( key , out entry ) ;
681
+
682
+ internal bool TryAdd ( object key , CacheEntry entry )
683
+ => key is string s ? _stringEntries . TryAdd ( s , entry ) : _nonStringEntries . TryAdd ( key , entry ) ;
684
+
685
+ internal bool TryUpdate ( object key , CacheEntry entry , CacheEntry comparison )
686
+ => key is string s ? _stringEntries . TryUpdate ( s , entry , comparison ) : _nonStringEntries . TryUpdate ( key , entry , comparison ) ;
687
+
688
+ public IEnumerable < CacheEntry > GetAllValues ( )
689
+ {
690
+ // note this mimics the outgoing code in that we don't just access
691
+ // .Values, which has additional overheads; this is only used for rare
692
+ // calls - compaction, clear, etc - so the additional overhead of a
693
+ // generated enumerator is not alarming
694
+ foreach ( KeyValuePair < string , CacheEntry > entry in _stringEntries )
695
+ {
696
+ yield return entry . Value ;
697
+ }
698
+ foreach ( KeyValuePair < object , CacheEntry > entry in _nonStringEntries )
699
+ {
700
+ yield return entry . Value ;
701
+ }
702
+ }
703
+
704
+ public IEnumerable < object > GetAllKeys ( )
705
+ {
706
+ foreach ( KeyValuePair < string , CacheEntry > pairs in _stringEntries )
707
+ {
708
+ yield return pairs . Key ;
709
+ }
710
+ foreach ( KeyValuePair < object , CacheEntry > pairs in _nonStringEntries )
711
+ {
712
+ yield return pairs . Key ;
713
+ }
714
+ }
715
+
716
+ private ICollection < KeyValuePair < string , CacheEntry > > StringEntriesCollection => _stringEntries ;
717
+ private ICollection < KeyValuePair < object , CacheEntry > > NonStringEntriesCollection => _nonStringEntries ;
683
718
684
- internal int Count => _entries . Count ;
719
+ internal int Count => _stringEntries . Count + _nonStringEntries . Count ;
685
720
686
721
internal long Size => Volatile . Read ( ref _cacheSize ) ;
687
722
688
723
internal void RemoveEntry ( CacheEntry entry , MemoryCacheOptions options )
689
724
{
690
- if ( EntriesCollection . Remove ( new KeyValuePair < object , CacheEntry > ( entry . Key , entry ) ) )
725
+ if ( entry . Key is string s )
726
+ {
727
+ if ( StringEntriesCollection . Remove ( new KeyValuePair < string , CacheEntry > ( s , entry ) ) )
728
+ {
729
+ if ( options . SizeLimit . HasValue )
730
+ {
731
+ Interlocked . Add ( ref _cacheSize , - entry . Size ) ;
732
+ }
733
+ entry . InvokeEvictionCallbacks ( ) ;
734
+ }
735
+ }
736
+ else if ( NonStringEntriesCollection . Remove ( new KeyValuePair < object , CacheEntry > ( entry . Key , entry ) ) )
691
737
{
692
738
if ( options . SizeLimit . HasValue )
693
739
{
@@ -696,6 +742,35 @@ internal void RemoveEntry(CacheEntry entry, MemoryCacheOptions options)
696
742
entry . InvokeEvictionCallbacks ( ) ;
697
743
}
698
744
}
745
+
746
+ #if NETCOREAPP
747
+ // on .NET Core, the inbuilt comparer has Marvin built in; no need to intercept
748
+ private static class StringKeyComparer
749
+ {
750
+ internal static IEqualityComparer < string > Instance => EqualityComparer < string > . Default ;
751
+ }
752
+ #else
753
+ // otherwise, we need a custom comparer that manually implements Marvin
754
+ private sealed class StringKeyComparer : IEqualityComparer < string > , IEqualityComparer
755
+ {
756
+ private StringKeyComparer ( ) { }
757
+
758
+ internal static readonly IEqualityComparer < string > Instance = new StringKeyComparer ( ) ;
759
+
760
+ // special-case string keys and use Marvin hashing
761
+ public int GetHashCode ( string ? s ) => s is null ? 0
762
+ : Marvin . ComputeHash32 ( MemoryMarshal . AsBytes ( s . AsSpan ( ) ) , Marvin . DefaultSeed ) ;
763
+
764
+ public bool Equals ( string ? x , string ? y )
765
+ => string . Equals ( x , y ) ;
766
+
767
+ bool IEqualityComparer . Equals ( object x , object y )
768
+ => object . Equals ( x , y ) ;
769
+
770
+ int IEqualityComparer . GetHashCode ( object obj )
771
+ => obj is string s ? GetHashCode ( s ) : 0 ;
772
+ }
773
+ #endif
699
774
}
700
775
}
701
776
}
0 commit comments