@@ -71,7 +71,7 @@ public virtual T Get<T>(string key)
71
71
72
72
var item = CacheProvider . Get ( key ) ;
73
73
74
- return GetValueFromLazy < T > ( item ) ;
74
+ return GetValueFromLazy < T > ( item , out _ ) ;
75
75
}
76
76
77
77
public virtual Task < T > GetAsync < T > ( string key )
@@ -80,25 +80,27 @@ public virtual Task<T> GetAsync<T>(string key)
80
80
81
81
var item = CacheProvider . Get ( key ) ;
82
82
83
- return GetValueFromAsyncLazy < T > ( item ) ;
83
+ return GetValueFromAsyncLazy < T > ( item , out _ ) ;
84
84
}
85
85
86
86
public virtual T GetOrAdd < T > ( string key , Func < ICacheEntry , T > addItemFactory )
87
87
{
88
88
ValidateKey ( key ) ;
89
89
90
90
object cacheItem ;
91
+
92
+ object CacheFactory ( ICacheEntry entry ) =>
93
+ new Lazy < T > ( ( ) =>
94
+ {
95
+ var result = addItemFactory ( entry ) ;
96
+ EnsureEvictionCallbackDoesNotReturnTheAsyncOrLazy < T > ( entry . PostEvictionCallbacks ) ;
97
+ return result ;
98
+ } ) ;
99
+
91
100
locker . Wait ( ) ; //TODO: do we really need this? Could we just lock on the key?
92
101
try
93
102
{
94
- cacheItem = CacheProvider . GetOrCreate < object > ( key , entry =>
95
- new Lazy < T > ( ( ) =>
96
- {
97
- var result = addItemFactory ( entry ) ;
98
- EnsureEvictionCallbackDoesNotReturnTheAsyncOrLazy < T > ( entry . PostEvictionCallbacks ) ;
99
- return result ;
100
- } )
101
- ) ;
103
+ cacheItem = CacheProvider . GetOrCreate < object > ( key , CacheFactory ) ;
102
104
}
103
105
finally
104
106
{
@@ -107,7 +109,25 @@ public virtual T GetOrAdd<T>(string key, Func<ICacheEntry, T> addItemFactory)
107
109
108
110
try
109
111
{
110
- return GetValueFromLazy < T > ( cacheItem ) ;
112
+ var result = GetValueFromLazy < T > ( cacheItem , out var valueHasChangedType ) ;
113
+
114
+ // if we get a cache hit but for something with the wrong type we need to evict it, start again and cache the new item instead
115
+ if ( valueHasChangedType )
116
+ {
117
+ CacheProvider . Remove ( key ) ;
118
+ locker . Wait ( ) ; //TODO: do we really need this? Could we just lock on the key?
119
+ try
120
+ {
121
+ cacheItem = CacheProvider . GetOrCreate < object > ( key , CacheFactory ) ;
122
+ }
123
+ finally
124
+ {
125
+ locker . Release ( ) ;
126
+ }
127
+ result = GetValueFromLazy < T > ( cacheItem , out _ /* we just evicted so type change cannot happen this time */ ) ;
128
+ }
129
+
130
+ return result ;
111
131
}
112
132
catch //addItemFactory errored so do not cache the exception
113
133
{
@@ -138,16 +158,18 @@ public virtual async Task<T> GetOrAddAsync<T>(string key, Func<ICacheEntry, Task
138
158
await locker . WaitAsync ( )
139
159
. ConfigureAwait (
140
160
false ) ; //TODO: do we really need to lock everything here - faster if we could lock on just the key?
161
+
162
+ object CacheFactory ( ICacheEntry entry ) =>
163
+ new AsyncLazy < T > ( ( ) =>
164
+ {
165
+ var result = addItemFactory ( entry ) ;
166
+ EnsureEvictionCallbackDoesNotReturnTheAsyncOrLazy < T > ( entry . PostEvictionCallbacks ) ;
167
+ return result ;
168
+ } ) ;
169
+
141
170
try
142
171
{
143
- cacheItem = CacheProvider . GetOrCreate < object > ( key , entry =>
144
- new AsyncLazy < T > ( ( ) =>
145
- {
146
- var result = addItemFactory ( entry ) ;
147
- EnsureEvictionCallbackDoesNotReturnTheAsyncOrLazy < T > ( entry . PostEvictionCallbacks ) ;
148
- return result ;
149
- } )
150
- ) ;
172
+ cacheItem = CacheProvider . GetOrCreate < object > ( key , CacheFactory ) ;
151
173
}
152
174
finally
153
175
{
@@ -156,7 +178,26 @@ await locker.WaitAsync()
156
178
157
179
try
158
180
{
159
- var result = GetValueFromAsyncLazy < T > ( cacheItem ) ;
181
+ var result = GetValueFromAsyncLazy < T > ( cacheItem , out var valueHasChangedType ) ;
182
+
183
+ // if we get a cache hit but for something with the wrong type we need to evict it, start again and cache the new item instead
184
+ if ( valueHasChangedType )
185
+ {
186
+ CacheProvider . Remove ( key ) ;
187
+ await locker . WaitAsync ( )
188
+ . ConfigureAwait (
189
+ false ) ; //TODO: do we really need to lock everything here - faster if we could lock on just the key?
190
+ try
191
+ {
192
+ cacheItem = CacheProvider . GetOrCreate < object > ( key , CacheFactory ) ;
193
+ }
194
+ finally
195
+ {
196
+ locker . Release ( ) ;
197
+ }
198
+ result = GetValueFromAsyncLazy < T > ( cacheItem , out _ /* we just evicted so type change cannot happen this time */ ) ;
199
+ }
200
+
160
201
161
202
if ( result . IsCanceled || result . IsFaulted )
162
203
CacheProvider . Remove ( key ) ;
@@ -170,8 +211,9 @@ await locker.WaitAsync()
170
211
}
171
212
}
172
213
173
- protected virtual T GetValueFromLazy < T > ( object item )
214
+ protected virtual T GetValueFromLazy < T > ( object item , out bool valueHasChangedType )
174
215
{
216
+ valueHasChangedType = false ;
175
217
switch ( item )
176
218
{
177
219
case Lazy < T > lazy :
@@ -187,11 +229,21 @@ protected virtual T GetValueFromLazy<T>(object item)
187
229
return task . Result ;
188
230
}
189
231
232
+ // if they have cached something else with the same key we need to tell caller to reset the cached item
233
+ // although this is probably not the fastest this should not get called on the main use case
234
+ // where you just hit the first switch case above.
235
+ var itemsType = item ? . GetType ( ) ;
236
+ if ( itemsType != null && itemsType . IsGenericType && itemsType . GetGenericTypeDefinition ( ) == typeof ( Lazy < > ) )
237
+ {
238
+ valueHasChangedType = true ;
239
+ }
240
+
190
241
return default ( T ) ;
191
242
}
192
243
193
- protected virtual Task < T > GetValueFromAsyncLazy < T > ( object item )
244
+ protected virtual Task < T > GetValueFromAsyncLazy < T > ( object item , out bool valueHasChangedType )
194
245
{
246
+ valueHasChangedType = false ;
195
247
switch ( item )
196
248
{
197
249
case AsyncLazy < T > asyncLazy :
@@ -205,6 +257,15 @@ protected virtual Task<T> GetValueFromAsyncLazy<T>(object item)
205
257
return Task . FromResult ( variable ) ;
206
258
}
207
259
260
+ // if they have cached something else with the same key we need to tell caller to reset the cached item
261
+ // although this is probably not the fastest this should not get called on the main use case
262
+ // where you just hit the first switch case above.
263
+ var itemsType = item ? . GetType ( ) ;
264
+ if ( itemsType != null && itemsType . IsGenericType && itemsType . GetGenericTypeDefinition ( ) == typeof ( AsyncLazy < > ) )
265
+ {
266
+ valueHasChangedType = true ;
267
+ }
268
+
208
269
return Task . FromResult ( default ( T ) ) ;
209
270
}
210
271
0 commit comments