///////////////////////////////////////////////////////////////////////////////
//
// 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 WaitHandle.WaitAny()
///
internal sealed class TestWaitAny
{
internal static TestLog Expect;
private const int numHandles = 7;
private const int numHandlesStress = 72;
private static readonly TimeSpan waitTimeout = TimeSpan.FromMilliseconds(100);
private static WaitHandle[] handles;
private static ManualResetEvent evtGate;
private static int counter;
private static int readyCount;
internal static void Run()
{
///TODO: harden code path and then enable this test case
//TestNullArray();
TestTrivialTimeout();
TestTrivialSameHandleArray();
TestTrivialEachPosition();
TestThreadTimeout();
TestThreadEachPosition();
TestThreadMultipleWaitAny();
TestThreadMixtureSyncObjects();
}
///
/// WaitAny on a mixture of AutoResetEvent, ManualResetEvent, and Mutex
///
private static void TestThreadMixtureSyncObjects()
{
Console.Write(" test thread mixture of sync objects ");
Cleanup();
Console.Write('.');
evtGate = new ManualResetEvent(false);
handles = new WaitHandle[numHandlesStress];
for (int i = 0; i < handles.Length; i++) {
switch(i % 3) {
case 0:
// auto reset events, half set and half reset
handles[i] = new AutoResetEvent(i % 6 == 0);
break;
case 1:
// mutexes, half initially owned and half not initially owned
handles[i] = new Mutex(i % 6 != 1);
break;
case 2:
// manual reset events, all reset
handles[i] = new ManualResetEvent(false);
break;
}
}
Thread[] threads = new Thread[numHandlesStress];
for (int i = 0; i < threads.Length; i++) {
threads[i] = new Thread(new ThreadStart(TimeoutWaitMain));
((!)threads[i]).Start();
Thread.Yield();
}
// wait for all waiter threads to be ready
while (true) {
if (Thread.VolatileRead(ref readyCount) == numHandlesStress) {
break;
}
Thread.Yield();
}
// let the waiter threads go
evtGate.Set();
for (int i = 0; i < threads.Length; i++) {
((!)threads[i]).Join();
}
// half of the mutexes, half of the auto reset events, and all manual reset events
// should timeout. therefore the counter is
// numHandlesStress - (numHandlesStress + 3) / 6 * 2
if (Thread.VolatileRead(ref counter) !=
numHandlesStress - (numHandlesStress + 3) / 6 * 2) {
Expect.Fail(string.Format("unexpected counter {0}", counter));
return;
}
Cleanup();
Console.WriteLine("OK");
}
///
/// Multiple threads call WaitAny() on an array of wait handles of the same type
///
private static void TestThreadMultipleWaitAny()
{
Console.Write(" test thread multiple wait any ");
//
// wait on multiple mutexes
//
Cleanup();
Console.Write('.');
evtGate = new ManualResetEvent(false);
handles = new WaitHandle[numHandlesStress];
for (int i = 0; i < handles.Length; i++) {
handles[i] = new Mutex();
}
Thread[] threads = new Thread[numHandlesStress];
for (int i = 0; i < threads.Length; i++) {
threads[i] = new Thread(new ThreadStart(TimeoutWaitMain));
((!)threads[i]).Start();
Thread.Yield();
}
// wait for all waiter threads to be ready
while (true) {
if (Thread.VolatileRead(ref readyCount) == numHandlesStress) {
break;
}
Thread.Yield();
}
evtGate.Set();
for (int i = 0; i < threads.Length; i++) {
((!)threads[i]).Join();
}
if (Thread.VolatileRead(ref counter) != 0) {
Expect.Fail("unexpected counter");
return;
}
//
// wait on multiple auto reset events
//
Cleanup();
Console.Write('.');
evtGate.Reset();
handles = new WaitHandle[numHandlesStress];
for (int i = 0; i < handles.Length; i++) {
handles[i] = new AutoResetEvent(false);
}
threads = new Thread[numHandlesStress];
for (int i = 0; i < threads.Length; i++) {
threads[i] = new Thread(new ThreadStart(TimeoutWaitMain));
((!)threads[i]).Start();
Thread.Yield();
}
// set half of the events
for (int i = 0; i < handles.Length; i += 2) {
((AutoResetEvent!)handles[i]).Set();
}
// wait for all waiter threads to be ready
while (true) {
if (Thread.VolatileRead(ref readyCount) == numHandlesStress) {
break;
}
Thread.Yield();
}
evtGate.Set();
for (int i = 0; i < threads.Length; i++) {
((!)threads[i]).Join();
}
// half of the events are set, the rest should timeout, therefore the counter
// should be numHandlesStress - numHandlesStress / 2
if (Thread.VolatileRead(ref counter) != numHandlesStress - numHandlesStress / 2) {
Expect.Fail("unexpected counter");
return;
}
Cleanup();
Console.WriteLine("OK");
}
///
/// poke each position of WaitAny handles to make sure we don't have blind spot.
///
private static void TestTrivialEachPosition()
{
Console.Write(" test trivial each position ");
int ret;
//
// initialize an array of auto reset events, all set, and call WaitAny
// multiple times, it should cycle through the array, after the array is
// thoroughly walked, the next WaitAny should timeout
//
handles = new WaitHandle[numHandles];
for (int i = 0; i < handles.Length; i++) {
handles[i] = new AutoResetEvent(true);
}
for (int i = 0; i < handles.Length; i++) {
Console.Write('.');
ret = WaitHandle.WaitAny(handles);
if (ret != i) {
Expect.Fail(string.Format("expect wait succeed on handles[{0}]", i));
return;
}
}
Console.Write('.');
ret = WaitHandle.WaitAny(handles, waitTimeout);
if (ret != -1) {
Expect.Fail("wait should timeout");
return;
}
Cleanup();
//
// now start with an array of auto reset events all reset,
// then poke each position at a time, WaitAny should success on that position
//
handles = new WaitHandle[numHandles];
for (int i = 0; i < handles.Length; i++) {
handles[i] = new AutoResetEvent(false);
}
for (int i = handles.Length-1; i >= 0; i--) {
((AutoResetEvent!)handles[i]).Set();
Console.Write('.');
ret = WaitHandle.WaitAny(handles);
if (ret != i) {
Expect.Fail(string.Format("expect wait succeed on handles[{0}]", i));
return;
}
}
Console.Write('.');
ret = WaitHandle.WaitAny(handles, waitTimeout);
if (ret != -1) {
Expect.Fail("wait should timeout");
return;
}
Cleanup();
Console.WriteLine("OK");
}
///
/// poke each position of WaitAny handles and call WaitAny on a different thread
///
private static void TestThreadEachPosition()
{
Console.Write(" test thread each position ");
Cleanup();
//
// initialize an array of auto reset events, all set, and call WaitAny
// on multiple threads, they should cycle through the array, after the array is
// thoroughly walked, the next WaitAny should timeout
//
Console.Write('.');
evtGate = new ManualResetEvent(false);
handles = new WaitHandle[numHandles];
for (int i = 0; i < handles.Length; i++) {
handles[i] = new AutoResetEvent(true);
}
Thread[] threads = new Thread[numHandles];
for (int i = 0; i < threads.Length; i++) {
threads[i] = new Thread(new ThreadStart(WaitMain));
((!)threads[i]).Start();
}
evtGate.Set();
for (int i = 0; i < threads.Length; i++) {
((!)threads[i]).Join();
}
// all wait should be successful, therefore counter is 1+2+...+numHandles
if (Thread.VolatileRead(ref counter) != numHandles * (numHandles + 1) / 2) {
Expect.Fail("unexpected counter");
return;
}
Console.Write('.');
int ret = WaitHandle.WaitAny(handles, waitTimeout);
if (ret != -1) {
Expect.Fail("wait should timeout");
return;
}
Cleanup();
//
// now start with an array of auto reset events all reset,
// then poke each position at a time, WaitAny should success on that position
//
Console.Write('.');
handles = new WaitHandle[numHandles];
for (int i = 0; i < handles.Length; i++) {
handles[i] = new AutoResetEvent(false);
}
threads = new Thread[numHandles];
for (int i = 0; i < threads.Length; i++) {
threads[i] = new Thread(new ThreadStart(WaitMain));
((!)threads[i]).Start();
}
evtGate.Set();
for (int i = handles.Length-1; i >= 0; i--) {
((AutoResetEvent!)handles[i]).Set();
Thread.Yield();
}
for (int i = 0; i < threads.Length; i++) {
((!)threads[i]).Join();
}
// all wait should be successful, therefore counter is 1+2+...+numHandles
if (Thread.VolatileRead(ref counter) != numHandles * (numHandles + 1) / 2) {
Expect.Fail("unexpected counter");
return;
}
Console.Write('.');
ret = WaitHandle.WaitAny(handles, waitTimeout);
if (ret != -1) {
Expect.Fail("wait should timeout");
return;
}
Cleanup();
Console.WriteLine("OK");
}
///
/// Thread entry point of the waiter thread.
/// Counter is incremented by the successful wait position plus one
///
private static void WaitMain()
{
evtGate.WaitOne();
int ret = WaitHandle.WaitAny(handles);
// there is no Interlocked.Add on int :( Do it the hard way
int oldValue, newValue;
do {
oldValue = Thread.VolatileRead(ref counter);
newValue = oldValue + (ret + 1);
}
while (oldValue != Interlocked.CompareExchange(ref counter, newValue, oldValue));
}
///
/// WaitAny on an array of handles where all elements are referening the same instance
///
private static void TestTrivialSameHandleArray()
{
Console.Write(" test trivial same handle array ");
//
// same instance of manual reset event
//
Console.Write('.');
ManualResetEvent mevt = new ManualResetEvent(true);
handles = new WaitHandle[numHandles];
for (int i = 0; i < handles.Length; i++) {
handles[i] = mevt;
}
int ret = WaitHandle.WaitAny(handles);
if (ret != 0) {
Expect.Fail("expect wait succeed on handles[0]");
return;
}
ret = WaitHandle.WaitAny(handles);
if (ret != 0) {
Expect.Fail("expect wait succeed on handles[0] again");
return;
}
Cleanup();
//
// same instance of auto reset event
//
Console.Write('.');
AutoResetEvent aevt = new AutoResetEvent(true);
handles = new WaitHandle[numHandles];
for (int i = 0; i < handles.Length; i++) {
handles[i] = aevt;
}
ret = WaitHandle.WaitAny(handles);
if (ret != 0) {
Expect.Fail("expect wait succeed on handles[0]");
return;
}
ret = WaitHandle.WaitAny(handles, waitTimeout);
if (ret != -1) {
Expect.Fail("wait should timeout");
return;
}
Cleanup();
Console.WriteLine("OK");
}
///
/// Test WaitAny on an array with is null or contains null elements
///
private static void TestNullArray()
{
Console.Write(" test null array ");
Console.Write('.');
handles = null;
bool exception = false;
try {
WaitHandle.WaitAny(handles);
}
catch (ArgumentNullException) {
exception = true;
}
if (!exception) {
Expect.Fail("expecting exception");
return;
}
Console.Write('.');
handles = new WaitHandle[3] { null, null, null };
exception = false;
try {
WaitHandle.WaitAny(handles);
}
catch (ArgumentNullException) {
exception = true;
}
if (!exception) {
Expect.Fail("expecting exception");
return;
}
Console.WriteLine("OK");
}
///
/// Test WaitAny() on an array of unsignaled handles and timeout
///
private static void TestThreadTimeout()
{
Console.Write(" test thread timeout ");
//
// timeout on unsignaled auto reset events
//
Cleanup();
Console.Write('.');
evtGate = new ManualResetEvent(false);
handles = new WaitHandle[numHandles];
for (int i = 0; i < handles.Length; i++) {
handles[i] = new AutoResetEvent(false);
}
TestThreadTimeoutWorker();
//
// timeout on unsignaled manual reset events
//
Cleanup();
Console.Write('.');
evtGate = new ManualResetEvent(false);
handles = new WaitHandle[numHandles];
for (int i = 0; i < handles.Length; i++) {
handles[i] = new ManualResetEvent(false);
}
TestThreadTimeoutWorker();
//
// timeout on owned mutexes
//
Cleanup();
Console.Write('.');
evtGate.Reset();
handles = new WaitHandle[numHandles];
for (int i = 0; i < handles.Length; i++) {
handles[i] = new Mutex();
((!)handles[i]).WaitOne();
}
TestThreadTimeoutWorker();
//
// timeout on owned
//
Cleanup();
Console.Write('.');
evtGate.Reset();
handles = new WaitHandle[numHandles];
for (int i = 0; i < handles.Length; i++) {
handles[i] = new Mutex(true);
}
TestThreadTimeoutWorker();
Cleanup();
Console.WriteLine("OK");
}
///
/// create threads WaitAny on an array of unsignaled handles,
/// they should all timeout
///
private static void TestThreadTimeoutWorker()
{
Thread[] threads = new Thread[numHandles];
for (int i = 0; i < threads.Length; i++) {
threads[i] = new Thread(new ThreadStart(TimeoutWaitMain));
((!)threads[i]).Start();
}
evtGate.Set();
for (int i = 0; i < threads.Length; i++) {
((!)threads[i]).Join();
}
// all wait should timeout, therefore counter == numHandles
if (Thread.VolatileRead(ref counter) != numHandles) {
Expect.Fail("unexpected number of timeouts");
}
}
///
/// Thread entry point for waiter.
/// Increment readyCount before waiting on the gate, then performs
/// WaitAny, and increment the counter if wait timeout
///
private static void TimeoutWaitMain()
{
Interlocked.Increment(ref readyCount);
evtGate.WaitOne();
int ret = WaitHandle.WaitAny(handles, waitTimeout);
if (ret == -1) {
// increment counter only if wait timeout
Interlocked.Increment(ref counter);
}
}
///
/// Test WaitAny on an array of unsignaled handles and the wait should timeout
///
private static void TestTrivialTimeout()
{
Console.Write(" test trivial timeout ");
//
// wait on unsignaled manual reset event
//
Console.Write('.');
handles = new WaitHandle[3] {
new ManualResetEvent(false),
new ManualResetEvent(false),
new ManualResetEvent(false)
};
int ret = WaitHandle.WaitAny(handles, waitTimeout);
if (ret != -1) {
Expect.Fail("wait should timeout");
return;
}
Cleanup();
//
// wait on unsignaled auto reset event
//
Console.Write('.');
handles = new WaitHandle[3] {
new AutoResetEvent(false),
new AutoResetEvent(false),
new AutoResetEvent(false)
};
ret = WaitHandle.WaitAny(handles, waitTimeout);
if (ret != -1) {
Expect.Fail("wait should timeout");
return;
}
Cleanup();
//
// wait on mixture of unsignaled events
//
Console.Write('.');
handles = new WaitHandle[4] {
new AutoResetEvent(false),
new ManualResetEvent(false),
new AutoResetEvent(false),
new ManualResetEvent(false)
};
ret = WaitHandle.WaitAny(handles, waitTimeout);
if (ret != -1) {
Expect.Fail("wait should timeout");
return;
}
Cleanup();
Console.WriteLine("OK");
}
///
/// Release all wait handles, reset counters
///
private static void Cleanup()
{
if (handles != null) {
for (int i = 0; i < handles.Length; i++) {
if (handles[i] != null) {
((!)handles[i]).Close();
}
}
handles = null;
}
readyCount = 0;
counter = 0;
}
}
}