9

I'm facing a strange problem with ASP.NET MemoryCaching in a MVC 3 ASP.NET application.

Each time an action is executed, I check if its LoginInfo are actually stored in the MemoryCache (code has been simplified, but core is as follow):

[NonAction]
protected override void OnAuthorization(AuthorizationContext filterContext) {
  Boolean autorizzato = false;
  LoginInfo me = CacheUtils.GetLoginData(User.Identity.Name);
  if (me == null)
  {
    me = LoginData.UserLogin(User.Identity.Name);
    CacheUtils.SetLoginInfo(User.Identity.Name, me);
  }
  // Test if the object is really in the memory cache
  if (CacheUtils.GetLoginData(User.Identity.Name) == null) {
     throw new Exception("IMPOSSIBLE");
  } 
}

The GetLoginInfo is:

 public static LoginInfo GetLoginData(String Username)
        {
            LoginInfo local = null;
            ObjectCache cache = MemoryCache.Default;
            if (cache.Contains(Username.ToUpper()))
            {
                local = (LoginInfo)cache.Get(Username.ToUpper());
            }
            else
            {
                log.Warn("User " + Username + " not found  in cache");
            }
            return local;
        }

The SetLoginInfo is:

        public static void SetLoginInfo (String Username, LoginInfo Info)
        {
            ObjectCache cache = MemoryCache.Default;
            if ((Username != null) && (Info != null))
            {
                if (cache.Contains(Username.ToUpper()))
                {
                    cache.Remove(Username.ToUpper());
                }
                cache.Add(Username.ToUpper(), Info, new CacheItemPolicy());
            }
            else
            {
                log.Error("NotFound...");
            }
       }

The code is pretty straightforward, but sometimes (totally randomly), just after adding the LoginInfo to the MemoryCache, this results empty, the just added Object is not present, therefore I got the Exception.

I'm testing this both on Cassini and IIS 7, it seems not related to AppPool reusability (enabled in IIS 7), I've tested with several Caching policies, but cannot make it work

What Am I missing/Failing ?

PS: forgive me for my bad english

BigMike
  • 6,683
  • 1
  • 23
  • 24
  • Do you have a method called `GetLoginInfo` and `GetLoginData`, or is this a typo? – Danny Tuppeny Oct 21 '11 at 17:46
  • Have you confirmed `LoginData.UserLogin(User.Identity.Name);` isn't returning null? – Danny Tuppeny Oct 21 '11 at 17:47
  • @DannyTuppeny: different name for setter/getter is a typo in my cut'n paste. The UserLogin method doesn't return null, it reads from a db tables user profiles, btw, cross checked with inspecting all variables. I suspect that my entry in cache is disposed by something just after I add it. WIll try to add a RemoveCallback to see if that's the case. – BigMike Oct 23 '11 at 10:27
  • @BigMike, looks like `MemoryCache.Default` is process-scoped. Are you sure you're not experiencing a race condition between two threads? For instance, `Get()` could occur on thread A between `Remove()` and `Add()` on thread B. – Frédéric Hamidi Oct 23 '11 at 11:08
  • @FrédéricHamidi haven't thought about such scenario, can be a good insight. Actually the application runs in ASP.NET app pool (and if they hadn't change it, should be a thread pool of the very same IIS process). I'll investigate this tomorrow as soon as I'll be back to work. Thanks. – BigMike Oct 23 '11 at 11:12
  • Also if you're low on memory or something, things could be discarded from the cache immediately? – Danny Tuppeny Oct 23 '11 at 11:19
  • @DannyTuppeny: the whole object is a few hundreds of bytes and during tests I'm running with 10/15 fake users. Target machine is a preprod server with tons of RAM. Maybe there's some setting in .NET for defining initial space of cache ? – BigMike Oct 23 '11 at 11:31
  • @BigMike Sadly, I don't really know much about the cache. I've just seen issues on one of our systems where thigns were *immediately* evicted from cache in low RAM :( – Danny Tuppeny Oct 23 '11 at 15:12

3 Answers3

3

Looking at the code for MemoryCache using a decomplier there is the following private function

private void OnUnhandledException(object sender, UnhandledExceptionEventArgs eventArgs)
{
  if (!eventArgs.IsTerminating)
    return;
  this.Dispose();
}

There is an unhandled exception handler setup by every MemoryCache for the current domain Thread.GetDomain() so if there is ever any exception in your application that is not caught which may be common in a website it disposes the MemoryCache for ever and cannot be reused this is especially relevant for IIS apps as apposed to windows applications that just exit on unhanded exceptions.

JProgrammer
  • 2,750
  • 2
  • 25
  • 36
  • Interesting. We've removed the cache for now, as soon as we've got budget to spend on that app I will verify this. – BigMike Oct 02 '12 at 07:19
2

The MemoryCache has limited size. For the Default instance, is't heuristic value (according to MSDN).

Have you tried to set Priority property on CacheItemPolicy instance to NotRemovable?

You can have race-condition because the Contains-Remove-Add sequence in SetLoginInfo is not atomic - try to use Set method instead.

Btw. you are working on web application so why not to use System.Web.Caching.Cache instead?

Augi
  • 350
  • 1
  • 2
  • 10
  • Hi Augi. Long time passed since I asked this, code is not up to date, I'll fix it asap. Actually we've changed our caching strategy to plain database tables. IFRC I've tried also using the Set method and the System.Web.Caching.Cache with no success.. However in the next weeks I've to work on a brand new MVC app and I'll give it a try to this. (I foresee a lot of caching but on a dedicated cache server, it'll be fun). However +1 for giving good hints. Thanks. – BigMike May 08 '12 at 10:36
1

I believe you are running into a problem that Scott Hanselman identified as a .NET 4 bug. Please see here: MemoryCache Empty : Returns null after being set

Community
  • 1
  • 1
James McCormack
  • 9,217
  • 3
  • 47
  • 57
  • Probably you're right, this question has been open for so long I almost forgot it. On our application we simply changed architecture to avoid the use of cache. – BigMike Apr 28 '14 at 14:13