Skip to content

Instantly share code, notes, and snippets.

@davepermen
Created November 7, 2024 12:47
Show Gist options
  • Save davepermen/7047b61c5312ec764b9972011e1b832e to your computer and use it in GitHub Desktop.
Save davepermen/7047b61c5312ec764b9972011e1b832e to your computer and use it in GitHub Desktop.
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.InteropServices;
namespace ProcessTracking;
// From https://stackoverflow.com/a/37034966/1528847
class ProcessTracker(bool closeOnExit = true)
{
ChildProcessTracker? current = closeOnExit ? new() : null;
readonly HashSet<Process> tracked = [];
public void Track(Process p)
{
tracked.Add(p);
current?.Track(p);
}
public bool CloseTrackedOnExit
{
get => current != null;
set
{
if (current != null && value == false)
{
current.DeactivateTracking();
current.Dispose();
current = null;
}
if (current == null && value == true)
{
current = new();
foreach (var p in tracked)
{
current.Track(p);
}
}
}
}
}
class ChildProcessTracker : IDisposable
{
private readonly nint s_jobHandle;
public ChildProcessTracker()
{
if (Environment.OSVersion.Version < new Version(6, 2))
return;
s_jobHandle = CreateJobObject(nint.Zero, "ChildProcessTracker" + Process.GetCurrentProcess().Id);
SetTracking(true);
}
public Process? Track(Process? process)
{
if (s_jobHandle != nint.Zero && process != null)
{
if (!AssignProcessToJobObject(s_jobHandle, process.Handle) && !process.HasExited)
throw new Win32Exception();
}
return process;
}
private void SetTracking(bool tracking)
{
var extendedInfo = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION
{
BasicLimitInformation = new()
{
LimitFlags = tracking ? JOBOBJECTLIMIT.JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE : 0
}
};
int length = Marshal.SizeOf(typeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION));
nint extendedInfoPtr = Marshal.AllocHGlobal(length);
try
{
Marshal.StructureToPtr(extendedInfo, extendedInfoPtr, false);
if (!SetInformationJobObject(s_jobHandle, JobObjectInfoType.ExtendedLimitInformation, extendedInfoPtr, (uint)length))
{
throw new Win32Exception();
}
}
finally
{
Marshal.FreeHGlobal(extendedInfoPtr);
}
}
public void DeactivateTracking() => SetTracking(false);
public void Dispose() => CloseHandle(s_jobHandle);
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
static extern nint CreateJobObject(nint lpJobAttributes, string name);
[DllImport("kernel32.dll")]
static extern bool SetInformationJobObject(nint job, JobObjectInfoType infoType,
nint lpJobObjectInfo, uint cbJobObjectInfoLength);
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool AssignProcessToJobObject(nint job, nint process);
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool CloseHandle(nint handle);
enum JobObjectInfoType
{
AssociateCompletionPortInformation = 7,
BasicLimitInformation = 2,
BasicUIRestrictions = 4,
EndOfJobTimeInformation = 6,
ExtendedLimitInformation = 9,
SecurityLimitInformation = 5,
GroupInformation = 11
}
[StructLayout(LayoutKind.Sequential)]
struct JOBOBJECT_BASIC_LIMIT_INFORMATION
{
public long PerProcessUserTimeLimit;
public long PerJobUserTimeLimit;
public JOBOBJECTLIMIT LimitFlags;
public nuint MinimumWorkingSetSize;
public nuint MaximumWorkingSetSize;
public uint ActiveProcessLimit;
public long Affinity;
public uint PriorityClass;
public uint SchedulingClass;
}
[Flags]
enum JOBOBJECTLIMIT : uint
{
JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE = 0x2000
}
[StructLayout(LayoutKind.Sequential)]
struct IO_COUNTERS
{
public ulong ReadOperationCount;
public ulong WriteOperationCount;
public ulong OtherOperationCount;
public ulong ReadTransferCount;
public ulong WriteTransferCount;
public ulong OtherTransferCount;
}
[StructLayout(LayoutKind.Sequential)]
struct JOBOBJECT_EXTENDED_LIMIT_INFORMATION
{
public JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation;
public IO_COUNTERS IoInfo;
public nuint ProcessMemoryLimit;
public nuint JobMemoryLimit;
public nuint PeakProcessMemoryUsed;
public nuint PeakJobMemoryUsed;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment