Software Development Tip: Avoid Race Conditions Using Tester-Doer Pattern

Software Development Tip: Avoid Race Conditions Using Tester-Doer Pattern

by David Hayden ( Florida .NET Developer ), Filed: Design Patterns

 

One of the patterns that came up today in a code review was the Tester-Doer Pattern. One sees this pattern a lot especially when the “doer” side of the equation has a pretty decent performance penalty. Essentially, you test a condition to make sure you need to do the “doer“ operation and pay that penalty.

A perfect example from an Enterprise Library point of view is in the Logging Application Block. Let's say in your code that the logging of a message is fairly expensive in terms of performance based on the information you need to collect in order to properly log the message. Maybe to properly log the message you would need to query a database or a web service. Before paying that penalty, however, you want to make sure there are no logging filters that would stop the message from being logged and make all that effort a moot point.

You can use the Tester-Doer Pattern to only log the message if indeed the message will pass the filters and be logged. In this case we ask the logging application block via Logger.ShouldLog(logEntry) to see if the message will pass through the filters before getting additional expensive information and logging the message. Here is an example pulled directly from the documentation:

 

LogEntry logEntry = new LogEntry();
logEntry.Priority = 2;
logEntry.Categories.Add("Trace");
logEntry.Categories.Add("UI Events");

if (Logger.ShouldLog(logEntry))
{
    // Event will be logged according to current
// configuration.
Perform operations (possibly
// expensive) to gather
additional information
// for the event to be logged.
}

 

The problem that can arise with the Tester-Doer Pattern is when there is the possibility of another thread changing the state of a shared resource in between querying the state of that resource ( the Tester ) and then acting on that resource ( the Doer ).

Let's take an example I see all too often when using the ASP.NET Cache Object:

 

// Tester
if (Cache["data"] != null)
{
    // What happens if another thread
    // removes "data" from the cache at
    // this instance in time?
    // This next statement will throw an
    // exception.
    
    // Doer
    ds = (DataSet)Cache["data"];
}

 

In between the Tester and the Doer another thread could come along and remove “data” from the cache and cause an exception to be thrown.

The code could be better written as:

 

object data = Cache["data"];
if (data != null)
    ds = (DataSet)data;

 

Now you don't have the problem with another thread changing the data as you do when using the previous Tester-Doer Pattern.

Hope this helps!

 

by David Hayden ( Florida .NET Developer ), Filed: Design Patterns

 

posted on Wednesday, September 26, 2007 9:49 PM

Main

News

Green Tea

.NET Development

Enterprise Library

Patterns & Practices