/////////////////////////////////////////////////////////////////////////////// // // Microsoft Research Singularity // // Copyright (c) Microsoft Corporation. All rights reserved. // // File: Job.cs // // Note: // using System; using System.Text; using System.GC; using System.Threading; using System.Collections; using System.Diagnostics; using Microsoft.Singularity; using Microsoft.SingSharp; using Microsoft.Singularity.V1.Services; using Microsoft.Singularity.V1.Processes; using Microsoft.Singularity.Io; using Microsoft.Singularity.FileSystem; using Microsoft.Singularity.Channels; using Microsoft.Singularity.Applications; using Microsoft.Singularity.Directory; namespace Microsoft.Singularity.ConsoleApplications { // See WaitForChildThread below internal contract WaitForChildContract { out message Finish(); state Start: one {Finish! -> Done;} state Done: one {} } #region JobClasses public abstract class Job { private string! commandLine; protected TRef! stdinCell; public readonly int Id; private static int IdGenerator = 0; public string Name { get { return commandLine; } } protected Job(string! command, [Claims] UnicodePipeContract.Imp:READY! stdin) { this.commandLine = command; this.stdinCell = new TRef(stdin); this.Id = IdGenerator++; } public UnicodePipeContract.Imp:READY! AcquireStdin() { return this.stdinCell.Acquire(); } public void ReleaseStdin([Claims] UnicodePipeContract.Imp:READY! stdin) { this.stdinCell.Release(stdin); } public string StatusName { get { switch (this.Status) { case ProcessState.Stopped: return "Stopped"; case ProcessState.Suspended: return "Suspended"; case ProcessState.Active: return "Active"; default: return "Unknown"; } } } public ProcessState Status { get { Process p = this.Process; if (p == null) { return ProcessState.Stopped; } return p.State; } } public int ExitCode { get { Process p = this.Process; if (p == null) { return 0; } return p.ExitCode; } } public virtual void Dispose() { } public abstract Process Process { get; } public void Stop() { Process p = this.Process; if (p != null) { p.Stop(); } } public void Suspend() { Process p = this.Process; if (p != null) { p.Suspend(false); } } public void Resume() { Process p = this.Process; if (p != null) { p.Resume(false); } } } public class SingleProcessJob : Job { Process process; private static string! CommandLineString(string[]! command) { StringBuilder sb = new StringBuilder(); foreach (string s in command) { sb.Append(s); sb.Append(' '); } return sb.ToString(); } public SingleProcessJob(string[]! command, Process p, [Claims] UnicodePipeContract.Imp:READY! stdin) : base(CommandLineString(command), stdin) { this.process = p; } public override Process Process { get { return this.process; } } public override void Dispose() { Process p = this.process; if (p != null) { p.Dispose(true); } base.Dispose(); } } public class PipeJob : Job { Process[]! processes ; public PipeJob(string! command, [Claims] UnicodePipeContract.Imp:READY! stdin, Process[]! processes) : base(command, stdin) { this.processes = processes; } public override Process Process { get { int c = processes.Length; if (c <= 0) return null; return (Process)this.processes[c-1]; } } public override void Dispose() { foreach (Process! p in this.processes) { p.Dispose(true); } base.Dispose(); } } #endregion public class JobControl : ITracked { PipeMultiplexer! outputControl; Hashtable! jobs; // maps ints to jobs ParameterProcessor parameterProcessor; public JobControl([Claims] PipeMultiplexer! outputControl) { this.outputControl = outputControl; this.jobs = new Hashtable(); } public UnicodePipeContract.Imp:READY FreshStdout() { expose(this) { return this.outputControl.FreshClient(); } } private void RemoveStoppedJobs() { ArrayList toRemove = new ArrayList(); foreach (int i in jobs.Keys) { Job job = (Job)this.jobs[i]; assert job != null; if (job.Status == ProcessState.Stopped) { job.Dispose(); toRemove.Add(i); } } foreach (int i in toRemove) { Console.WriteLine("[{0}] Done.", i); this.jobs.Remove(i); } } /// /// Provides enumeration of non-exited jobs /// public IEnumerator GetEnumerator() { RemoveStoppedJobs(); return this.jobs.Keys.GetEnumerator(); } public Job this[int i] { get { return (Job)this.jobs[i]; } } public void Add(Job! job) { jobs[job.Id] = job; } public void Dispose() { this.outputControl.Dispose(); } void ITracked.Acquire() {} void ITracked.Release() {} void ITracked.Expose() {} void ITracked.UnExpose() {} } public class ConsoleJob : ITracked { private Process process; private string[] cmdLine; private string cmdName; private Job job; public ConsoleJob() { Binder.Initialize(); } public bool CreateProcess( DirectoryServiceContract.Imp:Ready! ds, JobControl! jobControl, String[]! commandLine, ParameterProcessor! parameterProcessor ) { string path; string action; bool ok; assert parameterProcessor != null; assert jobControl != null; assert ds != null; if (!ds.InState(DirectoryServiceContract.Ready.Value)) { Console.WriteLine("ConsoleJob:CreateProcess: directory service endpoint not in Ready state"); return false; } //DebugStub.Break(); string cmdName = commandLine[0]; if (cmdName == null) { Console.WriteLine("ConsoleJob.CreateProcess: no command name given"); return false; } Console.Write(" ConsoleJob.CreateProcess: cmd="); for (int i = 0; i < commandLine.Length; i++) { Console.Write("{0} ",commandLine[i]); } Console.Write("\n"); try { UnicodePipeContract.Imp:READY childStdout; expose(this) { childStdout = jobControl.FreshStdout(); } if (childStdout == null) { // output multiplexer dead (which means that we shouldn't use Console.WriteLine DebugStub.WriteLine("-- Can't get new output pipe"); return false; } Manifest manifest = Binder.LoadManifest(ds, cmdName); if (manifest != null) { if (manifest.HasParameters()) { //Console.WriteLine("Has parameters"); ok = false; action = null; if (parameterProcessor != null) { ok = parameterProcessor.ProcessParameters(commandLine, manifest, out process, out action); } if (!ok) { //Console.WriteLine(" failed to process parameters"); delete childStdout; return false; } else { assert process != null; Console.WriteLine(" ConsoleJob: setting endpoints for {0}, action=({1})", cmdName, action); int result = manifest.SetEndpoints(process, action, false); if (result < 0) { Console.WriteLine("Unable to set all endpoints for this process."); delete childStdout; return false; } } } else { DebugStub.WriteLine("manifest has no parameters. Must be old style: NOT SUPPORTED"); //Console.WriteLine("manifest has no parameters"); //this.process = new Process(commandLine, null, 2); delete childStdout; return false; } } else { Console.WriteLine("'{0}' is not a command or has no manifest", commandLine[0]); //process = new Process(commandLine, null, 2); delete childStdout; return false; } UnicodePipeContract.Imp! stdinImp; UnicodePipeContract.Exp! stdinExp; UnicodePipeContract.NewChannel(out stdinImp, out stdinExp); this.job = new SingleProcessJob(commandLine, this.process, stdinImp); this.process.SetStartupEndpoint(0, (Endpoint * in ExHeap) stdinExp); this.process.SetStartupEndpoint(1, (Endpoint * in ExHeap) childStdout); jobControl.Add(this.job); ok = true; } catch (ProcessCreateException) { Console.Write("Unsupported command: {0}", commandLine[0]); ok = false; } catch (Exception e) { Console.Write("Can't create {0}: Exception '{1}' caught.", commandLine[0], e.Message); ok = false; } return ok; } public bool Start() { if (this.process == null) return false; this.process.Start(); return true; } public void Dispose() { Job j = this.job; if (j != null) { j.Dispose(); } } /// /// Copy and echo characters from shell stdin to job stdin. /// Wait for either the job to exit gracefully, or for the user /// to press control-c or control-z. /// /// Known limitation: if the child process opens /// its own keyboard channel, the shell may never see the control-c /// message. /// public int WaitForJob() { if (this.job == null) { DebugStub.WriteLine("ConsoleJob.WaitForJob: job is null!"); return -1; } if (this.process == null) { DebugStub.WriteLine("ConsoleJob.WaitForJob: process is null!"); return -1; } WaitForChildContract.Imp! imp; WaitForChildContract.Exp! exp; WaitForChildContract.NewChannel(out imp, out exp); try { WaitForChildThread.StartWaiting(job.Process, new TRef(exp)); while (true) { // invariant exChar != null && childStdInReady.Head(ep) || // exChar == null && childStdInReady.Empty switch receive { case imp.Finish(): //Console.WriteLine("finish"); int exitCode = job.ExitCode; job.Dispose(); return exitCode; } } } finally { delete imp; } } // TODO: better ways to wait on a child process private class WaitForChildThread { private Process! process; private TRef! expRef; private WaitForChildThread(Process! process, TRef! expRef) { this.process = process; this.expRef = expRef; base(); } public static void StartWaiting(Process process, TRef! expRef) { if (process == null) { WaitForChildContract.Exp exp = expRef.Acquire(); exp.SendFinish(); delete exp; return; } WaitForChildThread wft = new WaitForChildThread(process, expRef); Thread t = new Thread(new ThreadStart(wft.Wait)); t.Start(); } private void Wait() { process.Join(); WaitForChildContract.Exp exp = expRef.Acquire(); exp.SendFinish(); delete exp; } } public static JobControl! StartOutputPipeThread() { UnicodePipeContract.Exp! newOutputExp; UnicodePipeContract.Imp! newOutputImp; UnicodePipeContract.NewChannel(out newOutputImp, out newOutputExp); UnicodePipeContract.Imp stdOut = ConsoleOutput.Swap(newOutputImp); if (stdOut == null) { DebugStub.WriteLine("Shell not connected to a pipe output!"); throw new ApplicationException("Can't go on"); } PipeMultiplexer pm = PipeMultiplexer.Start(stdOut, newOutputExp); return new JobControl(pm); } void ITracked.Acquire() {} void ITracked.Release() {} void ITracked.Expose() {} void ITracked.UnExpose() {} } }