Wednesday, April 11, 2007

The debugger may cheat you on cache.

Cache is pretty tricky to work with. On one side, you have to use it to improve the performance of your application, On the other side, cache is very tricky to work with and needs careful consideration when putting it into your application.

I recently works on a state government project which uses a lot of cache in the application. Simply put, it uses a static class to cache a lot of lookup tables. One simple example is like this:

public static class Cache
{
public LookupTable TestTypes
{
if(HttpRunTime.Cache["TestTypes"]==null)
{
HttpRunTime.Cache.Insert("TestTypes", CreateTestTypes(), SqlDependency);
}
return HttpRunTime.Cache["TestTypes"];
}
}

This code looks perfect, but it throws out mysterious exceptions a lot of times.

There are two issues here:

1> Don't run HttpRunTime.Cache["TestTypes"] twice, even HttpRunTime.Cache["TestTypes"]==null returns false, when you actually gets the value from HttpRunTime.Cache["TestTypes"], it could be reevaluated and return null.
2> Even you insert HttpRunTime.Cache.Insert("TestTypes", CreateTestTypes(), SqlDependency) here, the insertion may fail, say, there is an out of memory situation, you cannot gurantee HttpRunTime.Cache["TestTypes"] is not null.

LookupTable lookupTable=Cache.TestTypes; If you assert the value returned by the lookupTable is not equal to null, like this: Debug.Assert(lookupTable!=null), you will see a strange phenomena when the debugger stops at this point: the lookupTable is null, but the right side of the expression is NOT null.

This is because the Cache.TestTypes is reevaluated by the debugger, and its value is not the same value when the code executes, which was NULL. I know it's a little bit confusing. When you put the Cache.TestTypes in the watcher window, the debugger actually checks if HttpRunTime.Cache["TestTypes"] is null again. And if it is, it will insert the value into the cache again.

A more robust way is handle the cache like this:

public static class Cache
{
public LookupTable TestTypes
{
TestTypes testTypes= HttpRunTime.Cache["TestTypes"];
if(testTypes==null)
{
testTypes=CreateTestTypes();
HttpRunTime.Cache.Insert("TestTypes", testTypes, SqlDependency);
}
return testTypes;
}
}

References:
1. K. Scott Allen Wierd Caching
2. Peter Johnson HttpRuntime.Cache vs. HttpContext.Current.Cache
3. 蝈蝈俊.net HttpRuntime.Cache vs. HttpContext.Current.Cache





No comments: