///////////////////////////////////////////////////////////////////////////////
//
// 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);
}
}
}
}