/////////////////////////////////////////////////////////////////////////////// // // Microsoft Research Singularity // // Copyright (c) Microsoft Corporation. All rights reserved. // using System; using System.Diagnostics; using System.Collections; using System.Threading; using Microsoft.SingSharp; using Microsoft.Singularity.UnitTest; using Microsoft.Singularity.Applications; using Microsoft.Singularity.Directory; using Microsoft.Singularity.Io; using Microsoft.Singularity.Channels; namespace Microsoft.Singularity.Applications { /// /// Test cases for creating processes in parallel. /// internal sealed class TestProcessCreation { private const int numThreads = 16; private const int iterations = 16; private const string commandLine = "fib"; private static int idGenerator; private static ManualResetEvent evtGate; /// /// Run test to create processes. The child processes don't output /// to the console. Instead, we swallow their output with a null pipe. /// internal static void RunWithNoChildOutput() { Console.Write(" test parallel process creation with no child output... "); evtGate = new ManualResetEvent(false); // Start the worker threads Thread[] threads = new Thread[numThreads]; for (int i = 0; i < threads.Length; i++) { threads[i] = new Thread(new ThreadStart(ProcessCreationMainWithNoOutput)); ((!)threads[i]).Start(); Thread.Yield(); } // Give the worker threads a bit time to initialize // then signal the start Thread.Sleep(1000); evtGate.Set(); // Wait for all worker threads to finish for (int i = 0; i < threads.Length; i++) { ((!)threads[i]).Join(); } // If we didn't crash, then this test is successful. Console.WriteLine("OK"); } /// /// Run test to create processes in parallel. The child /// processes output to the console through PipeMultiplexer. /// internal static void RunWithChildOutput() { Console.Write(" test parallel process creation with child output... "); evtGate = new ManualResetEvent(false); // Start the worker threads WorkerThread[] threads = new WorkerThread[numThreads]; for (int i = 0; i < threads.Length; i++) { PipeMultiplexer! mux = CreatePipeMultiplexer(); WorkerThread worker = new WorkerThread(mux); worker.thread = new Thread(worker.ThreadRoutine); threads[i] = worker; } // We want to create all of the muxes first, then create // all of the threads, then start them. foreach (WorkerThread! worker in threads) { worker.thread.Start(); Thread.Yield(); } // Give the worker threads a bit time to initialize // then signal the start Thread.Sleep(1000); evtGate.Set(); // Wait for all worker threads to finish foreach (WorkerThread! worker in threads) { worker.thread.Join(); } foreach (WorkerThread! worker in threads) { PipeMultiplexer! mux = worker.muxRef.Acquire(); delete mux; } // If we didn't crash, then this test is successful. Console.WriteLine(); Console.WriteLine("OK"); } /// /// Thread main function that creates processes. The child /// processes don't output to the console. Instead, we swallow /// their output with a null pipe. /// private static void ProcessCreationMainWithNoOutput() { int id = Interlocked.Increment(ref idGenerator) - 1; NullPipeControl.Imp:START! control = StartNullPipe(); DirectoryServiceContract.Imp! directoryService = DirectoryService.NewClientEndpoint(); // Wait for the signal evtGate.WaitOne(); // Create many processes Process[] children = new Process[iterations]; try { for (int i = 0; i < iterations; i++) { UnicodePipeContract.Imp! childStdInImp; UnicodePipeContract.Exp! childStdInExp; UnicodePipeContract.NewChannel(out childStdInImp, out childStdInExp); UnicodePipeContract.Imp! childStdOutImp = CreateNullPipeClient(control); //Manifest manifest; children[i] = Binder.CreateProcess(directoryService, new string[1]{commandLine}, childStdInExp, childStdOutImp); delete childStdInImp; if (children[i] == null) { throw new ProcessCreateException("Failed to create process"); } ((!)children[i]).Start(); if (id == 0) { Console.Write('.'); } } } finally { delete directoryService; delete control; } // Wait for all children to finish for (int i = 0; i < children.Length; i++) { ((!)children[i]).Join(); } } private contract NullPipeControl { in message NewInput(UnicodePipeContract.Exp:EXPMOVABLE! client); out message Ack(); state START: NewInput? -> Ack! -> START; } private static UnicodePipeContract.Imp:READY! CreateNullPipeClient( NullPipeControl.Imp:START! control) { UnicodePipeContract.Imp! client; UnicodePipeContract.Exp! exp; UnicodePipeContract.NewChannel(out client, out exp, UnicodePipeContract.EXPMOVABLE.Value); control.SendNewInput(exp); switch receive { case control.Ack(): switch receive { case client.Moved(): return client; case client.ChannelClosed(): throw new ProcessCreateException( "Failed to create null pipe: client receve"); } case control.ChannelClosed(): throw new ProcessCreateException( "Failed to create null pipe: control receive"); } } private static NullPipeControl.Imp:START! StartNullPipe() { NullPipeControl.Imp! controlImp; NullPipeControl.Exp! controlExp; NullPipeControl.NewChannel(out controlImp, out controlExp); NullPipeThread pipeThread = new NullPipeThread(controlExp); Thread t = new Thread(new ThreadStart(pipeThread.PumpMessage)); t.Start(); return controlImp; } private class NullPipeThread { TRef! controlExp; internal NullPipeThread([Claims] NullPipeControl.Exp:START! control) { this.controlExp = new TRef(control); } internal void PumpMessage() { NullPipeControl.Exp:START! control = this.controlExp.Acquire(); ESet clients = new ESet(); bool done = false; try { while (!done) { switch receive { case control.NewInput(client): client.SendMoved(); clients.Add(client); control.SendAck(); break; case control.ChannelClosed(): done = true; break; case client.Write(buffer, index, count) in clients: client.SendAckWrite(buffer); clients.Add(client); continue; case client.ChannelClosed() in clients: delete client; continue; } } } finally { clients.Dispose(); delete control; } } } private class WorkerThread { public WorkerThread([Claims]PipeMultiplexer! mux) { this.muxRef = new TContainer(mux); } public TContainer! muxRef; public Thread thread; public void ThreadRoutine() { int totalProcessesCreated = 0; int goalCountProcessesCreated = 0x100; int maxActiveProcesses = 0x10; ArrayList processes = new ArrayList(); PipeMultiplexer! mux = muxRef.Acquire(); DirectoryServiceContract.Imp! rootdir = DirectoryService.NewClientEndpoint(); try { for (;;) { Thread.Yield(); // scan child processes and remove dead ones int i = 0; while (i < processes.Count) { Process! process = (Process!)processes[i]; if (process.Join(TimeSpan.Zero)) { // oooo, it's dead. // Debug.WriteLine(String.Format("yay, process {0} died", process.Id)); process.Dispose(true); processes.RemoveAt(i); } else { // process not dead yet i++; } } bool canCreateProcess = (processes.Count < maxActiveProcesses && totalProcessesCreated < goalCountProcessesCreated); if (canCreateProcess) { string[] childArgs = new string[1]{ commandLine }; Process process = Binder.CreateProcess(rootdir, childArgs, mux); if (process == null) { Debug.WriteLine("FAILED to create child process."); Debugger.Break(); break; } processes.Add(process); process.Start(); // Debug.WriteLine(String.Format("created child process (id {0}), this is {1}/{2}", process.Id, totalProcessesCreated, goalCountProcessesCreated)); totalProcessesCreated++; } if (!canCreateProcess) { // Debug.WriteLine("snore"); Thread.Sleep(250); } if (processes.Count == 0 && totalProcessesCreated == goalCountProcessesCreated) { Debug.WriteLine("goal has been created. worker thread terminating."); break; } } foreach (Process! process in processes) { Debug.WriteLine("Terminating child process: " + process.Id); process.Stop(); } foreach (Process! process in processes) { Debug.WriteLine("Waiting for child process: " + process.Id); process.Join(); process.Dispose(true); } processes.Clear(); } finally { delete rootdir; muxRef.Release(mux); } } } // Redirect our standard output into a multiplexer so we can interleave // output from child processes private static PipeMultiplexer! CreatePipeMultiplexer() { // Swap our real stdOut with a newly created one UnicodePipeContract.Exp! newOutputExp; UnicodePipeContract.Imp! newOutputImp; UnicodePipeContract.NewChannel(out newOutputImp, out newOutputExp); UnicodePipeContract.Imp stdOut = ConsoleOutput.Swap(newOutputImp); if (stdOut == null) { throw new Exception("test expects a STDOUT pipe"); } // Use a mux to splice our own output together with the child // processes we will run. return PipeMultiplexer.Start(stdOut, newOutputExp); } } }