Search code examples
c#.netmultithreadingreaderwriterlock

ReaderWriterLock.UpgradeToWriterLock does not throw exception after timeout elapses?


I stumbled upon a really weird issue with ReaderWriterLock when writing a unit test. I tried testing UpgradeToWriterLock method with the timeout option set to 50 milliseconds.

On the main thread I take the reader lock and then start numerous tasks. On the tasks I take reader lock as well and then try to upgrade to the writer with a timeout. This should fail on every single one as the the main thread is holding the read lock. As the timout is 50 milliseconds the tasks should throw a timeout exception and finish. If I start the over 10 tasks they don't. They get stuck on the UpgradeToWriterLock.

Can anyone explain it? The whole source code below.

    [TestMethod]
    public void UpgradeLockFailTest()
    {
        // strangely when more than 10 threads then it gets stuck on UpgradeToWriterLock regardless of the timeout
        const int THREADS_COUNT = 20;
        // 50 milliseconds
        const int TIMEOUT = 50;

        // create the main reader writer lock
        ReaderWriterLock rwl = new ReaderWriterLock();

        // acquire the reader lock on the main thread
        rwl.AcquireReaderLock(TIMEOUT);

        // create and start all the tasks
        Task[] tasks = new Task[THREADS_COUNT];
        for (int i = 0; i < THREADS_COUNT; i++)
        {
            tasks[i] = Task.Factory.StartNew(() =>
            {
                try
                {
                    // acquire the reader lock on the worker thread
                    rwl.AcquireReaderLock(TIMEOUT);

                    // acquire the writer lock on the worker thread 
                    rwl.UpgradeToWriterLock(TIMEOUT); // <-- GETS STUCK HERE AND DOESN'T RESPECT TIMEOUT

                }
                finally
                {
                    rwl.ReleaseLock();
                }

            });
        }

        // should be enough for all the tasks to be created
        Thread.Sleep(2000);

        try
        {
            // wait for all tasks
            Task.WaitAll(tasks); // <-- GETS STUCK HERE BECAUSE THE TASKS ARE STUCK ON UpgradeToWriterLock
        }
        catch (AggregateException ae)
        {
            Assert.AreEqual(THREADS_COUNT, ae.InnerExceptions.Count);
        }

        // release all the locks on the main thread
        rwl.ReleaseLock();
    }

Interesting is if I release the main thread reader lock before waiting on tasks everything works as expected. The correct number of timeout exceptions is thrown.


Solution

  • Are you sure they all get stuck, and not just the last one?

    From the UpgradeToWriterLock documentation:

    The time-out exception is not thrown until the thread that called the UpgradeToWriterLock method can reacquire the reader lock. If there are no other threads waiting for the writer lock, this happens immediately. However, if another thread is queued for the writer lock, the thread that called the UpgradeToWriterLock method cannot reacquire the reader lock until all current readers have released their locks, and one thread has acquired and released the writer lock. This is true even if the other thread that requested the writer lock requested it after the current thread called the UpgradeToWriterLock method.

    Note the multiple conditions that have to occur for the timeout exception to be thrown. "if another thread is queued for the writer lock, the thread that called the UpgradeToWriterLock method cannot reacquire the reader lock [and throw the exception] until" :

    1. all current readers have released their locks
    2. one thread has acquired and released the writer lock

    You never allow these conditions to occur for the last thread that attempts to upgrade, so you wait forever at UpgradeToWriterLock, and so WaitAll waits forever too. If your main thread also tried to upgrade before waiting, I think you would be ok.