Search code examples
c#code-cleanupapplication-shutdown

MemoryCache avoid refreshing on shutdown


I use a System.Runtime.MemoryCache in a web services integration layer to keep some product lists which are external and slow to retrieve. However, I want to refresh those as they expire to free my callers from waiting on it. For that reason I am prewarming this cache using IIS7.5 and have a RemovedCallback to reload the data when expired.

However, what happens when the web pool process dies gracefully? MemoryCache is disposable, so it will kick my object out, at which point I'll try to cram a new instance back, whilst putting the entire process on hold. Is there any way for me to safely detect that I should not reload data?

internal class ProductCache {
    private static object _lock = new object();
    private static string _cid = Guid.NewGuid().ToString();

    public static Product[] data 
    {
        get
        {
            lock (_lock)
            {
                if (!MemoryCache.Default.Contains(_cid))
                {
                    Product[] p;
                    try
                    {
                        // load data into p
                    }
                    catch (System.Exception e)
                    {
                        Helper.SafeSendExceptionEmail("Integrator.Caching", e);
                        throw e;
                    }
                    MemoryCache.Default.Add(_cid, p, new CacheItemPolicy()
                    {
                        AbsoluteExpiration = DateTimeOffset.Now.AddHours(8),
                        RemovedCallback = arg =>
                        {
                            // query again to force reload
                            var d = data;
                        }
                    });
                }
                return (Product[])MemoryCache.Default[_cid];
            }
        }
    }
}

Solution

  • Ok, by digging through MemoryCache and MemoryCacheStore source, it seems that cache is auto-disposed on domain unload at which point it disposes of all its stores, which in turn remove cache items with CacheSpecificEviction reason. This reason is not used anywhere else so it must represent the "I'm dying" reason (they could have been more clear in the docs though)

        public void Dispose()
        {
            if (Interlocked.Exchange(ref this._disposed, 1) == 0)
            {
                this._expires.EnableExpirationTimer(false);
                ArrayList list = new ArrayList(this._entries.Count);
                lock (this._entriesLock)
                {
                    foreach (DictionaryEntry entry in this._entries)
                    {
                        MemoryCacheEntry entry2 = entry.Value as MemoryCacheEntry;
                        list.Add(entry2);
                    }
                    foreach (MemoryCacheEntry entry3 in list)
                    {
                        MemoryCacheKey key = entry3;
                        entry3.State = EntryState.RemovingFromCache;
                        this._entries.Remove(key);
                    }
                }
                foreach (MemoryCacheEntry entry4 in list)
                {
                    this.RemoveFromCache(entry4, CacheEntryRemovedReason.CacheSpecificEviction, false);
                }
                this._insertBlock.Close();
            }
        }