OdeToCode IC Logo

.Text Threading Bug

Thursday, May 27, 2004

If you are one of the 7 regular readers here you might have noticed some problems over the last few weeks. Every so often .Text would display an error page with the message: “Value cannot be null. Parameter name: value”. Once the error happened it would stick around until the application restarted. Unfortunately, the error was appearing everyday. After asking around on some of the boards to no avail I did some sleuthing.

In .Text 0.95 the Stats class has the following method:

public static bool AddQuedStats(EntryView ev)
{
    //Check for the limit
    if(queuedStatsList.Count >= queuedAllowCount)
    {
        //aquire the lock
        lock(queuedStatsList.SyncRoot)
        {
            //make sure the pool queue was not cleared during a wait for the lock
            if(queuedStatsList.Count >= queuedAllowCount)
            {
                EntryView[] eva = new EntryView[queuedStatsList.Count];
                queuedStatsList.CopyTo(eva,0);
 
                ClearTrackEntryQueue(new EntryViewCollection(eva));
                queuedStatsList.Clear();            
            }
        }
    }
    queuedStatsList.Add(ev);
    return true;
}

The first highlighted method is in the call stack when the exception is thrown, but like any good threading bug the problem actually begins somewhere else: queuedStatsList.Add. The Add method is not thread safe and the collection eventually corrupts with a null reference appearing in a slot where an EntryView object reference should be. The EntryViewCollection ctor barfs when it tries to copy the null reference. Because the application can never clear the queue the exception keeps occurring until reset. I only checked 0.96 briefly, but it looks like the problem still exists. If you are running into this problem, one fix is to change the code and move the Add inside of the lock scope (and skip the double check locking):

public static bool AddQuedStats(EntryView ev)
{
    //aquire the lock
    lock(queuedStatsList.SyncRoot)
    {
        if(queuedStatsList.Count >= queuedAllowCount)
        {
            EntryView[] eva = new EntryView[queuedStatsList.Count];
            queuedStatsList.CopyTo(eva,0);
            ClearTrackEntryQueue(new EntryViewCollection(eva));
            queuedStatsList.Clear();         
            
        }
        queuedStatsList.Add(ev);
    }
    return true;
}

If you are having this problem and don’t want to recompile the application, set queueStats=”false” in the Tracking section of web.config.

It seems like I am the only one who was having this problem, which is odd because I certainly don’t have the same number of concurrent users as, say, blogs.msdn.com, but that’s multithreading for you.

I do want to say thanks to ScottW for all his .Text work, hopefully this will add just a little bit of improvement to some great software.