how to kill a process group using Python subprocess
Asked Answered
D

1

9

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?

Dominga answered 26/8, 2015 at 9:21 Comment(2)
Why is it insufficient? Maybe have a look at os.setprgidCatheterize
It is insufficient because it does not kill the child processes of Festival. I'll edit the question for clarity.Dominga
L
23

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.

Lithomarge answered 26/8, 2015 at 9:35 Comment(8)
Hi there. Thank you for your simplification suggestion. You are quite right in this case (though I want to use pipes for other more elaborate purposes). Regarding your send_signal(9) approach, the problem is that this is equivalent to something like kill -9 30875 as opposed to kill -9 -30875. Your approach does not kill the many child processes created by Festival, which is what I need.Dominga
there is an os.killpg(pg,signal) and process_Festival.pid is the process id.Lithomarge
dont use kill -9 but -1 so festival can clean up its children.Lithomarge
Thank you very much for your suggestions. I need to kill the process group rather than get Festival to terminate the child processes, because the child processes can continue running for a very long time before they are terminated, which is inappropriate for my purposes. Do you know how to use the os.killpg function with subprocess? It doesn't appear that it can be used with the PID returned by a single subprocess process.Dominga
there is an os.getpgid(pid) to get the process group from a pid.Lithomarge
Hmm, this is probably a silly question, but how should I get os.getpgid to not kill the Python script that is running it? Things seem to work when I use the argument preexec_fn = os.setsid when creating the process, but I don't understand it.Thanks again for all of your ideas on this.Dominga
If you mention this in your solution, I'm happy to accept it.Dominga
I have found a good article about the difference between sessions and process groups. andy-pearce.com/blog/posts/2013/Aug/process-groups-and-sessions . So, if you want to spawn a daemon process, you need to use os.setsid or start_new_session parameter. But if you are spawning just child processes which should die at the moment when you are logging off from the terminal, prefer to use preexec_fn=os.setpgrpAlurd

© 2022 - 2024 — McMap. All rights reserved.