Strange Behaviour with CacheManager.ContainsKey?

Topics: Caching Application Block
Oct 30, 2007 at 5:42 PM
Hello:

I am using the Caching Application Block 3.1 to store a generic list of business objects in SQL Server from a web service. When I add items to the cache using CacheManager.Add(...) with an expirationPollFrequency of 60 seconds and either an absolute expiration or a sliding expiration of let's say 2 minutes, I would expect to see the record in the CacheData table to be gone after 3 minutes maximum, or for the CacheManager.CointainsKey method to check whether the cache has expired before returning true. However, the record seems to stay around until it is next accessed. If it hasn't expired, great it stays around. If it has expired though, it stays there and is deleted after the access ( with ContainsKey apparently ) which is causing two problems 1) If a user does not access the cached object after it expires, the CacheData table might fill up fast which means I will have to manually purge it once in a while. 2) I get a skewed perception when I first call CacheManager.ContainsKey, because I am finding an expired key but I cannot then grab the object related with that key.

Has anyone else run into this problem? Have I not set some properties correctly or could this be a possible point of improvement in the next version?

Thanks!
Kevin Parkinson
Oct 30, 2007 at 7:09 PM
Hi Kevin,

Expirations should mostly occur through the expiration task, a periodically scheduled action that would mark the expired items and sweep them from the cache; the expiration check when the user requests an item is more a safeguard. This should take care of your concern #1, but there have been reports of expirations not happening in web scenarios but they couldn't be reproduced.

As for your concern #2, while there is no safety that an item that you though was in the cache was removed by the time you tried to retrieve it the fact that "Contains" doesn't check for expirations while "Get" does increases the risk of this happening. However, with appropriately set up expirations this shouldn't happen often.

Can you check whether background expirations are working for you? Here's a short repro in a console app and it's output:

class Program
{
static void Main(string[] args)
{
CacheManager manager = CacheFactory.GetCacheManager();
manager.Add("foo", "bar", CacheItemPriority.NotRemovable, new MyExpiration(), new AbsoluteTime(DateTime.Now.AddSeconds(30d)));
Console.WriteLine("Thread: {0}. Exists? {1}", Thread.CurrentThread.GetHashCode(), manager.Contains("foo"));

Thread.Sleep(90 * 1000);

Console.WriteLine("Thread: {0}. Exists? {1}", Thread.CurrentThread.GetHashCode(), manager.Contains("foo"));
}
}

internal class MyExpiration : ICacheItemRefreshAction
{
public void Refresh(string removedKey, object expiredValue, CacheItemRemovedReason removalReason)
{
Console.WriteLine("Thread: {0}. Expired!", Thread.CurrentThread.GetHashCode());
}
}

Output

Thread: 9. Exists? True
Thread: 6. Expired!
Thread: 9. Exists? False

Regards,
Fernando
Dec 18, 2007 at 5:19 PM
Hey Fernando:
Thanks for the code. It got the expected results when I ran it in a test console app and a Windows Forms app. However, I am getting unexpected results when running the code within a web service. The items are being added to the cache and the LastUpdateTime is being touched but the items DO NOT APPEAR to have been deleted from the cache when they expire.
My research ( using SQL Profiler )has revealed that the items in fact are being removed from the cache , but then seconds later, the same object ( same key ) is being added back. Have you ever had this issue? If so, how did you resolve this?
Also, one of my theories was that the account the CacheManager is using to add items is different than the one it is using to remove items and it is running into problems there.
Thoughts? Thanks for your help!!!
Dec 18, 2007 at 8:03 PM
Fernando and Co.:

Ok, it appears I was right about the authentication. When items are added to the cache, this is done with a different account than the one that is ( trying to ) deleting items. This has to do with the following:
  • My web service is impersonating a user defined service account on my domain. This account has sufficient privileges to execute stored procedures in the database.
  • My application pool is running as NT Authority/Network Service
  • NT Authority/Network Service does not have access to the stored procedures in the Caching database, nor is it desirable for it to have them obviously.

When I change the app pool to use the user defined service account, all is well. However, let’s just say that changing the app pool to run under this same service account is in fact also undesirable. What then?

Is it perhaps possible to run the assembly that wraps the CacheManager ( we wrote a base caching and paging library around Enterprise Caching ) to run under an account other than Network Service even when the app pool is running under that user?

Perhaps I will try and answer that question myself.

Thoughts?
Dec 19, 2007 at 2:51 PM
Hi Kevin,

This is a very interesting situation. The closest match to what you're asking I know of is to use the WindowsIdentity.Impersonate method (see http://msdn2.microsoft.com/en-us/library/chf6fbt4.aspx). Note that this is code based instead of per-assembly approach, and you would have to manage the login credentials (hopefully relying on a SecureString to hold the password). It could get tricky since expirations run in a different thread so simply wrapping may not be an option.

Please let us know what you can find. I'll try to look into this a bit more.

Regards,
Fernando