I am trying to implement a tray icon, that lets me control a process (jackd). Most notably
The killing is causing me headaches. I believe, when I read stdout and stderr I must do this in an extra thread/process to keep the icon responsive. So I used fork() to create a child process and called setsid()
on the child. That child process then invokes jackd using the subprocess
module
from subprocess import PIPE, STDOUT, Popen
def runAndLog(self, cmd):
po = Popen(cmd, stdout=PIPE, stderr=STDOUT)
line = po.stdout.readline()
while line:
print(line.rstrip())
line = po.stdout.readline()
self.runAndLog(["jackd","-R", "-P80", "-t5000", "-dfirewire", "-r44100", "-p256", "-n3"])
Now I was hoping that I could get my forked child process and jackd into the same process group, so I could kill both in one go, but to my surprise Popen
created a new process group and a new session:
PID PPID PGID SID COMMAND
5790 5788 5788 31258 python (parent, main)
5791 5790 5791 5791 python (forked child after setsid - ok)
5804 5791 5804 5804 jackd (grandchild created by Popen)
So, killing the child won't kill the grandchild and I don't see any clean way of making the grandchild's PID (5804) known to main
in order to kill it explicitly. I could probably use some signal trickery to have the dying child kill the grandchild, but I hesitate to do this.
I also cannot use setpgid
on the jackd process to force it into the child's process group, because its SID is different from my child's SID.
I believe I either
subprocess
, but which still alows me to read stdout and stderr.but I don't know how to do either. Admittedly my knowledge about process groups is somewhat limited.
/home/martin >python --version
Python 2.7.1
Update Oct 12
I found that the new session isn't created by python but by jackd
itself. When I replace jackd with xterm
, everything is fine.
PID PPID PGID SID COMMAND
12239 4024 12239 4024 python (parent, main)
12244 12239 12244 12244 python (forked child after setsid - ok)
12249 12244 12244 12244 xterm (grandchild has same PGID and SID - ok)
I also found this
these jack days calls setsid() to get itself established as a new process group leader
That only leaves the options to
main
and kill it explicitly, orAny advice on that?
.
Since your grandchild sets its own session and sid, you cannot expect group-kill to work properly. You have three options:
Iterate over the whole tree of children processes, and kill them all starting from the parents (so that they would not restart the children). Ugly and overcomplicated solution.
Use a thread in the main (root) process to control the jack
child directly. The intermediate child process is probably not necessary here, as it gives no benefits. This is the most simple solution possible.
If you still have to have the intermediate child process, the only way is to catch the termination signals sent to the child, and transmit them to the grandchild:
child.py
:
import signal
import subprocess
# The signal handler for the child process, which passes it further to the grandchild.
def handler(signum, frame):
print('Killing self, killing the children')
os.kill(proc.pid, signal.SIGTERM)
# wait for the process to die gracefully for N secs.
time.sleep(5)
os.kill(proc.pid, signal.SIGKILL)
cmd = ["jackd","-R", "-P80", "-t5000", "-dfirewire", "-r44100", "-p256", "-n3"]
po = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
signal.signal(signal.SIGTERM, handler)
for line in po.stdout:
print(line.rstrip())
Please note that the signal handler is installed before the process communication begins (line reading).
Whenever your root process will be killing the child process "gracefully" (e.g., with SIGTERM), the signal will be passed to the grandchild, and it will be terminated/killed too. You can even extend the logic of killing and waiting and force-killing it.
However, remember that if the child process is killed with SIGKILL, there will be no chances to catch this signal, and the grandchild will remain running orphan. Because SIGKILL is not catchable by design.