615 lines
21 KiB
C#
615 lines
21 KiB
C#
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// Microsoft Research Singularity
|
||
|
//
|
||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||
|
//
|
||
|
|
||
|
using System;
|
||
|
using System.Threading;
|
||
|
using Microsoft.Singularity.UnitTest;
|
||
|
|
||
|
namespace Microsoft.Singularity.Applications
|
||
|
{
|
||
|
/// <summary>
|
||
|
/// Test cases for WaitHandle.WaitAny()
|
||
|
/// </summary>
|
||
|
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();
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// WaitAny on a mixture of AutoResetEvent, ManualResetEvent, and Mutex
|
||
|
/// </summary>
|
||
|
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");
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Multiple threads call WaitAny() on an array of wait handles of the same type
|
||
|
/// </summary>
|
||
|
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");
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// poke each position of WaitAny handles to make sure we don't have blind spot.
|
||
|
/// </summary>
|
||
|
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");
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// poke each position of WaitAny handles and call WaitAny on a different thread
|
||
|
/// </summary>
|
||
|
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");
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Thread entry point of the waiter thread.
|
||
|
/// Counter is incremented by the successful wait position plus one
|
||
|
/// </summary>
|
||
|
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));
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// WaitAny on an array of handles where all elements are referening the same instance
|
||
|
/// </summary>
|
||
|
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");
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Test WaitAny on an array with is null or contains null elements
|
||
|
/// </summary>
|
||
|
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");
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Test WaitAny() on an array of unsignaled handles and timeout
|
||
|
/// </summary>
|
||
|
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");
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// create threads WaitAny on an array of unsignaled handles,
|
||
|
/// they should all timeout
|
||
|
/// </summary>
|
||
|
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");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Thread entry point for waiter.
|
||
|
/// Increment readyCount before waiting on the gate, then performs
|
||
|
/// WaitAny, and increment the counter if wait timeout
|
||
|
/// </summary>
|
||
|
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);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Test WaitAny on an array of unsignaled handles and the wait should timeout
|
||
|
/// </summary>
|
||
|
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");
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Release all wait handles, reset counters
|
||
|
/// </summary>
|
||
|
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;
|
||
|
}
|
||
|
}
|
||
|
}
|