Kill child process when parent process is killed

Viewed 103255

I'm creating new processes using System.Diagnostics.Process class from my application.

I want this processes to be killed when/if my application has crashed. But if I kill my application from Task Manager, child processes are not killed.

Is there any way to make child processes dependent on parent process?

16 Answers

From this forum, credit to 'Josh'.

Application.Quit() and Process.Kill() are possible solutions, but have proven to be unreliable. When your main application dies, you are still left with child processes running. What we really want is for the child processes to die as soon as the main process dies.

The solution is to use "job objects" http://msdn.microsoft.com/en-us/library/ms682409(VS.85).aspx.

The idea is to create a "job object" for your main application, and register your child processes with the job object. If the main process dies, the OS will take care of terminating the child processes.

public enum JobObjectInfoType
{
    AssociateCompletionPortInformation = 7,
    BasicLimitInformation = 2,
    BasicUIRestrictions = 4,
    EndOfJobTimeInformation = 6,
    ExtendedLimitInformation = 9,
    SecurityLimitInformation = 5,
    GroupInformation = 11
}

[StructLayout(LayoutKind.Sequential)]
public struct SECURITY_ATTRIBUTES
{
    public int nLength;
    public IntPtr lpSecurityDescriptor;
    public int bInheritHandle;
}

[StructLayout(LayoutKind.Sequential)]
struct JOBOBJECT_BASIC_LIMIT_INFORMATION
{
    public Int64 PerProcessUserTimeLimit;
    public Int64 PerJobUserTimeLimit;
    public Int16 LimitFlags;
    public UInt32 MinimumWorkingSetSize;
    public UInt32 MaximumWorkingSetSize;
    public Int16 ActiveProcessLimit;
    public Int64 Affinity;
    public Int16 PriorityClass;
    public Int16 SchedulingClass;
}

[StructLayout(LayoutKind.Sequential)]
struct IO_COUNTERS
{
    public UInt64 ReadOperationCount;
    public UInt64 WriteOperationCount;
    public UInt64 OtherOperationCount;
    public UInt64 ReadTransferCount;
    public UInt64 WriteTransferCount;
    public UInt64 OtherTransferCount;
}

[StructLayout(LayoutKind.Sequential)]
struct JOBOBJECT_EXTENDED_LIMIT_INFORMATION
{
    public JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation;
    public IO_COUNTERS IoInfo;
    public UInt32 ProcessMemoryLimit;
    public UInt32 JobMemoryLimit;
    public UInt32 PeakProcessMemoryUsed;
    public UInt32 PeakJobMemoryUsed;
}

public class Job : IDisposable
{
    [DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
    static extern IntPtr CreateJobObject(object a, string lpName);

    [DllImport("kernel32.dll")]
    static extern bool SetInformationJobObject(IntPtr hJob, JobObjectInfoType infoType, IntPtr lpJobObjectInfo, uint cbJobObjectInfoLength);

    [DllImport("kernel32.dll", SetLastError = true)]
    static extern bool AssignProcessToJobObject(IntPtr job, IntPtr process);

    private IntPtr m_handle;
    private bool m_disposed = false;

    public Job()
    {
        m_handle = CreateJobObject(null, null);

        JOBOBJECT_BASIC_LIMIT_INFORMATION info = new JOBOBJECT_BASIC_LIMIT_INFORMATION();
        info.LimitFlags = 0x2000;

        JOBOBJECT_EXTENDED_LIMIT_INFORMATION extendedInfo = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION();
        extendedInfo.BasicLimitInformation = info;

        int length = Marshal.SizeOf(typeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION));
        IntPtr extendedInfoPtr = Marshal.AllocHGlobal(length);
        Marshal.StructureToPtr(extendedInfo, extendedInfoPtr, false);

        if (!SetInformationJobObject(m_handle, JobObjectInfoType.ExtendedLimitInformation, extendedInfoPtr, (uint)length))
            throw new Exception(string.Format("Unable to set information.  Error: {0}", Marshal.GetLastWin32Error()));
    }

    #region IDisposable Members

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    #endregion

    private void Dispose(bool disposing)
    {
        if (m_disposed)
            return;

        if (disposing) {}

        Close();
        m_disposed = true;
    }

    public void Close()
    {
        Win32.CloseHandle(m_handle);
        m_handle = IntPtr.Zero;
    }

    public bool AddProcess(IntPtr handle)
    {
        return AssignProcessToJobObject(m_handle, handle);
    }

}

Looking at the constructor ...

JOBOBJECT_BASIC_LIMIT_INFORMATION info = new JOBOBJECT_BASIC_LIMIT_INFORMATION();
info.LimitFlags = 0x2000;

The key here is to setup the job object properly. In the constructor I'm setting the "limits" to 0x2000, which is the numeric value for JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE.

MSDN defines this flag as:

Causes all processes associated with the job to terminate when the last handle to the job is closed.

Once this class is setup...you just have to register each child process with the job. For example:

[DllImport("user32.dll", SetLastError = true)]
public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);

Excel.Application app = new Excel.ApplicationClass();

uint pid = 0;
Win32.GetWindowThreadProcessId(new IntPtr(app.Hwnd), out pid);
 job.AddProcess(Process.GetProcessById((int)pid).Handle);

Yet another addition to the abundant richness of solutions proposed so far....

The problem with many of them is that they rely upon the parent and child process to shut down in an orderly manner, which isn't always true when development is underway. I found that my child process was often being orphaned whenever I terminated the parent process in the debugger, which required me to kill the orphaned process(es) with Task Manager in order to rebuild my solution.

The solution: Pass the parent process ID in on the commandline (or even less invasive, in the environment variables) of the child process.

In the parent process, the process ID is available as:

Process.CurrentProcess().Id;

In the child process:

Process parentProcess = Process.GetProcessById(parentProcessId);
parentProcess.Exited += (s, e) =>
{
    // clean up what you can.
    this.Dispose();
    // maybe log an error
    ....

    // And terminate with prejudice! 
    //(since something has already gone terribly wrong)
    Process.GetCurrentProcess().Kill();
};

I am of two minds as to whether this is acceptable practice in production code. On the one hand, this should never happen. But on the other hand, it may mean the difference between restarting a process, and rebooting a production server. And what should never happen often does.

And it sure is useful while debugging orderly shutdown problems.

Solution that worked for me:

When creating process add tag process.EnableRaisingEvents = true;:

csc = new Process();
csc.StartInfo.UseShellExecute = false;
csc.StartInfo.CreateNoWindow = true;
csc.StartInfo.FileName = Path.Combine(HLib.path_dataFolder, "csc.exe");
csc.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
csc.StartInfo.ErrorDialog = false;
csc.StartInfo.RedirectStandardInput = true;
csc.StartInfo.RedirectStandardOutput = true;
csc.EnableRaisingEvents = true;
csc.Start();

Just my 2018 version. Use it aside your Main() method.

    using System.Management;
    using System.Diagnostics;

    ...

    // Called when the Main Window is closed
    protected override void OnClosed(EventArgs EventArgs)
    {
        string query = "Select * From Win32_Process Where ParentProcessId = " + Process.GetCurrentProcess().Id;
        ManagementObjectSearcher searcher = new ManagementObjectSearcher(query);
        ManagementObjectCollection processList = searcher.Get();
        foreach (var obj in processList)
        {
            object data = obj.Properties["processid"].Value;
            if (data != null)
            {
                // retrieve the process
                var childId = Convert.ToInt32(data);
                var childProcess = Process.GetProcessById(childId);

                // ensure the current process is still live
                if (childProcess != null) childProcess.Kill();
            }
        }
        Environment.Exit(0);
    }

I had the same problem. I was creating child processes that never got killed if my main app crashed. I had to destroy manually child processes when debugging. I found that there was no need to make the children somewhat depend on parent. In my main, I added a try catch to do a CleanUp() of child processes on exit.

    static void Main()
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        try
        {
            Application.Run(new frmMonitorSensors());
        }
        catch(Exception ex)
        {
            CleanUp();
            ErrorLogging.Add(ex.ToString());
        }
    }

    static private void CleanUp()
    {
        List<string> processesToKill = new List<string>() { "Process1", "Process2" };
        foreach (string toKill in processesToKill)
        {
            Process[] processes = Process.GetProcessesByName(toKill);
            foreach (Process p in processes)
            {
                p.Kill();
            }
        }
    }
Related