Search code examples
.netcachingasp.net-corememorycache

.Net Core MemoryCache PostEvictionCallback not working properly


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();
        }
    }
}

Solution

  • 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.