source: branches/2924_DotNetCoreMigration/HeuristicLab.PluginInfrastructure/3.3/Isolation/RunnerHost.cs @ 17013

Last change on this file since 17013 was 17013, checked in by dpiringe, 2 years ago

#2924:

  • some changes in CLIApplication.cs to reduce unnecessary allocation of string objects
  • renamed AppTest to ConsoleOptimizer and fixed race condition
  • replaced enum RunnerJob with class RunnerMessage for more control of saved data
  • changed usage of BinaryFormatter with HEAL.Attic, following types are now Storable:
    • ConsoleOptimizer
    • InspectApplication
    • ApplicationBase
    • ApplicationRunner
    • AssemblyInfo
    • Runner
    • UniPath
    • RunnerMessage
  • switched QuietMode from ApplicationRunner to IRunner
  • DockerRunnerHost can now automatically build docker images for linux and windows containers (also identifies which container type is active) -> removes the condition to have the image preinstalled
    • to achieve this, there are two new folders DockerLinuxBuild and DockerWindowsBuild included in build output, which include Dockerfiles to build images for linux and windows container
  • added NuGet package System.CodeDom to project HeuristicLab.Scripting-3.3
  • added method Send(RunnerMessage) to IRunnerHost and transferred methods Pause and Resume to IRunner
  • added internal reference of RunnerHost in Runner
  • added abstract method SendMessage(RunnerMessage) in RunnerHost which gets called from method Send(RunnerMessage)
  • because of a Google.Protobuf "bug", RunnerMessages cannot get serialized/deserialized directly on stream -> workaround with a byte array, which gets written and read
    • additionally, the length of the array gets sent first (length as integer -> 4 bytes)
    • static method in RunnerMessage to read a message from a stream
    • the method SendMessage(RunnerMessage) in RunnerHost implements this functionality
File size: 5.7 KB
Line 
1using System;
2using System.Diagnostics;
3using System.IO;
4using System.Threading;
5using System.Threading.Tasks;
6using HEAL.Attic;
7
8namespace HeuristicLab.PluginInfrastructure {
9  /// <summary>
10  /// Enum for the different RunnerStates.
11  /// </summary>
12  public enum RunnerState {
13    Created,
14    Cancelled,
15    Starting,
16    Running,
17    Stopped
18  }
19
20  /// <summary>
21  /// Abstract <c>RunnerBase</c> class, which implements the IRunner interface.
22  /// Every specific runner implementation should inherit this class.
23  /// </summary>
24  public abstract class RunnerHost : IRunnerHost {
25
26    #region Vars
27    protected Process process;
28    private ProtoBufSerializer serializer = new ProtoBufSerializer();
29    #endregion
30
31    #region Properties
32    /// <summary>
33    /// Set this to true, if console output should be disabled.
34    /// </summary>
35    public bool QuietMode { get; set; }
36
37
38    /// <summary>
39    /// The programm, which should be used. For example 'docker'.
40    /// </summary>
41    protected string Program { get; private set; }
42    /// <summary>
43    /// The specific start arguments for the programm.
44    /// </summary>
45    protected string StartArgument { get; private set; }
46    protected string UserName { get; private set; }
47    protected string Password { get; private set; }
48    protected string Domain { get; private set; }
49    protected IRunner Runner { get; set; }
50
51    public RunnerState State { get; protected set; } = RunnerState.Created;
52    #endregion
53
54    #region Constructors
55
56    protected RunnerHost(string program, string startArgument, string userName, string password, string domain) {
57      Program = program;
58      StartArgument = startArgument + " --StartAsRunnerHost";
59      UserName = userName;
60      Password = password;
61      Domain = domain;
62    }
63    #endregion
64
65    public virtual void Run(IRunner runner) => RunAsync(runner, null).Wait();
66    public virtual async Task RunAsync(IRunner runner, CancellationToken? token = null) {
67      if (State != RunnerState.Created)
68        throw new InvalidOperationException("Runner must be in state 'Created'.");
69      State = RunnerState.Starting;
70      Runner = runner;
71      process = new Process {
72        StartInfo = new ProcessStartInfo {
73          FileName = Program,
74          Arguments = StartArgument,
75          UseShellExecute = false,
76          RedirectStandardOutput = true,
77          RedirectStandardInput = true,
78          RedirectStandardError = true,
79          CreateNoWindow = false,
80          UserName = string.IsNullOrEmpty(UserName) ? null : UserName, // TODO: accounts testen: https://docs.microsoft.com/en-us/windows/security/identity-protection/access-control/local-accounts#sec-localsystem
81          PasswordInClearText = string.IsNullOrEmpty(Password) ? null : Password,
82          Domain = string.IsNullOrEmpty(Domain) ? null : Domain,
83          WorkingDirectory = Directory.GetCurrentDirectory()
84        },
85        EnableRaisingEvents = true
86      };
87      process.Start();
88
89      // registers a task for cancellation, prevents the use of polling (while-loop)
90      var task = RegisterCancellation(token.HasValue ? token.Value : CancellationToken.None);
91
92      // set runnerhost in runner
93      Runner r = Runner as Runner;
94      if (r != null) r.Host = this;
95
96      process.BeginOutputReadLine();
97      process.BeginErrorReadLine();
98
99      if (!QuietMode) {
100        process.OutputDataReceived += (s, e) => Console.WriteLine(e.Data);
101        process.ErrorDataReceived += (s, e) => Console.WriteLine(e.Data);
102      }
103
104      // write config to standardinput, runner listens on this and deserializes the config
105      SendMessage(new TransportRunnerMessage(Runner));
106
107      State = RunnerState.Running;
108      if (await task) State = RunnerState.Cancelled;
109      else State = RunnerState.Stopped;
110    }
111
112
113    public void Send(RunnerMessage runnerMessage) {
114      if (State != RunnerState.Running) throw new InvalidOperationException("Runner must be in state 'Running'!");
115      SendMessage(runnerMessage);
116    }
117
118    // because we need to transfer the runner with a TransportRunnerMessage in the starting state and the
119    // original send method should not be available until running state
120    protected virtual void SendMessage(RunnerMessage runnerMessage) {
121      runnerMessage.SendTime = DateTime.Now;
122      byte[] bytes = serializer.Serialize(runnerMessage);
123      byte[] size = BitConverter.GetBytes(bytes.Length);
124      process.StandardInput.BaseStream.Write(size, 0, size.Length);
125      process.StandardInput.BaseStream.Flush();
126      process.StandardInput.BaseStream.Write(bytes, 0, bytes.Length);
127      process.StandardInput.BaseStream.Flush();
128    }
129
130    /// <summary>
131    /// Creates a new LinkedTokenSource and a TaskCompletionSource.
132    /// When the specified token gets cancelled, a cancel requests gets send to the childprocess.
133    /// Afterwards the main process waits for the exit of the child process and sets a result of the TaskCompletionSource.
134    /// When the child process gets finished without requested cancellation, the linked token gets cancelled and a result set.
135    /// </summary>
136    protected Task<bool> RegisterCancellation(CancellationToken token) {
137      if (process != null && State == RunnerState.Starting) {
138        var cts = CancellationTokenSource.CreateLinkedTokenSource(token);
139        var tcs = new TaskCompletionSource<bool>();
140
141        process.Exited += (s, e) => cts.Cancel();
142        cts.Token.Register(() => {
143          if (!process.HasExited) {
144            Runner.Cancel();
145            process.WaitForExit();
146          }
147          tcs.SetResult(token.IsCancellationRequested);
148        });
149        return tcs.Task;
150      }
151      return null;
152    }
153  }
154}
Note: See TracBrowser for help on using the repository browser.