Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

OrderedLock in C#

0.00/5 (No votes)
18 Mar 2013 1  
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.

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 has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here