System.Threading.SynchronizationLockException

I got this exception a while ago when implementing a simple publisher/consumer scheme with 2 threads in C#. What this exception says is that you’re trying to use some operations without a lock held. In my case it was Monitor.Pulse(object).
These sort of operations conceptually need to be done with the lock on object held, for the same reason old Unix condition variable had to do certain operations with the lock held: to avoid race conditions where one thread can miss the signal and get blocked forever on a synchronization primitive.

So the first really easy way to catch this is check if you hold the lock, if not, easy, just add it.

The other situation is to lock one object and operate on another:

lock(Object1)
{
    if(condition)
    {
        Monitor.Wait(Object2)
    }
}

These scenarios are easy to catch and fix because they throw all the time. But this exception can occur in race conditions where it’s more difficult to catch. Let’s look at the following code:

Thread 1 (publisher):

lock(_queue)
{
     _queue.Add(new_element);
     Monitor.Pulse(_queue);
}

Thread 2 (consumer):

List queue;
lock(_queue)
{
     queue = _queue;
     _queue = new List();
}
ProcessQueue(queue);

The idea here is to try to optimize the way the list of type T is emptied and then processed. In particular, the optimization is that it doesn’t create a copy. The copy can be re-written, without this optimization as:

     queue = new List(_queue);
     _queue.Clear();

This has the problem that it allocates memory and copies data, all with the lock held.

But unfortunately, while the optimization works, it’s not entirely correct, as it created a hidden race condition. To illustrate the race condition, I’ll present it here, step by step, with the code running on both threads interlaced below:

1: Thread2:  lock(_queue) <- acquires lock on _queue
2: Thread1:  lock(_queue) <- lock is held by thread2, so thread1 gets blocked here
3: Thread2:  queue = _queue;
4: Thread2:  _queue = new List();
5: Thread2:  release lock on _queue
6: Thread1:  _queue.Add(new_element);
7: Thread1:  Monitor.Pulse(_queue);

At this step 7, Monitor.Pulse throws a System.Threading.SynchronizationLockException.
The reason is that thread1 acquired a lock on _queue but then thread2 changed the “meaning” of the reference _queue, so when thread1 pulses the _queue, it pulses a different object than the one it acquired the lock on. Thus the exception.

Once the problem is understood, the solution is simple: synchronize on an immutable object. You can create a dummy object and sync on that, rather than the mutable _queue.
Or you can implement the more inefficient version, which doesn’t actually change the object _queue refers to.

PS: To reproduce this relatively consistently, insert a Thread.Sleep(5000) in thread2, right after it acquired the lock.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s


%d bloggers like this: