views:

1999

answers:

5

Good aftenoon,

I created a super simple console app to test out the Enterprise Library Caching Application Block, and the behavior is blaffling. I'm hoping I screwed something that's easy to fix in the setup. Have each item expire after 5 seconds for testing purposes.

Basic setup -- "every second pick a number between 0 and 2. if the cache doesn't already have it, put it in there -- otherwise just grab it from the cache. Do this inside a LOCK statement to ensure thread safety.

APP.CONFIG:

<configuration>
  <configSections>
    <section name="cachingConfiguration" type="Microsoft.Practices.EnterpriseLibrary.Caching.Configuration.CacheManagerSettings, Microsoft.Practices.EnterpriseLibrary.Caching, Version=4.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
  </configSections>
  <cachingConfiguration defaultCacheManager="Cache Manager">
    <cacheManagers>
      <add expirationPollFrequencyInSeconds="1" maximumElementsInCacheBeforeScavenging="1000"
      numberToRemoveWhenScavenging="10" backingStoreName="Null Storage"
      type="Microsoft.Practices.EnterpriseLibrary.Caching.CacheManager, Microsoft.Practices.EnterpriseLibrary.Caching, Version=4.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
      name="Cache Manager" />
    </cacheManagers>
    <backingStores>
      <add encryptionProviderName="" type="Microsoft.Practices.EnterpriseLibrary.Caching.BackingStoreImplementations.NullBackingStore, Microsoft.Practices.EnterpriseLibrary.Caching, Version=4.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
      name="Null Storage" />
    </backingStores>
  </cachingConfiguration>
</configuration>

C#:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Practices.EnterpriseLibrary.Common;
using Microsoft.Practices.EnterpriseLibrary.Caching;
using Microsoft.Practices.EnterpriseLibrary.Caching.Expirations;

namespace ConsoleApplication1
{
    class Program
    {
        public static ICacheManager cache = CacheFactory.GetCacheManager("Cache Manager");
    static void Main(string[] args)
        {
            while (true)
            {
                System.Threading.Thread.Sleep(1000); // sleep for one second.
                var key = new Random().Next(3).ToString();
                string value;
                lock (cache)
                {
                    if (!cache.Contains(key))
                    {
                        cache.Add(key, key, CacheItemPriority.Normal, null, new SlidingTime(TimeSpan.FromSeconds(5)));
                    }
                    value = (string)cache.GetData(key);
                }
                Console.WriteLine("{0} --> '{1}'", key, value);
                //if (null == value) throw new Exception(); 
            }
        }
    }
}

OUPUT -- How can I prevent the cache to returning nulls?

2 --> '2'
1 --> '1'
2 --> '2'
0 --> '0'
2 --> '2'
0 --> '0'
1 --> ''
0 --> '0'
1 --> '1'
2 --> ''
0 --> '0'
2 --> '2'
0 --> '0'
1 --> ''
2 --> '2'
1 --> '1'
Press any key to continue . . .

Thanks in advance, -Alan.

+1  A: 

Although this might not fix your particular problem, double-checked locking is usually suggested...

if (!cache.Contains(key))
{
    lock(mylockobj)
    {
        if (!cache.Contains(key))
        {
             cache.Add(key, key)
        }
    }
}

Also possibly look into CacheItemRemovedCallback.

Axl
+6  A: 

I notice that you seem to be getting null back from the cache whenever that item hasn't been accessed during the previous 5 loop iterations (ie, 5 seconds). Could this be related to your 5 second expiry time?

It seems unlikely, but maybe you have a race condition and the items are dropping out of the cache between the Contains check and the GetData retrieval.

Try this change and see if it makes any difference to the output:

while (true)
{
    System.Threading.Thread.Sleep(1000);

    var key = new Random().Next(3).ToString();
    string value;

    lock (cache)
    {
        value = (string)cache.GetData(key);
        if (value == null)
        {
            value = key;
            cache.Add(key, value, CacheItemPriority.Normal, null,
                new SlidingTime(TimeSpan.FromSeconds(5)));
        }
    }
    Console.WriteLine("{0} --> '{1}'", key, value);
}
LukeH
I understand why people would do this, but honestly you'd figure the cache would be curteous enough not to make you go through this nonsense. Honestly, I just want to do 'GetData' and supply a RefreshAction and be done with it
AlanR
+7  A: 
Tuzo
it is, plus a little explanation. I marked your answer as the accepted answer, Luke. I also +1'd both.
AlanR
A: 

I have this same problem I have to go back and change my cache layer now, PITA

gdx
+1  A: 

One of the reasons that .Contains can come back as true and .GetData can return a null is that .GetData goes through the whole expiration system (it seems to only return data which isn't expired) and .Contains doesn't check to see if it's content is expired.

{
    cache.Add("key", "value", CacheItemPriority.Normal, 
              null, new SlidingTime(TimeSpan.FromSeconds(5)));
    System.Threading.Thread.Sleep(6000);
    Console.WriteLine(cache.Contains("key"));        /// true
    Console.WriteLine(cache.GetData("key") != null); /// false
    Console.WriteLine(cache.Contains("key"));        /// false
}

Another problem I had was that I couldn't tell whether the cache contained an entry with null as the value or the cache just didn't contain an entry for key. A workaround that I use is that if .GetData comes back with a null and .Contains is true, then a null was purposefully stored in the cache and is not expired.

EliThompson
I think it's possible for the `cache.Contains()` to return true, then have the data in the cache expire, then have `cache.GetData()` return null because the cache item has expired. The null return value wouldn't mean that a null value was stored in the cache. Granted, this is an unlikely edge case but I think it's possible.
Tuzo
@Tuzo Right. If the data expired, the GetData call to it would return null normally. You can only tell if a null was purposefully stored in cache if the data wasn't expired.
EliThompson