/////////////////////////////////////////////////////////////////////////////// // // Microsoft Research Singularity // // Copyright (c) Microsoft Corporation. All rights reserved. // using System; using System.Threading; using Microsoft.Singularity.UnitTest; namespace Microsoft.Singularity.Applications { /// /// Test cases for auto reset event class /// internal sealed class TestAutoResetEvent { internal static TestLog Expect; private const int MaxWaiters = 16; private static AutoResetEvent evt, evt1, evt2, evt3, evt4; private static ManualResetEvent evtGate; private static readonly TimeSpan trivialTestWaitTime = TimeSpan.FromMilliseconds(100); private static int counter; private static int readyCount; internal static void Run() { TestTrivialReset(); TestTrivialSet(); TestThreadSet(); TestThreadReset(); TestMultipleWaitOne(); TestSetAll(); TestDeadlock(); ///TODO: harden kernel code so it doesn't crash on this test, then enable the test //TestDispose(); } /// /// After the event is reset, test to make sure WaitOne timeout in the same thread /// private static void TestTrivialReset() { Console.Write(" test trivial reset "); // // reset by constructor // evt = new AutoResetEvent(false); Console.Write('.'); bool ret = evt.WaitOne(trivialTestWaitTime); if (ret) { Expect.Fail("wait should timeout"); return; } Console.Write('.'); ret = evt.WaitOne(trivialTestWaitTime); if (ret) { Expect.Fail("wait should timeout again"); return; } evt.Close(); // // reset by constructor and Reset() // evt = new AutoResetEvent(false); evt.Reset(); Console.Write('.'); ret = evt.WaitOne(trivialTestWaitTime); if (ret) { Expect.Fail("wait should timeout"); return; } Console.Write('.'); ret = evt.WaitOne(trivialTestWaitTime); if (ret) { Expect.Fail("wait should timeout again"); return; } evt.Close(); // // set by constructor and then explicitly Reset() // evt = new AutoResetEvent(true); evt.Reset(); Console.Write('.'); ret = evt.WaitOne(trivialTestWaitTime); if (ret) { Expect.Fail("wait should timeout"); return; } Console.Write('.'); ret = evt.WaitOne(trivialTestWaitTime); if (ret) { Expect.Fail("wait should timeout again"); return; } evt.Close(); // // Set() and Reset() several times // evt = new AutoResetEvent(true); evt.Set(); evt.Reset(); evt.Set(); evt.Reset(); Console.Write('.'); ret = evt.WaitOne(trivialTestWaitTime); if (ret) { Expect.Fail("wait should timeout"); return; } Console.Write('.'); ret = evt.WaitOne(trivialTestWaitTime); if (ret) { Expect.Fail("wait should timeout again"); return; } evt.Close(); evt = null; Console.WriteLine("OK"); } /// /// After the event is set, test to make sure WaitOne succeed in the same thread /// private static void TestTrivialSet() { Console.Write(" test Trivial set "); // // set by constructor // evt = new AutoResetEvent(true); Console.Write('.'); bool ret = evt.WaitOne(trivialTestWaitTime); if (!ret) { Expect.Fail("wait should succeed"); return; } Console.Write('.'); ret = evt.WaitOne(trivialTestWaitTime); if (ret) { Expect.Fail("wait should timeout"); return; } evt.Close(); // // set by constructor then explicitly Set() // evt = new AutoResetEvent(true); evt.Set(); Console.Write('.'); ret = evt.WaitOne(trivialTestWaitTime); if (!ret) { Expect.Fail("wait should succeed"); return; } Console.Write('.'); ret = evt.WaitOne(trivialTestWaitTime); if (ret) { Expect.Fail("wait should timeout"); return; } evt.Close(); // // reset by constructor then explicitly Set() // evt = new AutoResetEvent(false); evt.Set(); Console.Write('.'); ret = evt.WaitOne(trivialTestWaitTime); if (!ret) { Expect.Fail("wait should succeed"); return; } Console.Write('.'); ret = evt.WaitOne(trivialTestWaitTime); if (ret) { Expect.Fail("wait should timeout"); return; } evt.Close(); // // reset and set several times // evt = new AutoResetEvent(false); evt.Set(); evt.Reset(); evt.Set(); evt.Reset(); evt.Set(); Console.Write('.'); ret = evt.WaitOne(trivialTestWaitTime); if (!ret) { Expect.Fail("wait should succeed"); return; } Console.Write('.'); ret = evt.WaitOne(trivialTestWaitTime); if (ret) { Expect.Fail("wait should timeout"); return; } evt.Close(); evt = null; Console.WriteLine("OK"); } /// /// Set the event, and then WaitOne from different thread. The wait should succeed. /// private static void TestThreadSet() { Console.Write(" test thread set "); evt = new AutoResetEvent(true); Thread t = new Thread(new ThreadStart(WaiterMain)); t.Start(); t.Join(); Console.Write('.'); bool ret = evt.WaitOne(trivialTestWaitTime); if (ret) { Expect.Fail("wait should timeout"); return; } evt.Close(); evt = null; Console.WriteLine("OK"); } /// /// Reset the event and then WaitOne from different thread. The wait should timeout /// private static void TestThreadReset() { Console.Write(" test thread reset "); evt = new AutoResetEvent(false); counter = 0; Thread t = new Thread(new ThreadStart(NoSignalWaiterMain)); t.Start(); Console.Write('.'); t.Join(); // wait should timeout and therefore counter should be 1 if (Thread.VolatileRead(ref counter) != 1) { Expect.Fail("wait should timeout"); return; } Console.Write('.'); evt.Set(); evt.WaitOne(); evt.Close(); evt = null; Console.WriteLine("OK"); } /// /// multiple threads WaitOne on a reset auto reset event and master thread /// calls SetAll. all waiters should be unblocked by it. /// private static void TestSetAll() { Console.Write(" test set all "); readyCount = 0; counter = 0; // // multiple threads WaitOne on a reset auto reset event and master thread // calls SetAll. all waiters should be unblocked by it. // Console.Write('.'); evt = new AutoResetEvent(false); Thread[] threads = new Thread[MaxWaiters]; for (int i = 0; i < threads.Length; i++) { threads[i] = new Thread(new ThreadStart(WaiterMain)); ((!)threads[i]).Start(); Thread.Yield(); } // wait for all threads to be ready for (int t = 0; t < 50; t++) { if (Thread.VolatileRead(ref readyCount) == MaxWaiters) { break; } Thread.Sleep(100); } evt.SetAll(); for (int i = 0; i < threads.Length; i++) { ((!)threads[i]).Join(); } if (Thread.VolatileRead(ref counter) != MaxWaiters) { Expect.Fail("unexpected counter"); return; } evt.Close(); // // now multiple threads wait on the same event after SetAll, they should // all timeout except one // Console.Write('.'); counter = 0; evt = new AutoResetEvent(false); evt.SetAll(); threads = new Thread[MaxWaiters]; for (int i = 0; i < threads.Length; i++) { threads[i] = new Thread(new ThreadStart(NoSignalWaiterMain)); ((!)threads[i]).Start(); Thread.Yield(); } for (int i = 0; i < threads.Length; i++) { ((!)threads[i]).Join(); } // all waiter except one should timeout, therefore counter should be // number of waiters minus one if (Thread.VolatileRead(ref counter) != (MaxWaiters - 1)) { Expect.Fail("unexpected counter"); return; } evt.Close(); Console.WriteLine("OK"); } /// /// test multiple threads WaitOne() on the same event while the master /// thread call Set one at a time. /// private static void TestMultipleWaitOne() { Console.Write(" test multiple wait one "); evt = new AutoResetEvent(true); for (int n = 1; n < MaxWaiters; n++) { // reset counter counter = 0; int previousCounter = 0; int numSet = 0; bool timeout = true; evt.Set(); for (int i = 0; i < n; i++) { new Thread(new ThreadStart(WaiterMain)).Start(); } // Call Set() one at a time after observing a change in the counter for (int t = 0; t < 50; t++) { int currentCounter = Thread.VolatileRead(ref counter); if (currentCounter == n) { if (numSet != n - 1) { Expect.Fail(string.Format( "unexpected number of Set() with {0} threads", n)); return; } timeout = false; Console.Write('.'); break; } else if (previousCounter != currentCounter) { previousCounter = currentCounter; evt.Set(); numSet++; Thread.Yield(); } else { Thread.Sleep(100); } } if (timeout) { Expect.Fail(string.Format("timed out with {0} threads", n)); return; } } evt.Close(); evt = null; Console.WriteLine("OK"); } /// /// Test dead lock situation where two threads each waits the other to set an event /// private static void TestDeadlock() { Console.Write(" test deadlock ..."); evtGate = new ManualResetEvent(false); evt3 = new AutoResetEvent(false); evt4 = new AutoResetEvent(false); counter = 0; Thread t1 = new Thread(new ThreadStart(DeadlockMain1)); Thread t2 = new Thread(new ThreadStart(DeadlockMain2)); t1.Start(); t2.Start(); evt3.WaitOne(); evt4.WaitOne(); evtGate.Set(); t1.Join(); t2.Join(); // wait in both thread should timeout, therefore the counter should be 2 if (Thread.VolatileRead(ref counter) != 2) { Expect.Fail("expecting deadlock"); return; } Console.WriteLine("OK"); } /// /// Test the scenario where we dispose an event object then wait on it. /// private static void TestDispose() { Console.Write(" test dispose "); evt = new AutoResetEvent(false); evt.Close(); try { evt.WaitOne(); } catch (Exception ex) { Console.WriteLine(ex.ToString()); return; } Expect.Fail("expecting exception on disposed object"); } /// /// Thread entry point to test waiting on an event. /// increment the counter only if the wait timeout /// private static void NoSignalWaiterMain() { bool ret = evt.WaitOne(trivialTestWaitTime); if (!ret) { // increment counter if the wait timeout Interlocked.Increment(ref counter); } } /// /// Thread entry point to test waiting on an event. /// increment readyCount before wait begins and increment counter after wait succeeds /// private static void WaiterMain() { Interlocked.Increment(ref readyCount); evt.WaitOne(); Interlocked.Increment(ref counter); } /// /// Thread entry point to test dead lock /// increment counter if wait timeout /// private static void DeadlockMain1() { evt1 = new AutoResetEvent(false); evt3.Set(); evtGate.WaitOne(); bool ret = evt2.WaitOne(TimeSpan.FromSeconds(1)); if (!ret) { // increment counter if the wait timeout Interlocked.Increment(ref counter); } } /// /// Thread entry point to test dead lock /// increment counter if wait timeout /// private static void DeadlockMain2() { evt2 = new AutoResetEvent(false); evt4.Set(); evtGate.WaitOne(); bool ret = evt1.WaitOne(TimeSpan.FromSeconds(1)); if (!ret) { // increment counter if the wait timeout Interlocked.Increment(ref counter); } } } }