//
// ReaderWriterLockTest.cs - NUnit Test Cases for System.Threading.ReaderWriterLock
//
// Author:
//   Lluis Sanchez Gual (lluis@ximian.com)
//
// (C) 2004 Novell, Inc (http://www.novell.com)
// 

using NUnit.Framework;
using System;
using System.Threading;

namespace MonoTests.System.Threading
{
	[TestFixture]
	// DISABLED due to random hangs. Do not renable until you can run this
	// a few thousand times on an SMP box.
	[Category ("NotWorking")]
	public class ReaderWriterLockTest : Assertion
	{
		ReaderWriterLock rwlock;
		
		class ThreadRunner
		{
			public ThreadStart SecondaryThread;
			public Exception ResultException;
			public Thread RunningThread;
			
			public void Run ()
			{
				try
				{
					SecondaryThread();
				}
				catch (Exception ex)
				{
					ResultException = ex;
				}
			}
			
			public void Join ()
			{
				RunningThread.Join (5000);
				if (ResultException != null) throw ResultException;
			}
		}
		
		void RunThread (ThreadStart ts)
		{
			ThreadRunner tr = StartThread (ts);
			tr.Join ();
		}
		
		ThreadRunner StartThread (ThreadStart ts)
		{
			ThreadRunner tr = new ThreadRunner();
			tr.SecondaryThread = ts;
			Thread t = new Thread (new ThreadStart (tr.Run));
			tr.RunningThread = t;
			t.Start ();
			return tr;
		}
		
		[Test]
		public void TestIsReaderLockHeld ()
		{
			rwlock = new ReaderWriterLock ();
			Assert ("a1", !rwlock.IsReaderLockHeld);
			rwlock.AcquireReaderLock (500);
			Assert ("a2", rwlock.IsReaderLockHeld);
			RunThread (new ThreadStart (IsReaderLockHeld_2));
			rwlock.ReleaseReaderLock ();
		}
		
		private void IsReaderLockHeld_2 ()
		{
			Assert ("a3", !rwlock.IsReaderLockHeld);
		}
		
		[Test]
		public void TestIsWriterLockHeld ()
		{
			rwlock = new ReaderWriterLock ();
			Assert ("a1", !rwlock.IsWriterLockHeld);
			rwlock.AcquireWriterLock (500);
			Assert ("a2", rwlock.IsWriterLockHeld);
			RunThread (new ThreadStart (IsWriterLockHeld_2));
			rwlock.ReleaseWriterLock ();
		}
		
		private void IsWriterLockHeld_2 ()
		{
			Assert ("a3", !rwlock.IsWriterLockHeld);
		}
				
		[Test]
		public void TestAcquireLocks ()
		{
			rwlock = new ReaderWriterLock ();
			rwlock.AcquireReaderLock (500);
			rwlock.AcquireReaderLock (500);
			rwlock.ReleaseReaderLock ();
			Assert ("a1", rwlock.IsReaderLockHeld);			
			RunThread (new ThreadStart (AcquireLock_readerWorks));
			Assert ("a2", rwlock.IsReaderLockHeld);
			
			RunThread (new ThreadStart (AcquireLock_writerFails));
			rwlock.ReleaseReaderLock ();
			Assert ("a6", !rwlock.IsReaderLockHeld);
			
			RunThread (new ThreadStart (AcquireLock_writerWorks));
			
			rwlock.AcquireWriterLock (200);
			RunThread (new ThreadStart (AcquireLock_writerFails));
			RunThread (new ThreadStart (AcquireLock_readerFails));
			rwlock.ReleaseWriterLock ();
		}
		
		void AcquireLock_readerWorks ()
		{
			rwlock.AcquireReaderLock (200);
			rwlock.AcquireReaderLock (200);
			rwlock.ReleaseReaderLock ();
			Assert ("a3", rwlock.IsReaderLockHeld);
			rwlock.ReleaseReaderLock ();
			Assert ("a4", !rwlock.IsReaderLockHeld);
		}
		
		void AcquireLock_writerFails ()
		{
			try
			{
				rwlock.AcquireWriterLock (200);
				rwlock.ReleaseWriterLock ();
				throw new Exception ("Should not get writer lock");
			}
			catch (Exception)
			{
			}
		}
		
		void AcquireLock_writerWorks ()
		{
			rwlock.AcquireWriterLock (200);
			rwlock.ReleaseWriterLock ();
		}
		
		void AcquireLock_readerFails ()
		{
			try
			{
				rwlock.AcquireReaderLock (200);
				rwlock.ReleaseReaderLock ();
				throw new Exception ("Should not get reader lock");
			}
			catch (Exception)
			{
			}
		}
		
		[Test]
		public void TestReleaseRestoreReaderLock ()
		{
			rwlock = new ReaderWriterLock ();
			rwlock.AcquireReaderLock (500);
			rwlock.AcquireReaderLock (500);
			Assert ("r1", rwlock.IsReaderLockHeld);
			
			LockCookie co = rwlock.ReleaseLock ();
			RunThread (new ThreadStart (AcquireLock_writerWorks));
			
			rwlock.RestoreLock (ref co);
			RunThread (new ThreadStart (AcquireLock_writerFails));
			
			rwlock.ReleaseReaderLock ();
			Assert ("r2", rwlock.IsReaderLockHeld);
			rwlock.ReleaseReaderLock ();
			Assert ("r3", !rwlock.IsReaderLockHeld);
		}
		
		[Test]
		public void TestReleaseRestoreWriterLock ()
		{
			rwlock = new ReaderWriterLock ();
			rwlock.AcquireWriterLock (500);
			rwlock.AcquireWriterLock (500);
			Assert ("w1", rwlock.IsWriterLockHeld);
			
			LockCookie co = rwlock.ReleaseLock ();
			RunThread (new ThreadStart (AcquireLock_readerWorks));
			
			rwlock.RestoreLock (ref co);
			RunThread (new ThreadStart (AcquireLock_readerFails));
			
			rwlock.ReleaseWriterLock ();
			Assert ("w2", rwlock.IsWriterLockHeld);
			rwlock.ReleaseWriterLock ();
			Assert ("w3", !rwlock.IsWriterLockHeld);
		}
		
		[Test]
		public void TestUpgradeDowngradeLock ()
		{
			rwlock = new ReaderWriterLock ();
			rwlock.AcquireReaderLock (200);
			rwlock.AcquireReaderLock (200);
			
			LockCookie co = rwlock.UpgradeToWriterLock (200);
			Assert ("u1", !rwlock.IsReaderLockHeld);
			Assert ("u2", rwlock.IsWriterLockHeld);
			RunThread (new ThreadStart (AcquireLock_writerFails));
			
			rwlock.DowngradeFromWriterLock (ref co);
			Assert ("u3", rwlock.IsReaderLockHeld);
			Assert ("u4", !rwlock.IsWriterLockHeld);
			RunThread (new ThreadStart (AcquireLock_readerWorks));
			
			rwlock.ReleaseReaderLock ();
			Assert ("u5", rwlock.IsReaderLockHeld);
			rwlock.ReleaseReaderLock ();
			Assert ("u6", !rwlock.IsReaderLockHeld);
		}
		
		[Test]
		public void TestReaderInsideWriter ()
		{
			// Reader acquires and releases work like the writer equivalent
			
			rwlock = new ReaderWriterLock ();
			rwlock.AcquireWriterLock (-1);
			rwlock.AcquireReaderLock (-1);
			Assert ("u1", !rwlock.IsReaderLockHeld);
			Assert ("u2", rwlock.IsWriterLockHeld);
			rwlock.AcquireReaderLock (-1);
			Assert ("u3", !rwlock.IsReaderLockHeld);
			Assert ("u4", rwlock.IsWriterLockHeld);
			rwlock.ReleaseWriterLock ();
			Assert ("u5", !rwlock.IsReaderLockHeld);
			Assert ("u6", rwlock.IsWriterLockHeld);
			rwlock.ReleaseReaderLock ();
			Assert ("u7", !rwlock.IsReaderLockHeld);
			Assert ("u8", rwlock.IsWriterLockHeld);
			rwlock.ReleaseReaderLock ();
			Assert ("u9", !rwlock.IsReaderLockHeld);
			Assert ("u10", !rwlock.IsWriterLockHeld);
		}
		
		[Test]
		public void TestReaderMustWaitWriter ()
		{
			// A thread cannot get the reader lock if there are other threads
			// waiting for the writer lock.
			
			rwlock = new ReaderWriterLock ();
			rwlock.AcquireWriterLock (200);
			
			ThreadRunner tr = StartThread (new ThreadStart (ReaderMustWaitWriter_2));
			Thread.Sleep (200);
			
			RunThread (new ThreadStart (AcquireLock_readerFails));
			
			rwlock.ReleaseReaderLock ();
			tr.Join ();
		}
		
		void ReaderMustWaitWriter_2 ()
		{
			rwlock.AcquireWriterLock (2000);
			rwlock.ReleaseWriterLock ();
		}
		
		[Test]
		public void TestBug_55911 ()
		{
			rwlock = new ReaderWriterLock ();
			
			rwlock.AcquireReaderLock (Timeout.Infinite);
			try {
				LockCookie lc = rwlock.UpgradeToWriterLock (Timeout.Infinite);
			}
			finally { rwlock.ReleaseReaderLock(); }
			
			rwlock.AcquireReaderLock (Timeout.Infinite);
			try {
				LockCookie lc = rwlock.UpgradeToWriterLock (Timeout.Infinite);
			}
			finally { rwlock.ReleaseReaderLock(); }
		}
		
		[Test]
		public void TestBug_55909 ()
		{
			rwlock = new ReaderWriterLock ();
			ThreadRunner tr = StartThread (new ThreadStart(Bug_55909_Thread2));
			Thread.Sleep (200);
			rwlock.AcquireReaderLock (Timeout.Infinite);
			try {
				LockCookie lc = rwlock.UpgradeToWriterLock (Timeout.Infinite);
				Thread.Sleep (500);
			}
			finally { rwlock.ReleaseReaderLock(); }
			
			tr.Join ();
		}
		
		public void Bug_55909_Thread2 ()
		{
			rwlock.AcquireReaderLock(Timeout.Infinite);
			try {
				Thread.Sleep (1000);
				LockCookie lc = rwlock.UpgradeToWriterLock (Timeout.Infinite);
				Thread.Sleep (500);
			}
			finally { rwlock.ReleaseReaderLock(); }
		}
		
		[Test]
		public void TestBug_55909_bis ()
		{
			rwlock = new ReaderWriterLock ();
			ThreadRunner tr1 = StartThread (new ThreadStart(Bug_55909_bis_ReaderWriter));
			Thread.Sleep(100);
			ThreadRunner tr2 = StartThread (new ThreadStart(Bug_55909_bis_Reader));
			Thread.Sleep(100);
			ThreadRunner tr3 = StartThread (new ThreadStart(Bug_55909_bis_Writer));
			Thread.Sleep(100);
			ThreadRunner tr4 = StartThread (new ThreadStart(Bug_55909_bis_Reader));
			tr1.Join ();
			tr2.Join ();
			tr3.Join ();
			tr4.Join ();
		}
		
		void Bug_55909_bis_Reader ()
		{
			rwlock.AcquireReaderLock(-1);
			Thread.Sleep(2000);
			rwlock.ReleaseReaderLock();
		}

		void Bug_55909_bis_ReaderWriter ()
		{
			rwlock.AcquireReaderLock(-1);
			LockCookie lc = rwlock.UpgradeToWriterLock(-1);
			Thread.Sleep(1000);
			rwlock.DowngradeFromWriterLock(ref lc);
			rwlock.ReleaseReaderLock();
		}

		void Bug_55909_bis_Writer ()
		{
			rwlock.AcquireWriterLock(-1);
			rwlock.ReleaseWriterLock();
		}
		
	}
}
