singrdk/base/Libraries/Manifest/job.sg

492 lines
16 KiB
Plaintext

///////////////////////////////////////////////////////////////////////////////
//
// 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<UnicodePipeContract.Imp:READY>! 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<UnicodePipeContract.Imp:READY>(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);
}
}
/// <summary>
/// Provides enumeration of non-exited jobs
/// </summary>
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();
}
}
/// <summary>
/// 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.
/// </summary>
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<WaitForChildContract.Exp:Start>(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<WaitForChildContract.Exp:Start>! expRef;
private WaitForChildThread(Process! process,
TRef<WaitForChildContract.Exp:Start>! expRef)
{
this.process = process;
this.expRef = expRef;
base();
}
public static void StartWaiting(Process process,
TRef<WaitForChildContract.Exp:Start>! 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() {}
}
}