How terminate child processes when parent process terminated in C#
Asked Answered
I

5

17

Task: Auto kill all child processes if parent process terminate. Parent procees can be terminated not only in correct way, but also by killing in ProcessExplorer, for example. How can I do it?

Similar question in С topic advice to use Job objects. How to use it in C# without exporting external DLL?


I tried to use Job Objects. But this code doesn't work properly:

  var job = PInvoke.CreateJobObject(null, null);
  var jobli = new PInvoke.JOBOBJECT_BASIC_LIMIT_INFORMATION();

  jobli.LimitFlags = PInvoke.LimitFlags.JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE
                   | PInvoke.LimitFlags.JOB_OBJECT_LIMIT_PRIORITY_CLASS
                   | PInvoke.LimitFlags.JOB_OBJECT_LIMIT_JOB_TIME
                   | PInvoke.LimitFlags.JOB_OBJECT_LIMIT_DIE_ON_UNHANDLED_EXCEPTION
                   | PInvoke.LimitFlags.JOB_OBJECT_LIMIT_JOB_MEMORY;

  var res = PInvoke.SetInformationJobObject(job, PInvoke.JOBOBJECTINFOCLASS.JobObjectBasicLimitInformation, jobli, 48);

  if (!res)
  {
    int b = PInvoke.GetLastError();
    Console.WriteLine("Error " + b);
  }

  var Prc = Process.Start(...);

  PInvoke.AssignProcessToJobObject(job, Prc.Handle);

PInvoke.SetInformationJobObject returns with error. GetLastError returns error 24. However, PInvoke.AssignProcessToJobObject works and child process added to Job Queue (I can see it in ProcessExplorer). But, because PInvoke.SetInformationJobObject don't work - spawned process stay alive when I kill parent one.

What do I have incorrect in this code?

Idalia answered 13/7, 2010 at 8:7 Comment(1)
The other question answer seem to be good to me, just pinvoke the functions from kernel32. pinvoke.net/default.aspx/kernel32.assignprocesstojobobjectElodea
T
9

To kill a process tree on windows, given only the parent process or process id, you'll need to walk the process tree.

For that, you'll need a way to get the parent process id for a given process.

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Diagnostics;
using System.Management;

namespace KillProcessTree
{

public static class MyExtensions
{
    public static int GetParentProcessId(this Process p)
    {
        int parentId = 0;
        try
        {
            ManagementObject mo = new ManagementObject("win32_process.handle='" + p.Id + "'");
            mo.Get();
            parentId = Convert.ToInt32(mo["ParentProcessId"]);
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.ToString());
            parentId = 0;
        }
        return parentId;
    }
}

Once you have that, actually killing the tree is not hard.

class Program
{
    /// <summary>
    /// Kill specified process and all child processes
    /// </summary>
    static void Main(string[] args)
    {
        if (args.Length < 1)
        {
            Console.WriteLine("Usage: KillProcessTree <pid>");
            return;
        }

        int pid = int.Parse(args[0]);

        Process root = Process.GetProcessById(pid);
        if (root != null)
        {
            Console.WriteLine("KillProcessTree " + pid);

            var list = new List<Process>();
            GetProcessAndChildren(Process.GetProcesses(), root, list, 1);

            // kill each process
            foreach (Process p in list)
            {
                try
                {
                    p.Kill();
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.ToString());
                }
            }
        }
        else
        {
            Console.WriteLine("Unknown process id: " + root);
        }
    }

    /// <summary>
    /// Get process and children
    /// We use postorder (bottom up) traversal; good as any when you kill a process tree </summary>
    /// </summary>
    /// <param name="plist">Array of all processes</param>
    /// <param name="parent">Parent process</param>
    /// <param name="output">Output list</param>
    /// <param name="indent">Indent level</param>
    private static void GetProcessAndChildren(Process[] plist, Process parent, List<Process> output, int indent)
    {
        foreach (Process p in plist)
        {
            if (p.GetParentProcessId() == parent.Id)
            {
                GetProcessAndChildren(plist, p, output, indent + 1);
            }
        }
        output.Add(parent);
        Console.WriteLine(String.Format("{0," + indent*4 + "} {1}", parent.Id, parent.MainModule.ModuleName));
    }
}
} // namespace
Textile answered 15/4, 2011 at 17:8 Comment(0)
T
9

I tried the code above and indeed, it does not work, complaining of a bad size. The reason for this is that the structure used changes size depending on the host platform; the original code fragment (seen on a dozen websites) assumes a 32 bit application.

Switch the structure to this (note the IntPtr resizing members) and it will work. At least it did for me.

[StructLayout(LayoutKind.Sequential)]
struct JOBOBJECT_BASIC_LIMIT_INFORMATION
{
    public Int64 PerProcessUserTimeLimit;
    public Int64 PerJobUserTimeLimit;
    public Int16 LimitFlags;
    public UIntPtr MinimumWorkingSetSize;
    public UIntPtr MaximumWorkingSetSize;
    public Int16 ActiveProcessLimit;
    public Int64 Affinity;
    public Int16 PriorityClass;
    public Int16 SchedulingClass;
}
Tauro answered 12/5, 2011 at 9:46 Comment(0)
M
5

You can pass ProcessID of the parent process as an argument to the child process. And then child processes will be responsible for checking from time to time whether the parent process still running. (By calling Process.GetProcessById.)

Another way to track existence of the parent process is to use Mutex synchronization primitive. Parent application will initially create a global mutex with the name known by children. Children can check from time to time whether the mutex still exists and terminate if not. (Once the parent process is closed the mutex will be destroyed by the system automatically, regardless of the way it way closed.)

Morphophoneme answered 13/7, 2010 at 9:0 Comment(4)
Both advices doesn't usefull - child processes are not mine. They can be any programs.Idalia
@LionSoft: Can you have another child process which will be responsible for creating those child processes? Then that process can check whether the parent process is still running and kill other children if not.Morphophoneme
But what to do if that "another child process" will be forced terminated?Idalia
@LionSoft: You can recreate it again by the parent process. So if the parent process gets killed then child-manager process will kill other children and quit; but if the child-manager is killed, then the parent process will create it again.Morphophoneme
H
3

Did you pay attention to the error code? Error 24 is ERROR_BAD_LENGTH, which probably means that 48 isn't the right length of the structure. I think it's 44, but you should do a sizeof to be sure.

Hyperpyrexia answered 22/7, 2010 at 12:18 Comment(0)
S
2

Windows does not force child processes to close when a parent process closes. When you select "Kill Tree" in a tool like Task Manager or Process explorer, the tool actually finds all child processes and kill them one by one.

If you want to ensure that child processes are cleaned when your application terminates, you can create a ProcessManager class that implements IDisposable that actually creates the processes, keeps track of their instances and calls Kill on each one of them on Dispose, e.g.

public class ProcessManager:IDisposable
{
    List<Process> processes=new List<Process>();

    public Process Start(ProcessStartInfo info)
    {
        var newProcess = Process.Start(info);
        newProcess.EnableRaisingEvents = true
        processes.Add(newProcess);
        newProcess.Exited += (sender, e) => processes.Remove(newProcess);
        return newProcess;
    }

    ~ProcessManager()
    {
        Dispose(false);
    }

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

    protected virtual void Dispose(bool disposing)
    {
        foreach (var process in processes)
        {
            try
            {
                if (!process.HasExited)
                    process.Kill();
            }
            catch{}                    
        }
    }
}
Seleta answered 13/7, 2010 at 9:35 Comment(7)
Unfortunately, when I hardly kill process in ProcessExplorer the process have no chance to do finalization code. So, your exaple will be work only when parent process terminate correctly. BTW, to correct work your example, it have to add line newProcess.EnableRaisingEvents = true; before assigning Exiting event.Idalia
As I said, Windows just does not kill child processes when a parent process dies. There is no OS mechanism to enforce that. A child proces does NOT belong to its parent. If you want to spawn processing jobs that are guaranteed to be cleaned when a process dies, you have to use threads. You are right about EnableRaisingEvents, fixed it.Seleta
There are at least two OS mechanisms to kill spawned processes: 1. Attach to child process as debugger. 2. Use Job Objects with JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE flag But I can't work both this methods so far. :(Idalia
dispose does not guarantee anythingBolden
@Bolden what do you mean? The answer never says that Dispose guarantees something. The class is used to keep track of launched processes and terminate them when done. The Dispose pattern is a common way to do thatSeleta
As I close the main program, Dispose is not calledBolden
Did you use a using block? This is a main C# feature, not something I cooked up for this answer. If the application ends without calling Dispose, just like any other class, the ~ProcessManager destructor will be called when the class gets garbage-collected. At this point though, any .NET objects used by the class may have been GC'd themselves. Process follows the same pattern but its Dispose() method only closes the handle to the remote process, it doesn't kill itSeleta

© 2022 - 2024 — McMap. All rights reserved.