Click here to Skip to main content
15,881,204 members
Articles / General Programming / Threads
Tip/Trick

OrderedLock in C#

Rate me:
Please Sign up or sign in to vote.
0.00/5 (No votes)
18 Mar 2013CPOL 16.4K   11   2
OrderedLock in C# to catch potential deadlocks at runtime

Introduction

This is an implementation of OrderedLock in C# that enforces ordered locking pattern by prohibiting users of this class from acquiring locks outside of a pre-defined fixed order.

Background

Deadlocks commonly occur in multi-threaded applications due to circular waiting. For example, if a thread has acquired lock A and is waiting on lock B while another thread has already acquired lock B but is waiting on lock A. Ordered locking pattern is a practice that enforces that locks are always acquired in the same order. That is acquire lock A before acquiring lock B or vice versa.

Using the Code

OrderedLock uses an integer identifier to create an increasing order of locks in an application. It throws an exception when an attempt is made to acquire locks out of the defined fixed order. It uses thread local storage to maintain a hash table of all locks acquired by the current thread to ensure that the order is always maintained.

C#
public class someResource
{
    private OrderedLock lock1 = new OrderedLock(1);
    private OrderedLock lock2 = new OrderedLock(2);
    
    public void lockInOrder()
    {
        lock1.AcquireWriteLock();
        lock2.AcquireWriteLock();
        // do something
        lock1.ReleaseWriteLock();
        lock2.ReleaseWriteLock();
    }
    
    public void lockOutOfOrder()
    {
        lock2.AcquireReadLock();
        lock1.AcquireReadLock(); // throws exception
        // read something
        lock2.ReleaseReadLock();
        lock1.ReleaseReadLock();
    }
}

public class OrderedLock : IDisposable
{
    private static readonly ConcurrentDictionary<int, object> 
    createdLocks = new ConcurrentDictionary<int, object>();
    [ThreadStatic]
    private static ISet<int> acquiredLocks;
    
    private readonly ThreadLocal<int> refCount = new ThreadLocal<int>(false);
    private readonly ReaderWriterLockSlim locker = 
    	new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
    private readonly int id;
    
    /// <exception cref="InvalidOperationException">Duplicate identifier detected</exception>
    public OrderedLock(int id)
    {
        if (!createdLocks.TryAdd(id, null))
        {
            throw new InvalidOperationException("Duplicate identifier detected");
        }
        
        this.id = id;
        this.refCount.Value = 0;
    }
    
    public void AcquireReadLock()
    {
        this.CheckLockOrder();
        this.locker.EnterReadLock();
    }
    
    public void AcquireWriteLock()
    {
        this.CheckLockOrder();
        this.locker.EnterWriteLock();
    }
    
    public void ReleaseReadLock()
    {
        this.refCount.Value--;
        this.locker.ExitReadLock();
        if (this.refCount.Value == 0)
        {
            acquiredLocks.Remove(this.id);
        }
    }
    
    public void ReleaseWriteLock()
    {
        this.refCount.Value--;
        this.locker.ExitWriteLock();
        if (this.refCount.Value == 0)
        {
            acquiredLocks.Remove(this.id);
        }
    }
    
    public void Dispose()
    {
        while (this.locker.IsWriteLockHeld)
        {
            this.ReleaseWriteLock();
        }
        
        while (this.locker.IsReadLockHeld)
        {
            ReleaseReadLock();
        }
        
        this.locker.Dispose();
        this.refCount.Dispose();
        GC.SuppressFinalize(this);
    }
    
    /// <exception cref="InvalidOperationException">Invalid order of locking detected</exception>
    private void CheckLockOrder()
    {
        if (acquiredLocks == null)
        {
            acquiredLocks = new HashSet<int>();
        }
        
        if (!acquiredLocks.Contains(this.id))
        {
            if (acquiredLocks.Any() && acquiredLocks.Max() > this.id)
            {
                throw new InvalidOperationException("Invalid order of locking detected");
            }
            
            acquiredLocks.Add(this.id);
        }
        
        this.refCount.Value++;
    }
}

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Hong Kong Hong Kong
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionNo tests Pin
Nathan Evans18-Mar-13 2:15
Nathan Evans18-Mar-13 2:15 
AnswerRe: No tests Pin
PavyBez18-Mar-13 21:01
PavyBez18-Mar-13 21:01 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.