how to kill a process group using Python subprocess

d3pd picture d3pd · Aug 26, 2015 · Viewed 9.1k times · Source

I am trying to do the equivalent of the following using Python subprocess:

>cat /var/log/dmesg | festival --tts &
[1] 30875
>kill -9 -30875

Note that I am killing the process group (as indicated by the negative sign prepending the process ID number) in order to kill all of the child processes Festival launches.

In Python, I currently have the following code, wherein two processes are created and linked via a pipe.

process_cat = subprocess.Popen([
    "cat",
    "/var/log/dmesg"
], stdout = subprocess.PIPE)
process_Festival = subprocess.Popen([
    "festival",
    "--tts"
], stdin = process_cat.stdout, stdout = subprocess.PIPE)

How should I kill these processes and their child processes in a way equivalent to the Bash way shown above? The following approach is insufficient because it does not kill the child processes:

os.kill(process_cat.pid, signal.SIGKILL)
os.kill(process_Festival.pid, signal.SIGKILL)

Is there a more elegant way to do this, perhaps using just one process?

Answer

meuh picture meuh · Aug 26, 2015

You can simplify this a lot as you rarely need cat |. Eg:

process_Festival = subprocess.Popen(["festival", "--tts", "/var/log/dmesg"])

then later

process_Festival.send_signal(1)

If you kill festival with a signal like SIGHUP rather than SIGKILL it will clean up any subprocesses properly.


There's a very good explanation of how to create a new process group with python subprocess. Adding option preexec_fn=os.setsid to Popen:

process_Festival = subprocess.Popen(["festival", "--tts", "/var/log/dmesg"],preexec_fn=os.setsid)

You can then get the process group from the process id and signal it:

pgrp = os.getpgid(process_Festival.pid)
os.killpg(pgrp, signal.SIGINT)

Note: since Python 3.2 you can also use start_new_session=True in the Popen call instead of preexec_fn.