2
2
// The .NET Foundation licenses this file to you under the MIT license.
3
3
4
4
using System . Diagnostics ;
5
+ using System . Linq ;
5
6
using Microsoft . Extensions . Caching . Memory ;
6
7
7
8
namespace Microsoft . AspNetCore . OutputCaching . Memory ;
8
9
9
10
internal sealed class MemoryOutputCacheStore : IOutputCacheStore
10
11
{
11
12
private readonly MemoryCache _cache ;
12
- private readonly Dictionary < string , HashSet < string > > _taggedEntries = new ( ) ;
13
+ private readonly Dictionary < string , HashSet < TaggedEntry > > _taggedEntries = [ ] ;
13
14
private readonly object _tagsLock = new ( ) ;
14
15
15
16
internal MemoryOutputCacheStore ( MemoryCache cache )
@@ -20,7 +21,7 @@ internal MemoryOutputCacheStore(MemoryCache cache)
20
21
}
21
22
22
23
// For testing
23
- internal Dictionary < string , HashSet < string > > TaggedEntries => _taggedEntries ;
24
+ internal Dictionary < string , HashSet < string > > TaggedEntries => _taggedEntries . ToDictionary ( kvp => kvp . Key , kvp => kvp . Value . Select ( t => t . Key ) . ToHashSet ( ) ) ;
24
25
25
26
public ValueTask EvictByTagAsync ( string tag , CancellationToken cancellationToken )
26
27
{
@@ -30,7 +31,7 @@ public ValueTask EvictByTagAsync(string tag, CancellationToken cancellationToken
30
31
{
31
32
if ( _taggedEntries . TryGetValue ( tag , out var keys ) )
32
33
{
33
- if ( keys != null && keys . Count > 0 )
34
+ if ( keys is { Count : > 0 } )
34
35
{
35
36
// If MemoryCache changed to run eviction callbacks inline in Remove, iterating over keys could throw
36
37
// To prevent allocating a copy of the keys we check if the eviction callback ran,
@@ -40,7 +41,7 @@ public ValueTask EvictByTagAsync(string tag, CancellationToken cancellationToken
40
41
while ( i > 0 )
41
42
{
42
43
var oldCount = keys . Count ;
43
- foreach ( var key in keys )
44
+ foreach ( var ( key , _ ) in keys )
44
45
{
45
46
_cache . Remove ( key ) ;
46
47
i -- ;
@@ -74,6 +75,8 @@ public ValueTask SetAsync(string key, byte[] value, string[]? tags, TimeSpan val
74
75
ArgumentNullException . ThrowIfNull ( key ) ;
75
76
ArgumentNullException . ThrowIfNull ( value ) ;
76
77
78
+ var entryId = Guid . NewGuid ( ) ;
79
+
77
80
if ( tags != null )
78
81
{
79
82
// Lock with SetEntry() to prevent EvictByTagAsync() from trying to remove a tag whose entry hasn't been added yet.
@@ -90,27 +93,27 @@ public ValueTask SetAsync(string key, byte[] value, string[]? tags, TimeSpan val
90
93
91
94
if ( ! _taggedEntries . TryGetValue ( tag , out var keys ) )
92
95
{
93
- keys = new HashSet < string > ( ) ;
96
+ keys = new HashSet < TaggedEntry > ( ) ;
94
97
_taggedEntries [ tag ] = keys ;
95
98
}
96
99
97
100
Debug . Assert ( keys != null ) ;
98
101
99
- keys . Add ( key ) ;
102
+ keys . Add ( new TaggedEntry ( key , entryId ) ) ;
100
103
}
101
104
102
- SetEntry ( key , value , tags , validFor ) ;
105
+ SetEntry ( key , value , tags , validFor , entryId ) ;
103
106
}
104
107
}
105
108
else
106
109
{
107
- SetEntry ( key , value , tags , validFor ) ;
110
+ SetEntry ( key , value , tags , validFor , entryId ) ;
108
111
}
109
112
110
113
return ValueTask . CompletedTask ;
111
114
}
112
115
113
- void SetEntry ( string key , byte [ ] value , string [ ] ? tags , TimeSpan validFor )
116
+ private void SetEntry ( string key , byte [ ] value , string [ ] ? tags , TimeSpan validFor , Guid entryId )
114
117
{
115
118
Debug . Assert ( key != null ) ;
116
119
@@ -120,30 +123,33 @@ void SetEntry(string key, byte[] value, string[]? tags, TimeSpan validFor)
120
123
Size = value . Length
121
124
} ;
122
125
123
- if ( tags != null && tags . Length > 0 )
126
+ if ( tags is { Length : > 0 } )
124
127
{
125
128
// Remove cache keys from tag lists when the entry is evicted
126
- options . RegisterPostEvictionCallback ( RemoveFromTags , tags ) ;
129
+ options . RegisterPostEvictionCallback ( RemoveFromTags , ( tags , entryId ) ) ;
127
130
}
128
131
129
132
_cache . Set ( key , value , options ) ;
130
133
}
131
134
132
- void RemoveFromTags ( object key , object ? value , EvictionReason reason , object ? state )
135
+ private void RemoveFromTags ( object key , object ? value , EvictionReason reason , object ? state )
133
136
{
134
- var tags = state as string [ ] ;
137
+ Debug . Assert ( state != null ) ;
138
+
139
+ var ( tags , entryId ) = ( ( string [ ] Tags , Guid EntryId ) ) state ;
135
140
136
141
Debug . Assert ( tags != null ) ;
137
142
Debug . Assert ( tags . Length > 0 ) ;
138
143
Debug . Assert ( key is string ) ;
144
+ Debug . Assert ( entryId != Guid . Empty ) ;
139
145
140
146
lock ( _tagsLock )
141
147
{
142
148
foreach ( var tag in tags )
143
149
{
144
150
if ( _taggedEntries . TryGetValue ( tag , out var tagged ) )
145
151
{
146
- tagged . Remove ( ( string ) key ) ;
152
+ tagged . Remove ( new TaggedEntry ( ( string ) key , entryId ) ) ;
147
153
148
154
// Remove the collection if there is no more keys in it
149
155
if ( tagged . Count == 0 )
@@ -154,4 +160,6 @@ void RemoveFromTags(object key, object? value, EvictionReason reason, object? st
154
160
}
155
161
}
156
162
}
163
+
164
+ private record TaggedEntry ( string Key , Guid EntryId ) ;
157
165
}
0 commit comments