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?

Community
  • 1
  • 1
LionSoft
  • 1,469
  • 2
  • 15
  • 20
  • The other question answer seem to be good to me, just pinvoke the functions from kernel32. http://www.pinvoke.net/default.aspx/kernel32.assignprocesstojobobject – Julien Roncaglia Jul 13 '10 at 08:14

5 Answers5

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
milkplus
  • 33,007
  • 7
  • 30
  • 31
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;
}
Jim Chaney
  • 91
  • 1
  • 1
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.)

Regent
  • 5,502
  • 3
  • 33
  • 59
  • 1
    Both advices doesn't usefull - child processes are not mine. They can be any programs. – LionSoft Jul 13 '10 at 09:32
  • 2
    @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. – Regent Jul 13 '10 at 10:31
  • But what to do if that "another child process" will be forced terminated? – LionSoft Jul 13 '10 at 11:22
  • 1
    @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. – Regent Jul 13 '10 at 13:43
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.

Gabe
  • 84,912
  • 12
  • 139
  • 238
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{}                    
        }
    }
}
Panagiotis Kanavos
  • 120,703
  • 13
  • 188
  • 236
  • 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. – LionSoft Jul 13 '10 at 09:43
  • 2
    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. – Panagiotis Kanavos Jul 14 '10 at 09:08
  • 4
    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. :( – LionSoft Jul 15 '10 at 05:39
  • dispose does not guarantee anything – pf12345678910 Jun 13 '23 at 11:00
  • @pf12345678910 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 that – Panagiotis Kanavos Jun 13 '23 at 11:11
  • As I close the main program, Dispose is not called – pf12345678910 Jun 13 '23 at 11:21
  • 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](https://referencesource.microsoft.com/#system/services/monitoring/system/diagnosticts/Process.cs,1282) but its `Dispose()` method only closes the handle to the remote process, it doesn't kill it – Panagiotis Kanavos Jun 13 '23 at 12:47