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.