using System;
using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace HeuristicLab.PluginInfrastructure {
///
/// Enum for the different RunnerStates.
///
public enum RunnerState {
Created,
Cancelled,
Starting,
Running,
Paused,
Stopping,
Stopped
}
///
/// Abstract RunnerBase class, which implements the IRunner interface.
/// Every specific runner implementation should inherit this class.
///
public abstract class RunnerHost : IRunnerHost {
#region Vars
protected Process process;
#endregion
#region Properties
///
/// The programm, which should be used. For example 'docker'.
///
protected string Program { get; private set; }
///
/// The specific start arguments for the programm.
///
protected string StartArgument { get; private set; }
protected string UserName { get; private set; }
protected string Password { get; private set; }
protected string Domain { get; private set; }
///
/// The runner state.
///
public RunnerState State { get; protected set; } = RunnerState.Created;
#endregion
#region Constructors
protected RunnerHost(string program, string startArgument, string userName, string password, string domain) {
Program = program;
StartArgument = startArgument + " --StartAsRunnerHost";
UserName = userName;
Password = password;
Domain = domain;
}
#endregion
public virtual void Run(IRunner runner) => RunAsync(runner, null).Wait();
public virtual async Task RunAsync(IRunner runner, CancellationToken? token = null) {
if (State != RunnerState.Created)
throw new InvalidOperationException("Runner must be in state 'Created'.");
State = RunnerState.Starting;
process = new Process {
StartInfo = new ProcessStartInfo {
FileName = Program,
Arguments = StartArgument,
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardInput = true,
RedirectStandardError = true,
CreateNoWindow = false,
UserName = string.IsNullOrEmpty(UserName) ? null : UserName,
PasswordInClearText = string.IsNullOrEmpty(Password) ? null : Password,
Domain = string.IsNullOrEmpty(Domain) ? null : Domain,
WorkingDirectory = Directory.GetCurrentDirectory()
},
EnableRaisingEvents = true
};
process.Start();
// registers a task for cancellation, prevents the use of polling (while-loop)
var task = RegisterCancellation(token.HasValue ? token.Value : CancellationToken.None);
// write config to standardinput, runner listens on this and deserializes the config
Runner.Serialize(runner, process.StandardInput.BaseStream);
process.BeginOutputReadLine();
process.BeginErrorReadLine();
if (!runner.QuietMode) {
process.OutputDataReceived += (s, e) => Console.WriteLine(e.Data);
process.ErrorDataReceived += (s, e) => Console.WriteLine(e.Data);
}
State = RunnerState.Running;
if (await task) State = RunnerState.Cancelled;
else State = RunnerState.Stopped;
}
///
/// Pauses the runners. The childprocess gets notified with the help of the standard input and a serialized RunnerJob.
/// To pause the runner, the state must be "Running".
///
public void Pause() {
if (State != RunnerState.Running) throw new InvalidOperationException("Runner must be in state 'Running'!");
Runner.Pause(process.StandardInput.BaseStream);
State = RunnerState.Paused;
}
///
/// Resumes a paused runner. The childprocess gets notified with the help of the standard input and a serialized RunnerJob.
/// To resume the runner, the state must be "Paused".
///
public void Resume() {
if (State != RunnerState.Paused) throw new InvalidOperationException("Runner must be in state 'Paused'!");
Runner.Resume(process.StandardInput.BaseStream);
State = RunnerState.Running;
}
///
/// Creates a new LinkedTokenSource and a TaskCompletionSource.
/// When the specified token gets cancelled, a cancel requests gets send to the childprocess.
/// Afterwards the main process waits for the exit of the child process and sets a result of the TaskCompletionSource.
/// When the child process gets finished without requested cancellation, the linked token gets cancelled and a result set.
///
private Task RegisterCancellation(CancellationToken token) {
if (process != null && State == RunnerState.Starting) {
var cts = CancellationTokenSource.CreateLinkedTokenSource(token);
var tcs = new TaskCompletionSource();
process.Exited += (s, e) => cts.Cancel();
cts.Token.Register(() => {
if (!process.HasExited) {
Runner.Cancel(process.StandardInput.BaseStream);
process.WaitForExit();
}
tcs.SetResult(token.IsCancellationRequested);
});
return tcs.Task;
}
return null;
}
}
}