/////////////////////////////////////////////////////////////////////////////// // // 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 { /// /// A test of Monitor.PulseAll. A collection of threads waits on /// a single monitor for a pulse all. /// internal sealed class PulseAllTest { int waiterCount = 50; int timeoutSeconds = 2; int passedBarrier = 0; bool waiterRunning = false; volatile bool timedOut = false; volatile bool stop = false; volatile int generation = 0; ManualResetEvent! controllerReady = new ManualResetEvent(false); ManualResetEvent! finishedEvent = new ManualResetEvent(false); AutoResetEvent! barrierEvent = new AutoResetEvent(false); object! monitor = new object(); bool []! visited; private static void Yield() { #if SINGULARITY Thread.Yield(); #else Thread.Sleep(0); #endif } public void WatchdogThreadMain() { TimeSpan delta = TimeSpan.FromSeconds(timeoutSeconds); int now = this.generation; int last = 0; do { last = now; if (finishedEvent.WaitOne(delta)) { return; } Yield(); now = this.generation; } while (last != now); DebugStub.Break(); this.timedOut = true; } public void ControllerThreadMain() { const int iterations = 1000; const int YieldFudge = 50; // Signal worker threads to start controllerReady.Set(); Yield(); for (int i = 1; i <= iterations; i++) { barrierEvent.WaitOne(); // There is a potential // race between the last thread reaching the barrier // setting the barrierEvent and reaching the Monitor.Wait // call. By yielding a few times we should give the // thread enough time to get there. for (int n = 0; n < YieldFudge; n++) Yield(); lock (this.monitor) { // Reset check state this.passedBarrier = 0; for (int j = 0; j < visited.Length; j++) { visited[j] = false; } // Set stop flag if end is reached this.stop = (i == iterations); // Wake up waiters Monitor.PulseAll(this.monitor); } } finishedEvent.Set(); } public void WaiterThreadMain() { while (!stop) { Monitor.Enter(this.monitor); try { Assert.True(waiterRunning == false, "Two waiters have monitor."); waiterRunning = true; Assert.False(this.visited[this.passedBarrier], "Thread already reached barrier."); this.visited[this.passedBarrier] = true; this.passedBarrier++; this.generation++; Assert.LessOrEqual(this.passedBarrier, waiterCount, "More waiters passed barrier than expected."); if (this.passedBarrier == this.waiterCount) { barrierEvent.Set(); } this.waiterRunning = false; Monitor.Wait(this.monitor); this.waiterRunning = true; } finally { this.waiterRunning = false; Monitor.Exit(this.monitor); } } } public void RunTest() { Thread watchdog = new Thread(new ThreadStart(WatchdogThreadMain)); Thread controller = new Thread(new ThreadStart(ControllerThreadMain)); watchdog.Start(); controller.Start(); controllerReady.WaitOne(); for (int i = 0; i < waiterCount; i++) { Thread t = new Thread(new ThreadStart(WaiterThreadMain)); t.Start(); } while (!controller.Join(TimeSpan.FromMilliseconds(100))) { Assert.False(this.timedOut, "Timed out!"); } watchdog.Join(); } public PulseAllTest(int threadCount, int timeoutSeconds) { this.waiterCount = threadCount; this.timeoutSeconds = timeoutSeconds; this.visited = new bool [threadCount]; } public static void FewThreadsTest() { (new PulseAllTest(8, 2)).RunTest(); } public static void ManyThreadsTest() { (new PulseAllTest(50, 2)).RunTest(); } } }