StephenCleary / AsyncEx

A helper library for async/await.
MIT License
3.51k stars 358 forks source link

Deadlock with AsyncReaderWriterLock #255

Open blair-ahlquist opened 2 years ago

blair-ahlquist commented 2 years ago

The following code deadlocks:

        [Test]
        public async Task NitoMultipleReadThenWriteAsync()
        {
            AsyncReaderWriterLock asyncLock = new AsyncReaderWriterLock();

            void enterReadThenWrite()
            {
                using (asyncLock.ReaderLock()) { }
                using (asyncLock.WriterLock()) { }
            }

            await Task.WhenAll(Enumerable.Range(0, 100).Select(x => Task.Run(enterReadThenWrite)));
        }
blair-ahlquist commented 2 years ago

Hmm... I went back to this after encountering similar issues with code using SemaphoreSlim. The above code eventually completes after over a minute!

StephenCleary commented 2 years ago

I haven't had a chance to run this yet, but that is what I suspected.

This isn't a deadlock, but it is the result of thread pool exhaustion. All the AsyncEx primitives (as well as SemaphoreSlim) require a free thread pool thread to unlock, and if all the thread pool threads are busy waiting, this can cause extremely slow progress. The thread pool will (eventually) add another thread, which is then used either to block immediately (enterReadThenWrite), or used to unlock one of the primitives and then blocked immediately.

As such, this is a duplicate of #107, which I am planning to fix eventually, but haven't yet had time.