I'm using MemoryCache in my asp.net application. After my application runs for hours, w3wp process consume so much memory, it can reach to 10 Gigabytes of memory and after while the application becomes unresponsive and IIS resets it automatically (by ping mechanism).
I've profiled the application process and find that MemoryCache create Multiple instances of MemoryCacheStore internally (equal to number of CPUs), and each instance consumes much memory!
it seems that for some reason MemoryCache stores same data in multiple instances and this causes huge memory consumption.
As you see in following image I have 16 CPUs and 16 of MemoryCacheStore is created internally by single MemoryCache instance. I've checked source code of MemoryCache and found that it creates an array of MemoryCacheStore of Environment.Processors count but didn't found that why all of this instances communes memory and how can prevent this?
public partial class MemoryCacheManager : DisposableObject, ICacheManager
{
const string LockRecursionExceptionMessage = "Acquiring identical cache items recursively is not supported. Key: {0}";
// Wwe put a special string into cache if value is null,
// otherwise our 'Contains()' would always return false,
// which is bad if we intentionally wanted to save NULL values.
public const string FakeNull = "__[NULL]__";
private readonly Work<ICacheScopeAccessor> _scopeAccessor;
private MemoryCache _cache;
private readonly ConcurrentDictionary<string, SemaphoreSlim> _keyLocks = new ConcurrentDictionary<string, SemaphoreSlim>();
public MemoryCacheManager(Work<ICacheScopeAccessor> scopeAccessor)
{
_scopeAccessor = scopeAccessor;
_cache = CreateCache();
}
private MemoryCache CreateCache()
{
return new MemoryCache("SmartStore" + DateTime.Now.ToString("yyyy_MM_dd_HH_mm_ss"));
}
public bool IsDistributedCache
{
get { return false; }
}
private bool TryGet<T>(string key, bool independent, out T value)
{
value = default(T);
object obj = _cache.Get(key);
if (obj != null)
{
// Make the parent scope's entry depend on this
if (!independent)
{
_scopeAccessor.Value.PropagateKey(key);
}
if (obj.Equals(FakeNull))
{
return true;
}
value = (T)obj;
return true;
}
return false;
}
public T Get<T>(string key, bool independent = false)
{
TryGet(key, independent, out T value);
return value;
}
public T Get<T>(string key, Func<T> acquirer, TimeSpan? duration = null, bool independent = false)
{
if (TryGet(key, independent, out T value))
{
return value;
}
if (_scopeAccessor.Value.HasScope(key))
{
throw new LockRecursionException(LockRecursionExceptionMessage.FormatInvariant(key));
}
// Get the (semaphore) locker specific to this key
using (KeyedLock.Lock("cache:" + key, TimeSpan.FromMinutes(1)))
{
// Atomic operation must be outer locked
if (!TryGet(key, independent, out value))
{
using (_scopeAccessor.Value.BeginScope(key))
{
value = acquirer();
Put(key, value, duration, _scopeAccessor.Value.Current.Dependencies);
return value;
}
}
}
return value;
}
public async Task<T> GetAsync<T>(string key, Func<Task<T>> acquirer, TimeSpan? duration = null, bool independent = false)
{
if (TryGet(key, independent, out T value))
{
return value;
}
if (_scopeAccessor.Value.HasScope(key))
{
throw new LockRecursionException(LockRecursionExceptionMessage.FormatInvariant(key));
}
// Get the async (semaphore) locker specific to this key
using (await KeyedLock.LockAsync("cache:" + key, TimeSpan.FromMinutes(1)))
{
if (!TryGet(key, independent, out value))
{
using (_scopeAccessor.Value.BeginScope(key))
{
value = await acquirer();
Put(key, value, duration, _scopeAccessor.Value.Current.Dependencies);
return value;
}
}
}
return value;
}
public void Put(string key, object value, TimeSpan? duration = null, IEnumerable<string> dependencies = null)
{
_cache.Set(key, value ?? FakeNull, GetCacheItemPolicy(duration, dependencies));
}
public bool Contains(string key)
{
return _cache.Contains(key);
}
public void Remove(string key)
{
_cache.Remove(key);
}
public IEnumerable<string> Keys(string pattern)
{
Guard.NotEmpty(pattern, nameof(pattern));
var keys = _cache.AsParallel().Select(x => x.Key);
if (pattern.IsEmpty() || pattern == "*")
{
return keys.ToArray();
}
var wildcard = new Wildcard(pattern, RegexOptions.IgnoreCase);
return keys.Where(x => wildcard.IsMatch(x)).ToArray();
}
public int RemoveByPattern(string pattern)
{
lock (_cache)
{
var keysToRemove = Keys(pattern);
int count = 0;
// lock atomic operation
foreach (string key in keysToRemove)
{
_cache.Remove(key);
count++;
}
return count;
}
}
public void Clear()
{
// Faster way of clearing cache: https://stackoverflow.com/questions/8043381/how-do-i-clear-a-system-runtime-caching-memorycache
var oldCache = Interlocked.Exchange(ref _cache, CreateCache());
oldCache.Dispose();
GC.Collect();
}
public virtual ISet GetHashSet(string key, Func<IEnumerable<string>> acquirer = null)
{
var result = Get(key, () =>
{
var set = new MemorySet(this);
var items = acquirer?.Invoke();
if (items != null)
{
set.AddRange(items);
}
return set;
});
return result;
}
private CacheItemPolicy GetCacheItemPolicy(TimeSpan? duration, IEnumerable<string> dependencies)
{
var absoluteExpiration = ObjectCache.InfiniteAbsoluteExpiration;
if (duration.HasValue)
{
absoluteExpiration = DateTime.UtcNow + duration.Value;
}
var cacheItemPolicy = new CacheItemPolicy
{
AbsoluteExpiration = absoluteExpiration,
SlidingExpiration = ObjectCache.NoSlidingExpiration
};
if (dependencies != null && dependencies.Any())
{
// INFO: we can only depend on existing items, otherwise this entry will be removed immediately.
dependencies = dependencies.Where(x => x != null && _cache.Contains(x));
if (dependencies.Any())
{
cacheItemPolicy.ChangeMonitors.Add(_cache.CreateCacheEntryChangeMonitor(dependencies));
}
}
//cacheItemPolicy.RemovedCallback = OnRemoveEntry;
return cacheItemPolicy;
}
//private void OnRemoveEntry(CacheEntryRemovedArguments args)
//{
// if (args.RemovedReason == CacheEntryRemovedReason.ChangeMonitorChanged)
// {
// Debug.WriteLine("MEMCACHE: remove depending entry '{0}'.".FormatInvariant(args.CacheItem.Key));
// }
//}
protected override void OnDispose(bool disposing)
{
if (disposing)
_cache.Dispose();
}
}
// how i use cache
_requestCache.Get(key, () =>
{
var query = _categoryRepository.Table;
if (!showHidden)
query = query.Where(c => c.Published);
query = query.Where(c => c.ParentCategoryId == parentCategoryId && !c.Deleted);
query = ApplyHiddenCategoriesFilter(query, storeId, showHidden);
var categories = query.OrderBy(x => x.DisplayOrder).ToList();
return categories;
});