C# ReaderWriterLockSlim поврежден после прерывания потока, который вызывает ExitReadLock

Я столкнулся со сценарием, когда ReaderWriterLockSlim, кажется, сломался после ряда юридических действий.

Поток это:

  1. Поток 1 берет писателя lock1
  2. Поток 2 пытается взять блокировку читателя lock1 -
  3. Поток 2 прерывается и вызывает lock1.ExitReadLock Поток 2 не получил блокировку. Кажется, что исключение должно быть брошено.
  4. Поток 1 выходит из блокировки писателя lock1
  5. Любой поток, который пытается взять lock1.EnterReadLock, будет блокироваться навсегда

После описанного выше этапа 3 отладчик показывает, что lock1.CurrentReadCount поврежден - похоже, переполнен до 0x7FFFFFF.

Интересно, кто-нибудь сталкивался с этим, или, может быть, я что-то упустил.

Код, который воспроизводит это:

[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);
}

3 ответа

Решение

Это похоже на ошибку в фреймворке (протестировано на v3.5 и v4.0). ExitReadLock() должен бросить SynchronizationLockException, но не в этом случае. В самом деле, вы можете вызвать очень похожую проблему гораздо проще с помощью следующего:

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

(По факту, ExitReadLock() повредит блокировку, если она вызывается без соответствия EnterReadLock() на любой теме, которая ранее вошла в замок.)

Проблема возникает только тогда, когда ReaderWriterLockSlim создается с помощью конструктора без параметров, или с LockRecursionPolicy.NoRecursion, Если создан с LockRecursionPolicy.SupportsRecursion, он не будет поврежден непревзойденным ExitReadLock(),

Если вы ожидаете, что поток чтения будет прерван во время ожидания входа в блокировку, я бы предложил изменить метод потока чтения на:

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

Читатель никогда не входит в замок чтения. Он сидит в ожидании выпуска записи. Когда он прерывается, вы пытаетесь выйти, даже если вы никогда не входили, в результате чего счетчик чтения опустился ниже 0, я думаю:)

Код, который исправляет то, на что указали @Lasse и @Jeremy:

static public void ReproTest()
{
    var rwlock = new ReaderWriterLockSlim();
    rwlock.EnterWriteLock();
    s_logger.Info("0:Enter");

    bool taken1 = false;
    var reader = new Thread(() =>
    {
        try
        {
            rwlock.EnterReadLock();
            s_logger.Info("1:Enter");
            // only set to true if taken
            taken1 = true;
        }
        catch (ThreadInterruptedException)
        {
            // only release if taken
            if (taken1)
                rwlock.ExitReadLock();
            taken1 = false;
        }
    });
    reader.Name = "Reader";
    reader.Start();
    Thread.Sleep(1000);

    reader.Interrupt();

    Thread.Sleep(1000);

    rwlock.ExitWriteLock();

    // 2nd taken variable here only so we can see state of taken1
    bool taken2 = taken1;
    while (!taken2)
    {
        taken2 = rwlock.TryEnterReadLock(1000);
        s_logger.Info("2:Enter");
    }

    Thread.Sleep(1000);
}

При запуске выходные данные отладки правильно показывают, что блокировка записи взята, 1-я блокировка чтения НЕ взята, и 2-я блокировка чтения взята

0:Enter
A first chance exception of type 'System.Threading.ThreadInterruptedException' occurred in mscorlib.dll
The thread 'Reader' (0x1358) has exited with code 0 (0x0).
2:Enter
Другие вопросы по тегам