Monday, April 9, 2012

Syncronization: deadlocks and how to identify those

These days synchronization become very actual problem for any developer - nowdays its really hard to even imagine a single threaded application. Of course with this, some headache arises with deadlocks. Usually developers use lock(...) { } statement to synchronize access to some code block, and that is one possible reason of a deadlock. Don't get me wrong - I don't say that if you use lock statement, then you have a deadlock, but what I say, is that if you have a deadlock - then its definitely happening on one of the lock statements you've used in your code. So what to do to avoid those ? Or actually, which will be more correct to say, how to identify the lock statement where the thread blocks causing a deadlock forever ?
Fortunately, as almost always, there is a very handy option under the hand - The Monitor class. You probably know, that the lock (obj){...} statements translates to the following code:
try
{
    Monitor.Enter(obj);
    // ...
}
finally
{
    Montior.Exit(obj);
}
To make this code "deadlock-proof", you need to replace the Monitor.Enter method call as follows:
try
{
    if(!Monitor.TryEnter(obj, TimeSpan.FromSeconds(2))
    {
        // Log deadlock reason in here
        throw new Exception("Unable to take exclusive lock on the given object");
    }
    // ...
}
finally
{
    Montior.Exit(obj);
}

Here we are passing a TimeStamp parameter to the TryEnter method, specifying the amount of time to wait for the lock. So if during the specified time period it is impossible to take lock over the specified object, then the method will return false, and we'll be able to log something, which will tell us the place in code where the actual deadlock is happening. Of course it's not so convenient to use whole this construct in code, so this can be extracted to an extension method like the following:

public static void SyncronizeCallByLocking(this Action argAction, object argLock)
{
  // Argument validation in here
  if (!Monitor.TryEnter(argLock, TimeSpan.FromSeconds(2000)))
  {
    // Log exception in here, if required
    throw new ApplicationException("Unable to lock the object");
  }
 
  try
  {
    argAction();
  }
  finally
  {
    Monitor.Exit(argLock);
  }
}

After this it's really easy to wrap the code block, which needs to be synchronized like this:
new Action(()=>
  {
    // Code which needs to be syncronized
  }).SyncronizeCallByLocking(objectToLock);

By using this technique you will definitely not waste many hours of struggling by trying to find the deadlock reasons in your application, and guessing where those can arise from. Of course, if you want after fixing all the issues you're free to convert back to using the lock statement instead of what was described, but I won't bother about it, because you don't know what changes may come later, and how those may affect your application.
Good luck with your development.
Post a Comment