/////////////////////////////////////////////////////////////////////////////// // // Microsoft Research Singularity // // Copyright (c) Microsoft Corporation. All rights reserved. // using System; using System.Threading; using Microsoft.Singularity.UnitTest; namespace Microsoft.Singularity.Applications { internal sealed class TestMutex { internal static TestLog Expect; private static readonly TimeSpan waitTimeout = TimeSpan.FromMilliseconds(100); private static Mutex mutex; private static ManualResetEvent evtGate; private const int numRoundsInContention = 65536; private static int counter; private static volatile int sum; internal static void Run() { ///TODO: harden kernel code so it doesn't crash on this then enable the test //TestIncorrectRelease(); TestCtor(); ///TODO: currently mutex supports recursion in kernel but not in applications, /// add support then enable this test case //TestRecursion(); TestContention(); // there are other mutex related test cases e.g. Tests\MonitorTest that provide more // test coverage. } /// /// Test two threads both running busy loops grabbing and releasing the same mutex /// private static void TestContention() { Console.Write(" test contention "); mutex = new Mutex(); evtGate = new ManualResetEvent(false); Thread t1 = new Thread(new ThreadStart(ContentionThreadMain)); Thread t2 = new Thread(new ThreadStart(ContentionThreadMain)); t1.Start(); Thread.Yield(); t2.Start(); Thread.Yield(); evtGate.Set(); t1.Join(); t2.Join(); Cleanup(); Console.WriteLine("OK"); } /// /// Thread entry point for contention test. Run a tight loop which acquires the mutex, /// does a little work, then releases it /// private static void ContentionThreadMain() { evtGate.WaitOne(); for (int i = 0; i < numRoundsInContention; i++) { mutex.AcquireMutex(); Thread.Yield(); for (int j = 0; j < 1000; j++) { // sum is volatile so this won't be optimized away sum = unchecked(sum + i * j * j / 23); } if (i % 10000 == 0) { Console.Write('.'); } Thread.Yield(); mutex.ReleaseMutex(); Thread.Yield(); } } /// /// Test the situation where we release the mutex without acquiring it first /// private static void TestIncorrectRelease() { Console.Write(" test incorrect release "); mutex = new Mutex(); try { mutex.ReleaseMutex(); } catch (InvalidOperationException) { Cleanup(); Console.WriteLine("OK"); return; } Expect.Fail("expecting exception"); } /// /// Test the constructor of Mutex /// private static void TestCtor() { Console.Write(" test constructor "); // // default ctor: initially owned = false // Cleanup(); Console.Write('.'); mutex = new Mutex(); Thread thread = new Thread(new ThreadStart(TimeoutWaitMain)); thread.Start(); thread.Join(); if (Thread.VolatileRead(ref counter) != 1) { Expect.Fail("expecting wait to succeed"); return; } // // explicit ctor: initially owned = false // Cleanup(); Console.Write('.'); mutex = new Mutex(false); thread = new Thread(new ThreadStart(TimeoutWaitMain)); thread.Start(); thread.Join(); if (Thread.VolatileRead(ref counter) != 1) { Expect.Fail("expecting wait to succeed"); return; } // // explicit ctor: initially owned = true // Cleanup(); Console.Write('.'); mutex = new Mutex(true); thread = new Thread(new ThreadStart(TimeoutWaitMain)); thread.Start(); thread.Join(); if (Thread.VolatileRead(ref counter) != 0) { Expect.Fail("expecting wait to timeout"); return; } // // explicit ctor: initially owned = true, then explicitly release // Cleanup(); Console.Write('.'); mutex = new Mutex(true); mutex.ReleaseMutex(); thread = new Thread(new ThreadStart(TimeoutWaitMain)); thread.Start(); thread.Join(); if (Thread.VolatileRead(ref counter) != 1) { Expect.Fail("expecting wait to succeed"); return; } // // explicit ctor: initially owned = false, then explicitly acquire // Cleanup(); Console.Write('.'); mutex = new Mutex(false); mutex.WaitOne(); thread = new Thread(new ThreadStart(TimeoutWaitMain)); thread.Start(); thread.Join(); if (Thread.VolatileRead(ref counter) != 0) { Expect.Fail("expecting wait to timeout"); return; } Cleanup(); Console.WriteLine("OK"); } /// /// Mutex supports recursion: the same thread can Acquire the same mutex /// multiple times successfully /// private static void TestRecursion() { Console.Write(" test recursion "); // // same number of acquire and release // Cleanup(); Console.Write('.'); mutex = new Mutex(); mutex.AcquireMutex(); mutex.AcquireMutex(); mutex.AcquireMutex(); mutex.ReleaseMutex(); mutex.ReleaseMutex(); mutex.ReleaseMutex(); Thread thread = new Thread(new ThreadStart(TimeoutWaitMain)); thread.Start(); thread.Join(); if (Thread.VolatileRead(ref counter) != 1) { Expect.Fail("expecting wait to succeed"); return; } // // same number of acquire and release, with the explicit ctor count as 1 // Cleanup(); Console.Write('.'); mutex = new Mutex(true); mutex.AcquireMutex(); mutex.AcquireMutex(); mutex.ReleaseMutex(); mutex.ReleaseMutex(); mutex.ReleaseMutex(); thread = new Thread(new ThreadStart(TimeoutWaitMain)); thread.Start(); thread.Join(); if (Thread.VolatileRead(ref counter) != 1) { Expect.Fail("expecting wait to succeed"); return; } // // acquire without release, the other thread will wait and timeout // Cleanup(); Console.Write('.'); mutex = new Mutex(); mutex.AcquireMutex(); mutex.AcquireMutex(); mutex.AcquireMutex(); thread = new Thread(new ThreadStart(TimeoutWaitMain)); thread.Start(); thread.Join(); if (Thread.VolatileRead(ref counter) != 0) { Expect.Fail("expecting wait to timeout"); return; } // // less release than acquire, the other thread will wait and timeout // Cleanup(); Console.Write('.'); mutex = new Mutex(); mutex.AcquireMutex(); mutex.AcquireMutex(); mutex.AcquireMutex(); mutex.ReleaseMutex(); mutex.ReleaseMutex(); thread = new Thread(new ThreadStart(TimeoutWaitMain)); thread.Start(); thread.Join(); if (Thread.VolatileRead(ref counter) != 0) { Expect.Fail("expecting wait to timeout"); return; } Cleanup(); Console.WriteLine("OK"); } /// /// The thread entry point for the waiter. /// increment counter only if the wait is successful /// private static void TimeoutWaitMain() { bool ret = mutex.WaitOne(waitTimeout); if (ret) { Interlocked.Increment(ref counter); } } /// /// Dispose the mutex, reset the counter /// private static void Cleanup() { if (mutex != null) { mutex.Close(); mutex = null; } counter = 0; } } }