8

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?

d3pd
  • 7,935
  • 24
  • 76
  • 127

1 Answers1

22

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.

Dimitri Merejkowsky
  • 1,001
  • 10
  • 12
meuh
  • 11,500
  • 2
  • 29
  • 45
  • 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. – d3pd Aug 26 '15 at 09:39
  • there is an `os.killpg(pg,signal)` and process_Festival.pid is the process id. – meuh Aug 26 '15 at 09:40
  • 2
    dont use kill -9 but -1 so festival can clean up its children. – meuh Aug 26 '15 at 09:49
  • 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. – d3pd Aug 26 '15 at 10:33
  • there is an `os.getpgid(pid)` to get the process group from a pid. – meuh Aug 26 '15 at 10:57
  • 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. – d3pd Aug 26 '15 at 11:37
  • If you mention this in your solution, I'm happy to accept it. – d3pd Aug 26 '15 at 11:46
  • 3
    I have found a good article about the difference between sessions and process groups. http://www.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.setpgrp` – Konstantin Nikitin Apr 11 '19 at 18:59