.Net Core MemoryCache PostEvictionCallback not working properly
Solution 1
It is happening because the item is not evicted till you query for the item and it checks the expiration
(From the Source of MemoryCacheStore.Get(MemoryCacheKey key)
)
internal MemoryCacheEntry Get(MemoryCacheKey key) {
MemoryCacheEntry entry = _entries[key] as MemoryCacheEntry;
// has it expired?
if (entry != null && entry.UtcAbsExp <= DateTime.UtcNow) {
Remove(key, entry, CacheEntryRemovedReason.Expired);
entry = null;
}
// update outside of lock
UpdateExpAndUsage(entry);
return entry;
}
or when Trim()
is called internally due to memory pressure
(From the Source of TrimInternal(int percent)
)
/*SNIP*/
trimmedOrExpired = _expires.FlushExpiredItems(true);
if (trimmedOrExpired < toTrim) {
trimmed = _usage.FlushUnderUsedItems(toTrim - trimmedOrExpired);
trimmedOrExpired += trimmed;
}
/*SNIP*/
If your system is not currently low enough on memory to trigger a trim then the only time items will be evicted is when they are attempted to be retrieved.
Solution 2
To add onto the accept answer and comments, you can force the cache to expire and evict automatically by using a expiring cancellation token.
int expirationMinutes = 60;
var expirationTime = DateTime.Now.Add(expirationMinutes);
var expirationToken = new CancellationChangeToken(
new CancellationTokenSource(TimeSpan.FromMinutes(expirationMinutes + .01)).Token);
var cacheEntryOptions = new MemoryCacheEntryOptions()
// Pin to cache.
.SetPriority(CacheItemPriority.NeverRemove)
// Set the actual expiration time
.SetAbsoluteExpiration(expirationTime)
// Force eviction to run
.AddExpirationToken(expirationToken)
// Add eviction callback
.RegisterPostEvictionCallback(callback: CacheItemRemoved, state: this);
`
The lack of built in timer behavior, which the old one used to have, is supposed to be by design and this is what was recommended in its place. See: https://github.com/aspnet/Caching/issues/248
Related videos on Youtube
mattinsalto
Updated on June 04, 2022Comments
-
mattinsalto almost 2 years
I have set cache items with sliding expiration in a Microsoft.Extensions.Caching.Memory.MemoryCache. I want to trigger a callback everytime a cache item expires, but callback isn't triggered until I query the cache for the expired cache item.
Here is the code:
using System; using Microsoft.Extensions.Caching.Memory; namespace Memcache { public class Program { private static MemoryCache _cache; private static int _cacheExpSecs; public static void Main(string[] args) { _cache = new MemoryCache(new MemoryCacheOptions()); _cacheExpSecs = 2; var cacheEntryOptions = new MemoryCacheEntryOptions() .SetSlidingExpiration(TimeSpan.FromSeconds(_cacheExpSecs)) .RegisterPostEvictionCallback(callback: EvictionCallback); _cache.Set(1, "One", cacheEntryOptions); _cache.Set(2, "Two", cacheEntryOptions); var autoEvent = new System.Threading.AutoResetEvent(false); System.Threading.Timer timer = new System.Threading.Timer(checkCache, autoEvent, 1000, 6000); Console.Read(); } private static void checkCache(Object o) { if(_cache.Get(1)!=null) { Console.WriteLine(string.Format(@"checkCache: Cache with key {0} will be removed manually and will trigger the callback.", 1)); _cache.Remove(1); } else { Console.WriteLine(string.Format("checkCache: Cache with key {0} is expired.", 1)); } if(_cache.Get(2) != null) { Console.WriteLine(string.Format("checkCache: Cache with key {0} will expire in {1} seconds, but won't trigger the callback until we check it's value again.", 2, _cacheExpSecs)); } else { Console.WriteLine(string.Format("checkCache: Cache with key {0} is expired.", 2)); } } private static void EvictionCallback(object key, object value, EvictionReason reason, object state) { Console.WriteLine(); Console.WriteLine("/*****************************************************/"); Console.WriteLine(string.Format("/* EvictionCallback: Cache with key {0} has expired. */", key)); Console.WriteLine("/*****************************************************/"); Console.WriteLine(); } } }
-
mattinsalto about 7 yearsThanks. There is ExpirationScanFrequency option in MemoryCacheOptions, but neither works.
-
Scott Chamberlain about 7 yearsYou might want to unaccept my answer for now. I totally overlooked this was .NET Core. Checking the Core source now that my statements are still correct
-
Scott Chamberlain about 7 yearsOk, the logic is still the same.
ExpirationScanFrequency
is the frequency it does a full scan after any kind of Get or Remove is peformed. If the time has been longer than theExpirationScanFrequency
it does a full scan of items instead of just the one it was working with, It still does not run a timer to perform the scans, they are all still done on demand when a action is perfomed. -
Scott Chamberlain about 7 yearsIf you wanted to you could call
_cache.Compact(0)
on your own timer and that would flush out the expired entries on a regular basis. -
mattinsalto about 7 yearsThank you, I'll use Compact(0). On the other hand, in .NET framework, System.Runtime.Caching.MemoryCache triggers the callback when sliding expiration cache item is expired.
-
Marco almost 4 yearsThanks, this was exactly what I was looking for and can confirm that cache does not need to be hit for this to trigger.