subprocess: deleting child processes in Windows

35,490

Solution 1

By using psutil:

import psutil, os

def kill_proc_tree(pid, including_parent=True):    
    parent = psutil.Process(pid)
    children = parent.children(recursive=True)
    for child in children:
        child.kill()
    gone, still_alive = psutil.wait_procs(children, timeout=5)
    if including_parent:
        parent.kill()
        parent.wait(5)

me = os.getpid()
kill_proc_tree(me)

Solution 2

Use taskkill with the /T flag

p = subprocess.Popen(...)
<wait>
subprocess.call(['taskkill', '/F', '/T', '/PID', str(p.pid)])

The flags to taskkill has the following docs:

TASKKILL [/S system [/U username [/P [password]]]]
         { [/FI filter] [/PID processid | /IM imagename] } [/T] [/F]

/S    system           Specifies the remote system to connect to.
/U    [domain\]user    Specifies the user context under which the
                       command should execute.
/P    [password]       Specifies the password for the given user
                       context. Prompts for input if omitted.
/FI   filter           Applies a filter to select a set of tasks.
                       Allows "*" to be used. ex. imagename eq acme*
/PID  processid        Specifies the PID of the process to be terminated.
                       Use TaskList to get the PID.
/IM   imagename        Specifies the image name of the process
                       to be terminated. Wildcard '*' can be used
                       to specify all tasks or image names.
/T                     Terminates the specified process and any
                       child processes which were started by it.
/F                     Specifies to forcefully terminate the process(es).
/?                     Displays this help message.

Or walk the process tree using comtypes and win32api:

def killsubprocesses(parent_pid):
    '''kill parent and all subprocess using COM/WMI and the win32api'''

    log = logging.getLogger('killprocesses')

    try:
        import comtypes.client
    except ImportError:
        log.debug("comtypes not present, not killing subprocesses")
        return

    logging.getLogger('comtypes').setLevel(logging.INFO)

    log.debug('Querying process tree...')

    # get pid and subprocess pids for all alive processes
    WMI = comtypes.client.CoGetObject('winmgmts:')
    processes = WMI.InstancesOf('Win32_Process')
    subprocess_pids = {} # parent pid -> list of child pids

    for process in processes:
        pid = process.Properties_('ProcessID').Value
        parent = process.Properties_('ParentProcessId').Value
        log.trace("process %i's parent is: %s" % (pid, parent))
        subprocess_pids.setdefault(parent, []).append(pid)
        subprocess_pids.setdefault(pid, [])

    # find which we need to kill
    log.debug('Determining subprocesses for pid %i...' % parent_pid)

    processes_to_kill = []
    parent_processes = [parent_pid]
    while parent_processes:
        current_pid = parent_processes.pop()
        subps = subprocess_pids[current_pid]
        log.debug("process %i children are: %s" % (current_pid, subps))
        parent_processes.extend(subps)
        processes_to_kill.extend(subps)

    # kill the subprocess tree
    if processes_to_kill:
        log.info('Process pid %i spawned %i subprocesses, terminating them...' % 
            (parent_pid, len(processes_to_kill)))
    else:
        log.debug('Process pid %i had no subprocesses.' % parent_pid)

    import ctypes
    kernel32 = ctypes.windll.kernel32
    for pid in processes_to_kill:
        hProcess = kernel32.OpenProcess(PROCESS_TERMINATE, FALSE, pid)
        if not hProcess:
            log.warning('Unable to open process pid %i for termination' % pid)
        else:
            log.debug('Terminating pid %i' % pid)                        
            kernel32.TerminateProcess(hProcess, 3)
            kernel32.CloseHandle(hProcess)

Solution 3

Here's example code for the Job object method, but instead of subprocess it uses win32api.CreateProcess

import win32process
import win32job
startup = win32process.STARTUPINFO()
(hProcess, hThread, processId, threadId) = win32process.CreateProcess(None, command, None, None, True, win32process.CREATE_BREAKAWAY_FROM_JOB, None, None, startup)

hJob = win32job.CreateJobObject(None, '')
extended_info = win32job.QueryInformationJobObject(hJob, win32job.JobObjectExtendedLimitInformation)
extended_info['BasicLimitInformation']['LimitFlags'] = win32job.JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE
win32job.SetInformationJobObject(hJob, win32job.JobObjectExtendedLimitInformation, extended_info)
win32job.AssignProcessToJobObject(hJob, hProcess)

Solution 4

This is a hard thing to do. Windows does not actually store a process tree in the process space. Nor is it possible to terminate a process and specify that it's children should also die.

One way around that is to use taskkill and tell it to wack the whole tree.

Another way to do it (assuming that you are spawning the top-level process) is to use a module that was developed with this sort of thing in mind: http://benjamin.smedbergs.us/blog/tag/killableprocess/

In order to do this generically for yourself, you have to spend some time building the list backwards. That is, a process stores pointers to it's PARENT, but parents appear to not store information about children.

So you have to look at all the processes in the system (which really isn't that hard), and then manually connect the dots yourself by looking at the parent process field. Then, you select the tree you are interested in and walk the whole thing, killing each node in turn, one by one.

Note that Windows doesn't update a child's parent pointer when the parent dies, so there may be gaps in your tree. I'm not aware of anything you can do about those.

Solution 5

Put the children in a NT Job object, then you can kill all children

Share:
35,490

Related videos on Youtube

Sridhar Ratnakumar
Author by

Sridhar Ratnakumar

Updated on September 01, 2021

Comments

  • Sridhar Ratnakumar
    Sridhar Ratnakumar over 2 years

    On Windows, subprocess.Popen.terminate calls win32's TerminalProcess. However, the behavior I see is that child processes of the process I am trying to terminate are still running. Why is that? How do I ensure all child processes started by the process are killed?

  • Sridhar Ratnakumar
    Sridhar Ratnakumar over 14 years
    Job objects seems like the right way to approach this problem; too bad that this isn't integrated with the subprocess module.
  • ICTMitchell
    ICTMitchell almost 12 years
    That code looks like it will only kill first-level children, not grandchildren etc. Might be an issue if you're launching build tools or .bat/.cmd files with cmd.exe. Unless get_children() means grandchildren too?
  • Giampaolo Rodolà
    Giampaolo Rodolà over 11 years
    Exactly. To include grandchildren you should specify the 'recursive' option as in parent.get_children(recursive=True)
  • Gilead
    Gilead over 11 years
    This is an old answer, but it was exactly what I was looking for -- a cross-platform way of killing all child processes. All the other answers that showed up on Google claim it cannot be done. Thank you for psutil!
  • jfs
    jfs almost 10 years
    +1: it seems like the most robust solution that works even if the parent process crashed. Here's the explanation on how it works
  • Eryk Sun
    Eryk Sun over 8 years
    Note that Windows doesn't maintain a process tree. parent.children(recursive=True) is building a tree on the fly by linking parent to child, so it won't find orphaned processes (i.e. if the parent died).
  • SaundersB
    SaundersB over 7 years
    Thank you for this.
  • Ken Pronovici
    Ken Pronovici over 6 years
    I was attempting to kill a Gradle build process kicked off with subprocess.Popen(). A simple process.terminate() or process.kill() did not work on WIndows 7, and neither did the psutils option above, but this did.
  • naitsirhc
    naitsirhc over 6 years
    Thanks very much for this! It's exactly what I was looking for. Would you agree to let the code in this answer to be used under open source licensing terms? BSD or MIT would be ideal, since they are compatible with Numpy, Pandas, Scipy, etc.
  • andreykyz
    andreykyz almost 4 years
    It doesn't work all time. If process can spawn randomly.
  • Elyasaf755
    Elyasaf755 over 2 years
    This is the only solution that worked for me after trying more about ~15 suggestions. Instantiate a subprocess using the JobPopen like this: with open(JobPopen(command, stdout=subprocess.PIPE, shell=True)) as p: # YOUR CODE HERE # And then, at the moment the script exits the 'with open' block - it will close that process.