Improve monitoring of auto-started apps' processes. (#578)

* Improve monitoring of auto-started apps' processes.

* Keep the child process monitoring timer running when the game is closed and child processes are not to be killed.
Only start the timer when necessary.

* Keep the child process monitoring timer running when the game is closed and child processes are not to be killed.
Only start the timer when necessary.
Remove redundant lock.
This commit is contained in:
Dor
2023-06-17 23:12:03 +03:00
committed by GitHub
parent 4ce4304680
commit e0273da357
+90 -17
View File
@@ -4,6 +4,7 @@ using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Timers;
namespace VRCX namespace VRCX
{ {
@@ -21,7 +22,9 @@ namespace VRCX
public readonly string AppShortcutDirectory; public readonly string AppShortcutDirectory;
private DateTime startTime = DateTime.Now; private DateTime startTime = DateTime.Now;
private Dictionary<string, Process> startedProcesses = new Dictionary<string, Process>(); private Dictionary<string, HashSet<int>> startedProcesses = new Dictionary<string, HashSet<int>>();
private readonly Timer childUpdateTimer;
private int timerTicks = 0;
private static readonly byte[] shortcutSignatureBytes = { 0x4C, 0x00, 0x00, 0x00 }; // signature for ShellLinkHeader private static readonly byte[] shortcutSignatureBytes = { 0x4C, 0x00, 0x00, 0x00 }; // signature for ShellLinkHeader
private const uint TH32CS_SNAPPROCESS = 2; private const uint TH32CS_SNAPPROCESS = 2;
@@ -68,24 +71,37 @@ namespace VRCX
ProcessMonitor.Instance.ProcessStarted += OnProcessStarted; ProcessMonitor.Instance.ProcessStarted += OnProcessStarted;
ProcessMonitor.Instance.ProcessExited += OnProcessExited; ProcessMonitor.Instance.ProcessExited += OnProcessExited;
childUpdateTimer = new Timer();
childUpdateTimer.Interval = 60000;
childUpdateTimer.Elapsed += ChildUpdateTimer_Elapsed;
} }
private void OnProcessExited(MonitoredProcess monitoredProcess) private void OnProcessExited(MonitoredProcess monitoredProcess)
{ {
if (startedProcesses.Count == 0 || !monitoredProcess.HasName(VRChatProcessName)) if (!monitoredProcess.HasName(VRChatProcessName))
return; return;
lock (startedProcesses)
{
if (KillChildrenOnExit) if (KillChildrenOnExit)
{
childUpdateTimer.Stop();
KillChildProcesses(); KillChildProcesses();
}
else else
UpdateChildProcesses(); UpdateChildProcesses();
} }
}
private void OnProcessStarted(MonitoredProcess monitoredProcess) private void OnProcessStarted(MonitoredProcess monitoredProcess)
{ {
if (!Enabled || !monitoredProcess.HasName(VRChatProcessName) || monitoredProcess.Process.StartTime < startTime) if (!Enabled || !monitoredProcess.HasName(VRChatProcessName) || monitoredProcess.Process.StartTime < startTime)
return; return;
lock (startedProcesses)
{
if (KillChildrenOnExit) if (KillChildrenOnExit)
KillChildProcesses(); KillChildProcesses();
else else
@@ -96,9 +112,15 @@ namespace VRCX
foreach (var file in shortcutFiles) foreach (var file in shortcutFiles)
{ {
if (!IsChildProcessRunning(file)) if (!IsChildProcessRunning(file))
{
StartChildProcess(file); StartChildProcess(file);
} }
if (shortcutFiles.Length == 0)
return;
timerTicks = 0;
childUpdateTimer.Interval = 1000;
childUpdateTimer.Start();
} }
} }
@@ -107,14 +129,16 @@ namespace VRCX
/// </summary> /// </summary>
internal void KillChildProcesses() internal void KillChildProcesses()
{ {
UpdateChildProcesses(); // Ensure the list contains all current child processes.
foreach (var pair in startedProcesses) foreach (var pair in startedProcesses)
{ {
var process = pair.Value; var processes = pair.Value;
if (!WinApi.HasProcessExited(process.Id)) foreach (var pid in processes)
{ {
KillProcessTree(process.Id); if (!WinApi.HasProcessExited(pid))
//process.Kill(); KillProcessTree(pid);
} }
} }
@@ -129,15 +153,17 @@ namespace VRCX
// It uses the CreateToolhelp32Snapshot winapi func to get a snapshot of all running processes, loops through them with Process32First/Process32Next, and kills any processes that have the given pid as their parent. // It uses the CreateToolhelp32Snapshot winapi func to get a snapshot of all running processes, loops through them with Process32First/Process32Next, and kills any processes that have the given pid as their parent.
/// <summary> /// <summary>
/// Kills a process and all of its child processes. /// Returns the child processes of a process.
/// </summary> /// </summary>
/// <param name="pid">The process ID of the parent process.</param> /// <param name="pid">The process ID of the parent process.</param>
public static void KillProcessTree(int pid) public static List<int> FindChildProcesses(int pid, bool recursive = true)
{ {
List<int> pids = new List<int>();
IntPtr snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); IntPtr snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (snapshot == IntPtr.Zero) if (snapshot == IntPtr.Zero)
{ {
return; return pids;
} }
// Gonna be honest, not gonna spin up a 32bit windows VM to make sure this works. but it should. // Gonna be honest, not gonna spin up a 32bit windows VM to make sure this works. but it should.
@@ -151,21 +177,39 @@ namespace VRCX
{ {
if (procEntry.th32ParentProcessID == pid) if (procEntry.th32ParentProcessID == pid)
{ {
KillProcessTree((int)procEntry.th32ProcessID); // Recursively kill child processes pids.Add((int)procEntry.th32ProcessID);
if(recursive) // Recursively find child processes
pids.AddRange(FindChildProcesses((int)procEntry.th32ProcessID));
} }
} }
while (Process32Next(snapshot, ref procEntry)); while (Process32Next(snapshot, ref procEntry));
} }
return pids;
}
/// <summary>
/// Kills a process and all of its child processes.
/// </summary>
/// <param name="pid">The process ID of the parent process.</param>
public static void KillProcessTree(int pid)
{
var pids = FindChildProcesses(pid);
pids.Add(pid); // Kill parent
foreach (int p in pids)
{
try try
{ {
Process proc = Process.GetProcessById(pid); using (Process proc = Process.GetProcessById(p))
proc.Kill(); proc.Kill();
} }
catch catch
{ {
} }
} }
}
/// <summary> /// <summary>
/// Starts a new child process. /// Starts a new child process.
@@ -173,8 +217,8 @@ namespace VRCX
/// <param name="path">The path.</param> /// <param name="path">The path.</param>
internal void StartChildProcess(string path) internal void StartChildProcess(string path)
{ {
var process = Process.Start(path); using (var process = Process.Start(path))
startedProcesses.Add(path, process); startedProcesses.Add(path, new HashSet<int>() { process.Id });
} }
/// <summary> /// <summary>
@@ -183,10 +227,22 @@ namespace VRCX
/// </summary> /// </summary>
internal void UpdateChildProcesses() internal void UpdateChildProcesses()
{ {
foreach (var pair in startedProcesses.ToList()) foreach (var pair in startedProcesses.ToArray())
{ {
var process = pair.Value; var processes = pair.Value;
if (WinApi.HasProcessExited(process.Id)) foreach (var pid in processes.ToArray())
{
bool recursiveChildSearch = processes.Count == 1; // Disable recursion when this list may already contain the entire process tree
var childProcesses = FindChildProcesses(pid, recursiveChildSearch);
foreach (int childPid in childProcesses) // Monitor child processes
processes.Add(childPid); // HashSet will prevent duplication
if (WinApi.HasProcessExited(pid))
processes.Remove(pid);
}
if (processes.Count == 0) // All processes associated with the shortcut have exited.
startedProcesses.Remove(pair.Key); startedProcesses.Remove(pair.Key);
} }
} }
@@ -210,11 +266,28 @@ namespace VRCX
internal void Exit() internal void Exit()
{ {
childUpdateTimer.Stop();
Enabled = false; Enabled = false;
lock (startedProcesses)
KillChildProcesses(); KillChildProcesses();
} }
private void ChildUpdateTimer_Elapsed(object sender, ElapsedEventArgs e)
{
lock (startedProcesses)
UpdateChildProcesses();
if (timerTicks < 5)
{
timerTicks++;
if(timerTicks == 5)
childUpdateTimer.Interval = 60000;
}
}
/// <summary> /// <summary>
/// Finds windows shortcut files in a given folder. /// Finds windows shortcut files in a given folder.
/// </summary> /// </summary>