Search code examples
c#readerwriterlockslim

c# ReaderWriterLockSlim corrupt after interrupt thread that invokes ExitReadLock


I encountered a scenario where ReaderWriterLockSlim seems to get broken after a series of legal actions.

The flow is:

  1. Thread 1 takes writer lock1
  2. Thread 2 tries to take reader lock1 - blocks
  3. Thread 2 is interrupted, and calls lock1.ExitReadLock Thread 2 didn't get the lock. It seems that an exception should have been thrown.
  4. Thread 1 exits writer lock of lock1
  5. Any thread that tries to take lock1.EnterReadLock will block forever

After stage 3 above, a debugger shows that lock1.CurrentReadCount is corrupt - seems to have overflowed down to 0x7FFFFFF.

I wonder if anyone had encountered this, or maybe I'm missing something.

The code that reproduces it:

[TestMethod]
public void ReproTest()
{
    var rwlock = new ReaderWriterLockSlim();
    rwlock.EnterWriteLock();

    bool taken = false;
    var reader = new Thread(() =>
    {
        try
        {
            rwlock.EnterReadLock();
            s_logger.Info("Enter");

        }
        catch (ThreadInterruptedException)
        {
            rwlock.ExitReadLock();
        }
    });
    reader.Name = "Reader";
    reader.Start();
    Thread.Sleep(1000);

    reader.Interrupt();

    Thread.Sleep(1000);

    rwlock.ExitWriteLock();

    while (!taken)
    {
        taken = rwlock.TryEnterReadLock(1000);
    }

    Thread.Sleep(1000);
}

Solution

  • This looks like a bug in the framework (tested on v3.5 and v4.0). The ExitReadLock() should throw a SynchronizationLockException, but doesn't in this case. Indeed, you can trigger a very similar issue much more simply with the following:

    rwlock.EnterReadLock();
    rwlock.ExitReadLock();
    // This should throw a SynchronizationLockException but doesn't
    rwlock.ExitReadLock();
    // At this point, rwlock.CurrentReaderCount = 0x0fffffff
    

    (In fact, ExitReadLock() will corrupt the lock if it's called without a matching EnterReadLock() on any thread that has previously entered the lock.)

    The issue only occurs when the ReaderWriterLockSlim is created using the parameterless constructor, or with LockRecursionPolicy.NoRecursion. If created with LockRecursionPolicy.SupportsRecursion, it will not be corrupted by the unmatched ExitReadLock().

    If you expect the reader thread to be interrupted whilst waiting for entry into lock, I would suggest changing the reader thread method to:

    var reader = new Thread(() =>
    {
        var entered = false;
        try
        {
            rwlock.EnterReadLock();
            entered = true;
            s_logger.Info("Enter");
        }
        finally
        {
            if (entered) rwlock.ExitReadLock();
        }
    });