212 lines
6.2 KiB
C#
212 lines
6.2 KiB
C#
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// Microsoft Research Singularity
|
||
|
//
|
||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||
|
//
|
||
|
// File: PulseTest.cs
|
||
|
//
|
||
|
// Note:
|
||
|
//
|
||
|
|
||
|
using System;
|
||
|
using System.Threading;
|
||
|
|
||
|
using Microsoft.Singularity.UnitTest;
|
||
|
|
||
|
namespace Microsoft.Singularity.Applications
|
||
|
{
|
||
|
/// <remarks>
|
||
|
/// A class for testing Monitor.Pulse.
|
||
|
///
|
||
|
/// <para> This class creates a group of monitors (Node class) that
|
||
|
/// a collection of threads then enters, spins, pulses, and exits.
|
||
|
/// </para>
|
||
|
/// </remarks>
|
||
|
internal sealed class PulseTest
|
||
|
{
|
||
|
internal class Node
|
||
|
{
|
||
|
volatile bool inVisit = false;
|
||
|
|
||
|
internal void BeginVisit()
|
||
|
{
|
||
|
Monitor.Enter(this);
|
||
|
Assert.False(inVisit, "Node is visited by two threads");
|
||
|
inVisit = true;
|
||
|
}
|
||
|
|
||
|
internal void EndVisit()
|
||
|
{
|
||
|
Assert.True(inVisit, "Unpaired visit ending");
|
||
|
Monitor.Pulse(this);
|
||
|
inVisit = false;
|
||
|
Monitor.Exit(this);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal sealed class VisitPath
|
||
|
{
|
||
|
int[]! path;
|
||
|
int remainLength;
|
||
|
int cycles;
|
||
|
|
||
|
internal VisitPath(int pathLength)
|
||
|
{
|
||
|
this.path = new int[pathLength];
|
||
|
this.remainLength = pathLength;
|
||
|
this.cycles = 0;
|
||
|
}
|
||
|
|
||
|
internal void Initialize()
|
||
|
{
|
||
|
// This is a separate method so
|
||
|
// we can compile compatibly on Win32. SGC wants
|
||
|
// the Sing# specific NonDelayed attribute otherwise.
|
||
|
for (int i = 0; i < this.remainLength; i++) {
|
||
|
this.path[i] = i;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal int Pick(int randomNumber)
|
||
|
{
|
||
|
int n = randomNumber % this.remainLength;
|
||
|
int r = this.path[n];
|
||
|
|
||
|
this.remainLength--;
|
||
|
this.path[r] = this.path[this.remainLength];
|
||
|
this.path[this.remainLength] = r;
|
||
|
|
||
|
if (this.remainLength == 0) {
|
||
|
this.cycles++;
|
||
|
this.remainLength = this.path.Length;
|
||
|
}
|
||
|
return r;
|
||
|
}
|
||
|
|
||
|
internal int Cycles { get { return this.cycles; } }
|
||
|
}
|
||
|
|
||
|
private Node[]! nodes;
|
||
|
private readonly int threadCount;
|
||
|
private readonly int maxCycles;
|
||
|
private volatile int startedCount;
|
||
|
private volatile int finishedCount;
|
||
|
private volatile int generation;
|
||
|
private ManualResetEvent! finishedEvent;
|
||
|
|
||
|
private static void Yield()
|
||
|
{
|
||
|
#if SINGULARITY
|
||
|
Thread.Yield();
|
||
|
#else
|
||
|
Thread.Sleep(0);
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
internal void VisitorThreadMain()
|
||
|
{
|
||
|
// Wait until all other threads have started
|
||
|
int threadNumber;
|
||
|
lock (this) {
|
||
|
threadNumber = startedCount++;
|
||
|
}
|
||
|
while (startedCount != this.threadCount) {
|
||
|
Yield();
|
||
|
}
|
||
|
|
||
|
Random rng = new Random(threadNumber);
|
||
|
VisitPath path = new VisitPath(this.nodes.Length);
|
||
|
path.Initialize();
|
||
|
|
||
|
while (path.Cycles != this.maxCycles) {
|
||
|
// Select a node previously unvisited in this cycle
|
||
|
Node/*!*/ n = /*^(!)^*/ nodes[path.Pick(rng.Next())];
|
||
|
n.BeginVisit();
|
||
|
|
||
|
// Yield up to number of threads to give other
|
||
|
// threads a chance to run
|
||
|
int yieldCount = rng.Next(this.threadCount);
|
||
|
while (yieldCount-- > 0)
|
||
|
Yield();
|
||
|
|
||
|
n.EndVisit();
|
||
|
// Console.Write("[{0}]", threadNumber);
|
||
|
this.generation++;
|
||
|
}
|
||
|
|
||
|
lock (this) {
|
||
|
this.finishedCount++;
|
||
|
if (this.finishedCount == this.threadCount) {
|
||
|
this.finishedEvent.Set();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void WatchdogThreadMain()
|
||
|
{
|
||
|
int last = this.generation;
|
||
|
for (;;) {
|
||
|
Thread.Sleep(TimeSpan.FromSeconds(5));
|
||
|
if (this.finishedCount == this.threadCount) {
|
||
|
return;
|
||
|
}
|
||
|
int now = this.generation;
|
||
|
Assert.True(last != now, "Deadlock detected.");
|
||
|
last = now;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal PulseTest(int numberOfNodes, int numberOfThreads, int iterations)
|
||
|
{
|
||
|
this.nodes = new Node[numberOfNodes];
|
||
|
this.threadCount = numberOfThreads;
|
||
|
this.maxCycles = iterations;
|
||
|
this.startedCount = 0;
|
||
|
this.finishedCount = 0;
|
||
|
this.finishedEvent = new ManualResetEvent(false);
|
||
|
}
|
||
|
|
||
|
private void Initialize()
|
||
|
{
|
||
|
for (int i = 0; i < nodes.Length; i++) {
|
||
|
nodes[i] = new Node();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal void RunTest()
|
||
|
{
|
||
|
for (int i = 0; i < this.threadCount; i++) {
|
||
|
(new Thread(new ThreadStart(VisitorThreadMain))).Start();
|
||
|
}
|
||
|
Thread watchdog = new Thread(new ThreadStart(WatchdogThreadMain));
|
||
|
watchdog.Start();
|
||
|
|
||
|
finishedEvent.WaitOne();
|
||
|
watchdog.Join();
|
||
|
Assert.True(Assert.Failures == 0, "Failures within threads.");
|
||
|
}
|
||
|
|
||
|
internal static void LowDensityTest()
|
||
|
{
|
||
|
PulseTest p = new PulseTest(128, 17, 20);
|
||
|
p.Initialize();
|
||
|
p.RunTest();
|
||
|
}
|
||
|
|
||
|
internal static void MediumDensityTest()
|
||
|
{
|
||
|
PulseTest p = new PulseTest(32, 32, 20);
|
||
|
p.Initialize();
|
||
|
p.RunTest();
|
||
|
}
|
||
|
|
||
|
internal static void HighDensityTest()
|
||
|
{
|
||
|
PulseTest p = new PulseTest(4, 128, 20);
|
||
|
p.Initialize();
|
||
|
p.RunTest();
|
||
|
}
|
||
|
}
|
||
|
}
|