Last active
December 16, 2024 10:28
-
-
Save AArnott/2609636d2f2369495abe76e8a01446a4 to your computer and use it in GitHub Desktop.
How to kill child processes when the parent process dies
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
CreateJobObject | |
JOBOBJECT_EXTENDED_LIMIT_INFORMATION | |
SetInformationJobObject | |
AssignProcessToJobObject |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Copyright (c) Andrew Arnott. All rights reserved. | |
// Licensed under the MIT license. | |
/* This is a derivative from multiple answers on https://stackoverflow.com/questions/3342941/kill-child-process-when-parent-process-is-killed */ | |
using System; | |
using System.ComponentModel; | |
using System.Diagnostics; | |
using Microsoft.Win32.SafeHandles; | |
using Windows.Win32.System.JobObjects; | |
using static Windows.Win32.PInvoke; | |
/// <summary> | |
/// Allows processes to be automatically killed if this parent process unexpectedly quits | |
/// (or when an instance of this class is disposed). | |
/// </summary> | |
/// <remarks> | |
/// This "just works" on Windows 8. | |
/// To support Windows Vista or Windows 7 requires an app.manifest with specific content | |
/// <see href="https://stackoverflow.com/a/9507862/46926">as described here</see>. | |
/// </remarks> | |
public class ProcessJobTracker : IDisposable | |
{ | |
private readonly object disposeLock = new object(); | |
private bool disposed; | |
/// <summary> | |
/// The job handle. | |
/// </summary> | |
/// <remarks> | |
/// Closing this handle would close all tracked processes. So we don't do it in this process | |
/// so that it happens automatically when our process exits. | |
/// </remarks> | |
private readonly SafeFileHandle jobHandle; | |
/// <summary> | |
/// Initializes a new instance of the <see cref="ProcessJobTracker"/> class. | |
/// </summary> | |
public unsafe ProcessJobTracker() | |
{ | |
#if NET5_0_OR_GREATER | |
if (OperatingSystem.IsWindowsVersionAtLeast(5, 1, 2600)) | |
#else | |
if (Environment.OSVersion.Platform == PlatformID.Win32NT && Environment.OSVersion.Version > new Version(5, 1, 2600)) | |
#endif | |
{ | |
// The job name is optional (and can be null) but it helps with diagnostics. | |
// If it's not null, it has to be unique. Use SysInternals' Handle command-line | |
// utility: handle -a ChildProcessTracker | |
string jobName = nameof(ProcessJobTracker) + Process.GetCurrentProcess().Id; | |
this.jobHandle = CreateJobObject(null, jobName); | |
var extendedInfo = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION | |
{ | |
BasicLimitInformation = new JOBOBJECT_BASIC_LIMIT_INFORMATION | |
{ | |
LimitFlags = JOB_OBJECT_LIMIT.JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE, | |
}, | |
}; | |
if (!SetInformationJobObject(this.jobHandle, JOBOBJECTINFOCLASS.JobObjectExtendedLimitInformation, &extendedInfo, (uint)sizeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION))) | |
{ | |
throw new Win32Exception(); | |
} | |
} | |
} | |
/// <summary> | |
/// Ensures a given process is killed when the current process exits. | |
/// </summary> | |
/// <param name="process">The process whose lifetime should never exceed the lifetime of the current process.</param> | |
public void AddProcess(Process process) | |
{ | |
if (process is null) | |
{ | |
throw new ArgumentNullException(nameof(process)); | |
} | |
#if NET5_0_OR_GREATER | |
if (OperatingSystem.IsWindowsVersionAtLeast(5, 1, 2600)) | |
#else | |
if (Environment.OSVersion.Platform == PlatformID.Win32NT && Environment.OSVersion.Version > new Version(5, 1, 2600)) | |
#endif | |
{ | |
bool success = AssignProcessToJobObject(this.jobHandle, new SafeFileHandle(process.Handle, ownsHandle: false)); | |
if (!success && !process.HasExited) | |
{ | |
throw new Win32Exception(); | |
} | |
} | |
} | |
/// <summary> | |
/// Kills all processes previously tracked with <see cref="AddProcess(Process)"/> by closing the Windows Job. | |
/// </summary> | |
public void Dispose() | |
{ | |
this.Dispose(true); | |
GC.SuppressFinalize(this); | |
} | |
protected virtual void Dispose(bool disposing) | |
{ | |
if (disposing) | |
{ | |
lock (this.disposeLock) | |
{ | |
if (!this.disposed) | |
{ | |
this.jobHandle?.Dispose(); | |
} | |
this.disposed = true; | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<ItemGroup> | |
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.1.619-beta" PrivateAssets="all" /> | |
</ItemGroup> |
@GieltjE Yes, we were missing the NativeMethods.txt file. I've updated the gist. The .txt file works with the Microsoft.Windows.CsWin32 package to declare the p/invoke methods and supporting types as part of the build.
There is a reference to a LICENSE.txt
file in the project root, but I cannot find it. Could you please check copyright and license information?
https://gist.github.com/AArnott/2609636d2f2369495abe76e8a01446a4#file-processjobtracker-cs-L1-L2
I removed the reference to LICENSE.txt. It's simply the MIT license.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Are we missing the PInvoke declarations?