335 lines
11 KiB
C#
335 lines
11 KiB
C#
using CaeGlobals;
|
|
using System;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.Management;
|
|
using System.Text;
|
|
using System.Threading;
|
|
|
|
namespace CaeJob
|
|
{
|
|
[Serializable]
|
|
public class ExecutableJob : NamedClass
|
|
{
|
|
// Variables
|
|
|
|
// 非序列化变量
|
|
[NonSerialized] protected Stopwatch _watch;
|
|
[NonSerialized] protected Stopwatch _updateWatch; // timer does not tick - use update watch
|
|
[NonSerialized] private Process _exe;
|
|
[NonSerialized] AutoResetEvent _outputWaitHandle;
|
|
[NonSerialized] AutoResetEvent _errorWaitHandle;
|
|
[NonSerialized] private StringBuilder _sbOutput;
|
|
[NonSerialized] private string _outputFileName;
|
|
[NonSerialized] private string _errorFileName;
|
|
[NonSerialized] private object _myLock;
|
|
|
|
// Properties
|
|
public override string Name
|
|
{
|
|
get => base.Name;
|
|
set
|
|
{
|
|
base.Name = value;
|
|
Argument = Name;
|
|
}
|
|
}
|
|
|
|
public string WorkDirectory { get; set; }
|
|
|
|
public string Executable { get; set; }
|
|
|
|
public string Argument { get; set; }
|
|
|
|
public JobStatus JobStatus { get; protected set; }
|
|
|
|
public string OutputData
|
|
{
|
|
get
|
|
{
|
|
try
|
|
{
|
|
if (_sbOutput != null)
|
|
{
|
|
if (_myLock == null)
|
|
{
|
|
_myLock = new object();
|
|
}
|
|
|
|
lock (_myLock)
|
|
{
|
|
return _sbOutput.ToString();
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
catch
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Events
|
|
public event Action<string> AppendOutput;
|
|
|
|
// Constructor
|
|
public ExecutableJob(string name, string executable, string argument, string workDirectory)
|
|
: base(name)
|
|
{
|
|
Executable = executable;
|
|
Argument = argument;
|
|
WorkDirectory = workDirectory;
|
|
//
|
|
_exe = null;
|
|
JobStatus = JobStatus.None;
|
|
_watch = null;
|
|
_updateWatch = null;
|
|
_sbOutput = null;
|
|
}
|
|
|
|
// Methods
|
|
public void Submit()
|
|
{
|
|
if (_myLock == null) _myLock = new object();
|
|
lock (_myLock)
|
|
{
|
|
if (_sbOutput == null) _sbOutput = new StringBuilder();
|
|
_sbOutput.Clear();
|
|
}
|
|
//
|
|
AddDataToOutput("Running command: " + Executable + " " + Argument);
|
|
//
|
|
_watch = new Stopwatch();
|
|
_updateWatch = new Stopwatch();
|
|
//
|
|
JobStatus = JobStatus.Running;
|
|
//
|
|
_watch.Start();
|
|
_updateWatch.Start();
|
|
//
|
|
Run();
|
|
//
|
|
RunCompleted();
|
|
}
|
|
|
|
private void Run()
|
|
{
|
|
if (!File.Exists(Executable)) throw new Exception("The file '" + Executable + "' does not exist.");
|
|
if (!Tools.WaitForFileToUnlock(Executable, 5000)) throw new Exception("The mesher is busy.");
|
|
//
|
|
string tmpName = Path.GetFileName(Name);
|
|
_outputFileName = Path.Combine(WorkDirectory, "_output_" + tmpName + ".txt");
|
|
_errorFileName = Path.Combine(WorkDirectory, "_error_" + tmpName + ".txt");
|
|
//
|
|
if (File.Exists(_outputFileName)) File.Delete(_outputFileName);
|
|
if (File.Exists(_errorFileName)) File.Delete(_errorFileName);
|
|
//
|
|
ProcessStartInfo psi = new ProcessStartInfo();
|
|
psi.CreateNoWindow = true;
|
|
psi.FileName = Executable;
|
|
psi.Arguments = Argument;
|
|
psi.WorkingDirectory = WorkDirectory;
|
|
psi.WindowStyle = ProcessWindowStyle.Normal;
|
|
psi.UseShellExecute = false;
|
|
psi.RedirectStandardOutput = true;
|
|
psi.RedirectStandardInput = true; // sometimes needed
|
|
psi.RedirectStandardError = true;
|
|
//
|
|
Debug.WriteLine(DateTime.Now + " Start proces: " + tmpName + " in: " + WorkDirectory);
|
|
//
|
|
_exe = new Process();
|
|
_exe.StartInfo = psi;
|
|
//
|
|
_outputWaitHandle = new AutoResetEvent(false);
|
|
_errorWaitHandle = new AutoResetEvent(false);
|
|
{
|
|
_exe.OutputDataReceived += _exe_OutputDataReceived;
|
|
_exe.ErrorDataReceived += _exe_ErrorDataReceived;
|
|
//
|
|
_exe.Start();
|
|
_exe.BeginOutputReadLine();
|
|
_exe.BeginErrorReadLine();
|
|
//
|
|
int ms = 1000 * 3600 * 24 * 7 * 3; // 3 weeks
|
|
//
|
|
if (_exe.WaitForExit(ms) && _outputWaitHandle.WaitOne(ms) && _errorWaitHandle.WaitOne(ms))
|
|
{
|
|
// Process completed. Check process. ExitCode here. After Kill() _jobStatus is Killed
|
|
if (JobStatus == JobStatus.Running)
|
|
{
|
|
JobStatus = JobStatus.OK;
|
|
if (_exe.ExitCode != 0) JobStatus = JobStatus.Failed;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Timed out.
|
|
Kill("Time out.");
|
|
JobStatus = JobStatus.TimedOut;
|
|
}
|
|
_exe.Close();
|
|
}
|
|
}
|
|
private void RunCompatible()
|
|
{
|
|
ProcessStartInfo psi = new ProcessStartInfo();
|
|
psi.FileName = Executable;
|
|
psi.Arguments = Argument;
|
|
psi.WorkingDirectory = WorkDirectory;
|
|
psi.UseShellExecute = false;
|
|
//
|
|
//SetEnvironmentVariables(psi);
|
|
//
|
|
_exe = new Process();
|
|
_exe.StartInfo = psi;
|
|
_exe.Start();
|
|
//
|
|
int ms = 1000 * 3600 * 24 * 7 * 3; // 3 weeks
|
|
if (_exe.WaitForExit(ms))
|
|
{
|
|
// Process completed. Check process.ExitCode here.
|
|
|
|
// after Kill() _jobStatus is Killed
|
|
JobStatus = JobStatus.OK;
|
|
}
|
|
else
|
|
{
|
|
// Timed out.
|
|
Kill("Time out.");
|
|
//Debug.WriteLine(DateTime.Now + " Timeout proces: " + Name + " in: " + _workDirectory);
|
|
JobStatus = JobStatus.TimedOut;
|
|
}
|
|
_exe.Close();
|
|
}
|
|
private void _exe_ErrorDataReceived(object sender, DataReceivedEventArgs e)
|
|
{
|
|
if (e.Data == null)
|
|
{
|
|
// The safe wait handle closes on kill
|
|
if (!_errorWaitHandle.SafeWaitHandle.IsClosed) _errorWaitHandle.Set();
|
|
}
|
|
else
|
|
{
|
|
File.AppendAllText(_errorFileName, e.Data + Environment.NewLine);
|
|
AddDataToOutput(e.Data);
|
|
}
|
|
}
|
|
private void _exe_OutputDataReceived(object sender, DataReceivedEventArgs e)
|
|
{
|
|
if (e.Data == null)
|
|
{
|
|
// The safe wait handle closes on kill
|
|
if (!_outputWaitHandle.SafeWaitHandle.IsClosed) _outputWaitHandle.Set();
|
|
}
|
|
else
|
|
{
|
|
AddDataToOutput(e.Data);
|
|
}
|
|
}
|
|
void RunCompleted()
|
|
{
|
|
_watch.Stop();
|
|
_updateWatch.Stop();
|
|
//
|
|
AddDataToOutput("");
|
|
AddDataToOutput("Elapsed time [s]: " + _watch.Elapsed.TotalSeconds.ToString());
|
|
//
|
|
UpdateOutput();
|
|
// Dereference the link to otheh objects
|
|
AppendOutput = null;
|
|
}
|
|
//
|
|
public void Kill(string message)
|
|
{
|
|
try
|
|
{
|
|
if (_exe != null)
|
|
{
|
|
AddDataToOutput(message);
|
|
//
|
|
_watch.Stop();
|
|
_updateWatch.Stop();
|
|
//
|
|
JobStatus = JobStatus.Killed; // this has to be here before _exe.Kill, to return the correct status
|
|
//
|
|
KillAllProcessesSpawnedBy((UInt32)_exe.Id);
|
|
//
|
|
_exe.Kill();
|
|
}
|
|
}
|
|
catch
|
|
{ }
|
|
finally
|
|
{
|
|
// Dereference the link to other objects
|
|
AppendOutput = null;
|
|
}
|
|
}
|
|
|
|
public static void KillAllProcessesSpawnedBy(UInt32 parentProcessId)
|
|
{
|
|
// NOTE: Process Ids are reused!
|
|
ManagementObjectSearcher searcher = new ManagementObjectSearcher(
|
|
"SELECT * " +
|
|
"FROM Win32_Process " +
|
|
"WHERE ParentProcessId=" + parentProcessId);
|
|
ManagementObjectCollection collection = searcher.Get();
|
|
if (collection.Count > 0)
|
|
{
|
|
foreach (var item in collection)
|
|
{
|
|
UInt32 childProcessId = (UInt32)item["ProcessId"];
|
|
if ((int)childProcessId != Process.GetCurrentProcess().Id)
|
|
{
|
|
|
|
KillAllProcessesSpawnedBy(childProcessId);
|
|
|
|
try
|
|
{
|
|
Process childProcess = Process.GetProcessById((int)childProcessId);
|
|
childProcess.Kill();
|
|
}
|
|
catch
|
|
{
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void AddDataToOutput(string data)
|
|
{
|
|
if (_myLock == null) _myLock = new object();
|
|
lock (_myLock) _sbOutput.AppendLine(data);
|
|
//
|
|
if (_updateWatch != null && _updateWatch.ElapsedMilliseconds > 1000)
|
|
{
|
|
UpdateOutput();
|
|
_updateWatch.Restart();
|
|
}
|
|
}
|
|
|
|
private void UpdateOutput()
|
|
{
|
|
try
|
|
{
|
|
AppendOutput?.Invoke(OutputData);
|
|
//
|
|
if (_myLock == null) _myLock = new object();
|
|
lock (_myLock)
|
|
{
|
|
//if (Tools.WaitForFileToUnlock(_outputFileName, 5000))
|
|
if (_outputFileName != null)
|
|
{
|
|
File.AppendAllText(_outputFileName, OutputData);
|
|
_sbOutput.Clear();
|
|
}
|
|
}
|
|
}
|
|
catch { }
|
|
}
|
|
}
|
|
}
|