Skip to content

AsyncReaderWriterLock deadlocks with upgradeable locks #275

@soerendd

Description

@soerendd

I try to use AsyncReaderWriterLock and the documentation states

Behavior of UpgradeToWriteLockAsync and DowngradeFromWriteLock methods is the same as in ReaderWriterLock. You need to be in the read lock to call the upgrade. After calling of DowngradeFromWriteLock method the current flow keeping the read lock so you need to call Release method to release the lock completely.

For me it does not work and ends with a deadlock. All tasks/threads can enter the UpgradeableRead (in the bcl version of ReaderWriterLockSlim its different, as also in the vs threading lib). But then all of them try to get the write and fail (probably because there are readers).
The code below shows all three version and the last one (dotnext) deadlocks. Maybe I'm doing it wrong.

Side-question: Actually I'm in the need of a async reader writer lock which lets in all readers and only upgrades if either all readers left or also waiting for a write (like the ReaderWriterLock but not the ReaderWriterLockSlim). Is there such a beast :-)?

using Microsoft.VisualStudio.Threading;
using AsyncReaderWriterLock = DotNext.Threading.AsyncReaderWriterLock;

namespace RWLockFailure;

class Program
{
    static async Task Main(string[] args)
    {
        await TestSyncLock();

        await TestSyncSlimLock();

        await TestVsAsyncLock();

        await TestDotNextAsyncLock();

        Console.WriteLine("Done");
    }

    private static async Task TestSyncSlimLock()
    {
        var rwLock = new ReaderWriterLockSlim();

        var tasks = new List<Task>(Enumerable.Range(0, 5).Select(_ => Task.Run(() => ConcurrentMethod(rwLock))));
        await Task.WhenAll(tasks);

        return;

        void ConcurrentMethod(ReaderWriterLockSlim lockToUse)
        {
            Console.WriteLine("SyncSlim: Enter concurrent method");
            lockToUse.EnterUpgradeableReadLock();
            try
            {
                Console.WriteLine("SyncSlim: got read lock");
                Console.Out.Flush();
                Thread.Sleep(2000);

                lockToUse.EnterWriteLock();
                try
                {
                    Console.WriteLine("SyncSlim: Got write lock");
                }
                finally
                {
                    lockToUse.ExitWriteLock();
                }
            }
            finally
            {
                lockToUse.ExitUpgradeableReadLock();
            }
        }
    }

    private static async Task TestSyncLock()
    {
        var rwLock = new ReaderWriterLock();

        var tasks = new List<Task>(Enumerable.Range(0, 5).Select(_ => Task.Run(() => ConcurrentMethod(rwLock))));
        await Task.WhenAll(tasks);

        return;

        void ConcurrentMethod(ReaderWriterLock lockToUse)
        {
            Console.WriteLine("Sync: Enter concurrent method");
            lockToUse.AcquireReaderLock(Timeout.Infinite);
            try
            {
                Console.WriteLine("Sync: got read lock");
                Console.Out.Flush();
                Thread.Sleep(2000);

                var cookie = lockToUse.UpgradeToWriterLock(Timeout.Infinite);
                try
                {
                    Console.WriteLine("Sync: Got write lock");
                }
                finally
                {
                    lockToUse.DowngradeFromWriterLock(ref cookie);
                }
            }
            finally
            {
                lockToUse.ReleaseReaderLock();
            }
        }
    }

    private static async Task TestVsAsyncLock()
    {
        var rwLock = new Microsoft.VisualStudio.Threading.AsyncReaderWriterLock(JoinableTaskContext.CreateNoOpContext());

        var tasks = new List<Task>(Enumerable.Range(0, 5).Select(_ => Task.Run(async () => await ConcurrentMethod(rwLock))));
        await Task.WhenAll(tasks);

        return;

        async Task ConcurrentMethod(Microsoft.VisualStudio.Threading.AsyncReaderWriterLock lockToUse)
        {
            Console.WriteLine("VsAsync: Enter concurrent method");
            var releaser = await lockToUse.UpgradeableReadLockAsync();
            try
            {
                Console.WriteLine("VsAsync: Got read lock");
                await Task.Delay(TimeSpan.FromSeconds(2));

                var writeLockReleaser = await lockToUse.WriteLockAsync();
                try
                {
                    Console.WriteLine("VsAsync: Got write lock");
                }
                finally
                {
                    await writeLockReleaser.DisposeAsync();
                }
            }
            finally
            {
                await releaser.DisposeAsync();
            }
        }
    }


    private static async Task TestDotNextAsyncLock()
    {
        var rwLock = new AsyncReaderWriterLock();

        var tasks = new List<Task>(Enumerable.Range(0, 5).Select(_ => Task.Run(async () => await ConcurrentMethod(rwLock))));
        await Task.WhenAll(tasks);

        return;

        async Task ConcurrentMethod(AsyncReaderWriterLock lockToUse)
        {
            Console.WriteLine("Async: Enter concurrent method");
            await lockToUse.EnterReadLockAsync();
            try
            {
                Console.WriteLine("Async: Got read lock");
                await Task.Delay(TimeSpan.FromSeconds(2));
                await lockToUse.UpgradeToWriteLockAsync();
                try
                {
                    Console.WriteLine("Async: Got write lock");
                }
                finally
                {
                    lockToUse.DowngradeFromWriteLock();
                }
            }
            finally
            {
                lockToUse.Release();
            }
        }
    }
}

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

Status

Opened

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions