Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SlidingExpiration and MemoryCache

Tags:

Looking at the documentation for MemoryCache I expected that if an object was accessed within the Expiration period the period would be refreshed. To be honest I think I inferred from the name 'Sliding' as much as anything.

However, it appears from this test

   [Test]     public void SlidingExpiryNotRefreshedOnTouch()     {         var memoryCache = new MemoryCache("donkey")         {             {                 "1",                 "jane",                 new CacheItemPolicy {SlidingExpiration = TimeSpan.FromSeconds(1) }             }         };         var enumerable = Enumerable.Repeat("1", 100)             .TakeWhile((id, index) =>             {                 Thread.Sleep(100);                 return memoryCache.Get(id) != null; // i.e. it still exists             })             .Select((id, index) => (index+2)*100.0/1000); // return the elapsed time         var expires = enumerable.Last(); // gets the last existing entry          expires.Should().BeGreaterThan(1.0);     } 

It fails and exhibits the behavior that the object is ejected once the TimeSpan is complete whether or not the object has been accessed. The Linq query is executed at the enumerable.Last(); statement, at which point it will only take while the cache has not expired. As soon as it stops the last item in the list will indicate how long the item lived in the cache for.

For Clarity This question is about the behaviour of MemoryCache. Not the linq query.

Is this anyone else's expectation (i.e. that the expiration does not slide with each touch)? Is there a mode that extends the lifetime of objects that are 'touched'?

Update I found even if I wrote a wrapper around the cache and re-added the object back to the cache every time I retrieved it, with another SlidingExpiration its still only honored the initial setting. To get it to work the way I desired I had to physically remove it from the cache before re-adding it! This could cause undesirable race conditions in a multi-threaded environment.

like image 312
Yoztastic Avatar asked Dec 30 '16 11:12

Yoztastic


People also ask

What is a MemoryCache?

Memory caching (often simply referred to as caching) is a technique in which computer applications temporarily store data in a computer's main memory (i.e., random access memory, or RAM) to enable fast retrievals of that data. The RAM that is used for the temporary storage is known as the cache.

What is Slidingexpiration 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.

What is sliding expiration and absolute expiration in caching?

Sliding ExpirationIn Absolute Expiration the cache will be expired after a particular time irrespective of the fact whether it has been used or not in that time span. Whereas, in Sliding Time Expiration, the cache will be expired after a particular time only if it has not been used during that time span.

What is MemoryCache 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.


1 Answers

 ... new CacheItemPolicy {SlidingExpiration = TimeSpan.FromSeconds(1) } 

This is not adequately documented in MSDN. You were a bit unlucky, 1 second is not enough. By a hair, use 2 seconds and you'll see it works just like you hoped it would. Tinker some more with FromMilliseconds() and you'll see that ~1.2 seconds is the happy minimum in this program.

Explaining this is rather convoluted, I have to talk about how MemoryCache avoids having to update the sliding timer every single time you access the cache. Which is relatively expensive, as you might imagine. Let's take a shortcut and take you to the relevant Reference Source code. Small enough to paste here:

    internal void UpdateSlidingExp(DateTime utcNow, CacheExpires expires) {         if (_slidingExp > TimeSpan.Zero) {             DateTime utcNewExpires = utcNow + _slidingExp;             if (utcNewExpires - _utcAbsExp >= CacheExpires.MIN_UPDATE_DELTA || utcNewExpires < _utcAbsExp) {                 expires.UtcUpdate(this, utcNewExpires);             }         }     } 

CacheExpires.MIN_UPDATE_DELTA is the crux, it prevents UtcUpdate() from being called. Or to put it another way, at least MIN_UPDATE_DELTA worth of time has to pass before it will update the sliding timer. The CacheExpired class is not indexed by the Reference Source, a hint that they are not entirely happy about the way it works :) But a decent decompiler can show you:

static CacheExpires() {     MIN_UPDATE_DELTA = new TimeSpan(0, 0, 1);     MIN_FLUSH_INTERVAL = new TimeSpan(0, 0, 1);     // etc... } 

In other words, hard-coded to 1 second. With no way to change it right now, that's pretty ugly. It takes ~1.2 seconds for the SlidingExpiration value in this test program because Thread.Sleep(100) does not actually sleep for 100 milliseconds, it takes a bit more. Or to put it another way, it will be the 11th Get() call that gets the sliding timer to slide in this test program. You didn't get that far.

Well, this ought to be documented but I'd guess this is subject to change. For now, you'll need to assume that a practical sliding expiration time should be at least 2 seconds.

like image 136
Hans Passant Avatar answered Sep 23 '22 16:09

Hans Passant