Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MemoryCache AbsoluteExpiration acting strange

I'm trying to use a MemoryCache in .net 4.5 to keep track of and automatically update various items, but it seems like no matter what I set as an AbsoluteExpiration it will always only expire in 15 seconds or more.

I want the cache items to expire every 5 seconds, but it always expires in at least 15 seconds, and if I move the expiration time out, it will end up being something like 15 seconds + my refresh interval, but never less than 15 seconds.

Is there some internal timer resolution that I'm not seeing? I looked through a bit of the reflected System.Runtime.Caching.MemoryCache code and nothing stood out to me, and I haven't been able to find anybody else who has this issue out on the internet.

I have a very basic example below that illustrates the problem.

What I want is for CacheEntryUpdate to be hit every 5 seconds or so and update with new data, but, as I've said, it only ever gets hit in 15+ seconds.

static MemoryCache MemCache; static int RefreshInterval = 5000;  protected void Page_Load(object sender, EventArgs e) {     if (MemCache == null)         MemCache = new MemoryCache("MemCache");      if (!MemCache.Contains("cacheItem"))     {         var cacheObj = new object();         var policy = new CacheItemPolicy         {             UpdateCallback = new CacheEntryUpdateCallback(CacheEntryUpdate),             AbsoluteExpiration = DateTimeOffset.UtcNow.AddMilliseconds(RefreshInterval)         };         var cacheItem = new CacheItem("cacheItem", cacheObj);         MemCache.Set("cacheItem", cacheItem, policy);     } }  private void CacheEntryUpdate(CacheEntryUpdateArguments args) {     var cacheItem = MemCache.GetCacheItem(args.Key);     var cacheObj = cacheItem.Value;      cacheItem.Value = cacheObj;     args.UpdatedCacheItem = cacheItem;     var policy = new CacheItemPolicy     {         UpdateCallback = new CacheEntryUpdateCallback(CacheEntryUpdate),         AbsoluteExpiration = DateTimeOffset.UtcNow.AddMilliseconds(RefreshInterval)     };     args.UpdatedCacheItemPolicy = policy; } 
like image 867
Jared Avatar asked Sep 27 '12 21:09

Jared


People also ask

How do I clear my MemoryCache default?

You can dispose the MemoryCache. Default cache and then re-set the private field singleton to null, to make it recreate the MemoryCache. Default.

What is sliding expiration cache?

Sliding expiration ensures that if the data is accessed within the specified time interval the cache item life span will be extended by the interval value. For example, a session is added with 10 minutes expiration.

How does MemoryCache work C#?

In-Memory Cache is used for when you want to implement cache in a single process. When the process dies, the cache dies with it. If you're running the same process on several servers, you will have a separate cache for each server. Persistent in-process Cache is when you back up your cache outside of process memory.


2 Answers

I've figured it out. There's an internal static readonly TimeSpan on System.Runtime.Caching.CacheExpires called _tsPerBucket that is hardcoded at 20 seconds.

Apparently, this field is what's used on the internal timers that run and check to see if cache items are expired.

I'm working around this by overwriting the value using reflection and clearing the default MemoryCache instance to reset everything. It seems to work, even if it is a giant hack.

Here's the updated code:

static MemoryCache MemCache; static int RefreshInterval = 1000;  protected void Page_Load(object sender, EventArgs e) {     if (MemCache == null)     {         const string assembly = "System.Runtime.Caching, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a";         var type = Type.GetType("System.Runtime.Caching.CacheExpires, " + assembly, true, true);         var field = type.GetField("_tsPerBucket", BindingFlags.Static | BindingFlags.NonPublic);         field.SetValue(null, TimeSpan.FromSeconds(1));          type = typeof(MemoryCache);         field = type.GetField("s_defaultCache", BindingFlags.Static | BindingFlags.NonPublic);         field.SetValue(null, null);          MemCache = new MemoryCache("MemCache");     }      if (!MemCache.Contains("cacheItem"))     {         var cacheObj = new object();         var policy = new CacheItemPolicy         {             UpdateCallback = new CacheEntryUpdateCallback(CacheEntryUpdate),             AbsoluteExpiration = DateTimeOffset.UtcNow.AddMilliseconds(RefreshInterval)         };         var cacheItem = new CacheItem("cacheItem", cacheObj);         MemCache.Set("cacheItem", cacheItem, policy);     } }  private void CacheEntryUpdate(CacheEntryUpdateArguments args) {     var cacheItem = MemCache.GetCacheItem(args.Key);     var cacheObj = cacheItem.Value;      cacheItem.Value = cacheObj;     args.UpdatedCacheItem = cacheItem;     var policy = new CacheItemPolicy     {         UpdateCallback = new CacheEntryUpdateCallback(CacheEntryUpdate),         AbsoluteExpiration = DateTimeOffset.UtcNow.AddMilliseconds(RefreshInterval)     };     args.UpdatedCacheItemPolicy = policy; } 
like image 55
Jared Avatar answered Oct 02 '22 09:10

Jared


Would you be willing/able to change from the older System.Runtime.Caching to the new Microsft.Extensions.Caching? version 1.x supports netstandard 1.3 and net451. If so then the improved API would support the usage you describe without hackery with reflection.

The MemoryCacheOptions object has a property ExpirationScanFrequency to allow you to control the scan frequency of the cache cleanup, see https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.caching.memory.memorycacheoptions.expirationscanfrequency?view=aspnetcore-2.0

Be aware that there is no longer expiration based on timers (this is a performance design decision), and so now memory pressure or calling one of the Get() based methods for the cached items are now the triggers for expiration. However you can force time based expiration using cancellation tokens, see this SO answer for an example https://stackoverflow.com/a/47949111/3140853.

like image 37
alastairtree Avatar answered Oct 02 '22 08:10

alastairtree