arbiter/Tadah.Arbiter/JobManager.cs

243 lines
7.3 KiB
C#

using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Tadah.Arbiter
{
public class JobManager
{
public static List<Job> OpenJobs = new();
[DllImport("User32.dll", CharSet = CharSet.Unicode)]
static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
[DllImport("User32.dll", CharSet = CharSet.Unicode)]
static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint processId);
[DllImport("User32.dll", CharSet = CharSet.Unicode)]
static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);
[DllImport("User32.dll", CharSet = CharSet.Unicode)]
static extern int GetWindowTextLength(IntPtr hWnd);
public static string GetWindowTitle(IntPtr hWnd)
{
var length = GetWindowTextLength(hWnd) + 1;
var title = new StringBuilder(length);
GetWindowText(hWnd, title, length);
return title.ToString();
}
public static string[] GetCommandLine(Proto.ClientVersion version, string scriptUrl, string jobId)
{
switch (version)
{
case Proto.ClientVersion.Taipei:
return new string[] { "Versions\\Taipei\\TadahServer.exe", $"-a https://tadah.rocks/Login/Negotiate.ashx -t 0 -j {scriptUrl} -jobId {jobId}" };
case Proto.ClientVersion.Tampa:
throw new Exception("Attempt to get command line for TampaJob");
default:
throw new Exception("Attempt to get command line for invalid version");
}
}
public static int GetAvailablePort()
{
int port = int.Parse(Configuration.AppSettings["BasePort"]);
for (int i = 0; i < Configuration.BasePlaceJobPort; i++)
{
if (OpenJobs.Find(job => job.Port == port) == null)
{
break;
}
else
{
port++;
}
}
return port;
}
public static Job OpenJob(string jobId, uint placeId, Proto.ClientVersion version)
{
Job job;
int port = GetAvailablePort();
if (version == Proto.ClientVersion.Tampa)
{
job = new Tampa.Job(jobId, placeId, version, port);
}
else if (version == Proto.ClientVersion.Taipei)
{
job = new Taipei.Job(jobId, placeId, version, port);
}
else
{
throw new Exception("Invalid ClientVersion");
}
OpenJobs.Add(job);
job.Start();
return job;
}
public static void CloseJob(string jobId, bool forceClose = false)
{
Job job = GetJobFromId(jobId);
if (job == null)
{
return;
}
job.Close(forceClose);
OpenJobs.Remove(job);
}
public static Job GetJobFromId(string jobId)
{
return OpenJobs.Find(job => job.Id == jobId);
}
public static bool JobExists(string jobId)
{
return GetJobFromId(jobId) != null;
}
public static bool IsValidVersion(object version)
{
if (!int.TryParse(version.ToString(), out int result))
{
return false;
}
return Enum.IsDefined(typeof(Proto.ClientVersion), result);
}
public static void MonitorCrashedJobs()
{
while (true)
{
IntPtr hWnd = FindWindow(null, "Tadah Crash");
GetWindowThreadProcessId(hWnd, out uint processId);
if (processId != 0)
{
Job crashedJob = OpenJobs.Find(Job => Job.Process.Id == processId);
if (crashedJob != null)
{
Log.Write($"[JobManager] '{crashedJob.Id}' has crashed! Closing Job...", LogSeverity.Warning);
crashedJob.Status = JobStatus.Crashed;
crashedJob.Close();
OpenJobs.Remove(crashedJob);
}
}
Thread.Sleep(5000);
}
}
public static void MonitorUnresponsiveJobs()
{
while (true)
{
try
{
foreach (Job job in OpenJobs)
{
if (job is Tampa.Job)
{
continue;
}
if (job.Status == JobStatus.Pending || job.Status == JobStatus.Monitored)
{
continue;
}
if (job.Version == Proto.ClientVersion.Taipei && (Unix.From(job.TimeStarted) + 5 < Unix.Now()) && !GetWindowTitle(job.Process.MainWindowHandle).Contains("Place1"))
{
job.IsRunning = false;
}
if (!job.IsRunning || job.Process.HasExited)
{
job.Close();
OpenJobs.Remove(job);
continue;
}
if (job.Process.Responding)
{
continue;
}
Task.Run(() => MonitorUnresponsiveJob(job));
}
}
catch (InvalidOperationException ex)
{
Log.Write($"[JobManager::MonitorUnresponsiveJobs] InvalidOperationException - {ex.Message}", LogSeverity.Debug);
}
Thread.Sleep(5000);
}
}
public static void CloseAllJobs()
{
foreach (Job OpenJob in OpenJobs)
{
OpenJob.Close();
}
OpenJobs.Clear();
Tampa.ProcessManager.CloseAllProcesses();
}
public static void MonitorUnresponsiveJob(Job job)
{
if (job is Job)
{
return;
}
Log.Write($"[JobManager] '{job.Id}' is not responding! Monitoring...", LogSeverity.Warning);
job.Status = JobStatus.Monitored;
for (int i = 0; i <= 30; i++)
{
Thread.Sleep(1000);
if (job.Process.Responding)
{
Log.Write($"[JobManager] '{job.Id}' has recovered from its unresponsive status!", LogSeverity.Information);
job.Status = JobStatus.Started;
break;
}
else if (i == 30)
{
Log.Write($"[JobManager] '{job.Id}' has been unresponsive for over 30 seconds. Closing Job...", LogSeverity.Warning);
job.Status = JobStatus.Crashed;
job.Close();
OpenJobs.Remove(job);
break;
}
}
return;
}
}
}