This is a follow-up to this question, which contains contradictory answers. I am also interested in an answer related to a more recent version of ASP.NET.
My application uses HttpRuntime.Cache to cache some lists of models that should never expire. They are loaded on application warmup and they are changed quite rarely, but read quite often.
My code is the following:
private void ReportRemovedCallback(string key, object value, CacheItemRemovedReason reason)
{
if (!ApplicationPoolService.IsShuttingDown())
{
var str = $"Removed cached item with key {key} and count {(value as IDictionary)?.Count}, reason {reason}";
LoggingService.Log(LogLevel.Info, str);
}
}
private IDictionary<int, T> ThreadSafeCacheAccessAction(Action<IDictionary<int, T>, bool> action = null)
{
// refresh cache if necessary
var dict = HttpRuntime.Cache[CacheDictKey] as IDictionary<int, T>;
bool invalidated = false;
if (dict == null)
{
lock (CacheLockObject)
{
// getting expiration times from model attribute
var cacheInfo = typeof(T).GetCustomAttributes(typeof(UseInCachedRepositoryAttribute), inherit: true).FirstOrDefault() as UseInCachedRepositoryAttribute;
int absoluteExpiration = cacheInfo?.AbsoluteExpiration ?? Constants.Cache.IndefiniteRetention;
int slidingExpiration = cacheInfo?.SlidingExpiration ?? Constants.Cache.NoSlidingExpiration;
dict = _modelRepository.AllNoTracking.ToList().Where(item => item.PkId != 0).ToDictionary(item => item.PkId, item => item);
HttpRuntime.Cache.Insert(CacheDictKey, dict, dependencies: null,
absoluteExpiration: DateTime.Now.AddMinutes(absoluteExpiration),
slidingExpiration: slidingExpiration <= 0 ? Cache.NoSlidingExpiration : TimeSpan.FromMinutes(slidingExpiration),
priority: CacheItemPriority.NotRemovable,
onRemoveCallback: ReportRemovedCallback);
invalidated = true;
}
}
Based on the documentation provided here, I have also included the following markup within the web.config:
<caching>
<cache disableExpiration="true" disableMemoryCollection="true" />
</caching>
However, from time to time, ReportRemovedCallback is called for item removal. My feeling is that caching configuration from web.config is ignored (the documentation clearly states it is outdated) and that CacheItemPriority.NotRemovable means only "a very high priority", not "never remove".
Question: is there a way to convince HttpRuntime.Cache to never remove some items? Or should I consider another caching mechanism?
Ok, so I have dug more and there is no definitive answer, but the following configuration from this old docs seems to apply regardless of trials to deny expiration:
So, if physical memory usage is above 90%, it will evict cache items. Since OS tends to use almost all physical memory for system cache (reported by Task Manager), this is not so unlikely as it sounds.
Alternative
I took MemoryCache for a spin, since it is very similar to
HttpRuntime.Cache. It provides similar functionality, but lacks theCacheEntryUpdateCallback(you can provide it, but complains with InvalidArgumentException if it is different fromnull).Now my code is the following:
After some tests, there were no extra removals and the memory consumption of
w3wp.exeraised as expected.More details are provided within this answer.